# Data opvragen

> De patronen voor paginering, filtering en sortering die elke lijstquery in de Tidal Control API deelt. Leer ze eenmalig, plus records tellen en foutafhandeling.

Source: https://tidalcontrol.com/nl/docs/api/querying-data

Alle lijstquery's in de Tidal Control API volgen dezelfde patronen voor paginering, filtering en sortering. Leer ze eenmalig en ze gelden overal.

## Verzoekstructuur

Elk GraphQL-verzoek is een `POST` naar `https://portal.tidalcontrol.com/graphql` met een JSON-body:

```json
{
  "query": "...",
  "variables": { ... }
}
```

Gebruik altijd `variables` in plaats van waarden rechtstreeks in de querystring te plaatsen. Dat houdt query's leesbaar en voorkomt injectieproblemen.

## Paginering

Alle `*_paged`-query's gebruiken **cursor-gebaseerde paginering** volgens de Relay Connection-specificatie. Vraag een pagina op met `first` (paginagrootte) en `after` (cursor van de vorige pagina):

```graphql
query ExportRisks($first: Int, $after: String) {
  risks_paged(first: $first, after: $after) {
    edges {
      node {
        id
        name
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

Variabelen:

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

Als `pageInfo.hasNextPage` `true` is, geef je `pageInfo.endCursor` door als `after` in het volgende verzoek.

### Alle pagina's ophalen (Python)

```python
def fetch_all(graphql_fn, query, page_size=50):
    """Haal alle pagina's op van een gepagineerde query."""
    results = []
    cursor = None

    while True:
        data = graphql_fn(query, {"first": page_size, "after": cursor})
        key = next(iter(data))
        page = data[key]
        results.extend(edge["node"] for edge in page["edges"])

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

    return results
```

> **tip:**

Een paginagrootte van `50` is een goede standaard. Je kunt hoger gaan, maar zeer grote pagina's kunnen de responstijd verlengen.

## Filtering

Geef een `filter`-argument mee om resultaten te beperken. Elke entiteit heeft zijn eigen filtertype met relevante velden. Alle filtervelden zijn optioneel. Laat velden die je niet nodig hebt weg.

Dit voorbeeld haalt alleen open issues met hoge prioriteit op:

```graphql
query FilteredIssues($filter: IssueFilter) {
  issues_paged(first: 50, filter: $filter) {
    edges {
      node {
        id
        name
        priority
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

Variabelen:

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

### Veelgebruikte filtervelden

Een handvol velden werkt op bijna dezelfde manier over vrijwel elke entiteit heen.

**Beschikbaar op de meeste entiteiten:**

- `archived: Boolean`: gearchiveerde records in- of uitsluiten
- `assignees: [UUID!]`: filteren op toegewezen gebruikers-ID's
- `search: String`: vrije-tekstzoekactie op naam/omschrijving

**Activiteiten (issues, taken):**

- `closed: Boolean`: open of gesloten activiteiten
- `controls: [UUID!]`: activiteiten gekoppeld aan specifieke maatregelen
- `assets: [UUID!]`: activiteiten gekoppeld aan specifieke middelen
- `status: [ActivityDueStatus!]`: `NOT_DUE_SOON`, `DUE_SOON`, `OVERDUE`, `NOT_SET`

**Alleen issues:**

- `priority: [Priority!]`: `LOW`, `MEDIUM`, `HIGH`
- `type: [IssueType!]`: bijv. `AUDIT_FINDING`, `INCIDENT`, `CONTROL_GAP`

**Risico's en maatregelen:**

- `frameworks: [UUID!]`: gekoppeld aan specifieke raamwerken
- `references: [UUID!]`: gekoppeld aan specifieke raamwerkreferenties

## Sortering

Geef een `sort`-array mee om de volgorde van resultaten te bepalen. Elk sorteerelement geeft een `field`-naam en een optionele `direction` (`ASC` of `DESC`) aan:

```graphql
query SortedRisks {
  risks_paged(
    first: 50,
    sort: [{ field: "sequenceId", direction: ASC }]
  ) {
    edges {
      node {
        sequenceId
        name
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
```

Je kunt meerdere sorteervelden opgeven. Veelgebruikte velden zijn `name`, `sequenceId`, `createdDate`, `notBefore` en `expires`.

## Records tellen

Gebruik `*_count`-query's om een totaaltelling op te halen zonder alle gegevens op te vragen:

```graphql
query {
  risks_count
  controls_count(filter: { archived: false })
  issues_count(filter: { closed: false, priority: ["HIGH"] })
}
```

## Foutafhandeling

Een geslaagd antwoord heeft altijd HTTP-statuscode `200`, ook wanneer de GraphQL-query zelf fouten bevat. Controleer de `errors`-array in de response-body:

```json
{
  "data": null,
  "errors": [
    {
      "message": "Unauthorized",
      "locations": [{ "line": 1, "column": 1 }]
    }
  ]
}
```

Veelvoorkomende fouten:

- **Unauthorized**: je token ontbreekt of is verlopen; authenticeer opnieuw
- **Field does not exist**: controleer de veldnaam in het schema
- **Variable … expected type …**: een filtervariabele heeft het verkeerde type
