# Issues exporteren

> Exporteer issues uit de Tidal Control API met de issues_paged-query. Bevindingen, hiaten, incidenten en actieplannen gekoppeld aan maatregelen en middelen.

Source: https://tidalcontrol.com/nl/docs/api/issues

Issues worden geëxporteerd met de `issues_paged`-query. Issues zijn bevindingen, hiaten, incidenten en actieplannen die gekoppeld zijn aan maatregelen en middelen.

## Basisquery

```graphql
query ExportIssues($first: Int, $after: String, $filter: IssueFilter) {
  issues_paged(first: $first, after: $after, filter: $filter) {
    edges {
      node {
        id
        sequenceId
        name
        description
        priority
        issueType
        notBefore
        expires
        closedDate
        reviewedDate
        createdDate
        controls {
          id
          sequenceId
          name
        }
        assets {
          id
          name
        }
        assignments {
          assignmentType
          user {
            name
            email
          }
        }
        attributes {
          key
          value
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

Variabelen voor de eerste pagina:

```json
{ "first": 50, "after": null, "filter": {} }
```

## Belangrijke velden

- `sequenceId`: het leesbare nummer zoals getoond in de UI (bijv. I-23)
- `priority`: `LOW`, `MEDIUM` of `HIGH`
- `issueType`: `GENERIC`, `AUDIT_FINDING`, `CONTROL_GAP`, `CONTROL_DEFICIENCY`, `ACTION_PLAN`, `IMPROVEMENT_OPPORTUNITY` of `INCIDENT`
- `closedDate`: ingesteld wanneer het issue gesloten is; `null` als het nog open is
- `reviewedDate`: ingesteld wanneer het issue beoordeeld is

## Volledig exportscript (Python)

Dit script authenticeert, doorloopt alle pagina's en schrijft het resultaat naar een CSV-bestand:

```python

GRAPHQL_URL = "https://portal.tidalcontrol.com/graphql"

QUERY = """
query ExportIssues($first: Int, $after: String, $filter: IssueFilter) {
  issues_paged(first: $first, after: $after, filter: $filter) {
    edges {
      node {
        id
        sequenceId
        name
        description
        priority
        issueType
        notBefore
        expires
        closedDate
        reviewedDate
        createdDate
        controls { id sequenceId name }
        assets { id name }
        assignments { assignmentType user { name email } }
      }
    }
    pageInfo { hasNextPage endCursor }
  }
}
"""

def graphql(query, variables, access_token):
    r = requests.post(
        GRAPHQL_URL,
        json={"query": query, "variables": variables},
        headers={"Authorization": f"Bearer {access_token}"},
    )
    r.raise_for_status()
    result = r.json()
    if "errors" in result:
        raise RuntimeError(result["errors"])
    return result["data"]

def export_issues(access_token, closed=None, priority=None, issue_type=None):
    issues = []
    cursor = None
    issue_filter = {}
    if closed is not None:
        issue_filter["closed"] = closed
    if priority:
        issue_filter["priority"] = priority
    if issue_type:
        issue_filter["type"] = issue_type

    while True:
        data = graphql(
            QUERY,
            {"first": 50, "after": cursor, "filter": issue_filter},
            access_token,
        )
        page = data["issues_paged"]
        issues.extend(edge["node"] for edge in page["edges"])

        if not page["pageInfo"]["hasNextPage"]:
            break
        cursor = page["pageInfo"]["endCursor"]

    return issues

def to_csv(issues, output_path):
    if not issues:
        print("Geen issues gevonden.")
        return

    with open(output_path, "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow([
            "ID", "Volgnummer", "Naam", "Omschrijving",
            "Prioriteit", "Type", "Startdatum", "Vervaldatum",
            "Aanmaakdatum", "Sluitdatum", "Beoordelingsdatum",
            "Gekoppelde maatregelen", "Gekoppelde middelen", "Eigenaren",
        ])
        for i in issues:
            owners = [
                a["user"]["email"]
                for a in i["assignments"]
                if a["assignmentType"] == "OWNER"
            ]
            writer.writerow([
                i["id"],
                i["sequenceId"],
                i["name"],
                i.get("description", ""),
                i.get("priority", ""),
                i.get("issueType", ""),
                i.get("notBefore", ""),
                i.get("expires", ""),
                i.get("createdDate", ""),
                i.get("closedDate", "") or "",
                i.get("reviewedDate", "") or "",
                ", ".join(c["name"] for c in i["controls"]),
                ", ".join(a["name"] for a in i["assets"]),
                ", ".join(owners),
            ])

    print(f"{len(issues)} issues geëxporteerd naar {output_path}")

if __name__ == "__main__":
    ACCESS_TOKEN = "eyJhbGci..."

    # Exporteer alle open, hoge-prioriteit issues
    issues = export_issues(ACCESS_TOKEN, closed=False, priority=["HIGH"])
    to_csv(issues, "issues_export.csv")
```

## Filtervoorbeelden

Geef een `filter` mee om de export te beperken. Dit zijn de combinaties waar het meest om gevraagd wordt:

**Alleen open issues:**

```json
{ "filter": { "closed": false } }
```

**Hoge-prioriteit open issues:**

```json
{ "filter": { "closed": false, "priority": ["HIGH"] } }
```

**Alleen auditbevindingen:**

```json
{ "filter": { "type": ["AUDIT_FINDING"] } }
```

**Issues gekoppeld aan een specifieke maatregel:**

```json
{ "filter": { "controls": ["control-uuid-hier"] } }
```

**Verlopen issues:**

```json
{ "filter": { "status": ["OVERDUE"] } }
```

## Alleen tellen

Om te controleren hoeveel issues er zijn voordat je de volledige set exporteert:

```graphql
query {
  issues_count(filter: { closed: false })
}
```
