-
Notifications
You must be signed in to change notification settings - Fork 880
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
34 changed files
with
4,149 additions
and
289 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module.exports = { | ||
roots: ['<rootDir>/src'], | ||
testMatch: ['**/?(*.)+(spec|test).+(ts|tsx|js)'], | ||
transform: { | ||
'^.+\\.(ts|tsx)$': 'ts-jest', | ||
}, | ||
modulePathIgnorePatterns: ['generated'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import * as React from 'react'; | ||
import {Modal, Tabs} from 'antd'; | ||
import {RolloutAnalysisRunInfo} from '../../../models/rollout/generated'; | ||
|
||
import MetricLabel from './metric-label/metric-label'; | ||
import {AnalysisPanel, MetricPanel} from './panels'; | ||
import {analysisEndTime, analysisStartTime, getAdjustedMetricPhase, metricStatusLabel, metricSubstatus, transformMetrics} from './transforms'; | ||
import {AnalysisStatus} from './types'; | ||
|
||
import classNames from 'classnames'; | ||
import './styles.scss'; | ||
|
||
const cx = classNames; | ||
|
||
interface AnalysisModalProps { | ||
analysis: RolloutAnalysisRunInfo; | ||
analysisName: string; | ||
images: string[]; | ||
onClose: () => void; | ||
open: boolean; | ||
revision: string; | ||
} | ||
|
||
export const AnalysisModal = ({analysis, analysisName, images, onClose, open, revision}: AnalysisModalProps) => { | ||
const analysisResults = analysis.specAndStatus?.status; | ||
|
||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
const analysisStart = analysisStartTime(analysis.objectMeta?.creationTimestamp); | ||
const analysisEnd = analysisEndTime(analysisResults?.metricResults ?? []); | ||
|
||
const analysisSubstatus = metricSubstatus( | ||
(analysisResults?.phase ?? AnalysisStatus.Unknown) as AnalysisStatus, | ||
analysisResults?.runSummary.failed ?? 0, | ||
analysisResults?.runSummary.error ?? 0, | ||
analysisResults?.runSummary.inconclusive ?? 0 | ||
); | ||
const transformedMetrics = transformMetrics(analysis.specAndStatus); | ||
|
||
const adjustedAnalysisStatus = getAdjustedMetricPhase(analysis.status as AnalysisStatus); | ||
|
||
const tabItems = [ | ||
{ | ||
label: <MetricLabel label='Summary' status={adjustedAnalysisStatus} substatus={analysisSubstatus} />, | ||
key: 'analysis-summary', | ||
children: ( | ||
<AnalysisPanel | ||
title={metricStatusLabel((analysis.status ?? AnalysisStatus.Unknown) as AnalysisStatus, analysis.failed ?? 0, analysis.error ?? 0, analysis.inconclusive ?? 0)} | ||
status={adjustedAnalysisStatus} | ||
substatus={analysisSubstatus} | ||
images={images} | ||
revision={revision} | ||
message={analysisResults.message} | ||
startTime={analysisStart} | ||
endTime={analysisEnd} | ||
/> | ||
), | ||
}, | ||
...Object.values(transformedMetrics) | ||
.sort((a, b) => a.name.localeCompare(b.name)) | ||
.map((metric) => ({ | ||
label: <MetricLabel label={metric.name} status={metric.status.adjustedPhase} substatus={metric.status.substatus} />, | ||
key: metric.name, | ||
children: ( | ||
<MetricPanel | ||
metricName={metric.name} | ||
status={(metric.status.phase ?? AnalysisStatus.Unknown) as AnalysisStatus} | ||
substatus={metric.status.substatus} | ||
metricSpec={metric.spec} | ||
metricResults={metric.status} | ||
/> | ||
), | ||
})), | ||
]; | ||
|
||
return ( | ||
<Modal centered open={open} title={analysisName} onCancel={onClose} width={866} footer={null}> | ||
<Tabs className={cx('tabs')} items={tabItems} tabPosition='left' size='small' tabBarGutter={12} /> | ||
</Modal> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import {AnalysisStatus, FunctionalStatus} from './types'; | ||
|
||
export const METRIC_FAILURE_LIMIT_DEFAULT = 0; | ||
export const METRIC_INCONCLUSIVE_LIMIT_DEFAULT = 0; | ||
export const METRIC_CONSECUTIVE_ERROR_LIMIT_DEFAULT = 4; | ||
|
||
export const ANALYSIS_STATUS_THEME_MAP: {[key in AnalysisStatus]: string} = { | ||
Successful: FunctionalStatus.SUCCESS, | ||
Error: FunctionalStatus.WARNING, | ||
Failed: FunctionalStatus.ERROR, | ||
Running: FunctionalStatus.IN_PROGRESS, | ||
Pending: FunctionalStatus.INACTIVE, | ||
Inconclusive: FunctionalStatus.WARNING, | ||
Unknown: FunctionalStatus.INACTIVE, // added by frontend | ||
}; |
19 changes: 19 additions & 0 deletions
19
ui/src/app/components/analysis-modal/criteria-list/criteria-list.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
@import '../theme/theme.scss'; | ||
|
||
.criteria-list { | ||
margin: 0; | ||
padding-left: 0; | ||
list-style-type: none; | ||
} | ||
|
||
.icon-pass { | ||
color: $success-foreground; | ||
} | ||
|
||
.icon-fail { | ||
color: $error-foreground; | ||
} | ||
|
||
.icon-pending { | ||
color: $in-progress-foreground; | ||
} |
116 changes: 116 additions & 0 deletions
116
ui/src/app/components/analysis-modal/criteria-list/criteria-list.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import * as React from 'react'; | ||
import {Space, Typography} from 'antd'; | ||
|
||
import {AnalysisStatus} from '../types'; | ||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; | ||
|
||
import {faCheck, faRotateRight, faXmark} from '@fortawesome/free-solid-svg-icons'; | ||
|
||
import classNames from 'classnames'; | ||
import './criteria-list.scss'; | ||
|
||
const {Text} = Typography; | ||
|
||
enum CriterionStatus { | ||
Fail = 'FAIL', | ||
Pass = 'PASS', | ||
InProgress = 'IN_PROGRESS', | ||
Pending = 'PENDING', | ||
} | ||
|
||
const defaultCriterionStatus = (analysisStatus: AnalysisStatus) => (analysisStatus === AnalysisStatus.Pending ? CriterionStatus.Pending : CriterionStatus.InProgress); | ||
|
||
const criterionLabel = (measurementLabel: string, maxAllowed: number) => (maxAllowed === 0 ? `No ${measurementLabel}.` : `Fewer than ${maxAllowed + 1} ${measurementLabel}.`); | ||
|
||
interface CriteriaListItemProps { | ||
children: React.ReactNode; | ||
showIcon: boolean; | ||
status: CriterionStatus; | ||
} | ||
|
||
const CriteriaListItem = ({children, showIcon, status}: CriteriaListItemProps) => { | ||
let StatusIcon: React.ReactNode | null = null; | ||
switch (status) { | ||
case CriterionStatus.Fail: { | ||
StatusIcon = <FontAwesomeIcon icon={faXmark} className={classNames('icon-fail')} />; | ||
break; | ||
} | ||
case CriterionStatus.Pass: { | ||
StatusIcon = <FontAwesomeIcon icon={faCheck} className={classNames('icon-pass')} />; | ||
break; | ||
} | ||
case CriterionStatus.InProgress: { | ||
StatusIcon = <FontAwesomeIcon icon={faRotateRight} className={classNames('icon-pending')} />; | ||
break; | ||
} | ||
case CriterionStatus.Pending: | ||
default: { | ||
break; | ||
} | ||
} | ||
|
||
return ( | ||
<li> | ||
<Space size='small'> | ||
{showIcon && <>{StatusIcon}</>} | ||
{children} | ||
</Space> | ||
</li> | ||
); | ||
}; | ||
|
||
interface CriteriaListProps { | ||
analysisStatus: AnalysisStatus; | ||
className?: string[] | string; | ||
consecutiveErrors: number; | ||
failures: number; | ||
inconclusives: number; | ||
maxConsecutiveErrors: number; | ||
maxFailures: number; | ||
maxInconclusives: number; | ||
showIcons: boolean; | ||
} | ||
|
||
const CriteriaList = ({ | ||
analysisStatus, | ||
className, | ||
consecutiveErrors, | ||
failures, | ||
inconclusives, | ||
maxConsecutiveErrors, | ||
maxFailures, | ||
maxInconclusives, | ||
showIcons, | ||
}: CriteriaListProps) => { | ||
let failureStatus = defaultCriterionStatus(analysisStatus); | ||
let errorStatus = defaultCriterionStatus(analysisStatus); | ||
let inconclusiveStatus = defaultCriterionStatus(analysisStatus); | ||
|
||
if (analysisStatus !== AnalysisStatus.Pending && analysisStatus !== AnalysisStatus.Running) { | ||
failureStatus = failures <= maxFailures ? CriterionStatus.Pass : CriterionStatus.Fail; | ||
errorStatus = consecutiveErrors <= maxConsecutiveErrors ? CriterionStatus.Pass : CriterionStatus.Fail; | ||
inconclusiveStatus = inconclusives <= maxInconclusives ? CriterionStatus.Pass : CriterionStatus.Fail; | ||
} | ||
|
||
return ( | ||
<ul className={classNames('criteria-list', className)}> | ||
{maxFailures > -1 && ( | ||
<CriteriaListItem status={failureStatus} showIcon={showIcons}> | ||
<Text>{criterionLabel('measurement failures', maxFailures)}</Text> | ||
</CriteriaListItem> | ||
)} | ||
{maxConsecutiveErrors > -1 && ( | ||
<CriteriaListItem status={errorStatus} showIcon={showIcons}> | ||
<Text>{criterionLabel('consecutive measurement errors', maxConsecutiveErrors)}</Text> | ||
</CriteriaListItem> | ||
)} | ||
{maxInconclusives > -1 && ( | ||
<CriteriaListItem status={inconclusiveStatus} showIcon={showIcons}> | ||
<Text>{criterionLabel('inconclusive measurements', maxInconclusives)}</Text> | ||
</CriteriaListItem> | ||
)} | ||
</ul> | ||
); | ||
}; | ||
|
||
export default CriteriaList; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.icon { | ||
font-size: 14px; | ||
} | ||
|
||
h4.title { | ||
margin: 0; // antd override | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import * as React from 'react'; | ||
|
||
import {Space, Typography} from 'antd'; | ||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; | ||
import {faMagnifyingGlassChart} from '@fortawesome/free-solid-svg-icons'; | ||
|
||
import StatusIndicator from '../status-indicator/status-indicator'; | ||
import {AnalysisStatus, FunctionalStatus} from '../types'; | ||
|
||
import classNames from 'classnames/bind'; | ||
import './header.scss'; | ||
|
||
const {Text, Title} = Typography; | ||
const cx = classNames; | ||
|
||
interface HeaderProps { | ||
className?: string[] | string; | ||
status: AnalysisStatus; | ||
substatus?: FunctionalStatus.ERROR | FunctionalStatus.WARNING; | ||
subtitle?: string; | ||
title: string; | ||
} | ||
|
||
const Header = ({className, status, substatus, subtitle, title}: HeaderProps) => ( | ||
<Space className={cx(className)} size='small' align='start'> | ||
<StatusIndicator size='large' status={status} substatus={substatus}> | ||
<FontAwesomeIcon icon={faMagnifyingGlassChart} className={cx('icon', 'fa')} /> | ||
</StatusIndicator> | ||
<div> | ||
<Title level={4} className={cx('title')}> | ||
{title} | ||
</Title> | ||
{subtitle && <Text type='secondary'>{subtitle}</Text>} | ||
</div> | ||
</Space> | ||
); | ||
|
||
export default Header; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import * as React from 'react'; | ||
|
||
import {Space, Typography} from 'antd'; | ||
|
||
import {AnalysisStatus} from '../types'; | ||
import StatusIndicator from '../status-indicator/status-indicator'; | ||
|
||
import classNames from 'classnames'; | ||
|
||
const {Text} = Typography; | ||
|
||
interface LegendItemProps { | ||
label: string; | ||
status: AnalysisStatus; | ||
} | ||
|
||
const LegendItem = ({label, status}: LegendItemProps) => ( | ||
<Space size={4}> | ||
<StatusIndicator size='small' status={status} /> | ||
<Text>{label}</Text> | ||
</Space> | ||
); | ||
|
||
const pluralize = (count: number, singular: string, plural: string) => (count === 1 ? singular : plural); | ||
|
||
interface LegendProps { | ||
className?: string[] | string; | ||
errors: number; | ||
failures: number; | ||
inconclusives: number; | ||
successes: number; | ||
} | ||
|
||
const Legend = ({className, errors, failures, inconclusives, successes}: LegendProps) => ( | ||
<Space className={classNames(className)} size='small'> | ||
<LegendItem status={AnalysisStatus.Successful} label={`${successes} ${pluralize(successes, 'Success', 'Successes')}`} /> | ||
<LegendItem status={AnalysisStatus.Failed} label={`${failures} ${pluralize(failures, 'Failure', 'Failures')}`} /> | ||
<LegendItem status={AnalysisStatus.Error} label={`${errors} ${pluralize(errors, 'Error', 'Errors')}`} /> | ||
{inconclusives > 0 && <LegendItem status={AnalysisStatus.Inconclusive} label={`${inconclusives} Inconclusive`} />} | ||
</Space> | ||
); | ||
|
||
export default Legend; | ||
export {LegendItem}; |
Oops, something went wrong.