Use domain model for VariantAnalysisHeader

This will change the VariantAnalysisHeader to take the VariantAnalysis
domain model instead of a large amount of props.

It also adds the `canceled` status to the `VariantAnalysisStatus` to
represent a stopped variant analysis.
This commit is contained in:
Koen Vlaswinkel
2022-09-22 11:58:26 +02:00
parent 9c076152cb
commit c9f65be721
7 changed files with 149 additions and 60 deletions

View File

@@ -34,6 +34,7 @@ export enum VariantAnalysisStatus {
InProgress = 'inProgress',
Succeeded = 'succeeded',
Failed = 'failed',
Canceled = 'canceled',
}
export enum VariantAnalysisFailureReason {

View File

@@ -1,10 +1,16 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
import { VariantAnalysisHeader } from '../../view/variant-analysis/VariantAnalysisHeader';
import { VariantAnalysisStatus } from '../../remote-queries/shared/variant-analysis';
import {
VariantAnalysis,
VariantAnalysisQueryLanguage,
VariantAnalysisRepoStatus,
VariantAnalysisScannedRepository,
VariantAnalysisStatus
} from '../../remote-queries/shared/variant-analysis';
export default {
title: 'Variant Analysis/Variant Analysis Header',
@@ -60,22 +66,65 @@ const Template: ComponentStory<typeof VariantAnalysisHeader> = (args) => (
<VariantAnalysisHeader {...args} />
);
const buildVariantAnalysis = (data: Partial<VariantAnalysis>) => ({
id: 1,
controllerRepoId: 1,
query: {
name: 'Query name',
filePath: 'example.ql',
language: VariantAnalysisQueryLanguage.Javascript,
},
databases: {},
status: VariantAnalysisStatus.InProgress,
...data,
});
const buildScannedRepo = (id: number, data?: Partial<VariantAnalysisScannedRepository>): VariantAnalysisScannedRepository => ({
repository: {
id: id,
fullName: `octodemo/hello-world-${id}`,
private: false,
},
analysisStatus: VariantAnalysisRepoStatus.Pending,
...data,
});
export const InProgress = Template.bind({});
InProgress.args = {
queryName: 'Query name',
queryFileName: 'example.ql',
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
totalRepositoryCount: 10,
completedRepositoryCount: 2,
resultCount: 99_999,
variantAnalysis: buildVariantAnalysis({
scannedRepos: [
buildScannedRepo(1, {
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
resultCount: 99_999,
}),
buildScannedRepo(2, {
analysisStatus: VariantAnalysisRepoStatus.Failed,
}),
buildScannedRepo(3, {
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
resultCount: 0,
}),
buildScannedRepo(4),
buildScannedRepo(5),
buildScannedRepo(6),
buildScannedRepo(7),
buildScannedRepo(8),
buildScannedRepo(9),
buildScannedRepo(10),
]
}),
};
export const Succeeded = Template.bind({});
Succeeded.args = {
...InProgress.args,
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
totalRepositoryCount: 1000,
completedRepositoryCount: 1000,
variantAnalysis: buildVariantAnalysis({
status: VariantAnalysisStatus.Succeeded,
scannedRepos: Array.from({ length: 1000 }, (_, i) => buildScannedRepo(i + 1, {
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
resultCount: 100,
}))
}),
duration: 720_000,
completedAt: new Date(1661263446000),
};
@@ -83,7 +132,9 @@ Succeeded.args = {
export const Failed = Template.bind({});
Failed.args = {
...InProgress.args,
variantAnalysisStatus: VariantAnalysisStatus.Failed,
variantAnalysis: buildVariantAnalysis({
status: VariantAnalysisStatus.Failed,
}),
duration: 10_000,
completedAt: new Date(1661263446000),
};

View File

@@ -46,7 +46,7 @@ Started.args = {
export const StartedWithWarnings = Template.bind({});
StartedWithWarnings.args = {
...Starting.args,
queryResult: 'warning',
hasWarnings: true,
};
export const Succeeded = Template.bind({});
@@ -64,7 +64,7 @@ SucceededWithWarnings.args = {
...Succeeded.args,
totalRepositoryCount: 10,
completedRepositoryCount: 2,
queryResult: 'warning',
hasWarnings: true,
};
export const Failed = Template.bind({});
@@ -78,5 +78,5 @@ Failed.args = {
export const Stopped = Template.bind({});
Stopped.args = {
...SucceededWithWarnings.args,
queryResult: 'stopped',
variantAnalysisStatus: VariantAnalysisStatus.Canceled,
};

View File

@@ -1,21 +1,14 @@
import * as React from 'react';
import { useMemo } from 'react';
import styled from 'styled-components';
import { VariantAnalysisStatus } from '../../remote-queries/shared/variant-analysis';
import { VariantAnalysis, VariantAnalysisRepoStatus } from '../../remote-queries/shared/variant-analysis';
import { QueryDetails } from './QueryDetails';
import { VariantAnalysisActions } from './VariantAnalysisActions';
import { VariantAnalysisStats } from './VariantAnalysisStats';
export type VariantAnalysisHeaderProps = {
queryName: string;
queryFileName: string;
variantAnalysisStatus: VariantAnalysisStatus;
variantAnalysis: VariantAnalysis;
totalRepositoryCount: number;
completedRepositoryCount?: number | undefined;
queryResult?: 'warning' | 'stopped';
resultCount?: number | undefined;
duration?: number | undefined;
completedAt?: Date | undefined;
@@ -42,15 +35,9 @@ const Row = styled.div`
`;
export const VariantAnalysisHeader = ({
queryName,
queryFileName,
totalRepositoryCount,
completedRepositoryCount,
queryResult,
resultCount,
variantAnalysis,
duration,
completedAt,
variantAnalysisStatus,
onOpenQueryFileClick,
onViewQueryTextClick,
onStopQueryClick,
@@ -58,28 +45,55 @@ export const VariantAnalysisHeader = ({
onExportResultsClick,
onViewLogsClick,
}: VariantAnalysisHeaderProps) => {
const totalRepositoryCount = useMemo(() => {
return variantAnalysis.scannedRepos?.length ?? 0;
}, [variantAnalysis.scannedRepos]);
const completedRepositoryCount = useMemo(() => {
return variantAnalysis.scannedRepos?.filter(repo => [
// All states that indicates the repository has been scanned and cannot
// change status anymore.
VariantAnalysisRepoStatus.Succeeded, VariantAnalysisRepoStatus.Failed,
VariantAnalysisRepoStatus.Canceled, VariantAnalysisRepoStatus.TimedOut,
].includes(repo.analysisStatus))?.length ?? 0;
}, [variantAnalysis.scannedRepos]);
const resultCount = useMemo(() => {
const reposWithResultCounts = variantAnalysis.scannedRepos?.filter(repo => repo.resultCount !== undefined);
if (reposWithResultCounts === undefined || reposWithResultCounts.length === 0) {
return undefined;
}
return reposWithResultCounts.map(repo => repo.resultCount ?? 0).reduce((a, b) => a + b, 0);
}, [variantAnalysis.scannedRepos]);
const hasSkippedRepos = useMemo(() => {
if (!variantAnalysis.skippedRepos) {
return false;
}
return Object.values(variantAnalysis.skippedRepos).some(skippedRepos => skippedRepos.length > 0);
}, [variantAnalysis.skippedRepos]);
return (
<Container>
<Row>
<QueryDetails
queryName={queryName}
queryFileName={queryFileName}
queryName={variantAnalysis.query.name}
queryFileName={variantAnalysis.query.filePath}
onOpenQueryFileClick={onOpenQueryFileClick}
onViewQueryTextClick={onViewQueryTextClick}
/>
<VariantAnalysisActions
variantAnalysisStatus={variantAnalysisStatus}
variantAnalysisStatus={variantAnalysis.status}
onStopQueryClick={onStopQueryClick}
onCopyRepositoryListClick={onCopyRepositoryListClick}
onExportResultsClick={onExportResultsClick}
/>
</Row>
<VariantAnalysisStats
variantAnalysisStatus={variantAnalysisStatus}
variantAnalysisStatus={variantAnalysis.status}
totalRepositoryCount={totalRepositoryCount}
completedRepositoryCount={completedRepositoryCount}
queryResult={queryResult}
resultCount={resultCount}
hasWarnings={hasSkippedRepos}
duration={duration}
completedAt={completedAt}
onViewLogsClick={onViewLogsClick}

View File

@@ -9,14 +9,14 @@ type Props = {
totalRepositoryCount: number;
completedRepositoryCount?: number | undefined;
queryResult?: 'warning' | 'stopped';
showWarning?: boolean;
};
export const VariantAnalysisRepositoriesStats = ({
variantAnalysisStatus,
totalRepositoryCount,
completedRepositoryCount = 0,
queryResult,
showWarning,
}: Props) => {
if (variantAnalysisStatus === VariantAnalysisStatus.Failed) {
return (
@@ -29,8 +29,8 @@ export const VariantAnalysisRepositoriesStats = ({
return (
<>
{formatDecimal(completedRepositoryCount)}/{formatDecimal(totalRepositoryCount)}
{queryResult && <><HorizontalSpace size={2} /><WarningIcon /></>}
{!queryResult && variantAnalysisStatus === VariantAnalysisStatus.Succeeded &&
{showWarning && <><HorizontalSpace size={2} /><WarningIcon /></>}
{!showWarning && variantAnalysisStatus === VariantAnalysisStatus.Succeeded &&
<><HorizontalSpace size={2} /><SuccessIcon label="Completed" /></>}
</>
);

View File

@@ -14,7 +14,7 @@ export type VariantAnalysisStatsProps = {
totalRepositoryCount: number;
completedRepositoryCount?: number | undefined;
queryResult?: 'warning' | 'stopped';
hasWarnings?: boolean;
resultCount?: number | undefined;
duration?: number | undefined;
@@ -33,7 +33,7 @@ export const VariantAnalysisStats = ({
variantAnalysisStatus,
totalRepositoryCount,
completedRepositoryCount = 0,
queryResult,
hasWarnings,
resultCount,
duration,
completedAt,
@@ -48,16 +48,16 @@ export const VariantAnalysisStats = ({
return 'Failed';
}
if (queryResult === 'warning') {
return 'Succeeded warnings';
}
if (queryResult === 'stopped') {
if (variantAnalysisStatus === VariantAnalysisStatus.Canceled) {
return 'Stopped';
}
if (variantAnalysisStatus === VariantAnalysisStatus.Succeeded && hasWarnings) {
return 'Succeeded warnings';
}
return 'Succeeded';
}, [variantAnalysisStatus, queryResult]);
}, [variantAnalysisStatus]);
return (
<Row>
@@ -69,7 +69,7 @@ export const VariantAnalysisStats = ({
variantAnalysisStatus={variantAnalysisStatus}
totalRepositoryCount={totalRepositoryCount}
completedRepositoryCount={completedRepositoryCount}
queryResult={queryResult}
showWarning={hasWarnings}
/>
</StatItem>
<StatItem title="Duration">

View File

@@ -39,16 +39,8 @@ describe(VariantAnalysisStats.name, () => {
expect(screen.getByText('654,321/123,456')).toBeInTheDocument();
});
it('renders a warning icon when the query result is a warning', () => {
render({ queryResult: 'warning' });
expect(screen.getByRole('img', {
name: 'Warning',
})).toBeInTheDocument();
});
it('renders a warning icon when the query result is stopped', () => {
render({ queryResult: 'stopped' });
it('renders a warning icon when has warnings is set', () => {
render({ hasWarnings: true });
expect(screen.getByRole('img', {
name: 'Warning',
@@ -77,4 +69,35 @@ describe(VariantAnalysisStats.name, () => {
userEvent.click(screen.getByText('View logs'));
expect(onViewLogsClick).toHaveBeenCalledTimes(1);
});
it('renders a running text when the variant analysis status is in progress', () => {
render({ variantAnalysisStatus: VariantAnalysisStatus.InProgress });
expect(screen.getByText('Running')).toBeInTheDocument();
});
it('renders a failed text when the variant analysis status is failed', () => {
render({ variantAnalysisStatus: VariantAnalysisStatus.Failed });
expect(screen.getByText('Failed')).toBeInTheDocument();
});
it('renders a stopped text when the variant analysis status is canceled', () => {
render({ variantAnalysisStatus: VariantAnalysisStatus.Canceled });
expect(screen.getByText('Stopped')).toBeInTheDocument();
});
it('renders a succeeded warnings text when the variant analysis status is succeeded and has warnings', () => {
render({ variantAnalysisStatus: VariantAnalysisStatus.Succeeded, hasWarnings: true });
expect(screen.getByText('Succeeded warnings')).toBeInTheDocument();
});
it('renders a succeeded text when the variant analysis status is succeeded', () => {
render({ variantAnalysisStatus: VariantAnalysisStatus.Succeeded });
expect(screen.getByText('Succeeded')).toBeInTheDocument();
expect(screen.queryByText('Succeeded warnings')).not.toBeInTheDocument();
});
});