diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx index 94cdadcde4ed5..a03d6a01d0509 100644 --- a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx +++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx @@ -16,44 +16,69 @@ * along with this program. If not, see . */ -import React from 'react'; +import React, {useEffect} from 'react'; import Table from 'design/DataTable'; -import { useParams } from 'react-router'; +import {useAsync} from "shared/hooks/useAsync"; + +import {useParams} from "react-router"; + +import {Indicator} from "design"; + +import {Danger} from "design/Alert"; import { FeatureBox } from 'teleport/components/Layout'; import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader'; -import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard'; import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus'; -import { IntegrationKind } from 'teleport/services/integrations'; +import {IntegrationKind, integrationService, UserTask} from "teleport/services/integrations"; + export function Tasks() { + const { name } = useParams<{ + type: IntegrationKind; + name: string; + }>(); + const { integrationAttempt } = useAwsOidcStatus(); const { data: integration } = integrationAttempt; + const [attempt, fetchTasks] = useAsync(() => + integrationService.fetchIntegrationUserTasksList(name) + ); + + useEffect(() => { + fetchTasks(); + }, []); + + if (attempt.status == 'processing') { + return ; + } + + if (attempt.status == 'error') { + return {attempt.statusText}; + } + + if (!attempt.data) { + return null; + } + return ( - {integration && ( - - )} - } + {/* todo (michellescripts) sync with Marco on timestamp field */} + + data={attempt.data.items} columns={[ { - key: 'type', + key: 'taskType', headerText: 'Type', isSortable: true, }, { - key: 'details', + key: 'name', headerText: 'Issue Details', isSortable: true, }, - { - key: 'timestamp', - headerText: 'Timestamp', - isSortable: true, - }, ]} emptyText={`No pending tasks`} isSearchable diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index e06911fd4befd..1f6424ca9c795 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -335,6 +335,8 @@ const cfg = { '/v1/webapi/sites/:clusterId/integrations/:name/stats', integrationRulesPath: '/v1/webapi/sites/:clusterId/integrations/:name/discoveryrules?resourceType=:resourceType?&startKey=:startKey?&query=:query?&search=:search?&sort=:sort?&limit=:limit?', + userTaskListByIntegrationPath: + '/v1/webapi/sites/:clusterId/usertask?integration=:name', thumbprintPath: '/v1/webapi/thumbprint', pingAwsOidcIntegrationPath: @@ -1027,6 +1029,14 @@ const cfg = { }); }, + getIntegrationUserTasksListUrl(name: string) { + const clusterId = cfg.proxyCluster; + return generatePath(cfg.api.userTaskListByIntegrationPath, { + clusterId, + name, + }); + }, + getPingAwsOidcIntegrationUrl({ integrationName, clusterId, diff --git a/web/packages/teleport/src/services/integrations/integrations.test.ts b/web/packages/teleport/src/services/integrations/integrations.test.ts index a799dd90246c8..68637fa311120 100644 --- a/web/packages/teleport/src/services/integrations/integrations.test.ts +++ b/web/packages/teleport/src/services/integrations/integrations.test.ts @@ -276,6 +276,48 @@ test('fetch integration rules: fetchIntegrationRules()', async () => { }); }); +test('fetch integration user task list: fetchIntegrationUserTasksList()', async () => { + // test a valid response + jest.spyOn(api, 'get').mockResolvedValue({ + items: [ + { + name: "task-name", + taskType: "task-type", + state: "task-state", + issueType: "issue-type", + integration: "name", + }, + ], + nextKey: 'some-key', + }); + + let response = await integrationService.fetchIntegrationUserTasksList('name'); + expect(api.get).toHaveBeenCalledWith( + cfg.getIntegrationUserTasksListUrl('name') + ); + expect(response).toEqual({ + nextKey: 'some-key', + items: [ + { + name: "task-name", + taskType: "task-type", + state: "task-state", + issueType: "issue-type", + integration: "name", + }, + ], + }); + + // test null response + jest.spyOn(api, 'get').mockResolvedValue(null); + + response = await integrationService.fetchIntegrationUserTasksList('name'); + expect(response).toEqual({ + nextKey: undefined, + items: [], + }); +}); + const nonAwsOidcIntegration = { name: 'non-aws-oidc-integration', subKind: 'abc', diff --git a/web/packages/teleport/src/services/integrations/integrations.ts b/web/packages/teleport/src/services/integrations/integrations.ts index f7fb6db4f4b2b..646418cc2e8a1 100644 --- a/web/packages/teleport/src/services/integrations/integrations.ts +++ b/web/packages/teleport/src/services/integrations/integrations.ts @@ -63,6 +63,7 @@ import { AwsOidcPingRequest, IntegrationWithSummary, IntegrationDiscoveryRules, + UserTasksListResponse, } from './types'; export const integrationService = { @@ -446,6 +447,19 @@ export const integrationService = { }; }); }, + + fetchIntegrationUserTasksList( + name: string, + ): Promise { + return api + .get(cfg.getIntegrationUserTasksListUrl(name)) + .then(resp => { + return { + items: resp?.items || [], + nextKey: resp?.nextKey, + }; + }); + }, }; export function makeIntegrations(json: any): Integration[] { diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts index d63cac5c85a96..4c1e49c8b0b39 100644 --- a/web/packages/teleport/src/services/integrations/types.ts +++ b/web/packages/teleport/src/services/integrations/types.ts @@ -306,6 +306,31 @@ export type IntegrationDiscoveryRules = { nextKey: string; }; +// UserTasksListResponse contains a list of UserTasks. +// In case of exceeding the pagination limit (either via query param `limit` or the default 1000) +// a `nextToken` is provided and should be used to obtain the next page (as a query param `startKey`) +export type UserTasksListResponse = { + // items is a list of resources retrieved. + items: UserTask[]; + // nextKey is the position to resume listing events. + nextKey: string; +}; + +// UserTask describes UserTask fields. +// Used for listing User Tasks without receiving all the details. +export type UserTask = { + // name is the UserTask name. + name: string; + // taskType identifies this task's type. + taskType: string; + // state is the state for the User Task. + state: string; + // issueType identifies this task's issue type. + issueType: string; + // integration is the Integration Name this User Task refers to. + integration: string; +}; + // IntegrationDiscoveryRule describes a discovery rule associated with an integration. export type IntegrationDiscoveryRule = { // resourceType indicates the type of resource that this rule targets.