Skip to content

Commit

Permalink
Add user tasks tables for aws resources
Browse files Browse the repository at this point in the history
  • Loading branch information
michellescripts committed Dec 27, 2024
1 parent ace89fd commit 1a2d64e
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ import { useHistory } from 'react-router';
export function AwsOidcHeader({
integration,
resource = undefined,
tasks = false,
}: {
integration: Integration;
resource?: AwsResource;
tasks?: boolean;
}) {
const history = useHistory();
const divider = (
Expand Down Expand Up @@ -62,7 +64,7 @@ export function AwsOidcHeader({
<Plugs size="small" />
</ButtonIcon>
</HoverTooltip>
{!resource ? (
{!resource && !tasks ? (
<>
{divider}
<Text typography="body3" color="text.slightlyMuted" ml={2}>
Expand All @@ -85,12 +87,24 @@ export function AwsOidcHeader({
>
{integration.name}
</ButtonText>
</>
)}
{resource && (
<>
{divider}
<Text typography="body3" color="text.slightlyMuted" ml={2}>
{resource.toUpperCase()}
</Text>
</>
)}
{tasks && (
<>
{divider}
<Text typography="body3" color="text.slightlyMuted" ml={2}>
Pending Tasks
</Text>
</>
)}
</Flex>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import cfg from 'teleport/config';
import { AwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';

import { Details } from 'teleport/Integrations/status/AwsOidc/Details/Details';
import { Tasks } from 'teleport/Integrations/status/AwsOidc/Tasks/Tasks';

import { AwsOidcDashboard } from './AwsOidcDashboard';

Expand All @@ -35,6 +36,12 @@ export function AwsOidcRoutes() {
path={cfg.routes.integrationStatusResources}
component={Details}
/>
<Route
key="aws-oidc-task-table"
exact
path={cfg.routes.integrationTasks}
component={Tasks}
/>
<Route
key="aws-oidc-dashboard"
exact
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { useEffect } from 'react';
import Table, { Cell } from 'design/DataTable';

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 { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
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 <Indicator />;
}

if (attempt.status == 'error') {
return <Danger>{attempt.statusText}</Danger>;
}

if (!attempt.data) {
return null;
}

return (
<FeatureBox css={{ maxWidth: '1400px', paddingTop: '16px', gap: '30px' }}>
{integration && <AwsOidcHeader integration={integration} tasks={true} />}
<Table<UserTask>
data={attempt.data.items}
columns={[
{
key: 'taskType',
headerText: 'Type',
isSortable: true,
},
{
key: 'issueType',
headerText: 'Issue Details',
isSortable: true,
},
{
key: 'lastStateChange',
headerText: 'Timestamp (UTC)',
isSortable: true,
render: (item: UserTask) => (
<Cell>{new Date(item.lastStateChange).toISOString()}</Cell>
),
},
]}
emptyText={`No pending tasks`}
isSearchable
/>
</FeatureBox>
);
}
11 changes: 11 additions & 0 deletions web/packages/teleport/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ const cfg = {
headlessSso: `/web/headless/:requestId`,
integrations: '/web/integrations',
integrationStatus: '/web/integrations/status/:type/:name',
integrationTasks: '/web/integrations/status/:type/:name/tasks',
integrationStatusResources:
'/web/integrations/status/:type/:name/resources/:resourceKind',
integrationEnroll: '/web/integrations/new/:type?',
Expand Down Expand Up @@ -334,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:
Expand Down Expand Up @@ -1026,6 +1029,14 @@ const cfg = {
});
},

getIntegrationUserTasksListUrl(name: string) {
const clusterId = cfg.proxyCluster;
return generatePath(cfg.api.userTaskListByIntegrationPath, {
clusterId,
name,
});
},

getPingAwsOidcIntegrationUrl({
integrationName,
clusterId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
10 changes: 10 additions & 0 deletions web/packages/teleport/src/services/integrations/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
AwsOidcPingRequest,
IntegrationWithSummary,
IntegrationDiscoveryRules,
UserTasksListResponse,
} from './types';

export const integrationService = {
Expand Down Expand Up @@ -446,6 +447,15 @@ export const integrationService = {
};
});
},

fetchIntegrationUserTasksList(name: string): Promise<UserTasksListResponse> {
return api.get(cfg.getIntegrationUserTasksListUrl(name)).then(resp => {
return {
items: resp?.items || [],
nextKey: resp?.nextKey,
};
});
},
};

export function makeIntegrations(json: any): Integration[] {
Expand Down
27 changes: 27 additions & 0 deletions web/packages/teleport/src/services/integrations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,33 @@ 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;
// lastStateChange indicates when the current's user task state was last changed.
lastStateChange: string;
};

// IntegrationDiscoveryRule describes a discovery rule associated with an integration.
export type IntegrationDiscoveryRule = {
// resourceType indicates the type of resource that this rule targets.
Expand Down

0 comments on commit 1a2d64e

Please sign in to comment.