From f1bf75e81a185c0cc43fb98ef9d076e627b8031d Mon Sep 17 00:00:00 2001 From: Jeff Puzzo Date: Wed, 26 Jun 2024 19:09:45 -0400 Subject: [PATCH] [RHOAIENG-8566] Duplicate run is not useful when its pipeline is disabled --- .../mocked/pipelines/pipelineCreateRuns.cy.ts | 59 +++++++++++++++---- .../tests/mocked/pipelines/pipelineRuns.cy.ts | 51 ++++++++++++++-- .../pipelineRun/PipelineRunTableRow.tsx | 15 ++--- .../pipelineRunJob/PipelineRunJobTableRow.tsx | 36 ++++++----- 4 files changed, 123 insertions(+), 38 deletions(-) diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineCreateRuns.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineCreateRuns.cy.ts index 4e270a2e84..7550a94742 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineCreateRuns.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineCreateRuns.cy.ts @@ -54,17 +54,21 @@ const initialMockRecurringRuns = [ }), ]; +const visitLegacyRunsPage = (pipelineId?: string, versionId?: string) => + pipelineRunsGlobal.visit( + projectName, + pipelineId || mockPipelineVersion.pipeline_id, + versionId || mockPipelineVersion.pipeline_version_id, + ); + describe('Pipeline create runs', () => { beforeEach(() => { initIntercepts(); - pipelineRunsGlobal.visit( - projectName, - mockPipelineVersion.pipeline_id, - mockPipelineVersion.pipeline_version_id, - ); }); it('renders the page with scheduled and active runs table data', () => { + visitLegacyRunsPage(); + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunJobTable.getRowByName('Test job').find().should('exist'); @@ -74,6 +78,8 @@ describe('Pipeline create runs', () => { describe('Runs', () => { it('switches to scheduled runs from triggered', () => { + visitLegacyRunsPage(); + // Mock experiments, pipelines & versions for form select dropdowns createRunPage.mockGetExperiments(projectName, mockExperiments); createRunPage.mockGetPipelines(projectName, [mockPipeline]); @@ -92,6 +98,8 @@ describe('Pipeline create runs', () => { }); it('creates an active run', () => { + visitLegacyRunsPage(); + const createRunParams: Partial = { display_name: 'New run', description: 'New run description', @@ -184,9 +192,10 @@ describe('Pipeline create runs', () => { activeRunsTable.mockGetActiveRuns([...initialMockRuns, mockDuplicateRun], projectName); // Navigate to clone run page for a given active run + cy.visitWithLogin(`/experiments/${projectName}/experiment-1/runs`); pipelineRunsGlobal.findActiveRunsTab().click(); activeRunsTable.getRowByName(mockRun.display_name).findKebabAction('Duplicate').click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/clone/${mockRun.run_id}`); + verifyRelativeURL(`/experiments/${projectName}/experiment-1/runs/clone/${mockRun.run_id}`); // Verify pre-populated values & submit cloneRunPage.findExperimentSelect().should('have.text', mockExperiment.display_name); @@ -221,10 +230,12 @@ describe('Pipeline create runs', () => { }); // Should redirect to the details of the newly cloned active run - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/view/${mockDuplicateRun.run_id}`); + verifyRelativeURL(`/experiments/${projectName}/experiment-1/runs/${mockDuplicateRun.run_id}`); }); it('create run with default and optional parameters', () => { + visitLegacyRunsPage(); + const createRunParams: Partial = { display_name: 'New run', description: 'New run description', @@ -348,6 +359,8 @@ describe('Pipeline create runs', () => { }); it('create run with all parameter types', () => { + visitLegacyRunsPage(); + const createRunParams: Partial = { display_name: 'New run', description: 'New run description', @@ -475,11 +488,12 @@ describe('Pipeline create runs', () => { experiment, ); }); - - pipelineRunsGlobal.findSchedulesTab().click(); }); it('switches to scheduled runs from triggered', () => { + visitLegacyRunsPage(); + pipelineRunsGlobal.findSchedulesTab().click(); + // Mock experiments, pipelines & versions for form select dropdowns createSchedulePage.mockGetExperiments(projectName, mockExperiments); createSchedulePage.mockGetPipelines(projectName, [mockPipeline]); @@ -498,6 +512,9 @@ describe('Pipeline create runs', () => { }); it('creates a schedule', () => { + visitLegacyRunsPage(); + pipelineRunsGlobal.findSchedulesTab().click(); + const createRecurringRunParams: Partial = { display_name: 'New job', description: 'New job description', @@ -593,12 +610,14 @@ describe('Pipeline create runs', () => { cloneSchedulePage.mockGetExperiment(projectName, mockExperiment); // Navigate to clone run page for a given schedule + cy.visitWithLogin(`/experiments/${projectName}/experiment-1/runs`); + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunJobTable .getRowByName(mockRecurringRun.display_name) .findKebabAction('Duplicate') .click(); verifyRelativeURL( - `/pipelines/${projectName}/pipelineRun/cloneJob/${mockRecurringRun.recurring_run_id}?runType=scheduled`, + `/experiments/${projectName}/experiment-1/schedules/clone/${mockRecurringRun.recurring_run_id}?runType=scheduled`, ); // Verify pre-populated values & submit @@ -644,11 +663,14 @@ describe('Pipeline create runs', () => { // Should be redirected to the schedule details page verifyRelativeURL( - `/pipelines/${projectName}/pipelineRunJob/view/${mockDuplicateRecurringRun.recurring_run_id}`, + `/experiments/${projectName}/experiment-1/schedules/${mockDuplicateRecurringRun.recurring_run_id}`, ); }); it('shows cron & periodic fields', () => { + visitLegacyRunsPage(); + + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunsGlobal.findScheduleRunButton().click(); createSchedulePage.findScheduledRunTypeSelector().click(); @@ -663,6 +685,9 @@ describe('Pipeline create runs', () => { }); it('should start concurrent at the max, 10', () => { + visitLegacyRunsPage(); + + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunsGlobal.findScheduleRunButton().click(); createSchedulePage.findMaxConcurrencyFieldMinus().should('be.enabled'); @@ -671,6 +696,9 @@ describe('Pipeline create runs', () => { }); it('should allow the concurrency to update via +/-', () => { + visitLegacyRunsPage(); + + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunsGlobal.findScheduleRunButton().click(); createSchedulePage.findMaxConcurrencyFieldMinus().click(); @@ -682,6 +710,9 @@ describe('Pipeline create runs', () => { }); it('should not allow concurrency to go under or above the bounds', () => { + visitLegacyRunsPage(); + + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunsGlobal.findScheduleRunButton().click(); createSchedulePage.findMaxConcurrencyFieldValue().fill('0'); @@ -692,6 +723,9 @@ describe('Pipeline create runs', () => { }); it('should hide and show date toggles', () => { + visitLegacyRunsPage(); + + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunsGlobal.findScheduleRunButton().click(); createSchedulePage.findStartDatePickerDate().should('not.be.visible'); @@ -708,6 +742,9 @@ describe('Pipeline create runs', () => { }); it('should see catch up is enabled by default', () => { + visitLegacyRunsPage(); + + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunsGlobal.findScheduleRunButton().click(); createSchedulePage.findCatchUpSwitchValue().should('be.checked'); diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineRuns.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineRuns.cy.ts index 99ba2e18e8..fa89cbd830 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineRuns.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineRuns.cy.ts @@ -24,6 +24,8 @@ import { bulkRestoreRunModal, archiveRunModal, bulkArchiveRunModal, + cloneRunPage, + cloneSchedulePage, } from '~/__tests__/cypress/cypress/pages/pipelines'; import { verifyRelativeURL } from '~/__tests__/cypress/cypress/utils/url'; import { be } from '~/__tests__/cypress/cypress/utils/should'; @@ -280,14 +282,15 @@ describe('Pipeline runs', () => { describe('with data', () => { beforeEach(() => { activeRunsTable.mockGetActiveRuns(mockActiveRuns, projectName); - pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); }); it('renders the page with table data', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); activeRunsTable.getRowByName('Test active run 1').find().should('exist'); }); it('archive a single run', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); const [runToArchive] = mockActiveRuns; activeRunsTable.mockArchiveRun(runToArchive.run_id, projectName); @@ -303,6 +306,7 @@ describe('Pipeline runs', () => { }); it('archive multiple runs', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); mockActiveRuns.forEach((activeRun) => { activeRunsTable.mockArchiveRun(activeRun.run_id, projectName); activeRunsTable.getRowByName(activeRun.display_name).findCheckbox().click(); @@ -322,19 +326,28 @@ describe('Pipeline runs', () => { describe('Navigation', () => { it('navigate to create run page', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); pipelineRunsGlobal.findCreateRunButton().click(); verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); }); + it('navigate to clone run page', () => { + cloneRunPage.mockGetExperiments(projectName, mockExperiments); + cloneRunPage.mockGetExperiment(projectName, mockExperiments[0]); + cy.visitWithLogin(`/experiments/${projectName}/test-experiment-1/runs`); + activeRunsTable .getRowByName(mockActiveRuns[0].display_name) .findKebabAction('Duplicate') .click(); + verifyRelativeURL( - `/pipelines/${projectName}/pipelineRun/clone/${mockActiveRuns[0].run_id}`, + `/experiments/${projectName}/test-experiment-1/runs/clone/${mockActiveRuns[0].run_id}`, ); }); + it('navigate between tabs', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); pipelineRunsGlobal.findArchivedRunsTab().click(); verifyRelativeURL( `/pipelines/${projectName}/pipeline/runs/${pipelineId}/${pipelineVersionId}?runType=archived`, @@ -348,7 +361,9 @@ describe('Pipeline runs', () => { `/pipelines/${projectName}/pipeline/runs/${pipelineId}/${pipelineVersionId}?runType=scheduled`, ); }); + it('navigate to run details page', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); activeRunsTable .getRowByName(mockActiveRuns[0].display_name) .findColumnName(mockActiveRuns[0].display_name) @@ -362,6 +377,8 @@ describe('Pipeline runs', () => { describe('Table filter', () => { it('filter by name', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); + // Verify initial run rows exist activeRunsTable.findRows().should('have.length', 3); @@ -386,6 +403,8 @@ describe('Pipeline runs', () => { }); it('filter by experiment', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); + // Mock initial list of experiments pipelineRunFilterBar.mockExperiments(mockExperiments, projectName); @@ -417,6 +436,8 @@ describe('Pipeline runs', () => { }); it('filter by started', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); + // Verify initial run rows exist activeRunsTable.findRows().should('have.length', 3); @@ -460,6 +481,8 @@ describe('Pipeline runs', () => { }); it('filter by status', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); + // Verify initial run rows exist activeRunsTable.findRows().should('have.length', 3); @@ -521,6 +544,8 @@ describe('Pipeline runs', () => { }); it('Sort by Name', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); + pipelineRunFilterBar.findSortButtonForActive('Run').click(); pipelineRunFilterBar.findSortButtonForActive('Run').should(be.sortAscending); pipelineRunFilterBar.findSortButtonForActive('Run').click(); @@ -919,10 +944,10 @@ describe('Pipeline runs', () => { describe('with data', () => { beforeEach(() => { pipelineRunJobTable.mockGetJobs(mockJobs, projectName); - pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); }); it('renders the page with table rows', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); pipelineRunJobTable.find().should('exist'); pipelineRunJobTable.getRowByName('test-pipeline').find().should('exist'); pipelineRunJobTable.getRowByName('other-pipeline').find().should('exist'); @@ -930,6 +955,7 @@ describe('Pipeline runs', () => { }); it('can disable a job', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); pipelineRunJobTable.mockDisableJob(mockJobs[0], projectName).as('disableJob'); pipelineRunJobTable .getRowByName(mockJobs[0].display_name) @@ -940,19 +966,30 @@ describe('Pipeline runs', () => { describe('Navigation', () => { it('navigate to create scheduled run page', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); pipelineRunsGlobal.findScheduleRunButton().click(); verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=scheduled`); }); + it('navigate to clone scheduled run page', () => { + cloneSchedulePage.mockGetExperiments(projectName, mockExperiments); + cloneSchedulePage.mockGetExperiment(projectName, mockExperiments[0]); + cy.visitWithLogin(`/experiments/${projectName}/test-experiment-1/runs`); + + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunJobTable .getRowByName(mockJobs[0].display_name) .findKebabAction('Duplicate') .click(); + verifyRelativeURL( - `/pipelines/${projectName}/pipelineRun/cloneJob/${mockJobs[0].recurring_run_id}?runType=scheduled`, + `/experiments/${projectName}/test-experiment-1/schedules/clone/${mockJobs[0].recurring_run_id}?runType=scheduled`, ); }); + it('navigate to scheduled run details page', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); + pipelineRunsGlobal.findSchedulesTab().click(); pipelineRunJobTable .getRowByName(mockJobs[0].display_name) .findColumnName(mockJobs[0].display_name) @@ -965,6 +1002,9 @@ describe('Pipeline runs', () => { describe('Table filter', () => { it('filter by name', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); + pipelineRunsGlobal.findSchedulesTab().click(); + // Verify initial job rows exist pipelineRunJobTable.findRows().should('have.length', 3); @@ -984,6 +1024,9 @@ describe('Pipeline runs', () => { }); it('Sort by Name', () => { + pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); + pipelineRunsGlobal.findSchedulesTab().click(); + pipelineRunFilterBar.findSortButtonforSchedules('Schedule').click(); pipelineRunFilterBar.findSortButtonforSchedules('Schedule').should(be.sortAscending); pipelineRunFilterBar.findSortButtonforSchedules('Schedule').click(); diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx index e3750cb67f..77c011734c 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx @@ -75,7 +75,7 @@ const PipelineRunTableRow: React.FC = ({ }, }), }, - cloneAction, + ...(version ? [cloneAction] : []), { isSeparator: true, }, @@ -99,7 +99,7 @@ const PipelineRunTableRow: React.FC = ({ .catch((e) => notification.error('Unable to stop the pipeline run.', e.message)); }, }, - cloneAction, + ...(version ? [cloneAction] : []), { isSeparator: true, }, @@ -112,15 +112,16 @@ const PipelineRunTableRow: React.FC = ({ runType, run.state, run.run_id, - navigate, - isExperimentsAvailable, - experimentId, - namespace, + version, + isExperimentArchived, onDelete, api, refreshAllAPI, notification, - isExperimentArchived, + navigate, + namespace, + isExperimentsAvailable, + experimentId, ]); return ( diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx index 93bf03b346..db38d95685 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx @@ -86,22 +86,26 @@ const PipelineRunJobTableRow: React.FC = ({ { - navigate({ - pathname: cloneScheduleRoute( - namespace, - job.recurring_run_id, - isExperimentsAvailable ? experimentId : undefined, - ), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }); - }, - }, - { - isSeparator: true, - }, + ...(version + ? [ + { + title: 'Duplicate', + onClick: () => { + navigate({ + pathname: cloneScheduleRoute( + namespace, + job.recurring_run_id, + isExperimentsAvailable ? experimentId : undefined, + ), + search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, + }); + }, + }, + { + isSeparator: true, + }, + ] + : []), { title: 'Delete', onClick: () => {