# 알러트 및 오류

## 개요

Panther API는 다음과 같은 알러트 작업을 지원합니다:

* 선택적 필터로 알러트 및 오류를 나열하기
* 특정 알러트의 세부 정보 가져오기
* 알러트와 관련된 로그 이벤트 가져오기
* 하나 이상의 알러트 상태 업데이트하기
* 알러트에 댓글 추가하기
* 다음 정보를 제공하여 하나 이상의 알러트에 사용자 할당 및 할당 취소하기:
  * 사용자의 ID
  * 사용자의 이메일 주소

콘솔의 API 플레이그라운드 또는 GraphQL-over-HTTP API를 사용하여 Panther의 API를 호출할 수 있습니다. 이러한 방법에 대한 자세한 내용은 [Panther API](https://docs.panther.com/ko/panther/api/..#step-1-choose-a-method-for-invoking-the-api).

아래 섹션에서 GraphQL 쿼리, 뮤테이션 및 핵심 알러트와 오류 작업에 대한 종단 간 워크플로 예제를 확인하세요.

## 일반적인 알러트 및 오류 작업

아래는 Panther에서 가장 일반적인 GraphQL 알러트 및 오류 작업들입니다. 이 예제들은 GraphQL 클라이언트(또는)로 전송해야 하는 문서를 보여줍니다 `curl`)을 사용하여 Panther의 GraphQL API를 호출하기 위해 전송해야 하는 문서를 보여줍니다.

#### 알러트 나열하기

사용자를 사용할 것이며, `알러트` 쿼리는 `input` 를 포함하는 `createdAtAfter` 와 `createdAtBefore`.

{% tabs %}
{% tab title="모든 알러트의 첫 페이지" %}

```graphql
# `FirstPageOfAllAlerts`는 이 작업의 별칭입니다
query FirstPageOfAllAlerts {
    alerts(input: { 
      createdAtAfter: "2022-01-01T00:00:00.000Z"
      createdAtBefore: "2023-01-01T00:00:00.000Z"
    }) {
      edges {
        node { # 여기에서 더 많은 알러트 관련 필드를 요청할 수 있습니다
          id
          title
          severity
          status
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
```

{% endtab %}

{% tab title="후속 알러트 페이지" %}

```graphql
# `AnotherPageOfAllAlerts`는 이 작업의 별칭입니다
query AnotherPageOfAllAlerts {
    alerts(input: { 
      createdAtAfter: "2022-01-01T00:00:00.000Z",
      createdAtBefore: "2023-01-01T00:00:00.000Z",
      cursor: "{\"creationTime\":{\"B\":null,\"BOOL\":null,\"BS\":null,\"L\":null,\"M\":null,\"N\":null,\"NS\":null,\"NULL\":null,\"S\":\"2022-08-11T21:16:12Z\",\"SS\":null},\"id\":{\"B\":null,\"BOOL\":null,\"BS\":null,\"L\":null,\"M\":null,\"N\":null,\"NS\":null,\"NULL\":null,\"S\":\"6472742357f3488aeb3f48a9c5da9262\",\"SS\":null},\"alertMonthPartition\":{\"B\":null,\"BOOL\":null,\"BS\":null,\"L\":null,\"M\":null,\"N\":null,\"NS\":null,\"NULL\":null,\"S\":\"202208\",\"SS\":null}}"
    }) {
      edges {
        node { # 여기에서 더 많은 알러트 관련 필드를 요청할 수 있습니다
          id
          title
          severity
          status
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
```

{% endtab %}

{% tab title="필터된 알러트 페이지" %}

```graphql
# `FilteredPageOfAlerts`는 이 작업의 별칭입니다
query FilteredPageOfAlerts {
    alerts(input: { 
      createdAtAfter: "2022-01-01T00:00:00.000Z",
      createdAtBefore: "2023-01-01T00:00:00.000Z",
      severities: [LOW], 
      statuses: [OPEN] 
    }) { 
      edges {
        node { # 여기에서 더 많은 알러트 관련 필드를 요청할 수 있습니다
          id
          title
          severity
          status
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
```

{% endtab %}
{% endtabs %}

#### 알러트 설명하기

```graphql
query AlertDetails {
    alert(id: "FAKE_ALERT_ID") {
       id
       title
       severity
       status
    }
  }
```

#### 알러트에서 디텍션 또는 시스템 오류 정보 가져오기

```graphql
query AlertDetails {
    alert(id: "FAKE_ALERT_ID") {
       id
       title
       severity
       origin {
         ... on Detection {
           id
           이름
         }
         ... on SystemError {
           relatedComponent
           type
         }
       }
    }
  }
```

#### 알러트와 관련된 로그 이벤트 가져오기

```graphql
query FirstPageOfAlertEvents {
  alert(id: "FAKE_ALERT_ID") {
    id,
    events(input: {
      cursor: "",
      pageSize: 25
    }) {
      edges {
        node
      }
      pageInfo {
        endCursor
      }
    }
  }
}
```

#### 하나 이상의 알러트 상태 업데이트하기

```graphql
mutation UpdateAlertStatus {
    updateAlertStatusById(
       input: {
          ids: ["FAKE_ALERT_ID_1","FAKE_ALERT_ID_2"]
          status: CLOSED # 하드코딩할 때 이것이 문자열이 아닌(`enum`) 것을 주목하세요
       }
    ) {
       alerts {
         id
         status
       }
    }
  }
```

#### 알러트에 댓글 추가하기

```graphql
mutation CreateAlertComment {
    createAlertComment(
       input: {
          alertId: "FAKE_ALERT_ID"
          body: "<p>This is HTML</p>"
          format: HTML # HTML 파싱을 비활성화하려면 PLAIN_TEXT도 사용할 수 있습니다
       }
    ) {
       comment {
         id
       }
    }
 
```

#### 사용자 ID를 제공하여 하나 이상의 알러트에 사용자 할당 및 할당 취소하기

```graphql
mutation UpdateAlertsAssigneeById {
  updateAlertsAssigneeById(
    input: {
      assigneeId: "FAKE_USER_ID"
      ids: ["FAKE_ALERT_ID_1","FAKE_ALERT_ID_2"]
    }
  ) {
    alerts {
      assignee {
        email
        givenName
        familyName
        id
      }
    }
  }
}
```

#### 사용자 이메일 주소를 제공하여 하나 이상의 알러트에 사용자 할당 및 할당 취소하기

```graphql
mutation UpdateAlertsAssigneeByEmail {
  updateAlertsAssigneeByEmail(
    input: {
      assigneeEmail: "fake@email.com"
      ids: ["FAKE_ALERT_ID_1","FAKE_ALERT_ID_2"]
    }
  ) {
    alerts {
      assignee {
        email
        givenName
        familyName
        id
      }
    }
  }
}
```

## 종단 간 예제

아래에서는 [일반 작업](#common-operations) 예제를 바탕으로 엔드투엔드 흐름을 소개합니다.

#### 특정 알러트 집합을 찾아 다음으로 표시하기 *Resolved:*

{% tabs %}
{% tab title="NodeJS" %}

```javascript
// npm install graphql graphql-request

import { GraphQLClient, gql } from 'graphql-request';

const client = new GraphQLClient(
  'YOUR_PANTHER_API_URL', 
  { headers: { 'X-API-Key': 'YOUR_API_KEY' } 
});

// `FindAlerts`는 쿼리의 별칭입니다. 완전히 생략할 수 있습니다.
const findAlerts = gql`
  query FindAlerts($input: AlertsInput!) {
  alerts(input: $input) {
    edges {
      node {
        id
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
`;

// `UpdateAlerts`는 쿼리의 별칭입니다. 완전히 생략할 수 있습니다.
const updateAlerts = gql`
  mutation UpdateAlerts($input: UpdateAlertStatusByIdInput!) {
    updateAlertStatusById(input: $input) {
      alerts {
        id
      }
    }
  }
`;

(async () => {
  try {
    
    // 모든 페이지에서 가져온 모든 알러트를 보관하는 누적기
    let allAlerts = [];
    // 루프를 종료할 시점을 알기 위한 헬퍼
    let hasMore = true;
    // 페이징 커서
    let cursor = null;

    // 더 이상 페이지가 없을 때까지 계속 페이지를 가져옵니다
    do {
      const queryData = await client.request(findAlerts, {
        input: {
          severities: ["LOW"],
          createdAtAfter: "2022-02-01T00:00:00.000Z",
          createdAtBefore: "2022-03-01T00:00:00.000Z",
          cursor
        }
      });

      allAlerts = [
        ...allAlerts,
        ...queryData.alerts.edges.map((edge) => edge.node)
      ];
      
      hasMore = queryData.alerts.pageInfo.hasNextPage;
      cursor = queryData.alerts.pageInfo.endCursor;
    } while (hasMore);

    // 이제 알러트를 업데이트합니다
    if (!allAlerts.length) {
      console.log("해결할 알러트를 찾을 수 없습니다");
      return;
    }
    const mutationData = await client.request(updateAlerts, {
      input: {
        ids: allAlerts.map((alert) => alert.id),
        status: "RESOLVED"
      }
    });

    console.log(
      `Resolved ${mutationData.updateAlertStatusById.alerts.length} alert(s)!`
    );
  } catch (err) {
    console.error(err.response);
  }
})();

```

{% endtab %}

{% tab title="Python" %}

```python
# pip install gql aiohttp

from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport

transport = AIOHTTPTransport(
  url="YOUR_PANTHER_API_URL",
  headers={"X-API-Key": "YOUR_API_KEY"}
)

client = Client(transport=transport, fetch_schema_from_transport=True)

# `FindAlerts`는 쿼리의 별칭입니다. 완전히 생략할 수 있습니다.
find_alerts = gql(
    """
    query FindAlerts($input: AlertsInput!) {
      alerts(input: $input) {
        edges {
          node {
            id
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
    """
)

# `UpdateAlerts`는 쿼리의 별칭입니다. 완전히 생략할 수 있습니다.
update_alerts = gql(
    """
    mutation UpdateAlerts($input: UpdateAlertStatusByIdInput!) {
      updateAlertStatusById(input: $input) {
        alerts {
          id
        }
      }
    }
    """
)

# 모든 페이지에서 가져온 모든 알러트를 보관하는 누적기
all_alerts = []
# 루프를 종료할 시점을 알기 위한 헬퍼
has_more = True
# 페이징 커서
cursor = None

# 더 이상 페이지가 없을 때까지 계속 페이지를 가져옵니다
while has_more:
    query_data = client.execute(
        find_alerts,
        variable_values={
            "input": {
                "severities": ["LOW"],
                "createdAtAfter": "2022-02-01T00:00:00.000Z",
                "createdAtBefore": "2022-03-01T00:00:00.000Z",
                "cursor": cursor
            }
        }
    )

    all_alerts.extend([edge["node"] for edge in query_data["alerts"]["edges"]])
    has_more = query_data["alerts"]["pageInfo"]["hasNextPage"]
    cursor = query_data["alerts"]["pageInfo"]["endCursor"]

# 이제 알러트를 업데이트합니다
if not len(all_alerts):
    print("해결할 알러트를 찾을 수 없습니다")
else:
    mutation_data = client.execute(
        update_alerts,
        variable_values={
            "input": {
                "ids": [alert["id"] for alert in all_alerts],
                "status": "RESOLVED"
            }
        }
    )

    print(f'Resolved {len(mutation_data["updateAlertStatusById"]["alerts"])} alert(s)!')
```

{% endtab %}
{% endtabs %}
