diff --git a/extension/src/cli/dvc/contract.ts b/extension/src/cli/dvc/contract.ts index 59e1169112..a02b7dc1b4 100644 --- a/extension/src/cli/dvc/contract.ts +++ b/extension/src/cli/dvc/contract.ts @@ -100,7 +100,7 @@ export interface PlotsData { [path: string]: Plot[] } -type PlotError = { +export type PlotError = { name: string rev: string source?: string diff --git a/extension/src/plots/errors/collect.test.ts b/extension/src/plots/errors/collect.test.ts new file mode 100644 index 0000000000..28183b4320 --- /dev/null +++ b/extension/src/plots/errors/collect.test.ts @@ -0,0 +1,137 @@ +import { join } from 'path' +import { collectErrors, collectImageErrors } from './collect' +import { EXPERIMENT_WORKSPACE_ID } from '../../cli/dvc/contract' + +describe('collectErrors', () => { + it('should remove errors that belong to revisions that have been fetched', () => { + const errors = collectErrors( + { data: {} }, + [EXPERIMENT_WORKSPACE_ID], + [ + { + msg: 'unexpected error', + name: 'fun::plot', + rev: EXPERIMENT_WORKSPACE_ID, + source: 'metrics.json', + type: 'unexpected' + } + ], + {} + ) + + expect(errors).toStrictEqual([]) + }) + + it('should collect new errors', () => { + const newError = { + msg: 'Blue screen of death', + name: 'fun::plot', + rev: 'd7ad114', + source: 'metrics.json', + type: 'unexpected' + } + + const errors = collectErrors( + { + data: {}, + errors: [newError] + }, + [EXPERIMENT_WORKSPACE_ID, 'd7ad114'], + [ + { + msg: 'unexpected error', + name: 'fun::plot', + rev: EXPERIMENT_WORKSPACE_ID, + source: 'metrics.json', + type: 'unexpected' + } + ], + { d7ad114: 'main' } + ) + + expect(errors).toStrictEqual([{ ...newError, rev: 'main' }]) + }) +}) + +describe('collectImageErrors', () => { + it('should return undefined if there are no errors for the image', () => { + const error = collectImageErrors( + 'misclassified.jpg', + EXPERIMENT_WORKSPACE_ID, + [] + ) + expect(error).toBeUndefined() + }) + + it('should collect a single error for an image', () => { + const path = join('training', 'plots', 'images', 'mispredicted.jpg') + const otherPath = 'other' + + const errors = [ + { + msg: `${otherPath} - file type error\nOnly JSON, YAML, CSV and TSV formats are supported.`, + name: otherPath, + rev: EXPERIMENT_WORKSPACE_ID, + source: otherPath, + type: 'PlotMetricTypeError' + }, + { + msg: "Could not find provided field ('ste') in data fields ('step, test/acc').", + name: otherPath, + rev: EXPERIMENT_WORKSPACE_ID, + source: otherPath, + type: 'FieldNotFoundError' + }, + { + msg: '', + name: path, + rev: EXPERIMENT_WORKSPACE_ID, + source: path, + type: 'FileNotFoundError' + }, + { + msg: '', + name: path, + rev: 'main', + source: path, + type: 'FileNotFoundError' + } + ] + + const error = collectImageErrors(path, EXPERIMENT_WORKSPACE_ID, errors) + expect(error).toStrictEqual(`FileNotFoundError: ${path} not found.`) + }) + + it('should concatenate errors together to give a single string', () => { + const path = join('training', 'plots', 'images', 'mispredicted.jpg') + + const errors = [ + { + msg: '', + name: path, + rev: EXPERIMENT_WORKSPACE_ID, + source: path, + type: 'FileNotFoundError' + }, + { + msg: 'catastrophic error', + name: path, + rev: EXPERIMENT_WORKSPACE_ID, + source: path, + type: 'SomeError' + }, + { + msg: '', + name: path, + rev: EXPERIMENT_WORKSPACE_ID, + source: path, + type: 'UNEXPECTEDERRRRROR' + } + ] + + const error = collectImageErrors(path, EXPERIMENT_WORKSPACE_ID, errors) + expect(error).toStrictEqual( + `FileNotFoundError: ${path} not found.\nSomeError: catastrophic error\nUNEXPECTEDERRRRROR` + ) + }) +}) diff --git a/extension/src/plots/errors/collect.ts b/extension/src/plots/errors/collect.ts new file mode 100644 index 0000000000..0da54d1b58 --- /dev/null +++ b/extension/src/plots/errors/collect.ts @@ -0,0 +1,53 @@ +import { PlotError, PlotsOutput } from '../../cli/dvc/contract' + +export const collectErrors = ( + data: PlotsOutput, + revs: string[], + errors: PlotError[], + cliIdToLabel: { [id: string]: string } +) => { + const existingErrors = errors.filter( + ({ rev }) => !revs.includes(cliIdToLabel[rev] || rev) + ) + const newErrors = data?.errors || [] + + return [ + ...existingErrors, + ...newErrors.map(error => { + const { rev } = error + return { + ...error, + rev: cliIdToLabel[rev] || rev + } + }) + ] +} + +const getMessage = (error: PlotError): string => { + const { msg, type, source } = error + + if (type === 'FileNotFoundError' && source && !msg) { + return `${type}: ${source} not found.` + } + return [type, msg].filter(Boolean).join(': ') +} + +export const collectImageErrors = ( + path: string, + revision: string, + errors: PlotError[] +): string | undefined => { + const msgs: string[] = [] + for (const error of errors) { + if (error.rev === revision && error.name === path) { + const msg = getMessage(error) + msgs.push(msg) + } + } + + if (msgs.length === 0) { + return undefined + } + + return msgs.join('\n') +} diff --git a/extension/src/plots/errors/model.ts b/extension/src/plots/errors/model.ts new file mode 100644 index 0000000000..accc22990f --- /dev/null +++ b/extension/src/plots/errors/model.ts @@ -0,0 +1,31 @@ +import { collectErrors, collectImageErrors } from './collect' +import { Disposable } from '../../class/dispose' +import { PlotError, PlotsOutputOrError } from '../../cli/dvc/contract' +import { isDvcError } from '../../cli/dvc/reader' + +export class ErrorsModel extends Disposable { + private readonly dvcRoot: string + + private errors: PlotError[] = [] + + constructor(dvcRoot: string) { + super() + this.dvcRoot = dvcRoot + } + + public transformAndSet( + data: PlotsOutputOrError, + revs: string[], + cliIdToLabel: { [id: string]: string } + ) { + if (isDvcError(data)) { + return + } + + this.errors = collectErrors(data, revs, this.errors, cliIdToLabel) + } + + public getImageErrors(path: string, revision: string) { + return collectImageErrors(path, revision, this.errors) + } +} diff --git a/extension/src/plots/index.ts b/extension/src/plots/index.ts index d61e04875e..dd55d186ca 100644 --- a/extension/src/plots/index.ts +++ b/extension/src/plots/index.ts @@ -3,6 +3,7 @@ import { Event, EventEmitter, Memento } from 'vscode' import { PlotsData as TPlotsData } from './webview/contract' import { WebviewMessages } from './webview/messages' import { PlotsData } from './data' +import { ErrorsModel } from './errors/model' import { PlotsModel } from './model' import { collectEncodingElements, collectScale } from './paths/collect' import { PathsModel } from './paths/model' @@ -31,6 +32,8 @@ export class Plots extends BaseRepository { private readonly paths: PathsModel private readonly data: PlotsData + private readonly errors: ErrorsModel + private webviewMessages: WebviewMessages constructor( @@ -43,8 +46,10 @@ export class Plots extends BaseRepository { ) { super(dvcRoot, webviewIcon) + this.errors = this.dispose.track(new ErrorsModel(this.dvcRoot)) + this.plots = this.dispose.track( - new PlotsModel(this.dvcRoot, experiments, workspaceState) + new PlotsModel(this.dvcRoot, experiments, this.errors, workspaceState) ) this.paths = this.dispose.track( new PathsModel(this.dvcRoot, workspaceState) @@ -213,9 +218,11 @@ export class Plots extends BaseRepository { private onDidUpdateData() { this.dispose.track( this.data.onDidUpdate(async ({ data, revs }) => { + const cliIdToLabel = this.plots.getCLIIdToLabel() await Promise.all([ this.plots.transformAndSetPlots(data, revs), - this.paths.transformAndSet(data, revs, this.plots.getCLIIdToLabel()) + this.paths.transformAndSet(data, revs, cliIdToLabel), + this.errors.transformAndSet(data, revs, cliIdToLabel) ]) this.notifyChanged() }) diff --git a/extension/src/plots/model/index.test.ts b/extension/src/plots/model/index.test.ts index 646bdd3f52..41595f1990 100644 --- a/extension/src/plots/model/index.test.ts +++ b/extension/src/plots/model/index.test.ts @@ -11,6 +11,7 @@ import { Experiments } from '../../experiments' import { PersistenceKey } from '../../persistence/constants' import { EXPERIMENT_WORKSPACE_ID } from '../../cli/dvc/contract' import { customPlotsOrderFixture } from '../../test/fixtures/expShow/base/customPlots' +import { ErrorsModel } from '../errors/model' const mockedRevisions = [ { displayColor: 'white', label: EXPERIMENT_WORKSPACE_ID }, @@ -41,6 +42,7 @@ describe('plotsModel', () => { getSelectedRevisions: mockedGetSelectedRevisions, isReady: () => Promise.resolve(undefined) } as unknown as Experiments, + { getImageErrors: () => undefined } as unknown as ErrorsModel, memento ) jest.clearAllMocks() @@ -64,6 +66,7 @@ describe('plotsModel', () => { getSelectedRevisions: mockedGetSelectedRevisions, isReady: () => Promise.resolve(undefined) } as unknown as Experiments, + { getImageErrors: () => undefined } as unknown as ErrorsModel, memento ) expect(model.getCustomPlotsOrder()).toStrictEqual([ diff --git a/extension/src/plots/model/index.ts b/extension/src/plots/model/index.ts index 7bf43cac76..76c992e29a 100644 --- a/extension/src/plots/model/index.ts +++ b/extension/src/plots/model/index.ts @@ -52,11 +52,13 @@ import { MultiSourceVariations } from '../multiSource/collect' import { isDvcError } from '../../cli/dvc/reader' +import { ErrorsModel } from '../errors/model' export type CustomCheckpointPlots = { [metric: string]: CheckpointPlot } export class PlotsModel extends ModelWithPersistence { private readonly experiments: Experiments + private readonly errors: ErrorsModel private nbItemsPerRowOrWidth: Record private height: Record @@ -77,10 +79,12 @@ export class PlotsModel extends ModelWithPersistence { constructor( dvcRoot: string, experiments: Experiments, + errors: ErrorsModel, workspaceState: Memento ) { super(dvcRoot, workspaceState) this.experiments = experiments + this.errors = errors this.nbItemsPerRowOrWidth = this.revive( PersistenceKey.PLOT_NB_ITEMS_PER_ROW_OR_WIDTH, @@ -494,7 +498,9 @@ export class PlotsModel extends ModelWithPersistence { for (const revision of selectedRevisions) { const image = this.comparisonData?.[revision]?.[path] + const error = this.errors.getImageErrors(path, revision) pathRevisions.revisions[revision] = { + error, revision, url: image?.url } diff --git a/extension/src/plots/paths/collect.test.ts b/extension/src/plots/paths/collect.test.ts index 5357cf5f1b..ac5812e58c 100644 --- a/extension/src/plots/paths/collect.test.ts +++ b/extension/src/plots/paths/collect.test.ts @@ -13,7 +13,7 @@ import plotsDiffFixture from '../../test/fixtures/plotsDiff/output' import { Shape, StrokeDash } from '../multiSource/constants' import { EXPERIMENT_WORKSPACE_ID } from '../../cli/dvc/contract' -describe('collectPath', () => { +describe('collectPaths', () => { const revisions = [ EXPERIMENT_WORKSPACE_ID, '53c3851', @@ -226,6 +226,56 @@ describe('collectPath', () => { } ]) }) + + it('should correctly collect error paths', () => { + const misspeltJpg = join('training', 'plots', 'images', 'mip.jpg') + const revisions = new Set([EXPERIMENT_WORKSPACE_ID]) + + const paths = collectPaths( + [], + { + data: {}, + errors: [ + { + msg: '', + name: misspeltJpg, + rev: EXPERIMENT_WORKSPACE_ID, + source: misspeltJpg, + type: 'FileNotFoundError' + } + ] + }, + [], + {} + ) + expect(paths).toHaveLength(4) + expect(paths).toStrictEqual([ + { + hasChildren: false, + parentPath: join('training', 'plots', 'images'), + path: misspeltJpg, + revisions + }, + { + hasChildren: true, + parentPath: join('training', 'plots'), + path: join('training', 'plots', 'images'), + revisions + }, + { + hasChildren: true, + parentPath: 'training', + path: join('training', 'plots'), + revisions + }, + { + hasChildren: true, + parentPath: undefined, + path: 'training', + revisions + } + ]) + }) }) describe('collectTemplateOrder', () => { diff --git a/extension/src/plots/paths/collect.ts b/extension/src/plots/paths/collect.ts index 7ee076847e..a64b8d46f5 100644 --- a/extension/src/plots/paths/collect.ts +++ b/extension/src/plots/paths/collect.ts @@ -7,6 +7,7 @@ import { } from '../webview/contract' import { EXPERIMENT_WORKSPACE_ID, + PlotError, PlotsData, PlotsOutput } from '../../cli/dvc/contract' @@ -165,30 +166,81 @@ const collectOrderedPath = ( return acc } -export const collectPaths = ( - existingPaths: PlotPath[], - output: PlotsOutput, - fetchedRevs: string[], - cliIdToLabel: { [id: string]: string } +const addRevisionsToPath = ( + acc: PlotPath[], + data: PlotsData, + path: string, + revisions: Set ): PlotPath[] => { - let acc: PlotPath[] = filterWorkspaceIfFetched(existingPaths, fetchedRevs) + if (revisions.size === 0) { + return acc + } + + const pathArray = getPathArray(path) - const { data } = output + for (let reverseIdx = pathArray.length; reverseIdx > 0; reverseIdx--) { + acc = collectOrderedPath(acc, data, revisions, pathArray, reverseIdx) + } + return acc +} +const collectDataPaths = ( + acc: PlotPath[], + data: PlotsData, + cliIdToLabel: { [id: string]: string } +) => { const paths = Object.keys(data) for (const path of paths) { const revisions = collectPathRevisions(data, path, cliIdToLabel) + acc = addRevisionsToPath(acc, data, path, revisions) + } + return acc +} - if (revisions.size === 0) { - continue +const collectErrorRevisions = ( + path: string, + errors: PlotError[], + cliIdToLabel: { [id: string]: string } +): Set => { + const acc = new Set() + for (const error of errors) { + const { name, rev } = error + if (name === path) { + acc.add(cliIdToLabel[rev] || rev) } + } + return acc +} + +const collectErrorPaths = ( + acc: PlotPath[], + data: PlotsData, + errors: PlotError[], + cliIdToLabel: { [id: string]: string } +) => { + const paths = errors.map(({ name }) => name) + for (const path of paths) { + const revisions = collectErrorRevisions(path, errors, cliIdToLabel) + acc = addRevisionsToPath(acc, data, path, revisions) + } + return acc +} + +export const collectPaths = ( + existingPaths: PlotPath[], + output: PlotsOutput, + fetchedRevs: string[], + cliIdToLabel: { [id: string]: string } +): PlotPath[] => { + let acc: PlotPath[] = filterWorkspaceIfFetched(existingPaths, fetchedRevs) - const pathArray = getPathArray(path) + const { data, errors } = output - for (let reverseIdx = pathArray.length; reverseIdx > 0; reverseIdx--) { - acc = collectOrderedPath(acc, data, revisions, pathArray, reverseIdx) - } + acc = collectDataPaths(acc, data, cliIdToLabel) + + if (errors?.length) { + acc = collectErrorPaths(acc, data, errors, cliIdToLabel) } return acc diff --git a/extension/src/plots/webview/contract.ts b/extension/src/plots/webview/contract.ts index 087b1d2c2a..9c7cfc3766 100644 --- a/extension/src/plots/webview/contract.ts +++ b/extension/src/plots/webview/contract.ts @@ -170,8 +170,9 @@ export interface TemplatePlotsData { } export type ComparisonPlot = { - url?: string + url: string | undefined revision: string + error: string | undefined } export enum PlotsDataKeys { diff --git a/extension/src/test/fixtures/plotsDiff/index.ts b/extension/src/test/fixtures/plotsDiff/index.ts index 2ab240b536..398a312183 100644 --- a/extension/src/test/fixtures/plotsDiff/index.ts +++ b/extension/src/test/fixtures/plotsDiff/index.ts @@ -699,7 +699,8 @@ export const getComparisonWebviewMessage = ( } revisionsAcc[revision] = { url: `${url}?${MOCK_IMAGE_MTIME}`, - revision + revision, + error: undefined } } diff --git a/webview/src/experiments/components/table/Cell.tsx b/webview/src/experiments/components/table/Cell.tsx index 5abe49a190..c253a140be 100644 --- a/webview/src/experiments/components/table/Cell.tsx +++ b/webview/src/experiments/components/table/Cell.tsx @@ -1,12 +1,12 @@ import { flexRender } from '@tanstack/react-table' import React, { ReactNode } from 'react' import cx from 'classnames' -import { ErrorTooltip } from './Errors' import styles from './styles.module.scss' import { CellProp, RowProp } from './interfaces' import { CellRowActionsProps, CellRowActions } from './CellRowActions' import { CellValue, isValueWithChanges } from './content/Cell' import { clickAndEnterProps } from '../../../util/props' +import { ErrorTooltip } from '../../../shared/components/errorTooltip/ErrorTooltip' const cellHasChanges = (cellValue: CellValue) => isValueWithChanges(cellValue) ? cellValue.changes : false diff --git a/webview/src/experiments/components/table/content/ErrorCell.tsx b/webview/src/experiments/components/table/content/ErrorCell.tsx index 53941bed79..ed77f7f351 100644 --- a/webview/src/experiments/components/table/content/ErrorCell.tsx +++ b/webview/src/experiments/components/table/content/ErrorCell.tsx @@ -1,7 +1,7 @@ import cx from 'classnames' import React from 'react' import { CellContents } from './CellContent' -import { ErrorTooltip } from '../Errors' +import { ErrorTooltip } from '../../../../shared/components/errorTooltip/ErrorTooltip' import styles from '../styles.module.scss' interface ErrorCellProps { diff --git a/webview/src/experiments/components/table/styles.module.scss b/webview/src/experiments/components/table/styles.module.scss index f3be785b17..3def6195ed 100644 --- a/webview/src/experiments/components/table/styles.module.scss +++ b/webview/src/experiments/components/table/styles.module.scss @@ -760,22 +760,6 @@ $bullet-size: calc(var(--design-unit) * 4px); color: $error-color; } -.errorIcon { - margin-left: 6px; - margin-top: 3px; -} - -.errorTooltip { - white-space: pre-wrap; - display: flex; - gap: 4px; - - svg { - min-width: 16px; - min-height: 16px; - } -} - .headerCellWrapper { @extend %headerCellPadding; } diff --git a/webview/src/plots/components/App.test.tsx b/webview/src/plots/components/App.test.tsx index 67e63c5691..b05eba0faf 100644 --- a/webview/src/plots/components/App.test.tsx +++ b/webview/src/plots/components/App.test.tsx @@ -247,7 +247,9 @@ describe('App', () => { plots: [ { path: 'training/plots/images/misclassified.jpg', - revisions: { ad2b5ec: { revision: 'ad2b5ec' } } + revisions: { + ad2b5ec: { error: undefined, revision: 'ad2b5ec', url: undefined } + } } ], revisions: [ diff --git a/webview/src/plots/components/comparisonTable/ComparisonTable.test.tsx b/webview/src/plots/components/comparisonTable/ComparisonTable.test.tsx index af27064988..cbc2d1db3e 100644 --- a/webview/src/plots/components/comparisonTable/ComparisonTable.test.tsx +++ b/webview/src/plots/components/comparisonTable/ComparisonTable.test.tsx @@ -263,7 +263,7 @@ describe('ComparisonTable', () => { expect(headers).toStrictEqual([...namedRevisions, newRevName]) }) - it('should display a refresh button for each revision that has a missing image', () => { + it('should not display a refresh button for a revision that does not contain an image', () => { const revisionWithNoData = 'missing-data' renderTable({ @@ -273,6 +273,39 @@ describe('ComparisonTable', () => { revisions: { ...revisions, [revisionWithNoData]: { + error: undefined, + revision: revisionWithNoData, + url: undefined + } + } + })), + revisions: [ + ...comparisonTableFixture.revisions, + { + displayColor: '#f56565', + fetched: true, + firstThreeColumns: [], + group: undefined, + id: 'noData', + revision: revisionWithNoData + } + ] + }) + + expect(screen.queryByText('Refresh')).not.toBeInTheDocument() + }) + + it('should display a refresh button for each revision that has an image with an error', () => { + const revisionWithNoData = 'missing-data' + + renderTable({ + ...comparisonTableFixture, + plots: comparisonTableFixture.plots.map(({ path, revisions }) => ({ + path, + revisions: { + ...revisions, + [revisionWithNoData]: { + error: 'this is an error', revision: revisionWithNoData, url: undefined } diff --git a/webview/src/plots/components/comparisonTable/ComparisonTableCell.tsx b/webview/src/plots/components/comparisonTable/ComparisonTableCell.tsx index d83e5fb9c5..e581014784 100644 --- a/webview/src/plots/components/comparisonTable/ComparisonTableCell.tsx +++ b/webview/src/plots/components/comparisonTable/ComparisonTableCell.tsx @@ -5,6 +5,8 @@ import styles from './styles.module.scss' import { RefreshButton } from '../../../shared/components/button/RefreshButton' import { sendMessage } from '../../../shared/vscode' import { zoomPlot } from '../messages' +import { Error } from '../../../shared/components/icons' +import { ErrorTooltip } from '../../../shared/components/errorTooltip/ErrorTooltip' type ComparisonTableCellProps = { path: string @@ -15,6 +17,7 @@ export const ComparisonTableCell: React.FC = ({ path, plot }) => { + const error = plot?.error const missing = plot?.fetched && !plot?.url if (!plot?.fetched) { @@ -28,15 +31,25 @@ export const ComparisonTableCell: React.FC = ({ if (missing) { return (
-

No Plot to Display.

- - sendMessage({ - payload: plot.revision, - type: MessageFromWebviewType.REFRESH_REVISION - }) - } - /> + {error ? ( + <> + +
+ +
+
+ + sendMessage({ + payload: plot.revision, + type: MessageFromWebviewType.REFRESH_REVISION + }) + } + /> + + ) : ( +

-

+ )}
) } diff --git a/webview/src/plots/components/comparisonTable/styles.module.scss b/webview/src/plots/components/comparisonTable/styles.module.scss index 4a3bf97625..dcaf2fc22b 100644 --- a/webview/src/plots/components/comparisonTable/styles.module.scss +++ b/webview/src/plots/components/comparisonTable/styles.module.scss @@ -98,7 +98,7 @@ $gap: 4px; background-color: $bg-color; border-style: solid; border-width: thin; - border-color: $watermark-color; + border-color: $bg-color; } .noImageContent { @@ -217,3 +217,12 @@ $gap: 4px; direction: rtl; white-space: nowrap; } + +.errorIcon { + color: $error-color; + margin: 6px; +} + +.emptyIcon { + font-size: 32px; +} diff --git a/webview/src/experiments/components/table/Errors.tsx b/webview/src/shared/components/errorTooltip/ErrorTooltip.tsx similarity index 77% rename from webview/src/experiments/components/table/Errors.tsx rename to webview/src/shared/components/errorTooltip/ErrorTooltip.tsx index 8eff065d73..481c7be4d0 100644 --- a/webview/src/experiments/components/table/Errors.tsx +++ b/webview/src/shared/components/errorTooltip/ErrorTooltip.tsx @@ -1,7 +1,7 @@ import React, { ReactElement } from 'react' import styles from './styles.module.scss' -import { Error } from '../../../shared/components/icons' -import Tooltip from '../../../shared/components/tooltip/Tooltip' +import { Error } from '../icons' +import Tooltip from '../tooltip/Tooltip' export const ErrorTooltip: React.FC<{ error?: string diff --git a/webview/src/shared/components/errorTooltip/styles.module.scss b/webview/src/shared/components/errorTooltip/styles.module.scss new file mode 100644 index 0000000000..cc137f5bdb --- /dev/null +++ b/webview/src/shared/components/errorTooltip/styles.module.scss @@ -0,0 +1,15 @@ +.errorIcon { + margin-left: 6px; + margin-top: 3px; +} + +.errorTooltip { + white-space: pre-wrap; + display: flex; + gap: 4px; + + svg { + min-width: 16px; + min-height: 16px; + } +} diff --git a/webview/src/stories/ComparisonTable.stories.tsx b/webview/src/stories/ComparisonTable.stories.tsx index 7828bb5359..4c9d7d87db 100644 --- a/webview/src/stories/ComparisonTable.stories.tsx +++ b/webview/src/stories/ComparisonTable.stories.tsx @@ -73,13 +73,23 @@ WithPinnedColumn.play = async ({ canvasElement }) => { const removeImages = ( path: string, revisionsData: ComparisonRevisionData + // eslint-disable-next-line sonarjs/cognitive-complexity ): ComparisonRevisionData => { const filteredRevisionData: ComparisonRevisionData = {} for (const [revision, data] of Object.entries(revisionsData)) { if ( - (path === comparisonTableFixture.plots[0].path && revision === 'main') || + (path === comparisonTableFixture.plots[0].path && + ['main', '4fb124a'].includes(revision)) || revision === EXPERIMENT_WORKSPACE_ID ) { + filteredRevisionData[revision] = { + error: + revision === 'main' + ? `FileNotFoundError: ${path} not found.` + : undefined, + revision, + url: undefined + } continue } filteredRevisionData[revision] = data