From b4b1b2ff061dd1246f4569da092c9830406781e9 Mon Sep 17 00:00:00 2001 From: Juntao Wang Date: Wed, 15 May 2024 17:48:45 -0400 Subject: [PATCH] Fix pipeline version multi-select issue --- .../__mocks__/mockPipelineVersionsProxy.ts | 433 +++++++++--------- .../e2e/pipelines/PipelineCreateRuns.cy.ts | 12 +- .../cypress/e2e/pipelines/Pipelines.cy.ts | 143 +++--- .../cypress/e2e/pipelines/PipelinesList.cy.ts | 9 +- .../cypress/cypress/pages/components/table.ts | 8 + .../cypress/pages/pipelines/pipelinesTable.ts | 35 +- .../table/TableRowTitleDescription.tsx | 4 +- .../tables/pipeline/PipelinesTableRow.tsx | 2 +- .../pipelineVersion/PipelineVersionTable.tsx | 1 + .../PipelineVersionTableRow.tsx | 3 +- .../usePipelineVersionsCheckboxTable.ts | 2 +- 11 files changed, 352 insertions(+), 300 deletions(-) diff --git a/frontend/src/__mocks__/mockPipelineVersionsProxy.ts b/frontend/src/__mocks__/mockPipelineVersionsProxy.ts index 8069e75610..0dff70165a 100644 --- a/frontend/src/__mocks__/mockPipelineVersionsProxy.ts +++ b/frontend/src/__mocks__/mockPipelineVersionsProxy.ts @@ -445,272 +445,277 @@ export const buildMockPipelineVersion = ( export const buildMockPipelineVersionV2 = ( pipelineVersion?: Partial, -): PipelineVersionKFv2 => ({ - pipeline_id: '8ce2d041-3eb9-41a0-828c-45209fdf1c20', - pipeline_version_id: '8ce2d04a0-828c-45209fdf1c20', - display_name: 'version-1', - created_at: '2023-12-07T16:08:01Z', - description: 'test', +): PipelineVersionKFv2 => { + /* eslint-disable @typescript-eslint/naming-convention */ + const display_name = pipelineVersion?.display_name || 'Test pipeline version'; + const pipeline_version_id = display_name.replace(/ /g, '-').toLowerCase(); + return { + pipeline_id: '8ce2d041-3eb9-41a0-828c-45209fdf1c20', + pipeline_version_id, + display_name, + created_at: '2023-12-07T16:08:01Z', + description: 'test', - pipeline_spec: { - platform_spec: { - platforms: { - kubernetes: { - deploymentSpec: { - executors: { - 'exec-normalize-dataset': { - container: { image: '' }, - pvcMount: [ - { - mountPath: '/data/1', - taskOutputParameter: { - outputParameterKey: 'name', - producerTask: 'create-dataset', + pipeline_spec: { + platform_spec: { + platforms: { + kubernetes: { + deploymentSpec: { + executors: { + 'exec-normalize-dataset': { + container: { image: '' }, + pvcMount: [ + { + mountPath: '/data/1', + taskOutputParameter: { + outputParameterKey: 'name', + producerTask: 'create-dataset', + }, }, - }, - ], - }, - 'exec-train-model': { - container: { image: '' }, - pvcMount: [ - { - mountPath: '/data/2', - taskOutputParameter: { - outputParameterKey: 'name', - producerTask: 'normalize-dataset', + ], + }, + 'exec-train-model': { + container: { image: '' }, + pvcMount: [ + { + mountPath: '/data/2', + taskOutputParameter: { + outputParameterKey: 'name', + producerTask: 'normalize-dataset', + }, }, - }, - ], + ], + }, }, }, }, }, }, - }, - pipeline_spec: { - components: { - 'comp-create-dataset': { - executorLabel: 'exec-create-dataset', - outputDefinitions: { - artifacts: { - iris_dataset: { - artifactType: { - schemaTitle: ArtifactType.DATASET, - schemaVersion: '0.0.1', + pipeline_spec: { + components: { + 'comp-create-dataset': { + executorLabel: 'exec-create-dataset', + outputDefinitions: { + artifacts: { + iris_dataset: { + artifactType: { + schemaTitle: ArtifactType.DATASET, + schemaVersion: '0.0.1', + }, }, }, }, }, - }, - 'comp-normalize-dataset': { - executorLabel: 'exec-normalize-dataset', - inputDefinitions: { - artifacts: { - input_iris_dataset: { - artifactType: { - schemaTitle: ArtifactType.DATASET, - schemaVersion: '0.0.1', + 'comp-normalize-dataset': { + executorLabel: 'exec-normalize-dataset', + inputDefinitions: { + artifacts: { + input_iris_dataset: { + artifactType: { + schemaTitle: ArtifactType.DATASET, + schemaVersion: '0.0.1', + }, }, }, - }, - parameters: { - min_max_scaler: { - parameterType: InputDefinitionParameterType.BOOLEAN, - }, - standard_scaler: { - parameterType: InputDefinitionParameterType.STRING, + parameters: { + min_max_scaler: { + parameterType: InputDefinitionParameterType.BOOLEAN, + }, + standard_scaler: { + parameterType: InputDefinitionParameterType.STRING, + }, }, }, - }, - outputDefinitions: { - artifacts: { - normalized_iris_dataset: { - artifactType: { - schemaTitle: ArtifactType.DATASET, - schemaVersion: '0.0.1', + outputDefinitions: { + artifacts: { + normalized_iris_dataset: { + artifactType: { + schemaTitle: ArtifactType.DATASET, + schemaVersion: '0.0.1', + }, }, }, }, }, - }, - 'comp-train-model': { - executorLabel: 'exec-train-model', - inputDefinitions: { - artifacts: { - normalized_iris_dataset: { - artifactType: { - schemaTitle: ArtifactType.DATASET, - schemaVersion: '0.0.1', + 'comp-train-model': { + executorLabel: 'exec-train-model', + inputDefinitions: { + artifacts: { + normalized_iris_dataset: { + artifactType: { + schemaTitle: ArtifactType.DATASET, + schemaVersion: '0.0.1', + }, }, }, - }, - parameters: { - n_neighbors: { - parameterType: InputDefinitionParameterType.INTEGER, + parameters: { + n_neighbors: { + parameterType: InputDefinitionParameterType.INTEGER, + }, }, }, - }, - outputDefinitions: { - artifacts: { - model: { - artifactType: { - schemaTitle: ArtifactType.MODEL, - schemaVersion: '0.0.1', + outputDefinitions: { + artifacts: { + model: { + artifactType: { + schemaTitle: ArtifactType.MODEL, + schemaVersion: '0.0.1', + }, }, }, }, }, }, - }, - deploymentSpec: { - executors: { - 'exec-create-dataset': { - container: { - args: ['--executor_input', '{{$}}', '--function_to_execute', 'create_dataset'], - command: [ - 'sh', - '-c', - '\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location \'kfp==2.6.0\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && "$0" "$@"\n', - 'sh', - '-ec', - 'program_path=$(mktemp -d)\n\nprintf "%s" "$0" > "$program_path/ephemeral_component.py"\n_KFP_RUNTIME=true python3 -m kfp.dsl.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@"\n', - "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import *\n\ndef create_dataset(iris_dataset: Output[Dataset]):\n import pandas as pd\n\n csv_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'\n col_names = [\n 'Sepal_Length', 'Sepal_Width', 'Petal_Length', 'Petal_Width', 'Labels'\n ]\n df = pd.read_csv(csv_url, names=col_names)\n\n with open(iris_dataset.path, 'w') as f:\n df.to_csv(f)\n\n", - ], - image: 'quay.io/hukhan/iris-base:1', - }, - }, - 'exec-normalize-dataset': { - container: { - args: ['--executor_input', '{{$}}', '--function_to_execute', 'normalize_dataset'], - command: [ - 'sh', - '-c', - '\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location \'kfp==2.6.0\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && "$0" "$@"\n', - 'sh', - '-ec', - 'program_path=$(mktemp -d)\n\nprintf "%s" "$0" > "$program_path/ephemeral_component.py"\n_KFP_RUNTIME=true python3 -m kfp.dsl.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@"\n', - "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import *\n\ndef normalize_dataset(\n input_iris_dataset: Input[Dataset],\n normalized_iris_dataset: Output[Dataset],\n standard_scaler: bool,\n min_max_scaler: bool,\n):\n if standard_scaler is min_max_scaler:\n raise ValueError(\n 'Exactly one of standard_scaler or min_max_scaler must be True.')\n\n import pandas as pd\n from sklearn.preprocessing import MinMaxScaler\n from sklearn.preprocessing import StandardScaler\n\n with open(input_iris_dataset.path) as f:\n df = pd.read_csv(f)\n labels = df.pop('Labels')\n\n if standard_scaler:\n scaler = StandardScaler()\n if min_max_scaler:\n scaler = MinMaxScaler()\n\n df = pd.DataFrame(scaler.fit_transform(df))\n df['Labels'] = labels\n normalized_iris_dataset.metadata['state'] = \"Normalized\"\n with open(normalized_iris_dataset.path, 'w') as f:\n df.to_csv(f)\n\n", - ], - image: 'quay.io/hukhan/iris-base:1', - }, - }, - 'exec-train-model': { - container: { - args: ['--executor_input', '{{$}}', '--function_to_execute', 'train_model'], - command: [ - 'sh', - '-c', - '\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location \'kfp==2.6.0\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && "$0" "$@"\n', - 'sh', - '-ec', - 'program_path=$(mktemp -d)\n\nprintf "%s" "$0" > "$program_path/ephemeral_component.py"\n_KFP_RUNTIME=true python3 -m kfp.dsl.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@"\n', - "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import *\n\ndef train_model(\n normalized_iris_dataset: Input[Dataset],\n model: Output[Model],\n n_neighbors: int,\n):\n import pickle\n\n import pandas as pd\n from sklearn.model_selection import train_test_split\n from sklearn.neighbors import KNeighborsClassifier\n\n with open(normalized_iris_dataset.path) as f:\n df = pd.read_csv(f)\n\n y = df.pop('Labels')\n X = df\n\n X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)\n\n clf = KNeighborsClassifier(n_neighbors=n_neighbors)\n clf.fit(X_train, y_train)\n\n model.metadata['framework'] = 'scikit-learn'\n with open(model.path, 'wb') as f:\n pickle.dump(clf, f)\n\n", - ], - image: 'quay.io/hukhan/iris-base:1', + deploymentSpec: { + executors: { + 'exec-create-dataset': { + container: { + args: ['--executor_input', '{{$}}', '--function_to_execute', 'create_dataset'], + command: [ + 'sh', + '-c', + '\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location \'kfp==2.6.0\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && "$0" "$@"\n', + 'sh', + '-ec', + 'program_path=$(mktemp -d)\n\nprintf "%s" "$0" > "$program_path/ephemeral_component.py"\n_KFP_RUNTIME=true python3 -m kfp.dsl.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@"\n', + "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import *\n\ndef create_dataset(iris_dataset: Output[Dataset]):\n import pandas as pd\n\n csv_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'\n col_names = [\n 'Sepal_Length', 'Sepal_Width', 'Petal_Length', 'Petal_Width', 'Labels'\n ]\n df = pd.read_csv(csv_url, names=col_names)\n\n with open(iris_dataset.path, 'w') as f:\n df.to_csv(f)\n\n", + ], + image: 'quay.io/hukhan/iris-base:1', + }, + }, + 'exec-normalize-dataset': { + container: { + args: ['--executor_input', '{{$}}', '--function_to_execute', 'normalize_dataset'], + command: [ + 'sh', + '-c', + '\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location \'kfp==2.6.0\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && "$0" "$@"\n', + 'sh', + '-ec', + 'program_path=$(mktemp -d)\n\nprintf "%s" "$0" > "$program_path/ephemeral_component.py"\n_KFP_RUNTIME=true python3 -m kfp.dsl.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@"\n', + "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import *\n\ndef normalize_dataset(\n input_iris_dataset: Input[Dataset],\n normalized_iris_dataset: Output[Dataset],\n standard_scaler: bool,\n min_max_scaler: bool,\n):\n if standard_scaler is min_max_scaler:\n raise ValueError(\n 'Exactly one of standard_scaler or min_max_scaler must be True.')\n\n import pandas as pd\n from sklearn.preprocessing import MinMaxScaler\n from sklearn.preprocessing import StandardScaler\n\n with open(input_iris_dataset.path) as f:\n df = pd.read_csv(f)\n labels = df.pop('Labels')\n\n if standard_scaler:\n scaler = StandardScaler()\n if min_max_scaler:\n scaler = MinMaxScaler()\n\n df = pd.DataFrame(scaler.fit_transform(df))\n df['Labels'] = labels\n normalized_iris_dataset.metadata['state'] = \"Normalized\"\n with open(normalized_iris_dataset.path, 'w') as f:\n df.to_csv(f)\n\n", + ], + image: 'quay.io/hukhan/iris-base:1', + }, + }, + 'exec-train-model': { + container: { + args: ['--executor_input', '{{$}}', '--function_to_execute', 'train_model'], + command: [ + 'sh', + '-c', + '\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location \'kfp==2.6.0\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && "$0" "$@"\n', + 'sh', + '-ec', + 'program_path=$(mktemp -d)\n\nprintf "%s" "$0" > "$program_path/ephemeral_component.py"\n_KFP_RUNTIME=true python3 -m kfp.dsl.executor_main --component_module_path "$program_path/ephemeral_component.py" "$@"\n', + "\nimport kfp\nfrom kfp import dsl\nfrom kfp.dsl import *\nfrom typing import *\n\ndef train_model(\n normalized_iris_dataset: Input[Dataset],\n model: Output[Model],\n n_neighbors: int,\n):\n import pickle\n\n import pandas as pd\n from sklearn.model_selection import train_test_split\n from sklearn.neighbors import KNeighborsClassifier\n\n with open(normalized_iris_dataset.path) as f:\n df = pd.read_csv(f)\n\n y = df.pop('Labels')\n X = df\n\n X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)\n\n clf = KNeighborsClassifier(n_neighbors=n_neighbors)\n clf.fit(X_train, y_train)\n\n model.metadata['framework'] = 'scikit-learn'\n with open(model.path, 'wb') as f:\n pickle.dump(clf, f)\n\n", + ], + image: 'quay.io/hukhan/iris-base:1', + }, }, }, }, - }, - pipelineInfo: { - name: 'iris-training-pipeline', - }, - root: { - dag: { - tasks: { - 'create-dataset': { - cachingOptions: { - enableCache: true, - }, - componentRef: { - name: 'comp-create-dataset', - }, - taskInfo: { - name: 'create-dataset', - }, - }, - 'normalize-dataset': { - cachingOptions: { - enableCache: true, - }, - componentRef: { - name: 'comp-normalize-dataset', + pipelineInfo: { + name: 'iris-training-pipeline', + }, + root: { + dag: { + tasks: { + 'create-dataset': { + cachingOptions: { + enableCache: true, + }, + componentRef: { + name: 'comp-create-dataset', + }, + taskInfo: { + name: 'create-dataset', + }, }, - dependentTasks: ['create-dataset'], - inputs: { - artifacts: { - input_iris_dataset: { - taskOutputArtifact: { - outputArtifactKey: 'iris_dataset', - producerTask: 'create-dataset', - }, - }, + 'normalize-dataset': { + cachingOptions: { + enableCache: true, }, - parameters: { - min_max_scaler: { - runtimeValue: { - constant: 'false', + componentRef: { + name: 'comp-normalize-dataset', + }, + dependentTasks: ['create-dataset'], + inputs: { + artifacts: { + input_iris_dataset: { + taskOutputArtifact: { + outputArtifactKey: 'iris_dataset', + producerTask: 'create-dataset', + }, }, }, - standard_scaler: { - runtimeValue: { - constant: 'true', + parameters: { + min_max_scaler: { + runtimeValue: { + constant: 'false', + }, + }, + standard_scaler: { + runtimeValue: { + constant: 'true', + }, }, }, }, + taskInfo: { + name: 'normalize-dataset', + }, }, - taskInfo: { - name: 'normalize-dataset', - }, - }, - 'train-model': { - cachingOptions: { - enableCache: true, - }, - componentRef: { - name: 'comp-train-model', - }, - dependentTasks: ['normalize-dataset'], - inputs: { - artifacts: { - normalized_iris_dataset: { - taskOutputArtifact: { - outputArtifactKey: 'normalized_iris_dataset', - producerTask: 'normalize-dataset', + 'train-model': { + cachingOptions: { + enableCache: true, + }, + componentRef: { + name: 'comp-train-model', + }, + dependentTasks: ['normalize-dataset'], + inputs: { + artifacts: { + normalized_iris_dataset: { + taskOutputArtifact: { + outputArtifactKey: 'normalized_iris_dataset', + producerTask: 'normalize-dataset', + }, }, }, - }, - parameters: { - n_neighbors: { - componentInputParameter: 'neighbors', + parameters: { + n_neighbors: { + componentInputParameter: 'neighbors', + }, }, }, - }, - taskInfo: { - name: 'train-model', + taskInfo: { + name: 'train-model', + }, }, }, }, - }, - inputDefinitions: { - parameters: { - min_max_scaler: { - parameterType: InputDefinitionParameterType.BOOLEAN, - }, - neighbors: { - parameterType: InputDefinitionParameterType.INTEGER, - }, - standard_scaler: { - parameterType: InputDefinitionParameterType.STRING, + inputDefinitions: { + parameters: { + min_max_scaler: { + parameterType: InputDefinitionParameterType.BOOLEAN, + }, + neighbors: { + parameterType: InputDefinitionParameterType.INTEGER, + }, + standard_scaler: { + parameterType: InputDefinitionParameterType.STRING, + }, }, }, }, + schemaVersion: '2.1.0', + sdkVersion: 'kfp-2.6.0', }, - schemaVersion: '2.1.0', - sdkVersion: 'kfp-2.6.0', }, - }, - ...pipelineVersion, -}); + ...pipelineVersion, + }; +}; export const buildMockPipelineVersions = ( versions: PipelineVersionKF[] = mockPipelineVersionsList, diff --git a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineCreateRuns.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineCreateRuns.cy.ts index 5810460402..85ae29fef7 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineCreateRuns.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelineCreateRuns.cy.ts @@ -152,7 +152,7 @@ describe('Pipeline create runs', () => { description: 'New run description', pipeline_version_reference: { pipeline_id: 'test-pipeline', - pipeline_version_id: '8ce2d04a0-828c-45209fdf1c20', + pipeline_version_id: 'test-pipeline-version', }, runtime_config: { parameters: { min_max_scaler: false, neighbors: 1, standard_scaler: 'yes' }, @@ -218,7 +218,7 @@ describe('Pipeline create runs', () => { description: '', pipeline_version_reference: { pipeline_id: 'test-pipeline', - pipeline_version_id: '8ce2d04a0-828c-45209fdf1c20', + pipeline_version_id: 'test-pipeline-version', }, runtime_config: { parameters: { min_max_scaler: false, neighbors: 1, standard_scaler: false }, @@ -343,7 +343,7 @@ describe('Pipeline create runs', () => { description: '', pipeline_version_reference: { pipeline_id: 'test-pipeline', - pipeline_version_id: '8ce2d04a0-828c-45209fdf1c20', + pipeline_version_id: 'test-pipeline-version', }, runtime_config: createRunParams.runtime_config, service_account: '', @@ -456,7 +456,7 @@ describe('Pipeline create runs', () => { description: '', pipeline_version_reference: { pipeline_id: 'test-pipeline', - pipeline_version_id: '8ce2d04a0-828c-45209fdf1c20', + pipeline_version_id: 'test-pipeline-version', }, runtime_config: createRunParams.runtime_config, service_account: '', @@ -554,7 +554,7 @@ describe('Pipeline create runs', () => { description: 'New job description', pipeline_version_reference: { pipeline_id: 'test-pipeline', - pipeline_version_id: '8ce2d04a0-828c-45209fdf1c20', + pipeline_version_id: 'test-pipeline-version', }, runtime_config: { parameters: { min_max_scaler: false, neighbors: 1, standard_scaler: 'no' }, @@ -626,7 +626,7 @@ describe('Pipeline create runs', () => { description: '', pipeline_version_reference: { pipeline_id: 'test-pipeline', - pipeline_version_id: '8ce2d04a0-828c-45209fdf1c20', + pipeline_version_id: 'test-pipeline-version', }, runtime_config: { parameters: { min_max_scaler: false, neighbors: 0, standard_scaler: 'yes' }, diff --git a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts index 9d5a655be3..faaf0599a0 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/Pipelines.cy.ts @@ -394,7 +394,7 @@ describe('Pipelines', () => { initIntercepts({}); pipelinesGlobal.visit(projectName); pipelinesTable.find(); - pipelinesTable.getRowByName('Test pipeline').find().should('exist'); + pipelinesTable.getRowById(initialMockPipeline.pipeline_id).find().should('exist'); }); describe('Table filtering and sorting', () => { @@ -420,13 +420,13 @@ describe('Pipelines', () => { pipelinesTable.findFilterTextField().type('Test pipeline 1'); pipelinesTable.mockGetPipelines( - mockPipelines.filter((mockpipeline) => - mockpipeline.display_name.includes('Test pipeline 1'), + mockPipelines.filter((mockPipeline) => + mockPipeline.display_name.includes('Test pipeline 1'), ), projectName, ); - pipelinesTable.getRowByName('Test pipeline 1').find().should('exist'); + pipelinesTable.getRowById(mockPipelines[0].pipeline_id).find().should('exist'); pipelinesTable.findRows().should('have.length', 1); }); @@ -451,7 +451,7 @@ describe('Pipelines', () => { pipelinesTable.findRows().should('have.length', 2); pipelinesTable.mockGetPipelines( - mockPipelines.filter((mockpipeline) => mockpipeline.created_at.includes('2024-01-30')), + mockPipelines.filter((mockPipeline) => mockPipeline.created_at.includes('2024-01-30')), projectName, ); @@ -459,7 +459,7 @@ describe('Pipelines', () => { pipelinesTable.findFilterTextField().type('2024-01-30'); pipelinesTable.findRows().should('have.length', 1); - pipelinesTable.getRowByName('Test pipeline 2').find().should('exist'); + pipelinesTable.getRowById(mockPipelines[1].pipeline_id).find().should('exist'); }); it('table with no result found', () => { @@ -468,10 +468,10 @@ describe('Pipelines', () => { pipelinesTable.selectFilterByName('Pipeline name'); pipelinesTable.findFilterTextField().type('abc'); - const mockPipeline = [initialMockPipeline]; + const mockPipelines = [initialMockPipeline]; pipelinesTable.mockGetPipelines( - mockPipeline.filter((mockpipeline) => mockpipeline.display_name.includes('abc')), + mockPipelines.filter((mockPipeline) => mockPipeline.display_name.includes('abc')), projectName, ); @@ -595,7 +595,7 @@ describe('Pipelines', () => { }); // Verify the uploaded pipeline is in the table - pipelinesTable.getRowByName('New pipeline').find().should('exist'); + pipelinesTable.getRowById(uploadedMockPipeline.pipeline_id).find().should('exist'); }); it('imports a new pipeline by url', () => { @@ -645,7 +645,7 @@ describe('Pipelines', () => { cy.wait('@refreshPipelines'); // Verify the uploaded pipeline is in the table - pipelinesTable.getRowByName('New pipeline').find().should('exist'); + pipelinesTable.getRowById(createdMockPipeline.pipeline_id).find().should('exist'); }); it('uploads a new pipeline version', () => { @@ -663,13 +663,15 @@ describe('Pipelines', () => { // Open the "Upload new version" modal pipelinesGlobal.findUploadVersionButton().click(); + const uploadedMockPipelineVersion = buildMockPipelineVersionV2(uploadVersionParams); + // Intercept upload/re-fetch of pipeline versions pipelineVersionImportModal .mockUploadVersion(uploadVersionParams, projectName) .as('uploadVersion'); pipelinesTable .mockGetPipelineVersions( - [initialMockPipelineVersion, buildMockPipelineVersionV2(uploadVersionParams)], + [initialMockPipelineVersion, uploadedMockPipelineVersion], initialMockPipeline.pipeline_id, projectName, ) @@ -709,8 +711,12 @@ describe('Pipelines', () => { }); // Verify the uploaded pipeline version is in the table - pipelinesTable.getRowByName('Test pipeline').toggleExpandByIndex(0); - pipelinesTable.getRowByName('New pipeline version').find().should('exist'); + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(uploadedMockPipelineVersion.pipeline_version_id) + .find() + .should('exist'); }); it('imports a new pipeline version by url', () => { @@ -730,10 +736,12 @@ describe('Pipelines', () => { // Open the "Upload new version" modal pipelinesGlobal.findUploadVersionButton().click(); + const uploadedMockPipelineVersion = buildMockPipelineVersionV2(createPipelineVersionParams); + // Intercept upload/re-fetch of pipeline versions pipelinesTable .mockGetPipelineVersions( - [initialMockPipelineVersion, buildMockPipelineVersionV2(createPipelineVersionParams)], + [initialMockPipelineVersion, uploadedMockPipelineVersion], initialMockPipeline.pipeline_id, projectName, ) @@ -756,8 +764,12 @@ describe('Pipelines', () => { cy.wait('@refreshVersions'); // Verify the uploaded pipeline version is in the table - pipelinesTable.getRowByName('Test pipeline').toggleExpandByIndex(0); - pipelinesTable.getRowByName('New pipeline version').find().should('exist'); + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(uploadedMockPipelineVersion.pipeline_version_id) + .find() + .should('exist'); }); it('delete a single pipeline', () => { @@ -769,7 +781,7 @@ describe('Pipelines', () => { // Check pipeline pipelinesTable - .getRowByName(initialMockPipeline.display_name) + .getRowById(initialMockPipeline.pipeline_id) .findKebabAction('Delete pipeline') .click(); pipelineDeleteModal.shouldBeOpen(); @@ -801,9 +813,10 @@ describe('Pipelines', () => { pipelinesTable.find(); // Check pipeline version - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); - pipelinesTable - .getRowByName(initialMockPipelineVersion.display_name) + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) .findKebabAction('Delete pipeline version') .click(); pipelineDeleteModal.shouldBeOpen(); @@ -818,7 +831,7 @@ describe('Pipelines', () => { pipelineDeleteModal.findSubmitButton().click(); cy.wait('@deleteVersion'); - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); + pipelineRow.findExpandButton().click(); cy.wait('@refreshVersions').then((interception) => { expect(interception.request.query).to.eql({ @@ -827,7 +840,7 @@ describe('Pipelines', () => { pipeline_id: 'test-pipeline', }); }); - pipelinesTable.getRowByName(initialMockPipeline.display_name).shouldNotHavePipelineVersion(); + pipelineRow.shouldNotHavePipelineVersion(); }); it('navigate to pipeline version details page', () => { @@ -836,10 +849,12 @@ describe('Pipelines', () => { // Wait for the pipelines table to load pipelinesTable.find(); - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); - pipelinesTable - .getRowByName(initialMockPipelineVersion.display_name) - .findPipelineName(initialMockPipelineVersion.display_name) + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + + pipelineRow + .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) + .findPipelineVersionLink() .click(); verifyRelativeURL( `/pipelines/${projectName}/pipeline/view/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}`, @@ -865,9 +880,15 @@ describe('Pipelines', () => { display_name: `${mockPipeline1.display_name} version 1`, }); + const mockPipeline1Version2 = buildMockPipelineVersionV2({ + pipeline_id: mockPipeline1.pipeline_id, + pipeline_version_id: 'test-pipeline-1-version-2', + display_name: `${mockPipeline1.display_name} version 2`, + }); + pipelinesTable.mockGetPipelines([mockPipeline1, mockPipeline2], projectName); pipelinesTable.mockGetPipelineVersions( - [mockPipeline1Version1], + [mockPipeline1Version1, mockPipeline1Version2], mockPipeline1.pipeline_id, projectName, ); @@ -880,12 +901,17 @@ describe('Pipelines', () => { pipelinesGlobal.visit(projectName); - // Check pipeline1 and one version in pipeline 2 - pipelinesTable.getRowByName(mockPipeline1.display_name).toggleExpandByIndex(0); - pipelinesTable.getRowByName(mockPipeline2.display_name).toggleExpandByIndex(1); + // Check pipeline1 version 1 and pipeline 2 + const pipelineRow1 = pipelinesTable.getRowById(mockPipeline1.pipeline_id); + pipelineRow1.findRowCheckbox().should('be.disabled'); + pipelineRow1.findExpandButton().click(); + pipelineRow1 + .getPipelineVersionRowById(mockPipeline1Version1.pipeline_version_id) + .findRowCheckbox() + .check(); - pipelinesTable.getRowByName(mockPipeline2.display_name).toggleCheckboxByRowName(); - pipelinesTable.getRowByName(mockPipeline1Version1.display_name).toggleCheckboxByRowName(); + const pipelineRow2 = pipelinesTable.getRowById(mockPipeline2.pipeline_id); + pipelineRow2.findRowCheckbox().should('be.enabled').check(); //Delete the selected pipeline and versions pipelinesGlobal.findDeleteButton().click(); @@ -906,8 +932,7 @@ describe('Pipelines', () => { cy.wait('@refreshVersions').then(() => { // Test deleted pipelinesTable.shouldRowNotBeVisible(mockPipeline2.display_name); - const pipelineTableRow = pipelinesTable.getRowByName(mockPipeline1.display_name); - pipelineTableRow.toggleExpandByIndex(0); + pipelinesTable.getRowById(mockPipeline1.pipeline_id).findExpandButton().click(); pipelinesTable.shouldRowNotBeVisible(mockPipeline1Version1.display_name); }); }); @@ -919,7 +944,7 @@ describe('Pipelines', () => { // Wait for the pipelines table to load pipelinesTable.find(); pipelinesTable - .getRowByName(initialMockPipeline.display_name) + .getRowById(initialMockPipeline.pipeline_id) .findKebabAction('Create run') .click(); verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); @@ -931,7 +956,7 @@ describe('Pipelines', () => { pipelinesTable.find(); pipelinesTable - .getRowByName(initialMockPipeline.display_name) + .getRowById(initialMockPipeline.pipeline_id) .findKebabAction('Schedule run') .click(); @@ -944,9 +969,10 @@ describe('Pipelines', () => { // Wait for the pipelines table to load pipelinesTable.find(); - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); - pipelinesTable - .getRowByName(initialMockPipelineVersion.display_name) + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) .findKebabAction('Create run') .click(); verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); @@ -957,9 +983,10 @@ describe('Pipelines', () => { pipelinesGlobal.visit(projectName); pipelinesTable.find(); - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); - pipelinesTable - .getRowByName(initialMockPipelineVersion.display_name) + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) .findKebabAction('Schedule run') .click(); @@ -972,9 +999,10 @@ describe('Pipelines', () => { // Wait for the pipelines table to load pipelinesTable.find(); - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); - pipelinesTable - .getRowByName(initialMockPipelineVersion.display_name) + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) .findKebabAction('View runs') .click(); verifyRelativeURL(`/pipelineRuns/${projectName}?runType=active`); @@ -985,22 +1013,23 @@ describe('Pipelines', () => { pipelinesGlobal.visit(projectName); pipelinesTable.find(); - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); - pipelinesTable - .getRowByName(initialMockPipelineVersion.display_name) + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) .findKebabAction('View schedules') .click(); verifyRelativeURL(`/pipelineRuns/${projectName}?runType=scheduled`); }); it('Table pagination', () => { - const mockPipelinesv2 = Array.from({ length: 25 }, (_, i) => + const mockPipelinesV2 = Array.from({ length: 25 }, (_, i) => buildMockPipelineV2({ display_name: `Test pipeline-${i}`, }), ); initIntercepts({ - mockPipelines: mockPipelinesv2.slice(0, 10), + mockPipelines: mockPipelinesV2.slice(0, 10), totalSize: 25, nextPageToken: 'page-2-token', }); @@ -1013,7 +1042,7 @@ describe('Pipelines', () => { }); }); - pipelinesTable.getRowByName('Test pipeline-0').find().should('exist'); + pipelinesTable.getRowById(mockPipelinesV2[0].pipeline_id).find().should('exist'); pipelinesTable.findRows().should('have.length', '10'); const pagination = tablePagination.top; @@ -1025,7 +1054,7 @@ describe('Pipelines', () => { method: 'GET', pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/pipelines`, }, - buildMockPipelines(mockPipelinesv2.slice(10, 20), 25), + buildMockPipelines(mockPipelinesV2.slice(10, 20), 25), ).as('refreshPipelines'); pagination.findNextButton().click(); @@ -1037,7 +1066,7 @@ describe('Pipelines', () => { }); }); - pipelinesTable.getRowByName('Test pipeline-10').find().should('exist'); + pipelinesTable.getRowById(mockPipelinesV2[10].pipeline_id).find().should('exist'); pipelinesTable.findRows().should('have.length', '10'); // test Previous button @@ -1046,7 +1075,7 @@ describe('Pipelines', () => { method: 'GET', pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/pipelines`, }, - buildMockPipelines(mockPipelinesv2.slice(0, 10), 25), + buildMockPipelines(mockPipelinesV2.slice(0, 10), 25), ).as('getFirstTenPipelines'); pagination.findPreviousButton().click(); @@ -1058,7 +1087,7 @@ describe('Pipelines', () => { }); }); - pipelinesTable.getRowByName('Test pipeline-0').find().should('exist'); + pipelinesTable.getRowById(mockPipelinesV2[0].pipeline_id).find().should('exist'); // 20 per page cy.intercept( @@ -1066,11 +1095,11 @@ describe('Pipelines', () => { method: 'GET', pathname: `/api/service/pipelines/${projectName}/dspa/apis/v2beta1/pipelines`, }, - buildMockPipelines(mockPipelinesv2.slice(0, 20), 22), + buildMockPipelines(mockPipelinesV2.slice(0, 20), 22), ); pagination.selectToggleOption('20 per page'); pagination.findPreviousButton().should('be.disabled'); - pipelinesTable.getRowByName('Test pipeline-19').find().should('exist'); + pipelinesTable.getRowById(mockPipelinesV2[19].pipeline_id).find().should('exist'); pipelinesTable.findRows().should('have.length', '20'); }); }); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesList.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesList.cy.ts index fb3b0984b0..88d46a0487 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesList.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesList.cy.ts @@ -147,10 +147,11 @@ describe('PipelinesList', () => { projectDetails.visitSection('test-project', 'pipelines-projects'); pipelinesTable.find(); - pipelinesTable.getRowByName(initialMockPipeline.display_name).toggleExpandByIndex(0); - pipelinesTable - .getRowByName(initialMockPipelineVersion.display_name) - .findPipelineName(initialMockPipelineVersion.display_name) + const pipelineRow = pipelinesTable.getRowById(initialMockPipeline.pipeline_id); + pipelineRow.findExpandButton().click(); + pipelineRow + .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) + .findPipelineVersionLink() .click(); verifyRelativeURL( `/projects/test-project/pipeline/view/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}`, diff --git a/frontend/src/__tests__/cypress/cypress/pages/components/table.ts b/frontend/src/__tests__/cypress/cypress/pages/components/table.ts index 4bd909c128..4fbc7d0aa1 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/components/table.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/components/table.ts @@ -1,6 +1,14 @@ import { Contextual } from '~/__tests__/cypress/cypress/pages/components/Contextual'; export class TableRow extends Contextual { + findExpandButton(): Cypress.Chainable> { + return this.find().findByRole('button', { name: 'Details' }); + } + + findRowCheckbox(): Cypress.Chainable> { + return this.find().find(`[data-label=Checkbox]`).find('input'); + } + shouldBeMarkedForDeletion(): this { this.find() .findByRole('button', { name: 'This resource is marked for deletion.' }) diff --git a/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelinesTable.ts b/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelinesTable.ts index 45a2ab7078..5f8f1a01a6 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelinesTable.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelinesTable.ts @@ -5,21 +5,29 @@ import { buildMockPipelineVersionsV2 } from '~/__mocks__/mockPipelineVersionsPro import { TableRow } from '~/__tests__/cypress/cypress/pages/components/table'; class PipelinesTableRow extends TableRow { - findPipelineName(name: string) { - return this.find().findByTestId(`table-row-title-${name}`).find('a'); + findPipelineVersionsTable() { + return this.find().parent().findByTestId(`pipeline-versions-table`); } - toggleExpandByIndex(index: number) { - this.find().find('button').should('have.attr', 'aria-label', 'Details').eq(index).click(); + shouldNotHavePipelineVersion() { + this.find().parents('tbody').findByTestId('no-pipeline-versions').should('exist'); + return this; } - toggleCheckboxByRowName() { - this.find().find(`[data-label=Checkbox]`).find('input').check(); + getPipelineVersionRowById(id: string) { + return new PipelineVersionsTableRow( + () => + this.findPipelineVersionsTable().findByTestId([ + 'pipeline-version-row', + id, + ]) as unknown as Cypress.Chainable>, + ); } +} - shouldNotHavePipelineVersion() { - this.find().parents('tbody').findByTestId('no-pipeline-versions').should('exist'); - return this; +class PipelineVersionsTableRow extends TableRow { + findPipelineVersionLink() { + return this.find().findByTestId(`table-row-title`).find('a'); } } @@ -38,9 +46,12 @@ class PipelinesTable { return this.find().find('thead').findByRole('button', { name }); } - getRowByName(name: string) { - return new PipelinesTableRow(() => - this.find().findByTestId(`table-row-title-${name}`).parents('tr'), + getRowById(id: string) { + return new PipelinesTableRow( + () => + this.find().findByTestId(['pipeline-row', id]) as unknown as Cypress.Chainable< + JQuery + >, ); } diff --git a/frontend/src/components/table/TableRowTitleDescription.tsx b/frontend/src/components/table/TableRowTitleDescription.tsx index 1d2e0c01a4..e6db33d0c9 100644 --- a/frontend/src/components/table/TableRowTitleDescription.tsx +++ b/frontend/src/components/table/TableRowTitleDescription.tsx @@ -11,7 +11,6 @@ type TableRowTitleDescriptionProps = { description?: string; descriptionAsMarkdown?: boolean; label?: React.ReactNode; - testId?: string; }; const TableRowTitleDescription: React.FC = ({ @@ -21,7 +20,6 @@ const TableRowTitleDescription: React.FC = ({ subtitle, descriptionAsMarkdown, label, - testId, }) => { let descriptionNode: React.ReactNode; if (description) { @@ -34,7 +32,7 @@ const TableRowTitleDescription: React.FC = ({ return ( <> - + {resource ? {title} : title} {subtitle} diff --git a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx index dfd75ff091..4fc07f0eb1 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx @@ -66,7 +66,7 @@ const PipelinesTableRow: React.FC = ({ return ( <> - + = ({ /> ) : null } + data-testid="pipeline-versions-table" /> = ({ const createdDate = new Date(version.created_at); return ( - + = ({ } description={version.description} descriptionAsMarkdown - testId={`table-row-title-${version.display_name}`} /> diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineVersion/usePipelineVersionsCheckboxTable.ts b/frontend/src/concepts/pipelines/content/tables/pipelineVersion/usePipelineVersionsCheckboxTable.ts index 7925b4be35..8a07abda2a 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineVersion/usePipelineVersionsCheckboxTable.ts +++ b/frontend/src/concepts/pipelines/content/tables/pipelineVersion/usePipelineVersionsCheckboxTable.ts @@ -14,7 +14,7 @@ const usePipelineVersionsCheckboxTable = ( versions, selectedVersions, setSelectedVersions, - React.useCallback((version) => version.pipeline_id, []), + React.useCallback((version) => version.pipeline_version_id, []), { selectAll: { disabled: pipelineChecked, ...(pipelineChecked ? { selected: true } : {}) } }, ); };