Skip to content

JobItem drops statusNotes from job responses (per-row detail lost) #1850

Description

@jacalata

JobItem.from_response at models/job_item.py:224 parses <notes> elements:

notes = [note.text for note in element.findall(".//t:notes", namespaces=ns)] or None

But the actual REST API response wraps notes in a <statusNotes> container with typed <statusNote> children — none of that is captured:

<statusNotes>
  <statusNote type="classifier" value="value" text="note" />
</statusNotes>

This is documented as the Query Job response schema — the doc lists enumerated type values like CountOfUsersAddedToSite, CountOfUsersSkipped, CountOfUsersWithInsufficientLicenses, CountOfUsersInformationUpdated, etc.

Reproducer

Verified live against Tableau Server 2025.1. POST /sites/<id>/users/import returns a job; polling GET /sites/<id>/jobs/<job-id> after completion returns XML like:

<job id="..." type="UserImport" ... finishCode="1">
  <statusNotes>
    <statusNote type="line" value="0" />
    <statusNote type="errorCode" value="1" />
    <statusNote type="message" value="..." />
    <statusNote type="username" value="unknown" />
  </statusNotes>
</job>

TSC's job.notes for this response is [].

Impact

Callers can't tell what happened at the row level. For user-import jobs, that means no way to distinguish "5 added, 1 skipped" from a general "job finished with finishCode=1". Any caller wanting to emit per-row status output has to bypass TSC and parse the raw XML themselves.

Proposed fix

Add a new structured status_notes property on JobItem, populated from <t:statusNotes>/<t:statusNote> with all three attributes preserved:

status_notes = [
    {"type": note.get("type"), "value": note.get("value"), "text": note.get("text")}
    for note in element.findall(".//t:statusNotes/t:statusNote", namespaces=ns)
]

Leave the existing notes: list[str] untouched for backwards compatibility.

Discovered while

Planning tabcmd createsiteusers --nowait / --silent-progress work (tableau/tabcmd#35). The current tabcmd 2 command prints one line per row imported; switching to bulk_add + wait_for_job for Classic parity needs per-row status back — which requires this data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions