Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show summary stats on aws-oidc dashboard #49516

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,184 @@

import React from 'react';

import { addHours } from 'date-fns';

import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard';
import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
import { IntegrationKind } from 'teleport/services/integrations';
import {
IntegrationKind,
ResourceTypeSummary,
} from 'teleport/services/integrations';
import { AwsOidcStatusContextState } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';

export default {
title: 'Teleport/Integrations/AwsOidc',
};

// Loaded dashboard with data for each aws resource and a navigation header
export function Dashboard() {
return (
<MockAwsOidcStatusProvider
value={{
attempt: {
status: 'success',
data: {
resourceType: 'integration',
name: 'integration-one',
kind: IntegrationKind.AwsOidc,
spec: {
roleArn: 'arn:aws:iam::111456789011:role/bar',
},
statusCode: 1,
},
statusText: '',
},
}}
>
<MockAwsOidcStatusProvider value={makeAwsOidcStatusContextState()}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

// Loaded dashboard with missing data for each aws resource and a navigation header
export function DashboardMissingData() {
const state = makeAwsOidcStatusContextState();
state.statsAttempt.data.awseks = undefined;
state.statsAttempt.data.awsrds = undefined;
state.statsAttempt.data.awsec2 = undefined;
return (
<MockAwsOidcStatusProvider value={state}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

// Loading screen
export function StatsProcessing() {
const props = makeAwsOidcStatusContextState({
statsAttempt: { status: 'processing', data: null, statusText: '' },
});
return (
<MockAwsOidcStatusProvider value={props}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

// No header, no loading indicator
export function IntegrationProcessing() {
const props = makeAwsOidcStatusContextState({
integrationAttempt: {
status: 'processing',
data: null,
statusText: '',
},
});
return (
<MockAwsOidcStatusProvider value={props}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

// Loaded error message
export function StatsFailed() {
const props = makeAwsOidcStatusContextState({
statsAttempt: {
status: 'error',
data: null,
statusText: 'failed to get stats',
error: {},
},
});
return (
<MockAwsOidcStatusProvider value={props}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

// Loaded dashboard with data for each aws resource but no navigation header
export function IntegrationFailed() {
const props = makeAwsOidcStatusContextState({
integrationAttempt: {
status: 'error',
data: null,
statusText: 'failed to get integration',
error: {},
},
});
return (
<MockAwsOidcStatusProvider value={props}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

// Blank screen
export function StatsNoData() {
const props = makeAwsOidcStatusContextState({
statsAttempt: { status: 'success', data: null, statusText: '' },
});
return (
<MockAwsOidcStatusProvider value={props}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

// No header, no loading indicator
export function IntegrationNoData() {
const props = makeAwsOidcStatusContextState({
integrationAttempt: {
status: 'success',
data: null,
statusText: '',
},
});
return (
<MockAwsOidcStatusProvider value={props}>
<AwsOidcDashboard />
</MockAwsOidcStatusProvider>
);
}

function makeAwsOidcStatusContextState(
overrides: Partial<AwsOidcStatusContextState> = {}
): AwsOidcStatusContextState {
return Object.assign(
{
integrationAttempt: {
status: 'success',
statusText: '',
data: {
resourceType: 'integration',
name: 'integration-one',
kind: IntegrationKind.AwsOidc,
spec: {
roleArn: 'arn:aws:iam::111456789011:role/bar',
},
statusCode: 1,
},
},
statsAttempt: {
status: 'success',
statusText: '',
data: {
name: 'integration-one',
subKind: IntegrationKind.AwsOidc,
awsoidc: {
roleArn: 'arn:aws:iam::111456789011:role/bar',
},
awsec2: makeResourceTypeSummary(),
awsrds: makeResourceTypeSummary(),
awseks: makeResourceTypeSummary(),
},
},
},
overrides
);
}

function makeResourceTypeSummary(
overrides: Partial<ResourceTypeSummary> = {}
): ResourceTypeSummary {
return Object.assign(
{
rulesCount: Math.floor(Math.random() * 100),
resourcesFound: Math.floor(Math.random() * 100),
resourcesEnrollmentFailed: Math.floor(Math.random() * 100),
resourcesEnrollmentSuccess: Math.floor(Math.random() * 100),
discoverLastSync: addHours(
new Date().getTime(),
-Math.floor(Math.random() * 100)
),
ecsDatabaseServiceCount: Math.floor(Math.random() * 100),
},
overrides
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@
import React from 'react';
import { render, screen } from 'design/utils/testing';

import { within } from '@testing-library/react';

import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard';
import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
import { IntegrationKind } from 'teleport/services/integrations';
import { addHours } from 'teleport/components/BannerList/useAlerts';

test('renders header', () => {
test('renders header and stats cards', () => {
render(
<MockAwsOidcStatusProvider
value={{
attempt: {
integrationAttempt: {
status: 'success',
statusText: '',
data: {
resourceType: 'integration',
name: 'integration-one',
Expand All @@ -38,7 +42,41 @@ test('renders header', () => {
},
statusCode: 1,
},
},
statsAttempt: {
status: 'success',
statusText: '',
data: {
name: 'integration-one',
subKind: IntegrationKind.AwsOidc,
awsoidc: {
roleArn: 'arn:aws:iam::111456789011:role/bar',
},
awsec2: {
rulesCount: 24,
resourcesFound: 12,
resourcesEnrollmentFailed: 3,
resourcesEnrollmentSuccess: 9,
discoverLastSync: new Date().getTime(),
ecsDatabaseServiceCount: 0, // irrelevant
},
awsrds: {
rulesCount: 14,
resourcesFound: 5,
resourcesEnrollmentFailed: 5,
resourcesEnrollmentSuccess: 0,
discoverLastSync: addHours(new Date().getTime(), -4),
ecsDatabaseServiceCount: 8, // relevant
},
awseks: {
rulesCount: 33,
resourcesFound: 3,
resourcesEnrollmentFailed: 0,
resourcesEnrollmentSuccess: 3,
discoverLastSync: addHours(new Date().getTime(), -48),
ecsDatabaseServiceCount: 0, // irrelevant
},
},
},
}}
>
Expand All @@ -53,4 +91,49 @@ test('renders header', () => {
expect(screen.getByText('integration-one')).toBeInTheDocument();
expect(screen.getByLabelText('status')).toHaveAttribute('kind', 'success');
expect(screen.getByLabelText('status')).toHaveTextContent('Running');

const ec2 = screen.getByTestId('ec2-stats');
expect(within(ec2).getByTestId('sync')).toHaveTextContent(
'Last Sync: 0 seconds ago'
);
expect(within(ec2).getByTestId('rules')).toHaveTextContent(
'Enrollment Rules 24'
);
expect(within(ec2).queryByTestId('rds-agents')).not.toBeInTheDocument();
expect(within(ec2).getByTestId('enrolled')).toHaveTextContent(
'Enrolled Instances 9'
);
expect(within(ec2).getByTestId('failed')).toHaveTextContent(
'Failed Instances 3'
);

const rds = screen.getByTestId('rds-stats');
expect(within(rds).getByTestId('sync')).toHaveTextContent(
'Last Sync: 4 hours ago'
);
expect(within(rds).getByTestId('rules')).toHaveTextContent(
'Enrollment Rules 14'
);
expect(within(rds).getByTestId('rds-agents')).toHaveTextContent('Agents 8');
expect(within(rds).getByTestId('enrolled')).toHaveTextContent(
'Enrolled Databases 0'
);
expect(within(rds).getByTestId('failed')).toHaveTextContent(
'Failed Databases 5'
);

const eks = screen.getByTestId('eks-stats');
expect(within(eks).getByTestId('sync')).toHaveTextContent(
'Last Sync: 2 days ago'
);
expect(within(eks).getByTestId('rules')).toHaveTextContent(
'Enrollment Rules 33'
);
expect(within(eks).queryByTestId('rds-agents')).not.toBeInTheDocument();
expect(within(eks).getByTestId('enrolled')).toHaveTextContent(
'Enrolled Clusters 3'
);
expect(within(eks).getByTestId('failed')).toHaveTextContent(
'Failed Clusters 0'
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,43 @@

import React from 'react';

import { Flex, H2, Indicator } from 'design';

import { Danger } from 'design/Alert';

import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader';
import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
import { FeatureBox } from 'teleport/components/Layout';
import {
AwsResource,
StatCard,
} from 'teleport/Integrations/status/AwsOidc/StatCard';

// todo (michellescripts) after routing, ensure this view can be sticky
export function AwsOidcDashboard() {
const { attempt } = useAwsOidcStatus();
const { statsAttempt, integrationAttempt } = useAwsOidcStatus();

if (statsAttempt.status == 'processing') {
return <Indicator />;
}
if (statsAttempt.status == 'error') {
return <Danger>{statsAttempt.statusText}</Danger>;
}
if (!statsAttempt.data) {
return null;
}

// todo (michellescripts) after routing, ensure this view can be sticky
const { awsec2, awseks, awsrds } = statsAttempt.data;
const { data: integration } = integrationAttempt;
return (
<FeatureBox css={{ maxWidth: '1400px', paddingTop: '16px' }}>
{attempt.data && <AwsOidcHeader integration={attempt.data} />}
Status for integration type aws-oidc is not supported
{integration && <AwsOidcHeader integration={integration} />}
<H2 my={3}>Auto-Enrollment</H2>
<Flex gap={3}>
<StatCard resource={AwsResource.ec2} summary={awsec2} />
<StatCard resource={AwsResource.rds} summary={awsrds} />
<StatCard resource={AwsResource.eks} summary={awseks} />
</Flex>
</FeatureBox>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function AwsOidcHeader({ integration }: { integration: Integration }) {
<ArrowLeft size="medium" />
</ButtonIcon>
</HoverTooltip>
<Text bold fontSize={6} mr={2}>
<Text bold fontSize={6} mx={2}>
{integration.name}
</Text>
<Label kind={labelKind} aria-label="status" px={3}>
Expand Down
Loading
Loading