Skip to content

Commit e47ec40

Browse files
Show aws summary stats on dashboard (#49516)
1 parent 7f23c81 commit e47ec40

File tree

9 files changed

+461
-28
lines changed

9 files changed

+461
-28
lines changed

web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.story.tsx

Lines changed: 168 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,184 @@
1818

1919
import React from 'react';
2020

21+
import { addHours } from 'date-fns';
22+
2123
import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard';
2224
import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
23-
import { IntegrationKind } from 'teleport/services/integrations';
25+
import {
26+
IntegrationKind,
27+
ResourceTypeSummary,
28+
} from 'teleport/services/integrations';
29+
import { AwsOidcStatusContextState } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
2430

2531
export default {
2632
title: 'Teleport/Integrations/AwsOidc',
2733
};
2834

35+
// Loaded dashboard with data for each aws resource and a navigation header
2936
export function Dashboard() {
3037
return (
31-
<MockAwsOidcStatusProvider
32-
value={{
33-
attempt: {
34-
status: 'success',
35-
data: {
36-
resourceType: 'integration',
37-
name: 'integration-one',
38-
kind: IntegrationKind.AwsOidc,
39-
spec: {
40-
roleArn: 'arn:aws:iam::111456789011:role/bar',
41-
},
42-
statusCode: 1,
43-
},
44-
statusText: '',
45-
},
46-
}}
47-
>
38+
<MockAwsOidcStatusProvider value={makeAwsOidcStatusContextState()}>
39+
<AwsOidcDashboard />
40+
</MockAwsOidcStatusProvider>
41+
);
42+
}
43+
44+
// Loaded dashboard with missing data for each aws resource and a navigation header
45+
export function DashboardMissingData() {
46+
const state = makeAwsOidcStatusContextState();
47+
state.statsAttempt.data.awseks = undefined;
48+
state.statsAttempt.data.awsrds = undefined;
49+
state.statsAttempt.data.awsec2 = undefined;
50+
return (
51+
<MockAwsOidcStatusProvider value={state}>
52+
<AwsOidcDashboard />
53+
</MockAwsOidcStatusProvider>
54+
);
55+
}
56+
57+
// Loading screen
58+
export function StatsProcessing() {
59+
const props = makeAwsOidcStatusContextState({
60+
statsAttempt: { status: 'processing', data: null, statusText: '' },
61+
});
62+
return (
63+
<MockAwsOidcStatusProvider value={props}>
64+
<AwsOidcDashboard />
65+
</MockAwsOidcStatusProvider>
66+
);
67+
}
68+
69+
// No header, no loading indicator
70+
export function IntegrationProcessing() {
71+
const props = makeAwsOidcStatusContextState({
72+
integrationAttempt: {
73+
status: 'processing',
74+
data: null,
75+
statusText: '',
76+
},
77+
});
78+
return (
79+
<MockAwsOidcStatusProvider value={props}>
4880
<AwsOidcDashboard />
4981
</MockAwsOidcStatusProvider>
5082
);
5183
}
84+
85+
// Loaded error message
86+
export function StatsFailed() {
87+
const props = makeAwsOidcStatusContextState({
88+
statsAttempt: {
89+
status: 'error',
90+
data: null,
91+
statusText: 'failed to get stats',
92+
error: {},
93+
},
94+
});
95+
return (
96+
<MockAwsOidcStatusProvider value={props}>
97+
<AwsOidcDashboard />
98+
</MockAwsOidcStatusProvider>
99+
);
100+
}
101+
102+
// Loaded dashboard with data for each aws resource but no navigation header
103+
export function IntegrationFailed() {
104+
const props = makeAwsOidcStatusContextState({
105+
integrationAttempt: {
106+
status: 'error',
107+
data: null,
108+
statusText: 'failed to get integration',
109+
error: {},
110+
},
111+
});
112+
return (
113+
<MockAwsOidcStatusProvider value={props}>
114+
<AwsOidcDashboard />
115+
</MockAwsOidcStatusProvider>
116+
);
117+
}
118+
119+
// Blank screen
120+
export function StatsNoData() {
121+
const props = makeAwsOidcStatusContextState({
122+
statsAttempt: { status: 'success', data: null, statusText: '' },
123+
});
124+
return (
125+
<MockAwsOidcStatusProvider value={props}>
126+
<AwsOidcDashboard />
127+
</MockAwsOidcStatusProvider>
128+
);
129+
}
130+
131+
// No header, no loading indicator
132+
export function IntegrationNoData() {
133+
const props = makeAwsOidcStatusContextState({
134+
integrationAttempt: {
135+
status: 'success',
136+
data: null,
137+
statusText: '',
138+
},
139+
});
140+
return (
141+
<MockAwsOidcStatusProvider value={props}>
142+
<AwsOidcDashboard />
143+
</MockAwsOidcStatusProvider>
144+
);
145+
}
146+
147+
function makeAwsOidcStatusContextState(
148+
overrides: Partial<AwsOidcStatusContextState> = {}
149+
): AwsOidcStatusContextState {
150+
return Object.assign(
151+
{
152+
integrationAttempt: {
153+
status: 'success',
154+
statusText: '',
155+
data: {
156+
resourceType: 'integration',
157+
name: 'integration-one',
158+
kind: IntegrationKind.AwsOidc,
159+
spec: {
160+
roleArn: 'arn:aws:iam::111456789011:role/bar',
161+
},
162+
statusCode: 1,
163+
},
164+
},
165+
statsAttempt: {
166+
status: 'success',
167+
statusText: '',
168+
data: {
169+
name: 'integration-one',
170+
subKind: IntegrationKind.AwsOidc,
171+
awsoidc: {
172+
roleArn: 'arn:aws:iam::111456789011:role/bar',
173+
},
174+
awsec2: makeResourceTypeSummary(),
175+
awsrds: makeResourceTypeSummary(),
176+
awseks: makeResourceTypeSummary(),
177+
},
178+
},
179+
},
180+
overrides
181+
);
182+
}
183+
184+
function makeResourceTypeSummary(
185+
overrides: Partial<ResourceTypeSummary> = {}
186+
): ResourceTypeSummary {
187+
return Object.assign(
188+
{
189+
rulesCount: Math.floor(Math.random() * 100),
190+
resourcesFound: Math.floor(Math.random() * 100),
191+
resourcesEnrollmentFailed: Math.floor(Math.random() * 100),
192+
resourcesEnrollmentSuccess: Math.floor(Math.random() * 100),
193+
discoverLastSync: addHours(
194+
new Date().getTime(),
195+
-Math.floor(Math.random() * 100)
196+
),
197+
ecsDatabaseServiceCount: Math.floor(Math.random() * 100),
198+
},
199+
overrides
200+
);
201+
}

web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.test.tsx

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919
import React from 'react';
2020
import { render, screen } from 'design/utils/testing';
2121

22+
import { within } from '@testing-library/react';
23+
2224
import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard';
2325
import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
2426
import { IntegrationKind } from 'teleport/services/integrations';
27+
import { addHours } from 'teleport/components/BannerList/useAlerts';
2528

26-
test('renders header', () => {
29+
test('renders header and stats cards', () => {
2730
render(
2831
<MockAwsOidcStatusProvider
2932
value={{
30-
attempt: {
33+
integrationAttempt: {
3134
status: 'success',
35+
statusText: '',
3236
data: {
3337
resourceType: 'integration',
3438
name: 'integration-one',
@@ -38,7 +42,41 @@ test('renders header', () => {
3842
},
3943
statusCode: 1,
4044
},
45+
},
46+
statsAttempt: {
47+
status: 'success',
4148
statusText: '',
49+
data: {
50+
name: 'integration-one',
51+
subKind: IntegrationKind.AwsOidc,
52+
awsoidc: {
53+
roleArn: 'arn:aws:iam::111456789011:role/bar',
54+
},
55+
awsec2: {
56+
rulesCount: 24,
57+
resourcesFound: 12,
58+
resourcesEnrollmentFailed: 3,
59+
resourcesEnrollmentSuccess: 9,
60+
discoverLastSync: new Date().getTime(),
61+
ecsDatabaseServiceCount: 0, // irrelevant
62+
},
63+
awsrds: {
64+
rulesCount: 14,
65+
resourcesFound: 5,
66+
resourcesEnrollmentFailed: 5,
67+
resourcesEnrollmentSuccess: 0,
68+
discoverLastSync: addHours(new Date().getTime(), -4),
69+
ecsDatabaseServiceCount: 8, // relevant
70+
},
71+
awseks: {
72+
rulesCount: 33,
73+
resourcesFound: 3,
74+
resourcesEnrollmentFailed: 0,
75+
resourcesEnrollmentSuccess: 3,
76+
discoverLastSync: addHours(new Date().getTime(), -48),
77+
ecsDatabaseServiceCount: 0, // irrelevant
78+
},
79+
},
4280
},
4381
}}
4482
>
@@ -53,4 +91,49 @@ test('renders header', () => {
5391
expect(screen.getByText('integration-one')).toBeInTheDocument();
5492
expect(screen.getByLabelText('status')).toHaveAttribute('kind', 'success');
5593
expect(screen.getByLabelText('status')).toHaveTextContent('Running');
94+
95+
const ec2 = screen.getByTestId('ec2-stats');
96+
expect(within(ec2).getByTestId('sync')).toHaveTextContent(
97+
'Last Sync: 0 seconds ago'
98+
);
99+
expect(within(ec2).getByTestId('rules')).toHaveTextContent(
100+
'Enrollment Rules 24'
101+
);
102+
expect(within(ec2).queryByTestId('rds-agents')).not.toBeInTheDocument();
103+
expect(within(ec2).getByTestId('enrolled')).toHaveTextContent(
104+
'Enrolled Instances 9'
105+
);
106+
expect(within(ec2).getByTestId('failed')).toHaveTextContent(
107+
'Failed Instances 3'
108+
);
109+
110+
const rds = screen.getByTestId('rds-stats');
111+
expect(within(rds).getByTestId('sync')).toHaveTextContent(
112+
'Last Sync: 4 hours ago'
113+
);
114+
expect(within(rds).getByTestId('rules')).toHaveTextContent(
115+
'Enrollment Rules 14'
116+
);
117+
expect(within(rds).getByTestId('rds-agents')).toHaveTextContent('Agents 8');
118+
expect(within(rds).getByTestId('enrolled')).toHaveTextContent(
119+
'Enrolled Databases 0'
120+
);
121+
expect(within(rds).getByTestId('failed')).toHaveTextContent(
122+
'Failed Databases 5'
123+
);
124+
125+
const eks = screen.getByTestId('eks-stats');
126+
expect(within(eks).getByTestId('sync')).toHaveTextContent(
127+
'Last Sync: 2 days ago'
128+
);
129+
expect(within(eks).getByTestId('rules')).toHaveTextContent(
130+
'Enrollment Rules 33'
131+
);
132+
expect(within(eks).queryByTestId('rds-agents')).not.toBeInTheDocument();
133+
expect(within(eks).getByTestId('enrolled')).toHaveTextContent(
134+
'Enrolled Clusters 3'
135+
);
136+
expect(within(eks).getByTestId('failed')).toHaveTextContent(
137+
'Failed Clusters 0'
138+
);
56139
});

web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,43 @@
1818

1919
import React from 'react';
2020

21+
import { Flex, H2, Indicator } from 'design';
22+
23+
import { Danger } from 'design/Alert';
24+
2125
import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader';
2226
import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
2327
import { FeatureBox } from 'teleport/components/Layout';
28+
import {
29+
AwsResource,
30+
StatCard,
31+
} from 'teleport/Integrations/status/AwsOidc/StatCard';
2432

25-
// todo (michellescripts) after routing, ensure this view can be sticky
2633
export function AwsOidcDashboard() {
27-
const { attempt } = useAwsOidcStatus();
34+
const { statsAttempt, integrationAttempt } = useAwsOidcStatus();
35+
36+
if (statsAttempt.status == 'processing') {
37+
return <Indicator />;
38+
}
39+
if (statsAttempt.status == 'error') {
40+
return <Danger>{statsAttempt.statusText}</Danger>;
41+
}
42+
if (!statsAttempt.data) {
43+
return null;
44+
}
2845

46+
// todo (michellescripts) after routing, ensure this view can be sticky
47+
const { awsec2, awseks, awsrds } = statsAttempt.data;
48+
const { data: integration } = integrationAttempt;
2949
return (
3050
<FeatureBox css={{ maxWidth: '1400px', paddingTop: '16px' }}>
31-
{attempt.data && <AwsOidcHeader integration={attempt.data} />}
32-
Status for integration type aws-oidc is not supported
51+
{integration && <AwsOidcHeader integration={integration} />}
52+
<H2 my={3}>Auto-Enrollment</H2>
53+
<Flex gap={3}>
54+
<StatCard resource={AwsResource.ec2} summary={awsec2} />
55+
<StatCard resource={AwsResource.rds} summary={awsrds} />
56+
<StatCard resource={AwsResource.eks} summary={awseks} />
57+
</Flex>
3358
</FeatureBox>
3459
);
3560
}

web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function AwsOidcHeader({ integration }: { integration: Integration }) {
4040
<ArrowLeft size="medium" />
4141
</ButtonIcon>
4242
</HoverTooltip>
43-
<Text bold fontSize={6} mr={2}>
43+
<Text bold fontSize={6} mx={2}>
4444
{integration.name}
4545
</Text>
4646
<Label kind={labelKind} aria-label="status" px={3}>

0 commit comments

Comments
 (0)