From b81f061c5c0b403ca98a8ddd2d5b07b6973ff98e Mon Sep 17 00:00:00 2001 From: Juntao Wang Date: Wed, 10 Jul 2024 16:48:04 -0400 Subject: [PATCH] Add no metrics state for compare runs metrics section --- .../mlmd/mockGetArtifactsByContext.ts | 138 +++++++++--------- .../cypress/pages/pipelines/compareRuns.ts | 22 ++- .../tests/mocked/pipelines/compareRuns.cy.ts | 87 ++++++++++- .../tests/mocked/pipelines/executions.cy.ts | 6 +- .../tests/mocked/pipelines/mlmdUtils.ts | 8 +- .../compareRuns/CompareRunsNoMetrics.tsx | 23 +++ .../ConfusionMatrixCompare.tsx | 31 +--- .../markdown/MarkdownCompare.tsx | 16 +- .../metricsSection/roc/RocCurveCompare.tsx | 30 +--- .../scalar/ScalarMetricTable.tsx | 30 ++-- .../compareRuns/CompareRunsMetricsSection.tsx | 17 ++- 11 files changed, 248 insertions(+), 160 deletions(-) create mode 100644 frontend/src/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics.tsx diff --git a/frontend/src/__mocks__/mlmd/mockGetArtifactsByContext.ts b/frontend/src/__mocks__/mlmd/mockGetArtifactsByContext.ts index 3e835f5ce4..2ea0c21489 100644 --- a/frontend/src/__mocks__/mlmd/mockGetArtifactsByContext.ts +++ b/frontend/src/__mocks__/mlmd/mockGetArtifactsByContext.ts @@ -1,113 +1,121 @@ import { Artifact, GetArtifactsByContextResponse } from '~/__mocks__/third_party/mlmd'; import createGrpcResponse, { GrpcResponse } from './utils'; -const mockedScalarMetricArtifact: Artifact = { +const mockedScalarMetricArtifact = (noMetrics?: boolean): Artifact => ({ id: 7, typeId: 17, type: 'system.Metrics', uri: 's3://aballant-pipelines/metrics-visualization-pipeline/f0b586ba-3e7b-4369-8d48-592e83cbbf73/digit-classification/metrics', properties: {}, - customProperties: { - accuracy: { doubleValue: 92 }, - displayName: { stringValue: 'metrics' }, - }, + customProperties: noMetrics + ? {} + : { + accuracy: { doubleValue: 92 }, + displayName: { stringValue: 'metrics' }, + }, state: 2, createTimeSinceEpoch: 1711765118976, lastUpdateTimeSinceEpoch: 1711765118976, -}; +}); -const mockedConfusionMatrixArtifact: Artifact = { +const mockedConfusionMatrixArtifact = (noMetrics?: boolean): Artifact => ({ id: 8, typeId: 18, type: 'system.ClassificationMetrics', uri: 's3://aballant-pipelines/metrics-visualization-pipeline/ccdfe85e-06cc-4a63-b10d-a12d688d2ec3/iris-sgdclassifier/metrics', properties: {}, - customProperties: { - confusionMatrix: { - structValue: { - struct: { - annotationSpecs: [ - { displayName: 'Setosa' }, - { displayName: 'Versicolour' }, - { displayName: 'Virginica' }, - ], - rows: [ - { - row: [38, 0, 0], - }, - { - row: [2, 19, 9], + customProperties: noMetrics + ? {} + : { + confusionMatrix: { + structValue: { + struct: { + annotationSpecs: [ + { displayName: 'Setosa' }, + { displayName: 'Versicolour' }, + { displayName: 'Virginica' }, + ], + rows: [ + { + row: [38, 0, 0], + }, + { + row: [2, 19, 9], + }, + { + row: [1, 17, 19], + }, + ], }, - { - row: [1, 17, 19], - }, - ], + }, + }, + displayName: { + stringValue: 'metrics', }, }, - }, - displayName: { - stringValue: 'metrics', - }, - }, state: 2, createTimeSinceEpoch: 1711765608345, lastUpdateTimeSinceEpoch: 1711765608345, -}; +}); -const mockedRocCurveArtifact: Artifact = { +const mockedRocCurveArtifact = (noMetrics?: boolean): Artifact => ({ id: 9, typeId: 18, type: 'system.ClassificationMetrics', uri: 's3://aballant-pipelines/metrics-visualization-pipeline/aa61378c-d507-4bde-aa18-9f8678b2beb6/wine-classification/metrics', properties: {}, - customProperties: { - confidenceMetrics: { - structValue: { - list: [ - { confidenceThreshold: 2, falsePositiveRate: 0, recall: 0 }, - { confidenceThreshold: 1, falsePositiveRate: 0, recall: 0.33962264150943394 }, - { confidenceThreshold: 0, falsePositiveRate: 0, recall: 0.6037735849056604 }, - { confidenceThreshold: 0.8, falsePositiveRate: 0, recall: 0.8490566037735849 }, - { confidenceThreshold: 0.6, falsePositiveRate: 0, recall: 0.8867924528301887 }, - { confidenceThreshold: 0.5, falsePositiveRate: 0.0125, recall: 0.9245283018867925 }, - { confidenceThreshold: 0.4, falsePositiveRate: 0.075, recall: 0.9622641509433962 }, - { confidenceThreshold: 0.3, falsePositiveRate: 0.0875, recall: 1 }, - { confidenceThreshold: 0.2, falsePositiveRate: 0.2375, recall: 1 }, - { confidenceThreshold: 0.1, falsePositiveRate: 0.475, recall: 1 }, - { confidenceThreshold: 0, falsePositiveRate: 1, recall: 1 }, - ], + customProperties: noMetrics + ? {} + : { + confidenceMetrics: { + structValue: { + list: [ + { confidenceThreshold: 2, falsePositiveRate: 0, recall: 0 }, + { confidenceThreshold: 1, falsePositiveRate: 0, recall: 0.33962264150943394 }, + { confidenceThreshold: 0, falsePositiveRate: 0, recall: 0.6037735849056604 }, + { confidenceThreshold: 0.8, falsePositiveRate: 0, recall: 0.8490566037735849 }, + { confidenceThreshold: 0.6, falsePositiveRate: 0, recall: 0.8867924528301887 }, + { confidenceThreshold: 0.5, falsePositiveRate: 0.0125, recall: 0.9245283018867925 }, + { confidenceThreshold: 0.4, falsePositiveRate: 0.075, recall: 0.9622641509433962 }, + { confidenceThreshold: 0.3, falsePositiveRate: 0.0875, recall: 1 }, + { confidenceThreshold: 0.2, falsePositiveRate: 0.2375, recall: 1 }, + { confidenceThreshold: 0.1, falsePositiveRate: 0.475, recall: 1 }, + { confidenceThreshold: 0, falsePositiveRate: 1, recall: 1 }, + ], + }, + }, + displayName: { + stringValue: 'metrics', + }, }, - }, - displayName: { - stringValue: 'metrics', - }, - }, state: 2, createTimeSinceEpoch: 1711766424068, lastUpdateTimeSinceEpoch: 1711766424068, -}; +}); -const mockedMarkdownArtifact: Artifact = { +const mockedMarkdownArtifact = (noMetrics?: boolean): Artifact => ({ id: 16, typeId: 19, type: 'system.Markdown', uri: 's3://aballant-pipelines/metrics-visualization-pipeline/16dbff18-a3d5-4684-90ac-4e6198a9da0f/markdown-visualization/markdown_artifact', properties: {}, - customProperties: { - displayName: { stringValue: 'markdown_artifact' }, - }, + customProperties: noMetrics + ? {} + : { + displayName: { stringValue: 'markdown_artifact' }, + }, state: 2, createTimeSinceEpoch: 1712841455267, lastUpdateTimeSinceEpoch: 1712841455267, -}; +}); -export const mockGetArtifactsByContext = (): GrpcResponse => { +export const mockGetArtifactsByContext = (noMetrics?: boolean): GrpcResponse => { const binary = GetArtifactsByContextResponse.encode({ artifacts: [ - mockedScalarMetricArtifact, - mockedConfusionMatrixArtifact, - mockedRocCurveArtifact, - mockedMarkdownArtifact, + mockedScalarMetricArtifact(noMetrics), + mockedConfusionMatrixArtifact(noMetrics), + mockedRocCurveArtifact(noMetrics), + mockedMarkdownArtifact(noMetrics), ], }).finish(); return createGrpcResponse(binary); diff --git a/frontend/src/__tests__/cypress/cypress/pages/pipelines/compareRuns.ts b/frontend/src/__tests__/cypress/cypress/pages/pipelines/compareRuns.ts index 8383adc86c..5dd47265f8 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/pipelines/compareRuns.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/pipelines/compareRuns.ts @@ -111,6 +111,10 @@ class RocCurveFilterTableRow extends TableRow { class CompareRunsRocCurve extends Contextual { findRocCurveEmptyState() { + return this.find().findByTestId('compare-runs-roc-curve-empty-state'); + } + + findRocCurveTableEmptyState() { return this.find().findByTestId('no-result-found-title'); } @@ -123,13 +127,17 @@ class CompareRunsRocCurve extends Contextual { ); } - findRocCruveSearchBar() { + findRocCurveSearchBar() { return this.find().findByTestId('roc-curve-search'); } findRocCurveGraph() { return this.find().findByTestId('roc-curve-graph'); } + + findRocCurveNoMetricsState() { + return this.find().findByTestId('compare-runs-roc-curve-no-data-state'); + } } class CompareRunsScalarMetrics extends Contextual { @@ -152,6 +160,10 @@ class CompareRunsScalarMetrics extends Contextual { findScalarMetricsEmptyState() { return this.find().findByTestId('compare-runs-scalar-metrics-empty-state'); } + + findScalarMetricsNoMetricsState() { + return this.find().findByTestId('compare-runs-scalar-metrics-no-data-state'); + } } class CompareRunsArtifactSelect extends Contextual { @@ -188,6 +200,10 @@ class CompareRunsMarkdown extends Contextual { this.find().findByTestId(`compare-runs-markdown-${runId}`), ); } + + findMarkdownNoMetricsState() { + return this.find().findByTestId('compare-runs-markdown-no-data-state'); + } } class CompareRunsConfusionMatrix extends Contextual { @@ -206,6 +222,10 @@ class CompareRunsConfusionMatrix extends Contextual { this.find().findByTestId(`compare-runs-confusion-matrix-${runId}`), ); } + + findConfusionMatrixNoMetricsState() { + return this.find().findByTestId('compare-runs-confusion-matrix-no-data-state'); + } } class ConfusionMatrixGraph extends Contextual { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/compareRuns.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/compareRuns.cy.ts index 97532c381c..3cbe29a453 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/compareRuns.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/compareRuns.cy.ts @@ -69,6 +69,17 @@ const mockRun2 = buildMockRunKF({ }, }); +const mockRun3 = buildMockRunKF({ + display_name: 'Run 3', + run_id: 'run-3', + pipeline_version_reference: { + pipeline_id: initialMockPipeline.pipeline_id, + pipeline_version_id: initialMockPipelineVersion.pipeline_version_id, + }, + experiment_id: mockExperiment.experiment_id, + runtime_config: { parameters: {} }, +}); + describe('Compare runs', () => { beforeEach(() => { initIntercepts(); @@ -204,6 +215,7 @@ describe('Compare runs', () => { compareRunsGlobal.visit(projectName, mockExperiment.experiment_id, [ mockRun.run_id, mockRun2.run_id, + mockRun3.run_id, ]); }); @@ -213,6 +225,15 @@ describe('Compare runs', () => { .findScalarMetricsTabContent() .findScalarMetricsEmptyState() .should('exist'); + compareRunsMetricsContent.findConfusionMatrixTab().click(); + compareRunsMetricsContent + .findConfusionMatrixTabContent() + .findConfusionMatrixEmptyState() + .should('exist'); + compareRunsMetricsContent.findRocCurveTab().click(); + compareRunsMetricsContent.findRocCurveTabContent().findRocCurveEmptyState().should('exist'); + compareRunsMetricsContent.findMarkdownTab().click(); + compareRunsMetricsContent.findMarkdownTabContent().findMarkdownEmptyState().should('exist'); }); it('displays scalar metrics table data based on selections from Run list', () => { @@ -297,13 +318,63 @@ describe('Compare runs', () => { it('displays ROC curve empty state when no artifacts are found', () => { compareRunsMetricsContent.findRocCurveTab().click(); const content = compareRunsMetricsContent.findRocCurveTabContent(); - content.findRocCruveSearchBar().type('invalid'); - content.findRocCurveEmptyState().should('exist'); + content.findRocCurveSearchBar().type('invalid'); + content.findRocCurveTableEmptyState().should('exist'); + }); + }); + + describe('No metrics', () => { + beforeEach(() => { + initIntercepts(true); + compareRunsGlobal.visit(projectName, mockExperiment.experiment_id, [ + mockRun.run_id, + mockRun2.run_id, + mockRun3.run_id, + ]); + }); + + it('shows no data state when the Runs list has selections but no metrics', () => { + compareRunsMetricsContent + .findScalarMetricsTabContent() + .findScalarMetricsNoMetricsState() + .should('exist'); + compareRunsMetricsContent.findConfusionMatrixTab().click(); + compareRunsMetricsContent + .findConfusionMatrixTabContent() + .findConfusionMatrixNoMetricsState() + .should('exist'); + compareRunsMetricsContent.findRocCurveTab().click(); + compareRunsMetricsContent + .findRocCurveTabContent() + .findRocCurveNoMetricsState() + .should('exist'); + compareRunsMetricsContent.findMarkdownTab().click(); + compareRunsMetricsContent + .findMarkdownTabContent() + .findMarkdownNoMetricsState() + .should('exist'); + }); + + it('shows empty state when the Runs list has no selections', () => { + compareRunsListTable.findSelectAllCheckbox().click(); // Uncheck all + compareRunsMetricsContent + .findScalarMetricsTabContent() + .findScalarMetricsEmptyState() + .should('exist'); + compareRunsMetricsContent.findConfusionMatrixTab().click(); + compareRunsMetricsContent + .findConfusionMatrixTabContent() + .findConfusionMatrixEmptyState() + .should('exist'); + compareRunsMetricsContent.findRocCurveTab().click(); + compareRunsMetricsContent.findRocCurveTabContent().findRocCurveEmptyState().should('exist'); + compareRunsMetricsContent.findMarkdownTab().click(); + compareRunsMetricsContent.findMarkdownTabContent().findMarkdownEmptyState().should('exist'); }); }); }); -const initIntercepts = () => { +const initIntercepts = (noMetrics?: boolean) => { cy.interceptOdh( 'GET /api/config', mockDashboardConfig({ disablePipelineExperiments: false, disableS3Endpoint: false }), @@ -366,5 +437,13 @@ const initIntercepts = () => { mockRun2, ); - initMlmdIntercepts(projectName); + cy.interceptOdh( + 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/runs/:runId', + { + path: { namespace: projectName, serviceName: 'dspa', runId: mockRun3.run_id }, + }, + mockRun3, + ); + + initMlmdIntercepts(projectName, { noMetrics }); }; diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/executions.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/executions.cy.ts index 80aa0b7f3a..cf3203394b 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/executions.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/executions.cy.ts @@ -188,10 +188,6 @@ const initIntercepts = (interceptMlmd: boolean, isExecutionsEmpty?: boolean) => ); if (interceptMlmd) { - if (isExecutionsEmpty) { - initMlmdIntercepts(projectName, true); - } else { - initMlmdIntercepts(projectName, false); - } + initMlmdIntercepts(projectName, { isExecutionsEmpty }); } }; diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/mlmdUtils.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/mlmdUtils.ts index 1958646cd1..aaa5cb9f05 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/mlmdUtils.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/mlmdUtils.ts @@ -6,7 +6,11 @@ import { mockGetExecutions, mockGetNoExecutions } from '~/__mocks__/mlmd/mockGet import { mockGetExecutionsByContext } from '~/__mocks__/mlmd/mockGetExecutionsByContext'; import { mockGetExecutionsByID } from '~/__mocks__/mlmd/mockGetExecutionsByID'; -export const initMlmdIntercepts = (projectName: string, isExecutionsEmpty?: boolean): void => { +export const initMlmdIntercepts = ( + projectName: string, + options: { isExecutionsEmpty?: boolean; noMetrics?: boolean } = {}, +): void => { + const { isExecutionsEmpty, noMetrics } = options; cy.interceptOdh( 'POST /api/service/mlmd/:namespace/:serviceName/ml_metadata.MetadataStoreService/GetArtifactTypes', { path: { namespace: projectName, serviceName: 'dspa' } }, @@ -20,7 +24,7 @@ export const initMlmdIntercepts = (projectName: string, isExecutionsEmpty?: bool cy.interceptOdh( 'POST /api/service/mlmd/:namespace/:serviceName/ml_metadata.MetadataStoreService/GetArtifactsByContext', { path: { namespace: projectName, serviceName: 'dspa' } }, - mockGetArtifactsByContext(), + mockGetArtifactsByContext(noMetrics), ); cy.interceptOdh( 'POST /api/service/mlmd/:namespace/:serviceName/ml_metadata.MetadataStoreService/GetExecutionsByContext', diff --git a/frontend/src/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics.tsx b/frontend/src/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics.tsx new file mode 100644 index 0000000000..e4de3e9ba1 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics.tsx @@ -0,0 +1,23 @@ +import { + EmptyState, + EmptyStateVariant, + EmptyStateHeader, + EmptyStateBody, +} from '@patternfly/react-core'; +import React from 'react'; + +type CompareRunsNoMetricsProps = Omit, 'children'> & { + title?: string; +}; + +export const CompareRunsNoMetrics: React.FC = ({ + title = 'No metrics to compare', + ...props +}) => ( + + + + The selected runs do not contain relevant metrics. Select different runs to compare. + + +); diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx index 44f31aa0dc..e731de9ade 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/confusionMatrix/ConfusionMatrixCompare.tsx @@ -1,15 +1,5 @@ import * as React from 'react'; -import { - Bullseye, - Divider, - EmptyState, - EmptyStateBody, - EmptyStateHeader, - EmptyStateVariant, - Flex, - FlexItem, - Spinner, -} from '@patternfly/react-core'; +import { Bullseye, Divider, Flex, FlexItem, Spinner } from '@patternfly/react-core'; import { PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes'; import { RunArtifact } from '~/concepts/pipelines/apiHooks/mlmd/types'; @@ -23,17 +13,20 @@ import { PipelineRunArtifactSelect } from '~/concepts/pipelines/content/compareR import { ConfusionMatrixConfig } from '~/concepts/pipelines/content/artifacts/charts/confusionMatrix/types'; import { buildConfusionMatrixConfig } from '~/concepts/pipelines/content/artifacts/charts/confusionMatrix/utils'; import ConfusionMatrix from '~/concepts/pipelines/content/artifacts/charts/confusionMatrix/ConfusionMatrix'; +import { CompareRunsNoMetrics } from '~/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics'; import { isConfusionMatrix } from './utils'; import { ConfusionMatrixConfigAndTitle } from './types'; type ConfusionMatrixCompareProps = { runArtifacts?: RunArtifact[]; isLoaded: boolean; + isEmpty: boolean; }; const ConfusionMatrixCompare: React.FC = ({ runArtifacts, isLoaded, + isEmpty, }) => { const [expandedGraph, setExpandedGraph] = React.useState< ConfusionMatrixConfigAndTitle | undefined @@ -94,21 +87,11 @@ const ConfusionMatrixCompare: React.FC = ({ ); } - if (!runArtifacts || runArtifacts.length === 0) { - return ; + if (isEmpty) { + return ; } if (Object.keys(configMap).length === 0) { - return ( - - - - There are no confusion matrix artifacts available on the selected runs. - - - ); + return ; } return ( diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/markdown/MarkdownCompare.tsx b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/markdown/MarkdownCompare.tsx index 9c3160d241..f365962ee5 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/markdown/MarkdownCompare.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/markdown/MarkdownCompare.tsx @@ -3,10 +3,6 @@ import { Alert, Bullseye, Divider, - EmptyState, - EmptyStateBody, - EmptyStateHeader, - EmptyStateVariant, Flex, FlexItem, Spinner, @@ -19,6 +15,7 @@ import MarkdownView from '~/components/MarkdownView'; import { MAX_STORAGE_OBJECT_SIZE } from '~/services/storageService'; import { bytesAsRoundedGiB } from '~/utilities/number'; import { PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes'; +import { CompareRunsNoMetrics } from '~/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics'; type MarkdownCompareProps = { configMap: Record; @@ -50,18 +47,11 @@ const MarkdownCompare: React.FC = ({ } if (isEmpty) { - return ; + return ; } if (Object.keys(configMap).length === 0) { - return ( - - - - There are no markdown artifacts available on the selected runs. - - - ); + return ; } const renderMarkdownWithSize = (config: MarkdownAndTitle) => ( diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/RocCurveCompare.tsx b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/RocCurveCompare.tsx index 628edf6422..b2ccda64b0 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/RocCurveCompare.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/roc/RocCurveCompare.tsx @@ -1,16 +1,5 @@ import * as React from 'react'; -import { - Bullseye, - EmptyState, - EmptyStateBody, - EmptyStateHeader, - EmptyStateVariant, - Flex, - Spinner, - Split, - SplitItem, - Text, -} from '@patternfly/react-core'; +import { Bullseye, Flex, Spinner, Split, SplitItem, Text } from '@patternfly/react-core'; import DashboardHelpTooltip from '~/concepts/dashboard/DashboardHelpTooltip'; import { useCheckboxTableBase } from '~/components/table'; import ROCCurve from '~/concepts/pipelines/content/artifacts/charts/ROCCurve'; @@ -21,6 +10,7 @@ import { getLinkedArtifactId, } from '~/concepts/pipelines/content/compareRuns/metricsSection/utils'; import { CompareRunsEmptyState } from '~/concepts/pipelines/content/compareRuns/CompareRunsEmptyState'; +import { CompareRunsNoMetrics } from '~/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics'; import RocCurveTable from './RocCurveTable'; import { FullArtifactPathsAndConfig } from './types'; import { isConfidenceMetric, buildRocCurveConfig } from './utils'; @@ -28,9 +18,10 @@ import { isConfidenceMetric, buildRocCurveConfig } from './utils'; type RocCurveCompareProps = { runArtifacts?: RunArtifact[]; isLoaded: boolean; + isEmpty: boolean; }; -const RocCurveCompare: React.FC = ({ runArtifacts, isLoaded }) => { +const RocCurveCompare: React.FC = ({ runArtifacts, isLoaded, isEmpty }) => { const [search, setSearch] = React.useState(''); const [selected, setSelected] = React.useState([]); @@ -93,18 +84,11 @@ const RocCurveCompare: React.FC = ({ runArtifacts, isLoade ); } - if (!runArtifacts || runArtifacts.length === 0) { - return ; + if (isEmpty) { + return ; } if (Object.keys(configs).length === 0) { - return ( - - - - There are no ROC curve artifacts available on the selected runs. - - - ); + return ; } return ( diff --git a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/scalar/ScalarMetricTable.tsx b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/scalar/ScalarMetricTable.tsx index d63ff39d14..1f7828010c 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/scalar/ScalarMetricTable.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/metricsSection/scalar/ScalarMetricTable.tsx @@ -1,27 +1,24 @@ import * as React from 'react'; import { InnerScrollContainer, TableVariant, Td, Tr } from '@patternfly/react-table'; -import { - Bullseye, - EmptyState, - EmptyStateBody, - EmptyStateHeader, - EmptyStateVariant, - Flex, - Spinner, - Switch, -} from '@patternfly/react-core'; +import { Bullseye, Flex, Spinner, Switch } from '@patternfly/react-core'; import { Table } from '~/components/table'; import { RunArtifact } from '~/concepts/pipelines/apiHooks/mlmd/types'; import { CompareRunsEmptyState } from '~/concepts/pipelines/content/compareRuns/CompareRunsEmptyState'; +import { CompareRunsNoMetrics } from '~/concepts/pipelines/content/compareRuns/CompareRunsNoMetrics'; import { ScalarTableData } from './types'; import { generateTableStructure } from './utils'; type ScalarMetricTableProps = { runArtifacts?: RunArtifact[]; isLoaded: boolean; + isEmpty: boolean; }; -const ScalarMetricTable: React.FC = ({ runArtifacts, isLoaded }) => { +const ScalarMetricTable: React.FC = ({ + runArtifacts, + isLoaded, + isEmpty, +}) => { const { columns, data, subColumns } = generateTableStructure(runArtifacts ?? []); const [isHideSameRowsChecked, setIsHideSameRowsChecked] = React.useState(false); @@ -69,18 +66,11 @@ const ScalarMetricTable: React.FC = ({ runArtifacts, isL ); } - if (!runArtifacts || runArtifacts.length === 0) { + if (isEmpty) { return ; } if (!hasScalarMetrics) { - return ( - - - - There are no scalar metric artifacts available on the selected runs. - - - ); + return ; } return ( diff --git a/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsMetricsSection.tsx b/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsMetricsSection.tsx index 14792d8bb1..328750fee4 100644 --- a/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsMetricsSection.tsx +++ b/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsMetricsSection.tsx @@ -56,6 +56,8 @@ export const CompareRunMetricsSection: React.FunctionComponent = () => { return [_.pick(configMap, selectedIds), _.pick(runMap, selectedIds)]; }, [configMap, runMap, selectedRuns]); + const isEmpty = selectedRuns.length === 0; + const filterSelected = React.useCallback( (runArtifact: RunArtifact) => selectedRuns.some((run) => run.run_id === runArtifact.run.run_id), [selectedRuns], @@ -82,7 +84,11 @@ export const CompareRunMetricsSection: React.FunctionComponent = () => { data-testid="compare-runs-scalar-metrics-tab" > - + { @@ -103,7 +110,11 @@ export const CompareRunMetricsSection: React.FunctionComponent = () => { data-testid="compare-runs-roc-curve-tab" > - + {isS3EndpointAvailable && ( @@ -116,7 +127,7 @@ export const CompareRunMetricsSection: React.FunctionComponent = () => {