Merge pull request #1544 from github/koesie10/scanned-repos-tab
Add analyzed repositories component
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Repository } from './repository';
|
||||
import { AnalysisAlert, AnalysisRawResults } from './analysis-result';
|
||||
|
||||
export interface VariantAnalysis {
|
||||
id: number,
|
||||
@@ -78,6 +79,12 @@ export interface VariantAnalysisSkippedRepositoryGroup {
|
||||
}>
|
||||
}
|
||||
|
||||
export interface VariantAnalysisScannedRepositoryResult {
|
||||
repositoryId: number;
|
||||
interpretedResults?: AnalysisAlert[];
|
||||
rawResults?: AnalysisRawResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures information needed to submit a variant
|
||||
* analysis for processing.
|
||||
@@ -102,16 +109,24 @@ export interface VariantAnalysisSubmission {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param repo
|
||||
* @returns whether the repo scan is in a completed state, i.e. it cannot normally change state anymore
|
||||
* @param status
|
||||
* @returns whether the status is in a completed state, i.e. it cannot normally change state anymore
|
||||
*/
|
||||
export function hasRepoScanCompleted(repo: VariantAnalysisScannedRepository): boolean {
|
||||
export function isCompletedAnalysisRepoStatus(status: VariantAnalysisRepoStatus): boolean {
|
||||
return [
|
||||
// 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);
|
||||
].includes(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param repo
|
||||
* @returns whether the repo scan is in a completed state, i.e. it cannot normally change state anymore
|
||||
*/
|
||||
export function hasRepoScanCompleted(repo: VariantAnalysisScannedRepository): boolean {
|
||||
return isCompletedAnalysisRepoStatus(repo.analysisStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
26
extensions/ql-vscode/src/stories/remote-queries/data/rawResults.json
generated
Normal file
26
extensions/ql-vscode/src/stories/remote-queries/data/rawResults.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema": {
|
||||
"name": "#select",
|
||||
"rows": 1,
|
||||
"columns": [
|
||||
{
|
||||
"kind": "i"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resultSet": {
|
||||
"schema": {
|
||||
"name": "#select",
|
||||
"rows": 1,
|
||||
"columns": [
|
||||
{
|
||||
"kind": "i"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rows": [[60688]]
|
||||
},
|
||||
"fileLinkPrefix": "https://github.com/facebook/create-react-app/blob/d960b9e38c062584ff6cfb1a70e1512509a966e7",
|
||||
"sourceLocationPrefix": "/home/runner/work/bulk-builder/bulk-builder",
|
||||
"capped": false
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
|
||||
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
|
||||
import { VariantAnalysisAnalyzedRepoItem } from '../../view/variant-analysis/VariantAnalysisAnalyzedRepoItem';
|
||||
import { VariantAnalysisRepoStatus } from '../../remote-queries/shared/variant-analysis';
|
||||
import { AnalysisAlert, AnalysisRawResults } from '../../remote-queries/shared/analysis-result';
|
||||
|
||||
import analysesResults from '../remote-queries/data/analysesResultsMessage.json';
|
||||
import rawResults from '../remote-queries/data/rawResults.json';
|
||||
|
||||
export default {
|
||||
title: 'Variant Analysis/Analyzed Repo Item',
|
||||
component: VariantAnalysisAnalyzedRepoItem,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<VariantAnalysisContainer>
|
||||
<Story />
|
||||
</VariantAnalysisContainer>
|
||||
)
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisAnalyzedRepoItem>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisAnalyzedRepoItem> = (args) => (
|
||||
<VariantAnalysisAnalyzedRepoItem {...args} />
|
||||
);
|
||||
|
||||
export const Pending = Template.bind({});
|
||||
Pending.args = {
|
||||
repository: {
|
||||
id: 63537249,
|
||||
fullName: 'facebook/create-react-app',
|
||||
private: false,
|
||||
},
|
||||
status: VariantAnalysisRepoStatus.Pending,
|
||||
};
|
||||
|
||||
export const InProgress = Template.bind({});
|
||||
InProgress.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.InProgress,
|
||||
interpretedResults: undefined,
|
||||
};
|
||||
|
||||
export const Failed = Template.bind({});
|
||||
Failed.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.Failed,
|
||||
interpretedResults: undefined,
|
||||
};
|
||||
|
||||
export const TimedOut = Template.bind({});
|
||||
TimedOut.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.TimedOut,
|
||||
};
|
||||
|
||||
export const Canceled = Template.bind({});
|
||||
Canceled.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.Canceled,
|
||||
};
|
||||
|
||||
export const InterpretedResults = Template.bind({});
|
||||
InterpretedResults.args = {
|
||||
...Pending.args,
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 198,
|
||||
interpretedResults: analysesResults.analysesResults.find(v => v.nwo === 'facebook/create-react-app')?.interpretedResults as unknown as AnalysisAlert[],
|
||||
};
|
||||
|
||||
export const RawResults = Template.bind({});
|
||||
RawResults.args = {
|
||||
...InterpretedResults.args,
|
||||
interpretedResults: undefined,
|
||||
resultCount: 1,
|
||||
rawResults: rawResults as unknown as AnalysisRawResults,
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
|
||||
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
|
||||
import { VariantAnalysisAnalyzedRepos } from '../../view/variant-analysis/VariantAnalysisAnalyzedRepos';
|
||||
import {
|
||||
VariantAnalysisQueryLanguage,
|
||||
VariantAnalysisRepoStatus,
|
||||
VariantAnalysisStatus
|
||||
} from '../../remote-queries/shared/variant-analysis';
|
||||
import { AnalysisAlert } from '../../remote-queries/shared/analysis-result';
|
||||
|
||||
import analysesResults from '../remote-queries/data/analysesResultsMessage.json';
|
||||
|
||||
export default {
|
||||
title: 'Variant Analysis/Analyzed Repos',
|
||||
component: VariantAnalysisAnalyzedRepos,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<VariantAnalysisContainer>
|
||||
<Story />
|
||||
</VariantAnalysisContainer>
|
||||
)
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisAnalyzedRepos>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisAnalyzedRepos> = (args) => (
|
||||
<VariantAnalysisAnalyzedRepos {...args} />
|
||||
);
|
||||
|
||||
const interpretedResultsForRepo = (nwo: string): AnalysisAlert[] | undefined => {
|
||||
return analysesResults.analysesResults.find(v => v.nwo === nwo)?.interpretedResults as unknown as AnalysisAlert[];
|
||||
};
|
||||
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
variantAnalysis: {
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
query: {
|
||||
name: 'Query name',
|
||||
filePath: 'example.ql',
|
||||
language: VariantAnalysisQueryLanguage.Javascript,
|
||||
},
|
||||
databases: {},
|
||||
status: VariantAnalysisStatus.InProgress,
|
||||
scannedRepos: [
|
||||
{
|
||||
repository: {
|
||||
id: 63537249,
|
||||
fullName: 'facebook/create-react-app',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Succeeded, resultCount: 198,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 167174,
|
||||
fullName: 'jquery/jquery',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 67,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 237159,
|
||||
fullName: 'expressjs/express',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 26,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 15062869,
|
||||
fullName: 'facebook/jest',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Failed,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 24195339,
|
||||
fullName: 'angular/angular',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.InProgress,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 24560307,
|
||||
fullName: 'babel/babel',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
},
|
||||
]
|
||||
},
|
||||
repositoryResults: [
|
||||
{
|
||||
repositoryId: 63537249,
|
||||
interpretedResults: interpretedResultsForRepo('facebook/create-react-app'),
|
||||
},
|
||||
{
|
||||
repositoryId: 167174,
|
||||
interpretedResults: interpretedResultsForRepo('jquery/jquery'),
|
||||
},
|
||||
{
|
||||
repositoryId: 237159,
|
||||
interpretedResults: interpretedResultsForRepo('expressjs/express'),
|
||||
}
|
||||
]
|
||||
}
|
||||
;
|
||||
13
extensions/ql-vscode/src/view/common/icon/LoadingIcon.tsx
Normal file
13
extensions/ql-vscode/src/view/common/icon/LoadingIcon.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { Codicon } from './Codicon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const LoadingIcon = ({
|
||||
label = 'Loading...',
|
||||
className,
|
||||
}: Props) => <Codicon name="loading" label={label} className={classNames(className, 'codicon-modifier-spin')} />;
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './Codicon';
|
||||
export * from './ErrorIcon';
|
||||
export * from './LoadingIcon';
|
||||
export * from './SuccessIcon';
|
||||
export * from './WarningIcon';
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { AnalysisAlert, AnalysisRawResults } from '../../remote-queries/shared/analysis-result';
|
||||
import AnalysisAlertResult from '../remote-queries/AnalysisAlertResult';
|
||||
import RawResultsTable from '../remote-queries/RawResultsTable';
|
||||
import { VariantAnalysisRepoStatus } from '../../remote-queries/shared/variant-analysis';
|
||||
import { Alert } from '../common';
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const AlertContainer = styled.div`
|
||||
margin-top: 1em;
|
||||
`;
|
||||
|
||||
const InterpretedResultsContainer = styled.ul`
|
||||
list-style-type: none;
|
||||
margin: 1em 0 0;
|
||||
padding: 0.5em 0 0 0;
|
||||
`;
|
||||
|
||||
const InterpretedResultItem = styled.li`
|
||||
margin-bottom: 1em;
|
||||
background-color: var(--vscode-notifications-background);
|
||||
`;
|
||||
|
||||
const RawResultsContainer = styled.div`
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
`;
|
||||
|
||||
export type AnalyzedRepoItemContentProps = {
|
||||
status: VariantAnalysisRepoStatus;
|
||||
|
||||
interpretedResults?: AnalysisAlert[];
|
||||
rawResults?: AnalysisRawResults;
|
||||
}
|
||||
|
||||
export const AnalyzedRepoItemContent = ({
|
||||
status,
|
||||
interpretedResults,
|
||||
rawResults,
|
||||
}: AnalyzedRepoItemContentProps) => {
|
||||
return (
|
||||
<ContentContainer>
|
||||
{status === VariantAnalysisRepoStatus.Failed && <AlertContainer>
|
||||
<Alert
|
||||
type="error"
|
||||
title="Failed"
|
||||
message="The query failed to run on this repository."
|
||||
/>
|
||||
</AlertContainer>}
|
||||
{status === VariantAnalysisRepoStatus.TimedOut && <AlertContainer>
|
||||
<Alert
|
||||
type="error"
|
||||
title="Timed out"
|
||||
message="The analysis ran out of time and we couldn't scan the repository."
|
||||
/>
|
||||
</AlertContainer>}
|
||||
{status === VariantAnalysisRepoStatus.Canceled && <AlertContainer>
|
||||
<Alert
|
||||
type="error"
|
||||
title="Canceled"
|
||||
message="The variant analysis or this repository was canceled."
|
||||
/>
|
||||
</AlertContainer>}
|
||||
{interpretedResults && (
|
||||
<InterpretedResultsContainer>
|
||||
{interpretedResults.map((r, i) =>
|
||||
<InterpretedResultItem key={i}>
|
||||
<AnalysisAlertResult alert={r} />
|
||||
</InterpretedResultItem>)}
|
||||
</InterpretedResultsContainer>
|
||||
)}
|
||||
{rawResults && (
|
||||
<RawResultsContainer>
|
||||
<RawResultsTable
|
||||
schema={rawResults.schema}
|
||||
results={rawResults.resultSet}
|
||||
fileLinkPrefix={rawResults.fileLinkPrefix}
|
||||
sourceLocationPrefix={rawResults.sourceLocationPrefix} />
|
||||
</RawResultsContainer>
|
||||
)}
|
||||
</ContentContainer>
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import * as React from 'react';
|
||||
import {
|
||||
VariantAnalysis as VariantAnalysisDomainModel,
|
||||
VariantAnalysisQueryLanguage,
|
||||
VariantAnalysisRepoStatus,
|
||||
VariantAnalysisRepoStatus, VariantAnalysisScannedRepositoryResult,
|
||||
VariantAnalysisStatus
|
||||
} from '../../remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysisContainer } from './VariantAnalysisContainer';
|
||||
@@ -29,7 +29,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
fullName: 'octodemo/hello-world-1',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
@@ -37,7 +37,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
fullName: 'octodemo/hello-world-2',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
analysisStatus: VariantAnalysisRepoStatus.Canceled,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
@@ -45,7 +45,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
fullName: 'octodemo/hello-world-3',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
analysisStatus: VariantAnalysisRepoStatus.TimedOut,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
@@ -53,7 +53,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
fullName: 'octodemo/hello-world-4',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
analysisStatus: VariantAnalysisRepoStatus.Failed,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
@@ -61,7 +61,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
fullName: 'octodemo/hello-world-5',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
analysisStatus: VariantAnalysisRepoStatus.InProgress,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
@@ -69,7 +69,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
fullName: 'octodemo/hello-world-6',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
analysisStatus: VariantAnalysisRepoStatus.InProgress,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
@@ -158,6 +158,42 @@ const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
},
|
||||
};
|
||||
|
||||
const repositoryResults: VariantAnalysisScannedRepositoryResult[] = [
|
||||
{
|
||||
repositoryId: 1,
|
||||
rawResults: {
|
||||
schema: {
|
||||
name: '#select',
|
||||
rows: 1,
|
||||
columns: [
|
||||
{
|
||||
kind: 'i'
|
||||
}
|
||||
]
|
||||
},
|
||||
resultSet: {
|
||||
schema: {
|
||||
name: '#select',
|
||||
rows: 1,
|
||||
columns: [
|
||||
{
|
||||
kind: 'i'
|
||||
}
|
||||
]
|
||||
},
|
||||
rows: [
|
||||
[
|
||||
60688
|
||||
]
|
||||
]
|
||||
},
|
||||
fileLinkPrefix: 'https://github.com/octodemo/hello-world-1/blob/59a2a6c7d9dde7a6ecb77c2f7e8197d6925c143b',
|
||||
sourceLocationPrefix: '/home/runner/work/bulk-builder/bulk-builder',
|
||||
capped: false
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function getContainerContents(variantAnalysis: VariantAnalysisDomainModel) {
|
||||
if (variantAnalysis.actionsWorkflowRunId === undefined) {
|
||||
return <VariantAnalysisLoading />;
|
||||
@@ -174,7 +210,10 @@ function getContainerContents(variantAnalysis: VariantAnalysisDomainModel) {
|
||||
onExportResultsClick={() => console.log('Export results')}
|
||||
onViewLogsClick={() => console.log('View logs')}
|
||||
/>
|
||||
<VariantAnalysisOutcomePanels variantAnalysis={variantAnalysis} />
|
||||
<VariantAnalysisOutcomePanels
|
||||
variantAnalysis={variantAnalysis}
|
||||
repositoryResults={repositoryResults}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VSCodeBadge } from '@vscode/webview-ui-toolkit/react';
|
||||
import { isCompletedAnalysisRepoStatus, VariantAnalysisRepoStatus } from '../../remote-queries/shared/variant-analysis';
|
||||
import { formatDecimal } from '../../pure/number';
|
||||
import { Codicon, ErrorIcon, LoadingIcon, SuccessIcon } from '../common';
|
||||
import { Repository } from '../../remote-queries/shared/repository';
|
||||
import { AnalysisAlert, AnalysisRawResults } from '../../remote-queries/shared/analysis-result';
|
||||
import { AnalyzedRepoItemContent } from './AnalyzedRepoItemContent';
|
||||
|
||||
// This will ensure that these icons have a className which we can use in the TitleContainer
|
||||
const ExpandCollapseCodicon = styled(Codicon)``;
|
||||
|
||||
const TitleContainer = styled.button`
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
align-items: center;
|
||||
|
||||
color: var(--vscode-editor-foreground);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
|
||||
${ExpandCollapseCodicon} {
|
||||
color: var(--vscode-disabledForeground);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Visibility = styled.span`
|
||||
font-size: 0.85em;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
`;
|
||||
|
||||
export type VariantAnalysisAnalyzedRepoItemProps = {
|
||||
repository: Repository;
|
||||
status: VariantAnalysisRepoStatus;
|
||||
resultCount?: number;
|
||||
|
||||
interpretedResults?: AnalysisAlert[];
|
||||
rawResults?: AnalysisRawResults;
|
||||
}
|
||||
|
||||
const getErrorLabel = (status: VariantAnalysisRepoStatus.Failed | VariantAnalysisRepoStatus.TimedOut | VariantAnalysisRepoStatus.Canceled): string => {
|
||||
switch (status) {
|
||||
case VariantAnalysisRepoStatus.Failed:
|
||||
return 'Failed';
|
||||
case VariantAnalysisRepoStatus.TimedOut:
|
||||
return 'Timed out';
|
||||
case VariantAnalysisRepoStatus.Canceled:
|
||||
return 'Canceled';
|
||||
}
|
||||
};
|
||||
|
||||
export const VariantAnalysisAnalyzedRepoItem = ({
|
||||
repository,
|
||||
status,
|
||||
resultCount,
|
||||
interpretedResults,
|
||||
rawResults,
|
||||
}: VariantAnalysisAnalyzedRepoItemProps) => {
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
|
||||
const toggleExpanded = useCallback(() => {
|
||||
setExpanded(oldIsExpanded => !oldIsExpanded);
|
||||
}, []);
|
||||
|
||||
const disabled = !isCompletedAnalysisRepoStatus(status);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TitleContainer onClick={toggleExpanded} disabled={disabled} aria-expanded={isExpanded}>
|
||||
{isExpanded ? <ExpandCollapseCodicon name="chevron-down" label="Collapse" /> : <ExpandCollapseCodicon name="chevron-right" label="Expand" />}
|
||||
<VSCodeBadge>{resultCount === undefined ? '-' : formatDecimal(resultCount)}</VSCodeBadge>
|
||||
<span>{repository.fullName}</span>
|
||||
<Visibility>{repository.private ? 'private' : 'public'}</Visibility>
|
||||
<span>
|
||||
{status === VariantAnalysisRepoStatus.Succeeded && <SuccessIcon />}
|
||||
{(status === VariantAnalysisRepoStatus.Failed || status === VariantAnalysisRepoStatus.TimedOut || status === VariantAnalysisRepoStatus.Canceled) && <ErrorIcon label={getErrorLabel(status)} />}
|
||||
{status === VariantAnalysisRepoStatus.InProgress && <LoadingIcon label="In progress" />}
|
||||
</span>
|
||||
</TitleContainer>
|
||||
{isExpanded && <AnalyzedRepoItemContent status={status} interpretedResults={interpretedResults} rawResults={rawResults} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VariantAnalysis, VariantAnalysisScannedRepositoryResult } from '../../remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysisAnalyzedRepoItem } from './VariantAnalysisAnalyzedRepoItem';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const VariantAnalysisAnalyzedRepos = () => {
|
||||
return <div>This is the analyzed view</div>;
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
`;
|
||||
|
||||
export type VariantAnalysisAnalyzedReposProps = {
|
||||
variantAnalysis: VariantAnalysis;
|
||||
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
|
||||
}
|
||||
|
||||
export const VariantAnalysisAnalyzedRepos = ({
|
||||
variantAnalysis,
|
||||
repositoryResults,
|
||||
}: VariantAnalysisAnalyzedReposProps) => {
|
||||
const repositoryResultsById = useMemo(() => {
|
||||
const map = new Map<number, VariantAnalysisScannedRepositoryResult>();
|
||||
repositoryResults?.forEach((repository) => {
|
||||
map.set(repository.repositoryId, repository);
|
||||
});
|
||||
return map;
|
||||
}, [repositoryResults]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{variantAnalysis.scannedRepos?.map(repository => {
|
||||
const results = repositoryResultsById.get(repository.repository.id);
|
||||
|
||||
return (
|
||||
<VariantAnalysisAnalyzedRepoItem
|
||||
key={repository.repository.id}
|
||||
repository={repository.repository}
|
||||
status={repository.analysisStatus}
|
||||
resultCount={repository.resultCount}
|
||||
interpretedResults={results?.interpretedResults}
|
||||
rawResults={results?.rawResults}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VSCodeBadge, VSCodePanels, VSCodePanelTab, VSCodePanelView } from '@vscode/webview-ui-toolkit/react';
|
||||
import { formatDecimal } from '../../pure/number';
|
||||
import { VariantAnalysis } from '../../remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysis, VariantAnalysisScannedRepositoryResult } from '../../remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysisAnalyzedRepos } from './VariantAnalysisAnalyzedRepos';
|
||||
import { VariantAnalysisNotFoundRepos } from './VariantAnalysisNotFoundRepos';
|
||||
import { VariantAnalysisNoCodeqlDbRepos } from './VariantAnalysisNoCodeqlDbRepos';
|
||||
@@ -10,6 +10,7 @@ import { Alert } from '../common';
|
||||
|
||||
export type VariantAnalysisOutcomePanelProps = {
|
||||
variantAnalysis: VariantAnalysis;
|
||||
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
|
||||
};
|
||||
|
||||
const Tab = styled(VSCodePanelTab)`
|
||||
@@ -33,7 +34,8 @@ const WarningsContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const VariantAnalysisOutcomePanels = ({
|
||||
variantAnalysis
|
||||
variantAnalysis,
|
||||
repositoryResults,
|
||||
}: VariantAnalysisOutcomePanelProps) => {
|
||||
const noCodeqlDbRepositoryCount = variantAnalysis.skippedRepos?.noCodeqlDbRepos?.repositoryCount ?? 0;
|
||||
const notFoundRepositoryCount = variantAnalysis.skippedRepos?.notFoundRepos?.repositoryCount ?? 0;
|
||||
@@ -63,7 +65,7 @@ export const VariantAnalysisOutcomePanels = ({
|
||||
return (
|
||||
<>
|
||||
{warnings}
|
||||
<VariantAnalysisAnalyzedRepos />
|
||||
<VariantAnalysisAnalyzedRepos variantAnalysis={variantAnalysis} repositoryResults={repositoryResults} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -88,7 +90,7 @@ export const VariantAnalysisOutcomePanels = ({
|
||||
<VSCodeBadge appearance="secondary">{formatDecimal(noCodeqlDbRepositoryCount)}</VSCodeBadge>
|
||||
</Tab>
|
||||
)}
|
||||
<VSCodePanelView><VariantAnalysisAnalyzedRepos /></VSCodePanelView>
|
||||
<VSCodePanelView><VariantAnalysisAnalyzedRepos variantAnalysis={variantAnalysis} repositoryResults={repositoryResults} /></VSCodePanelView>
|
||||
{notFoundRepositoryCount > 0 && <VSCodePanelView><VariantAnalysisNotFoundRepos /></VSCodePanelView>}
|
||||
{noCodeqlDbRepositoryCount > 0 && <VSCodePanelView><VariantAnalysisNoCodeqlDbRepos /></VSCodePanelView>}
|
||||
</VSCodePanels>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import * as React from 'react';
|
||||
import { render as reactRender, screen } from '@testing-library/react';
|
||||
import { VariantAnalysisRepoStatus } from '../../../remote-queries/shared/variant-analysis';
|
||||
import { AnalyzedRepoItemContent, AnalyzedRepoItemContentProps } from '../AnalyzedRepoItemContent';
|
||||
|
||||
describe(AnalyzedRepoItemContent.name, () => {
|
||||
const render = (props: Partial<AnalyzedRepoItemContentProps> = {}) => {
|
||||
return reactRender(
|
||||
<AnalyzedRepoItemContent
|
||||
status={VariantAnalysisRepoStatus.Succeeded}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders the succeeded state with interpreted results', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
interpretedResults: [
|
||||
{
|
||||
message: {
|
||||
tokens: [
|
||||
{
|
||||
t: 'text',
|
||||
text: 'This is an empty block.'
|
||||
}
|
||||
]
|
||||
},
|
||||
shortDescription: 'This is an empty block.',
|
||||
fileLink: {
|
||||
fileLinkPrefix: 'https://github.com/facebook/create-react-app/blob/f34d88e30c7d8be7181f728d1abc4fd8d5cd07d3',
|
||||
filePath: 'packages/create-react-app/createReactApp.js'
|
||||
},
|
||||
severity: 'Warning',
|
||||
codeSnippet: {
|
||||
startLine: 655,
|
||||
endLine: 662,
|
||||
text: ' try {\n callback();\n } catch (ignored) {\n // Callback might throw and fail, since it\'s a temp directory the\n // OS will clean it up eventually...\n }\n },\n });\n'
|
||||
},
|
||||
highlightedRegion: {
|
||||
startLine: 657,
|
||||
startColumn: 31,
|
||||
endLine: 660,
|
||||
endColumn: 14
|
||||
},
|
||||
codeFlows: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
expect(screen.getByText('This is an empty block.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the succeeded state with raw results', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
rawResults: {
|
||||
schema: {
|
||||
name: '#select',
|
||||
rows: 1,
|
||||
columns: [
|
||||
{
|
||||
kind: 'i'
|
||||
}
|
||||
]
|
||||
},
|
||||
resultSet: {
|
||||
schema: {
|
||||
name: '#select',
|
||||
rows: 1,
|
||||
columns: [
|
||||
{
|
||||
kind: 'i'
|
||||
}
|
||||
]
|
||||
},
|
||||
rows: [
|
||||
[
|
||||
60688
|
||||
]
|
||||
]
|
||||
},
|
||||
fileLinkPrefix: 'https://github.com/octodemo/hello-world-1/blob/59a2a6c7d9dde7a6ecb77c2f7e8197d6925c143b',
|
||||
sourceLocationPrefix: '/home/runner/work/bulk-builder/bulk-builder',
|
||||
capped: false
|
||||
}
|
||||
});
|
||||
|
||||
expect(screen.getByText('60688')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the failed state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Failed,
|
||||
});
|
||||
|
||||
expect(screen.getByText('Error: Failed')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the timed out state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.TimedOut,
|
||||
});
|
||||
|
||||
expect(screen.getByText('Error: Timed out')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the canceled state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Canceled,
|
||||
});
|
||||
|
||||
expect(screen.getByText('Error: Canceled')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
import * as React from 'react';
|
||||
import { render as reactRender, screen } from '@testing-library/react';
|
||||
import { VariantAnalysisRepoStatus } from '../../../remote-queries/shared/variant-analysis';
|
||||
import {
|
||||
VariantAnalysisAnalyzedRepoItem,
|
||||
VariantAnalysisAnalyzedRepoItemProps
|
||||
} from '../VariantAnalysisAnalyzedRepoItem';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe(VariantAnalysisAnalyzedRepoItem.name, () => {
|
||||
const render = (props: Partial<VariantAnalysisAnalyzedRepoItemProps> = {}) => {
|
||||
return reactRender(
|
||||
<VariantAnalysisAnalyzedRepoItem
|
||||
repository={{
|
||||
id: 1,
|
||||
fullName: 'octodemo/hello-world-1',
|
||||
private: false,
|
||||
}}
|
||||
status={VariantAnalysisRepoStatus.Pending}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders the pending state', () => {
|
||||
render();
|
||||
|
||||
expect(screen.getByText('octodemo/hello-world-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByRole('img', {
|
||||
// There should not be any icons, except the expand icon
|
||||
name: (name) => name.toLowerCase() !== 'expand',
|
||||
})).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByRole<HTMLButtonElement>('button', {
|
||||
expanded: false
|
||||
})).toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders the in progress state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.InProgress,
|
||||
});
|
||||
|
||||
expect(screen.getByRole('img', {
|
||||
name: 'In progress',
|
||||
})).toBeInTheDocument();
|
||||
expect(screen.getByRole<HTMLButtonElement>('button', {
|
||||
expanded: false
|
||||
})).toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders the succeeded state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Succeeded,
|
||||
resultCount: 178,
|
||||
});
|
||||
|
||||
expect(screen.getByRole('img', {
|
||||
name: 'Success',
|
||||
})).toBeInTheDocument();
|
||||
expect(screen.getByText('178')).toBeInTheDocument();
|
||||
expect(screen.getByRole<HTMLButtonElement>('button', {
|
||||
expanded: false
|
||||
})).toBeEnabled();
|
||||
});
|
||||
|
||||
it('renders the failed state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Failed,
|
||||
});
|
||||
|
||||
expect(screen.getByRole('img', {
|
||||
name: 'Failed',
|
||||
})).toBeInTheDocument();
|
||||
expect(screen.getByRole<HTMLButtonElement>('button', {
|
||||
expanded: false
|
||||
})).toBeEnabled();
|
||||
});
|
||||
|
||||
it('renders the timed out state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.TimedOut,
|
||||
});
|
||||
|
||||
expect(screen.getByRole('img', {
|
||||
name: 'Timed out',
|
||||
})).toBeInTheDocument();
|
||||
expect(screen.getByRole<HTMLButtonElement>('button', {
|
||||
expanded: false
|
||||
})).toBeEnabled();
|
||||
});
|
||||
|
||||
it('renders the canceled state', () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.Canceled,
|
||||
});
|
||||
|
||||
expect(screen.getByRole('img', {
|
||||
name: 'Canceled',
|
||||
})).toBeInTheDocument();
|
||||
expect(screen.getByRole<HTMLButtonElement>('button', {
|
||||
expanded: false
|
||||
})).toBeEnabled();
|
||||
});
|
||||
|
||||
it('shows the repo as public', () => {
|
||||
render({
|
||||
repository: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/hello-world-1',
|
||||
private: false,
|
||||
}
|
||||
});
|
||||
|
||||
expect(screen.getByText('public')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the repo as private', () => {
|
||||
render({
|
||||
repository: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/hello-world-1',
|
||||
private: true,
|
||||
}
|
||||
});
|
||||
|
||||
expect(screen.getByText('private')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can expand the repo item', async () => {
|
||||
render({
|
||||
status: VariantAnalysisRepoStatus.TimedOut,
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {
|
||||
expanded: false
|
||||
}));
|
||||
|
||||
screen.getByRole('button', {
|
||||
expanded: true,
|
||||
});
|
||||
screen.getByText('Error: Timed out');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import * as React from 'react';
|
||||
import { render as reactRender, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import {
|
||||
VariantAnalysisQueryLanguage,
|
||||
VariantAnalysisRepoStatus,
|
||||
VariantAnalysisStatus
|
||||
} from '../../../remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysisAnalyzedRepos, VariantAnalysisAnalyzedReposProps } from '../VariantAnalysisAnalyzedRepos';
|
||||
|
||||
describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
const defaultVariantAnalysis = {
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
actionsWorkflowRunId: 789263,
|
||||
query: {
|
||||
name: 'Example query',
|
||||
filePath: 'example.ql',
|
||||
language: VariantAnalysisQueryLanguage.Javascript,
|
||||
},
|
||||
databases: {},
|
||||
status: VariantAnalysisStatus.InProgress,
|
||||
scannedRepos: [
|
||||
{
|
||||
repository: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/hello-world-1',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Pending,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 2,
|
||||
fullName: 'octodemo/hello-world-2',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 3,
|
||||
fullName: 'octodemo/hello-world-3',
|
||||
private: true,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.Failed,
|
||||
},
|
||||
{
|
||||
repository: {
|
||||
id: 4,
|
||||
fullName: 'octodemo/hello-world-4',
|
||||
private: false,
|
||||
},
|
||||
analysisStatus: VariantAnalysisRepoStatus.InProgress,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const render = (props: Partial<VariantAnalysisAnalyzedReposProps> = {}) => {
|
||||
return reactRender(
|
||||
<VariantAnalysisAnalyzedRepos
|
||||
variantAnalysis={defaultVariantAnalysis}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders the repository names', () => {
|
||||
render();
|
||||
|
||||
expect(screen.getByText('octodemo/hello-world-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('octodemo/hello-world-2')).toBeInTheDocument();
|
||||
expect(screen.getByText('octodemo/hello-world-3')).toBeInTheDocument();
|
||||
expect(screen.getByText('octodemo/hello-world-4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the interpreted result for a succeeded repo', async () => {
|
||||
render({
|
||||
repositoryResults: [
|
||||
{
|
||||
repositoryId: 2,
|
||||
interpretedResults: [
|
||||
{
|
||||
message: {
|
||||
tokens: [
|
||||
{
|
||||
t: 'text',
|
||||
text: 'This is an empty block.'
|
||||
}
|
||||
]
|
||||
},
|
||||
shortDescription: 'This is an empty block.',
|
||||
fileLink: {
|
||||
fileLinkPrefix: 'https://github.com/facebook/create-react-app/blob/f34d88e30c7d8be7181f728d1abc4fd8d5cd07d3',
|
||||
filePath: 'packages/create-react-app/createReactApp.js'
|
||||
},
|
||||
severity: 'Warning',
|
||||
codeFlows: []
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
expect(screen.queryByText('This is an empty block.')).not.toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', {
|
||||
name: /octodemo\/hello-world-2/,
|
||||
}));
|
||||
expect(screen.getByText('This is an empty block.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user