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:
@@ -34,6 +34,7 @@ export enum VariantAnalysisStatus {
|
||||
InProgress = 'inProgress',
|
||||
Succeeded = 'succeeded',
|
||||
Failed = 'failed',
|
||||
Canceled = 'canceled',
|
||||
}
|
||||
|
||||
export enum VariantAnalysisFailureReason {
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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" /></>}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user