# Issues

> Read, raise, dismiss and reopen the issues and notes on a dataset

The `issues` resource reads and settles what checks, models, operators and
people said about a dataset's episodes. An issue (`blocks=True`) stops a keep
until it is dismissed or its episode is ruled. A note (`blocks=False`) never
blocks.

Methods on one issue take its full name
(`accounts/{a}/datasets/{d}/issues/{i}`), or its id plus `dataset=`. Bulk
methods take the dataset, then issue ids or names, or one `filter`. Replies are
the generated `qualia.v1` models; lists return a `PaginatedPage`.

## Count and list

```python
from qualia import Qualia

client = Qualia()

summary = client.issues.summarize(dataset_id, filter="state = OPEN")
for row in summary.rows:  # one row per code, most open first
    print(row.code, row.open_count, row.open_episode_count, row.dismissed_count)

page = client.issues.list(dataset_id, filter='code = "idle_tail" AND state = OPEN')
for issue in page.items:
    print(issue.name, issue.episode, issue.text, issue.measurement)
print(page.page_info.total_count, page.page_info.end_cursor)

# one take's issues and notes; episode="" reads the dataset-level ones
client.issues.list(dataset_id, episode=episode_id)
issue = client.issues.get(issue_id, dataset=dataset_id)
```

`filter` is AIP-160 over `episode`, `code`, `kind`, `state`, `blocks`,
`author.kind`, `author.user`, `dataset`, `check`, `recommended`, `create_time`
and `episode_state`. A person's own words have code `""`.

Through a mixture, `list` and `summarize` also return the issues upstream of
it, under their own names. Add `dataset = "<the mixture's name>"` to the filter
for the ones the mixture can dismiss.

## Dismiss and reopen

```python
result = client.issues.dismiss(
    dataset_id,
    filter='code = "idle_tail" AND state = OPEN',
    reason="idle tails are fine for this task",
)
print(result.dismissed_count)
for lock in result.locked:  # takes another person is editing
    print(lock.episode, lock.holder_name)

client.issues.reopen(dataset_id, [issue.name])
```

`dismiss` settles open blocking issues: seen, and the episode wanted as it
stands. No ruling moves. `cause="TRIM"` records that a trim cut the moment
away; the default is `"MANUAL"`.

`reopen` puts dismissed issues back. A person's ruling on an episode whose
blocking issue reopens is withdrawn: the episode is unreviewed again in that
dataset.

Both act only on issues scoped to the dataset given. A merged dataset also
takes the issues of the uploads it holds. Each call takes at most 10,000 names.
Takes another person is editing are left as they were and reported in
`locked_count` and `locked` (the first 100, with the holder's name).

## Raise, withdraw and reply

```python
issue = client.issues.create(
    dataset_id,
    "Gripper slips at the second grasp",
    episode=episode_id,
    time_seconds=12.5,
)
note = client.issues.create(dataset_id, "Recorded on the new rig", blocks=False)

client.issues.reply(issue.name, "Trimmed it away")
for reply in client.issues.replies(issue.name).items:
    print(reply.author.display_name, reply.text)

client.issues.delete(issue.name)  # force=True also deletes others' replies
```

A blocking issue on an episode withdraws a person's ruling on it in that
dataset; deleting the issue puts the ruling back if nothing has ruled since.
You can delete only what you raised. An API key deletes only what it raised
itself. `create` and `reply` take `request_id=`: a retry with the same id
within 24 hours returns the first call's result.
