diff --git a/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineRunsGlobal.ts b/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineRunsGlobal.ts index a93238fdf8..bc084851e4 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineRunsGlobal.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineRunsGlobal.ts @@ -1,4 +1,3 @@ -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; import { DeleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal'; class PipelineRunsGlobal { @@ -9,8 +8,8 @@ class PipelineRunsGlobal { runType?: 'active' | 'archived' | 'scheduled', ) { cy.visitWithLogin( - `/pipelines/${projectName}/pipeline/runs/${pipelineId}/${versionId}${ - runType ? `?${PipelineRunSearchParam.RunType}=${runType}` : '' + `/pipelines/${projectName}/${pipelineId}/${versionId}${ + runType === 'scheduled' ? '/schedules' : `/runs${runType ? `/${runType}` : ''}` }`, ); this.wait(); diff --git a/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts b/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts index 3401eb352a..bbf92908c6 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts @@ -37,7 +37,7 @@ class TaskDrawer extends Contextual { class PipelinesTopology { visit(namespace: string, pipelineId: string, pipelineVersionId: string) { - cy.visitWithLogin(`/pipelines/${namespace}/pipeline/view/${pipelineId}/${pipelineVersionId}`); + cy.visitWithLogin(`/pipelines/${namespace}/${pipelineId}/${pipelineVersionId}/view`); this.wait(); } @@ -108,7 +108,7 @@ class DetailsItem extends Contextual { class PipelineDetails extends PipelinesTopology { visit(namespace: string, pipelineId: string, pipelineVersionId: string) { - cy.visitWithLogin(`/pipelines/${namespace}/pipeline/view/${pipelineId}/${pipelineVersionId}`); + cy.visitWithLogin(`/pipelines/${namespace}/${pipelineId}/${pipelineVersionId}/view`); this.wait(); } @@ -180,8 +180,10 @@ class PipelineDetails extends PipelinesTopology { } class PipelineRunJobDetails extends RunDetails { - visit(namespace: string, pipelineId: string) { - cy.visitWithLogin(`/pipelines/${namespace}/pipelineRunJob/view/${pipelineId}`); + visit(namespace: string, pipelineId: string, pipelineVersionId: string, jobId?: string) { + cy.visitWithLogin( + `/pipelines/${namespace}/${pipelineId}/${pipelineVersionId}/schedules/${jobId}`, + ); this.wait(); } @@ -195,8 +197,8 @@ class PipelineRunJobDetails extends RunDetails { } class PipelineRunDetails extends RunDetails { - visit(namespace: string, pipelineId: string) { - cy.visitWithLogin(`/pipelines/${namespace}/pipelineRun/view/${pipelineId}`); + visit(namespace: string, pipelineId: string, pipelineVersionId: string, runId?: string) { + cy.visitWithLogin(`/pipelines/${namespace}/${pipelineId}/${pipelineVersionId}/runs/${runId}`); this.wait(); } diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/experiments.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/experiments.cy.ts index 1de1d76930..d3a1027731 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/experiments.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/experiments.cy.ts @@ -243,7 +243,7 @@ describe('Experiments', () => { it('navigates to the runs page when clicking an experiment name', () => { verifyRelativeURL(`/experiments/${projectName}/${mockExperiment.experiment_id}/runs`); - cy.findByLabelText('Breadcrumb').findByText('Experiments'); + cy.findByLabelText('Breadcrumb').findByText(`Experiments - ${projectName}`); }); it('has "Experiment" value pre-filled when on the "Create run" page', () => { @@ -311,7 +311,7 @@ describe('Runs page for archived experiment', () => { it('navigates to the runs page when clicking an experiment name', () => { verifyRelativeURL(`/experiments/${projectName}/${mockExperiment.experiment_id}/runs`); - cy.findByLabelText('Breadcrumb').findByText('Experiments'); + cy.findByLabelText('Breadcrumb').findByText(`Experiments - ${projectName}`); }); it('has empty state on active runs tab', () => { 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 7550a94742..581338ab56 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 @@ -91,10 +91,14 @@ describe('Pipeline create runs', () => { // Navigate to the 'Create run' page pipelineRunsGlobal.findCreateRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/create`, + ); createRunPage.find(); createRunPage.findRunTypeSwitchLink().click(); - cy.url().should('include', '?runType=scheduled'); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/schedules/create`, + ); }); it('creates an active run', () => { @@ -124,7 +128,9 @@ describe('Pipeline create runs', () => { // Navigate to the 'Create run' page pipelineRunsGlobal.findCreateRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/create`, + ); createRunPage.find(); // Fill out the form without a schedule and submit @@ -163,7 +169,9 @@ describe('Pipeline create runs', () => { }); // Should be redirected to the run details page - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/view/${createRunParams.run_id}`); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/${createRunParams.run_id}`, + ); }); it('duplicates an active run', () => { @@ -216,7 +224,6 @@ describe('Pipeline create runs', () => { cy.wait('@duplicateRun').then((interception) => { expect(interception.request.body).to.eql({ display_name: 'Duplicate of Test run', - description: '', pipeline_version_reference: { pipeline_id: 'test-pipeline', pipeline_version_id: 'test-pipeline-version', @@ -309,7 +316,9 @@ describe('Pipeline create runs', () => { // Navigate to the 'Create run' page pipelineRunsGlobal.findCreateRunButton().click(); - cy.url().should('include', '/pipelineRun/create'); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/create`, + ); createRunPage.find(); // Fill required fields @@ -355,7 +364,9 @@ describe('Pipeline create runs', () => { }); // Should be redirected to the run details page - cy.url().should('include', '/pipelineRun/view/new-run-id'); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/${createRunParams.run_id}`, + ); }); it('create run with all parameter types', () => { @@ -426,7 +437,9 @@ describe('Pipeline create runs', () => { // Navigate to the 'Create run' page pipelineRunsGlobal.findCreateRunButton().click(); - cy.url().should('include', '/pipelineRun/create'); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/create`, + ); createRunPage.find(); // Fill out the form with all input parameters @@ -469,7 +482,9 @@ describe('Pipeline create runs', () => { }); }); // Should be redirected to the run details page - cy.url().should('include', '/pipelineRun/view/new-run-id'); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/${createRunParams.run_id}`, + ); }); }); @@ -505,10 +520,14 @@ describe('Pipeline create runs', () => { // Navigate to the 'Create run' page pipelineRunsGlobal.findScheduleRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=scheduled`); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/schedules/create`, + ); createSchedulePage.find(); createSchedulePage.findRunTypeSwitchLink().click(); - cy.url().should('include', '?runType=active'); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/runs/create`, + ); }); it('creates a schedule', () => { @@ -539,7 +558,9 @@ describe('Pipeline create runs', () => { // Navigate to the 'Create run' page pipelineRunsGlobal.findScheduleRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=scheduled`); + verifyRelativeURL( + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/schedules/create`, + ); createSchedulePage.find(); // Fill out the form with a schedule and submit @@ -583,7 +604,7 @@ describe('Pipeline create runs', () => { // Should be redirected to the schedule details page verifyRelativeURL( - `/pipelines/${projectName}/pipelineRunJob/view/${createRecurringRunParams.recurring_run_id}`, + `/pipelines/${projectName}/${mockPipelineVersion.pipeline_id}/${mockPipelineVersion.pipeline_version_id}/schedules/${createRecurringRunParams.recurring_run_id}`, ); }); @@ -617,7 +638,7 @@ describe('Pipeline create runs', () => { .findKebabAction('Duplicate') .click(); verifyRelativeURL( - `/experiments/${projectName}/experiment-1/schedules/clone/${mockRecurringRun.recurring_run_id}?runType=scheduled`, + `/experiments/${projectName}/experiment-1/schedules/clone/${mockRecurringRun.recurring_run_id}`, ); // Verify pre-populated values & submit @@ -638,7 +659,6 @@ describe('Pipeline create runs', () => { cy.wait('@duplicateSchedule').then((interception) => { expect(interception.request.body).to.eql({ display_name: 'Duplicate of Test job', - description: '', pipeline_version_reference: { pipeline_id: 'test-pipeline', pipeline_version_id: 'test-pipeline-version', @@ -772,6 +792,19 @@ const initIntercepts = () => { }, { runs: initialMockRuns, total_size: initialMockRuns.length }, ); + cy.interceptOdh( + 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId', + { + path: { + namespace: projectName, + serviceName: 'dspa', + pipelineId: mockPipelineVersion.pipeline_id, + }, + }, + buildMockPipelineV2({ + pipeline_id: mockPipelineVersion.pipeline_id, + }), + ); cy.interceptOdh( 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId/versions/:pipelineVersionId', { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineDeleteRuns.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineDeleteRuns.cy.ts index 707de865e3..477c63d6d2 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineDeleteRuns.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelineDeleteRuns.cy.ts @@ -23,7 +23,7 @@ import { SecretModel, } from '~/__tests__/cypress/cypress/utils/models'; import { mockSuccessGoogleRpcStatus } from '~/__mocks__/mockGoogleRpcStatusKF'; -import { buildMockPipelineVersionV2 } from '~/__mocks__'; +import { buildMockPipelineV2, buildMockPipelineVersionV2 } from '~/__mocks__'; const initIntercepts = () => { cy.interceptOdh( @@ -72,6 +72,19 @@ const initIntercepts = () => { ); cy.interceptK8sList(NotebookModel, mockK8sResourceList([mockNotebookK8sResource({})])); cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockProjectK8sResource({})])); + cy.interceptOdh( + 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId', + { + path: { + namespace: 'test-project', + serviceName: 'dspa', + pipelineId: 'pipeline_id', + }, + }, + buildMockPipelineV2({ + pipeline_id: 'pipeline-id', + }), + ); cy.interceptOdh( 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId/versions/:pipelineVersionId', { 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 fa89cbd830..b7b104a69f 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 @@ -2,10 +2,8 @@ import startCase from 'lodash-es/startCase'; import { RuntimeStateKF, runtimeStateLabels } from '~/concepts/pipelines/kfTypes'; import { - mockDataSciencePipelineApplicationK8sResource, mockK8sResourceList, mockProjectK8sResource, - mockRouteK8sResource, buildMockRunKF, buildMockJobKF, buildMockPipelineVersionsV2, @@ -29,12 +27,9 @@ import { } from '~/__tests__/cypress/cypress/pages/pipelines'; import { verifyRelativeURL } from '~/__tests__/cypress/cypress/utils/url'; import { be } from '~/__tests__/cypress/cypress/utils/should'; -import { - DataSciencePipelineApplicationModel, - ProjectModel, - RouteModel, -} from '~/__tests__/cypress/cypress/utils/models'; +import { ProjectModel } from '~/__tests__/cypress/cypress/utils/models'; import { tablePagination } from '~/__tests__/cypress/cypress/pages/components/Pagination'; +import { dspaIntercepts } from '~/__tests__/cypress/cypress/tests/mocked/pipelines/intercepts'; const projectName = 'test-project-filters'; const pipelineId = 'test-pipeline'; @@ -170,7 +165,9 @@ describe('Pipeline runs', () => { it('navigate to create run page', () => { pipelineRunsGlobal.findCreateRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=active`); + verifyRelativeURL( + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/runs/create`, + ); }); }); @@ -328,7 +325,9 @@ describe('Pipeline runs', () => { it('navigate to create run page', () => { pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); pipelineRunsGlobal.findCreateRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); + verifyRelativeURL( + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/runs/create`, + ); }); it('navigate to clone run page', () => { @@ -350,15 +349,15 @@ describe('Pipeline runs', () => { pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'active'); pipelineRunsGlobal.findArchivedRunsTab().click(); verifyRelativeURL( - `/pipelines/${projectName}/pipeline/runs/${pipelineId}/${pipelineVersionId}?runType=archived`, + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/runs/archived`, ); pipelineRunsGlobal.findActiveRunsTab().click(); verifyRelativeURL( - `/pipelines/${projectName}/pipeline/runs/${pipelineId}/${pipelineVersionId}?runType=active`, + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/runs/active`, ); pipelineRunsGlobal.findSchedulesTab().click(); verifyRelativeURL( - `/pipelines/${projectName}/pipeline/runs/${pipelineId}/${pipelineVersionId}?runType=scheduled`, + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/schedules`, ); }); @@ -370,7 +369,7 @@ describe('Pipeline runs', () => { .click(); verifyRelativeURL( - `/pipelines/${projectName}/pipelineRun/view/${mockActiveRuns[0].run_id}`, + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/runs/${mockActiveRuns[0].run_id}`, ); }); }); @@ -768,9 +767,11 @@ describe('Pipeline runs', () => { pipelineRunJobTable.findEmptyState().should('exist'); }); - it('navigate to create run page', () => { + it('navigate to create schedule page', () => { pipelineRunsGlobal.findScheduleRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=scheduled`); + verifyRelativeURL( + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/schedules/create`, + ); }); }); @@ -968,7 +969,9 @@ describe('Pipeline runs', () => { it('navigate to create scheduled run page', () => { pipelineRunsGlobal.visit(projectName, pipelineId, pipelineVersionId, 'scheduled'); pipelineRunsGlobal.findScheduleRunButton().click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=scheduled`); + verifyRelativeURL( + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/schedules/create`, + ); }); it('navigate to clone scheduled run page', () => { @@ -983,7 +986,7 @@ describe('Pipeline runs', () => { .click(); verifyRelativeURL( - `/experiments/${projectName}/test-experiment-1/schedules/clone/${mockJobs[0].recurring_run_id}?runType=scheduled`, + `/experiments/${projectName}/test-experiment-1/schedules/clone/${mockJobs[0].recurring_run_id}`, ); }); @@ -995,7 +998,7 @@ describe('Pipeline runs', () => { .findColumnName(mockJobs[0].display_name) .click(); verifyRelativeURL( - `/pipelines/${projectName}/pipelineRunJob/view/${mockJobs[0].recurring_run_id}`, + `/pipelines/${projectName}/${pipelineId}/${pipelineVersionId}/schedules/${mockJobs[0].recurring_run_id}`, ); }); }); @@ -1038,7 +1041,7 @@ describe('Pipeline runs', () => { }); const initIntercepts = () => { - mockDspaIntercepts(); + dspaIntercepts(projectName); cy.interceptK8sList( ProjectModel, @@ -1055,6 +1058,20 @@ const initIntercepts = () => { buildMockPipelines([buildMockPipelineV2({ pipeline_id: pipelineId })]), ); + cy.interceptOdh( + 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId', + { + path: { + namespace: projectName, + serviceName: 'dspa', + pipelineId, + }, + }, + buildMockPipelineV2({ + pipeline_id: pipelineId, + }), + ); + cy.interceptOdh( 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId/versions', { path: { namespace: projectName, serviceName: 'dspa', pipelineId } }, @@ -1073,35 +1090,3 @@ const initIntercepts = () => { buildMockPipelineVersionV2({ pipeline_id: pipelineId, pipeline_version_id: pipelineVersionId }), ); }; - -const mockDspaIntercepts = () => { - cy.interceptK8s( - DataSciencePipelineApplicationModel, - mockDataSciencePipelineApplicationK8sResource({ - name: 'pipelines-definition', - namespace: projectName, - }), - ); - - cy.interceptK8sList( - DataSciencePipelineApplicationModel, - mockK8sResourceList([ - mockDataSciencePipelineApplicationK8sResource({ namespace: projectName }), - ]), - ); - - cy.interceptK8s( - DataSciencePipelineApplicationModel, - mockDataSciencePipelineApplicationK8sResource({ - namespace: projectName, - }), - ); - - cy.interceptK8s( - RouteModel, - mockRouteK8sResource({ - notebookName: 'ds-pipeline-dspa', - namespace: projectName, - }), - ); -}; diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelines.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelines.cy.ts index b245c949b6..524d0929b6 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelines.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelines.cy.ts @@ -862,7 +862,7 @@ describe('Pipelines', () => { .findPipelineVersionLink() .click(); verifyRelativeURL( - `/pipelines/${projectName}/pipeline/view/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}`, + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/view`, ); }); @@ -875,7 +875,7 @@ describe('Pipelines', () => { pipelineRow.findPipelineNameLink(initialMockPipeline.display_name).click(); verifyRelativeURL( - `/pipelines/${projectName}/pipeline/view/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}`, + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/view`, ); }); @@ -977,7 +977,9 @@ describe('Pipelines', () => { .getPipelineVersionRowById(initialMockPipelineVersion.pipeline_version_id) .findKebabAction('Create run') .click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); + verifyRelativeURL( + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/runs/create`, + ); }); it('navigates to "Schedule run" page from pipeline version row', () => { @@ -992,7 +994,9 @@ describe('Pipelines', () => { .findKebabAction('Create schedule') .click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=scheduled`); + verifyRelativeURL( + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/schedules/create`, + ); }); it('navigate to view runs page from pipeline version row', () => { @@ -1008,7 +1012,7 @@ describe('Pipelines', () => { .findKebabAction('View runs') .click(); verifyRelativeURL( - `/pipelines/${projectName}/pipeline/runs/test-pipeline/test-pipeline-version?runType=active`, + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/runs`, ); }); @@ -1024,7 +1028,7 @@ describe('Pipelines', () => { .findKebabAction('View schedules') .click(); verifyRelativeURL( - `/pipelines/${projectName}/pipeline/runs/test-pipeline/test-pipeline-version?runType=scheduled`, + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/schedules`, ); }); @@ -1171,6 +1175,29 @@ export const initIntercepts = ({ }, buildMockPipelineVersionsV2([initialMockPipelineVersion]), ); + cy.interceptOdh( + 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId', + { + path: { + namespace: projectName, + serviceName: 'dspa', + pipelineId: initialMockPipeline.pipeline_id, + }, + }, + initialMockPipeline, + ); + cy.interceptOdh( + 'GET /api/service/pipelines/:namespace/:serviceName/apis/v2beta1/pipelines/:pipelineId/versions/:pipelineVersionId', + { + path: { + namespace: projectName, + serviceName: 'dspa', + pipelineId: initialMockPipeline.pipeline_id, + pipelineVersionId: initialMockPipelineVersion.pipeline_version_id, + }, + }, + initialMockPipelineVersion, + ); }; const createDeleteVersionIntercept = (pipelineId: string, pipelineVersionId: string) => @@ -1200,7 +1227,9 @@ export const runCreateRunPageNavTest = (visitPipelineProjects: () => void): void // Wait for the pipelines table to load pipelinesTable.find(); pipelinesTable.getRowById(initialMockPipeline.pipeline_id).findKebabAction('Create run').click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create`); + verifyRelativeURL( + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/runs/create`, + ); }; export const runScheduleRunPageNavTest = (visitPipelineProjects: () => void): void => { @@ -1213,7 +1242,9 @@ export const runScheduleRunPageNavTest = (visitPipelineProjects: () => void): vo .findKebabAction('Create schedule') .click(); - verifyRelativeURL(`/pipelines/${projectName}/pipelineRun/create?runType=scheduled`); + verifyRelativeURL( + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/schedules/create`, + ); }; export const viewPipelineServerDetailsTest = (visitPipelineProjects: () => void): void => { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesList.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesList.cy.ts index 65cf6c3ef7..8c3b38844d 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesList.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesList.cy.ts @@ -139,7 +139,7 @@ describe('PipelinesList', () => { .findPipelineVersionLink() .click(); verifyRelativeURL( - `/projects/${projectName}/pipeline/view/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}`, + `/pipelines/${projectName}/${initialMockPipeline.pipeline_id}/${initialMockPipelineVersion.pipeline_version_id}/view`, ); }); diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesTopology.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesTopology.cy.ts index 650bc8d7f4..1e521cf8fa 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesTopology.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/pipelines/pipelinesTopology.cy.ts @@ -179,25 +179,29 @@ describe('Pipeline topology', () => { describe('Navigation', () => { it('Test pipeline details create run navigation', () => { pipelineDetails.selectActionDropdownItem('Create run'); - verifyRelativeURL(`/pipelines/${projectId}/pipelineRun/create`); + verifyRelativeURL( + `/pipelines/${projectId}/${mockVersion.pipeline_id}/${mockVersion.pipeline_version_id}/runs/create`, + ); }); it('navigates to "Schedule run" page on "Schedule run" click', () => { pipelineDetails.selectActionDropdownItem('Create schedule'); - verifyRelativeURL(`/pipelines/${projectId}/pipelineRun/create?runType=scheduled`); + verifyRelativeURL( + `/pipelines/${projectId}/${mockVersion.pipeline_id}/${mockVersion.pipeline_version_id}/schedules/create`, + ); }); it('Test pipeline details view runs navigation', () => { pipelineDetails.selectActionDropdownItem('View runs'); verifyRelativeURL( - `/pipelines/${projectId}/pipeline/runs/test-pipeline/test-version-id?runType=active`, + `/pipelines/${projectId}/${mockVersion.pipeline_id}/${mockVersion.pipeline_version_id}/runs`, ); }); it('navigates to "Schedules" on "View schedules" click', () => { pipelineDetails.selectActionDropdownItem('View schedules'); verifyRelativeURL( - `/pipelines/${projectId}/pipeline/runs/test-pipeline/test-version-id?runType=scheduled`, + `/pipelines/${projectId}/${mockVersion.pipeline_id}/${mockVersion.pipeline_version_id}/schedules`, ); }); }); @@ -253,7 +257,7 @@ describe('Pipeline topology', () => { pipelineDetails.selectPipelineVersionByName(mockVersion2.display_name); pipelineDetails.findPageTitle().should('have.text', 'test-version-2'); verifyRelativeURL( - `/pipelines/${projectId}/pipeline/view/${mockPipeline.pipeline_id}/${mockVersion2.pipeline_version_id}`, + `/pipelines/${projectId}/${mockPipeline.pipeline_id}/${mockVersion2.pipeline_version_id}/view`, ); }); @@ -284,7 +288,7 @@ describe('Pipeline topology', () => { pipelineVersionImportModal.submit(); verifyRelativeURL( - `/pipelines/${projectId}/pipeline/view/${mockPipeline.pipeline_id}/${mockVersion2.pipeline_version_id}`, + `/pipelines/${projectId}/${mockPipeline.pipeline_id}/${mockVersion2.pipeline_version_id}/view`, ); cy.wait('@uploadNewPipelineVersion').then((interception) => { expect(interception.request.body).to.containSubset({ @@ -310,21 +314,38 @@ describe('Pipeline topology', () => { }); it('Test pipeline run duplicate navigation', () => { - pipelineRunDetails.visit(projectId, mockRun.run_id); + pipelineRunDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockRun.run_id, + ); pipelineRunDetails.selectActionDropdownItem('Duplicate'); - verifyRelativeURL(`/pipelines/${projectId}/pipelineRun/clone/${mockRun.run_id}`); + verifyRelativeURL( + `/pipelines/${projectId}/${mockVersion.pipeline_id}/${mockVersion.pipeline_version_id}/runs/clone/${mockRun.run_id}`, + ); }); it('Test pipeline job duplicate navigation', () => { - pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); + pipelineRunJobDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockJob.recurring_run_id, + ); pipelineRunJobDetails.selectActionDropdownItem('Duplicate'); verifyRelativeURL( - `/pipelines/${projectId}/pipelineRun/cloneJob/${mockJob.recurring_run_id}?runType=scheduled`, + `/pipelines/${projectId}/${mockVersion.pipeline_id}/${mockVersion.pipeline_version_id}/schedules/clone/${mockJob.recurring_run_id}`, ); }); it('Test pipeline job delete navigation', () => { - pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); + pipelineRunJobDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockJob.recurring_run_id, + ); pipelineRunJobDetails.selectActionDropdownItem('Delete'); deleteModal.shouldBeOpen(); deleteModal.findInput().type(mockPipeline.display_name); @@ -339,14 +360,19 @@ describe('Pipeline topology', () => { }, }, {}, - ).as('deletepipelineRunJob'); + ).as('deletePipelineRunJob'); deleteModal.findSubmitButton().click(); - cy.wait('@deletepipelineRunJob'); + cy.wait('@deletePipelineRunJob'); }); it('Test pipeline job details project navigation', () => { - pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); + pipelineRunJobDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockJob.recurring_run_id, + ); pipelineRunJobDetails.findDetailsTab().click(); pipelineRunJobDetails.findDetailItem('Project').findValue().find('a').click(); @@ -354,12 +380,17 @@ describe('Pipeline topology', () => { }); it('Test pipeline job details pipeline version navigation', () => { - pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); + pipelineRunJobDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockJob.recurring_run_id, + ); pipelineRunJobDetails.findDetailsTab().click(); pipelineRunJobDetails.findDetailItem('Pipeline version').findValue().find('a').click(); verifyRelativeURL( - `/pipelines/${projectId}/pipeline/view/${mockJob.pipeline_version_reference.pipeline_id}/${mockJob.pipeline_version_reference.pipeline_version_id}`, + `/pipelines/${projectId}/${mockJob.pipeline_version_reference.pipeline_id}/${mockJob.pipeline_version_reference.pipeline_version_id}/view`, ); }); }); @@ -367,7 +398,12 @@ describe('Pipeline topology', () => { it('Test pipeline job tab details', () => { initIntercepts(); - pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); + pipelineRunJobDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockJob.recurring_run_id, + ); pipelineRunJobDetails.findDetailsTab().click(); pipelineRunJobDetails.findDetailItem('Name').findValue().contains(mockJob.display_name); @@ -387,7 +423,12 @@ describe('Pipeline topology', () => { it('Ensure that clicking on a node will open a right-side drawer', () => { initIntercepts(); - pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); + pipelineRunJobDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockJob.recurring_run_id, + ); pipelineDetails.findTaskNode('create-dataset').click(); const taskDrawer = pipelineDetails.getTaskDrawer(); @@ -405,7 +446,12 @@ describe('Pipeline topology', () => { it('Test pipeline triggered run tab details', () => { initIntercepts(); - pipelineRunDetails.visit(projectId, mockRun.run_id); + pipelineRunDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockRun.run_id, + ); pipelineRunJobDetails.findPipelineSpecTab(); pipelineRunDetails.findDetailsTab().click(); @@ -425,7 +471,12 @@ describe('Pipeline topology', () => { it('Test pipeline triggered run YAML output', () => { initIntercepts(); - pipelineRunDetails.visit(projectId, mockRun.run_id); + pipelineRunDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockRun.run_id, + ); pipelineRunDetails.findPipelineSpecTab().click(); pipelineRunDetails.findYamlOutput().click(); @@ -437,7 +488,12 @@ describe('Pipeline topology', () => { describe('Pipeline run Input/Output', () => { beforeEach(() => { initIntercepts(); - pipelineRunDetails.visit(projectId, mockRun.run_id); + pipelineRunDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockRun.run_id, + ); }); it('Test with input/output artifacts', () => { @@ -464,7 +520,12 @@ describe('Pipeline topology', () => { describe('Pipeline run Details', () => { beforeEach(() => { initIntercepts(); - pipelineRunDetails.visit(projectId, mockRun.run_id); + pipelineRunDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockRun.run_id, + ); }); it('Test with the details', () => { @@ -489,7 +550,12 @@ describe('Pipeline topology', () => { describe('Pipeline run volume mounts', () => { beforeEach(() => { initIntercepts(); - pipelineRunDetails.visit(projectId, mockRun.run_id); + pipelineRunDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockRun.run_id, + ); }); it('Test node with no volume mounts', () => { @@ -522,7 +588,12 @@ describe('Pipeline topology', () => { describe('Pipelines logs', () => { beforeEach(() => { initIntercepts(); - pipelineRunDetails.visit(projectId, mockRun.run_id); + pipelineRunDetails.visit( + projectId, + mockVersion.pipeline_id, + mockVersion.pipeline_version_id, + mockRun.run_id, + ); pipelineRunDetails.findTaskNode('create-dataset').click(); const rightDrawer = pipelineRunDetails.findRightDrawer(); rightDrawer.findRightDrawerDetailsTab().should('be.visible'); @@ -542,7 +613,7 @@ describe('Pipeline topology', () => { pipelineRunDetails.findCurrentStepLogs().should('not.be.disabled'); pipelineRunDetails.findDownloadStepsToggle().click(); pipelineRunDetails.findCurrentStepLogs().should('not.exist'); - // test whether the raw logs dropddown item is enabled when logs are available + // test whether the raw logs dropdown item is enabled when logs are available pipelineRunDetails.findLogsKebabToggle().click(); pipelineRunDetails.findRawLogs().should('not.be.disabled'); pipelineRunDetails.findLogsKebabToggle().click(); @@ -557,7 +628,7 @@ describe('Pipeline topology', () => { pipelineRunDetails.findDownloadStepsToggle().click(); pipelineRunDetails.findCurrentStepLogs().should('not.be.enabled'); pipelineRunDetails.findDownloadStepsToggle().click(); - // test whether the raw logs dropddown item is disabled when logs are not available + // test whether the raw logs dropdown item is disabled when logs are not available pipelineRunDetails.findLogsKebabToggle().click(); pipelineRunDetails.findRawLogs().should('not.be.enabled'); pipelineRunDetails.findLogsKebabToggle().click(); diff --git a/frontend/src/app/AppRoutes.tsx b/frontend/src/app/AppRoutes.tsx index 412cb0cdcb..468b0fb358 100644 --- a/frontend/src/app/AppRoutes.tsx +++ b/frontend/src/app/AppRoutes.tsx @@ -8,7 +8,6 @@ import { globArtifactsAll, globExecutionsAll, globExperimentsAll, - globPipelineRunsAll, globPipelinesAll, } from '~/routes'; import { useCheckJupyterEnabled } from '~/utilities/notebookControllerUtils'; @@ -33,9 +32,6 @@ const NotebookController = React.lazy( ); const GlobalPipelinesRoutes = React.lazy(() => import('../pages/pipelines/GlobalPipelinesRoutes')); -const GlobalPipelineRunsRoutes = React.lazy( - () => import('../pages/pipelines/GlobalPipelineRunsRoutes'), -); const GlobalPipelineExperimentRoutes = React.lazy( () => import('../pages/pipelines/GlobalPipelineExperimentsRoutes'), ); @@ -115,7 +111,6 @@ const AppRoutes: React.FC = () => { } /> } /> - } /> } /> } /> } /> diff --git a/frontend/src/concepts/pipelines/content/PipelineVersionLink.tsx b/frontend/src/concepts/pipelines/content/PipelineVersionLink.tsx index 3e6c9c8afc..ede782232e 100644 --- a/frontend/src/concepts/pipelines/content/PipelineVersionLink.tsx +++ b/frontend/src/concepts/pipelines/content/PipelineVersionLink.tsx @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; import { Skeleton, Tooltip } from '@patternfly/react-core'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; -import { routePipelineDetailsNamespace } from '~/routes'; +import { pipelineVersionDetailsRoute } from '~/routes'; import { NoRunContent } from './tables/renderUtils'; interface PipelineVersionLinkProps { @@ -41,11 +41,7 @@ export const PipelineVersionLink: React.FC = ({ return ( {version.display_name} diff --git a/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableRow.tsx b/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableRow.tsx index a31b6391c7..ad0616c810 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableRow.tsx @@ -37,7 +37,15 @@ const CompareRunTableRow: React.FC = ({ - + {run.display_name} diff --git a/frontend/src/concepts/pipelines/content/createRun/CloneRunPage.tsx b/frontend/src/concepts/pipelines/content/createRun/CloneRunPage.tsx index 7996a4a868..856f4d9619 100644 --- a/frontend/src/concepts/pipelines/content/createRun/CloneRunPage.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/CloneRunPage.tsx @@ -1,37 +1,60 @@ import React from 'react'; -import { Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'; +import { Breadcrumb, BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { useParams, Link } from 'react-router-dom'; import RunPage from '~/concepts/pipelines/content/createRun/RunPage'; import ApplicationsPage from '~/pages/ApplicationsPage'; -import useCloneRunData from '~/concepts/pipelines/content/createRun/useCloneRunData'; -import { PathProps, PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; -import { useGetSearchParamValues } from '~/utilities/useGetSearchParamValues'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; -import { asEnumMember } from '~/utilities/utils'; -import { runTypeCategory } from './types'; +import { PathProps } from '~/concepts/pipelines/content/types'; +import { ExperimentKFv2, PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; +import usePipelineRunById from '~/concepts/pipelines/apiHooks/usePipelineRunById'; +import { RunTypeOption } from './types'; -const CloneRunPage: React.FC = ({ breadcrumbPath, contextPath }) => { - const [run, loaded, error] = useCloneRunData(); - const { runType: runTypeString } = useGetSearchParamValues([PipelineRunSearchParam.RunType]); - const runType = asEnumMember(runTypeString, PipelineRunType); - const title = `Duplicate ${runTypeCategory[runType || PipelineRunType.ACTIVE]}`; +type CloneRunPageProps = { + detailsRedirect: (runId: string) => string; + contextExperiment?: ExperimentKFv2 | null; + contextPipeline?: PipelineKFv2 | null; + contextPipelineVersion?: PipelineVersionKFv2 | null; +}; + +const CloneRunPage: React.FC = ({ + breadcrumbPath, + contextPath, + detailsRedirect, + ...props +}) => { + const { runId } = useParams(); + const [run, loaded, error] = usePipelineRunById(runId); return ( - {breadcrumbPath(runType)} - - {run ? `Duplicate of ${run.display_name}` : 'Duplicate'} + {breadcrumbPath} + + {run ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + Duplicate run } loaded={loaded} loadError={error} empty={false} > - + ); }; diff --git a/frontend/src/concepts/pipelines/content/createRun/CloneSchedulePage.tsx b/frontend/src/concepts/pipelines/content/createRun/CloneSchedulePage.tsx new file mode 100644 index 0000000000..255c9b6ef7 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/createRun/CloneSchedulePage.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Breadcrumb, BreadcrumbItem, Truncate } from '@patternfly/react-core'; + +import { useParams, Link } from 'react-router-dom'; +import RunPage from '~/concepts/pipelines/content/createRun/RunPage'; +import ApplicationsPage from '~/pages/ApplicationsPage'; +import { PathProps } from '~/concepts/pipelines/content/types'; +import { ExperimentKFv2, PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; +import usePipelineRunJobById from '~/concepts/pipelines/apiHooks/usePipelineRunJobById'; +import { RunTypeOption } from './types'; + +type CloneSchedulePageProps = { + detailsRedirect: (jobId: string) => string; + contextExperiment?: ExperimentKFv2 | null; + contextPipeline?: PipelineKFv2 | null; + contextPipelineVersion?: PipelineVersionKFv2 | null; +}; + +const CloneSchedulePage: React.FC = ({ + breadcrumbPath, + contextPath, + detailsRedirect, + ...props +}) => { + const { recurringRunId } = useParams(); + const [job, loaded, error] = usePipelineRunJobById(recurringRunId); + + return ( + + {breadcrumbPath} + + {job ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + + Duplicate schedule + + } + loaded={loaded} + loadError={error} + empty={false} + > + + + ); +}; + +export default CloneSchedulePage; diff --git a/frontend/src/concepts/pipelines/content/createRun/CreateRunPage.tsx b/frontend/src/concepts/pipelines/content/createRun/CreateRunPage.tsx index 1ce7f64b25..4f5d7129be 100644 --- a/frontend/src/concepts/pipelines/content/createRun/CreateRunPage.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/CreateRunPage.tsx @@ -2,30 +2,39 @@ import * as React from 'react'; import { Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'; import RunPage from '~/concepts/pipelines/content/createRun/RunPage'; -import { PathProps, PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; +import { PathProps } from '~/concepts/pipelines/content/types'; import ApplicationsPage from '~/pages/ApplicationsPage'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; -import { useGetSearchParamValues } from '~/utilities/useGetSearchParamValues'; +import { RunTypeOption } from '~/concepts/pipelines/content/createRun/types'; +import { ExperimentKFv2, PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; -const CreateRunPage: React.FC = ({ breadcrumbPath, contextPath }) => { - const { runType } = useGetSearchParamValues([PipelineRunSearchParam.RunType]); - const title = `${runType === PipelineRunType.SCHEDULED ? 'Schedule' : 'Create'} run`; +type CreateRunPageProps = { + runType: RunTypeOption; + contextExperiment?: ExperimentKFv2 | null; + contextPipeline?: PipelineKFv2 | null; + contextPipelineVersion?: PipelineVersionKFv2 | null; +}; + +const CreateRunPage: React.FC = ({ + breadcrumbPath, + contextPath, + runType, + ...props +}) => { + const title = `Create ${runType === RunTypeOption.SCHEDULED ? 'schedule' : 'run'}`; return ( - {breadcrumbPath( - runType === PipelineRunType.SCHEDULED ? PipelineRunType.SCHEDULED : undefined, - )} + {breadcrumbPath} {title} } loaded empty={false} > - + ); }; diff --git a/frontend/src/concepts/pipelines/content/createRun/RunForm.tsx b/frontend/src/concepts/pipelines/content/createRun/RunForm.tsx index f915a10ea3..2ca75e042d 100644 --- a/frontend/src/concepts/pipelines/content/createRun/RunForm.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/RunForm.tsx @@ -4,12 +4,11 @@ import NameDescriptionField from '~/concepts/k8s/NameDescriptionField'; import { RunFormData, RunTypeOption } from '~/concepts/pipelines/content/createRun/types'; import { ValueOf } from '~/typeHelpers'; import { ParamsSection } from '~/concepts/pipelines/content/createRun/contentSections/ParamsSection'; -import { useLatestPipelineVersion } from '~/concepts/pipelines/apiHooks/useLatestPipelineVersion'; import RunTypeSectionScheduled from '~/concepts/pipelines/content/createRun/contentSections/RunTypeSectionScheduled'; import { PipelineVersionKFv2, RuntimeConfigParameters } from '~/concepts/pipelines/kfTypes'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; import ProjectAndExperimentSection from '~/concepts/pipelines/content/createRun/contentSections/ProjectAndExperimentSection'; import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; +import { useLatestPipelineVersion } from '~/concepts/pipelines/apiHooks/useLatestPipelineVersion'; import PipelineSection from './contentSections/PipelineSection'; import { RunTypeSection } from './contentSections/RunTypeSection'; import { CreateRunPageSections, RUN_NAME_CHARACTER_LIMIT, runPageSectionTitles } from './const'; @@ -17,15 +16,17 @@ import { getInputDefinitionParams } from './utils'; type RunFormProps = { data: RunFormData; - runType: PipelineRunType; onValueChange: (key: keyof RunFormData, value: ValueOf) => void; + isCloned: boolean; }; -const RunForm: React.FC = ({ data, runType, onValueChange }) => { +const RunForm: React.FC = ({ data, onValueChange, isCloned }) => { const [latestVersion] = useLatestPipelineVersion(data.pipeline?.pipeline_id); + // Use this state to avoid the pipeline version being set as the latest version at the initial load + const [initialLoadedState, setInitialLoadedState] = React.useState(true); const selectedVersion = data.version || latestVersion; const paramsRef = React.useRef(data.params); - const isSchedule = runType === PipelineRunType.SCHEDULED; + const isSchedule = data.runType.type === RunTypeOption.SCHEDULED; const updateInputParams = React.useCallback( (version: PipelineVersionKFv2 | undefined) => @@ -43,15 +44,15 @@ const RunForm: React.FC = ({ data, runType, onValueChange }) => { ); React.useEffect(() => { - if (latestVersion) { + if (!initialLoadedState && latestVersion) { onValueChange('version', latestVersion); updateInputParams(latestVersion); } - }, [latestVersion, onValueChange, updateInputParams]); + }, [initialLoadedState, latestVersion, onValueChange, updateInputParams]); return (
e.preventDefault()} maxWidth="500px"> - + = ({ data, runType, onValueChange }) => { version={selectedVersion} onValueChange={onValueChange} updateInputParams={updateInputParams} + setInitialLoadedState={setInitialLoadedState} /> = ({ cloneRun, contextPath, testId }) => { - const { namespace, experimentId } = useParams(); +const RunPage: React.FC = ({ + cloneRun, + contextPath, + testId, + runType, + contextExperiment, + contextPipeline, + contextPipelineVersion, +}) => { const location = useLocation(); - const { runType, triggerType: triggerTypeString } = useGetSearchParamValues([ - PipelineRunSearchParam.RunType, + const { + nameDesc: locationNameDesc, + pipeline: locationPipeline, + version: locationVersion, + experiment: locationExperiment, + } = location.state?.locationData || {}; + const { triggerType: triggerTypeString } = useGetSearchParamValues([ PipelineRunSearchParam.TriggerType, ]); const triggerType = asEnumMember(triggerTypeString, ScheduledType); - const isSchedule = runType === PipelineRunType.SCHEDULED; - - const cloneRunPipelineId = cloneRun?.pipeline_version_reference?.pipeline_id || ''; - const cloneRunVersionId = cloneRun?.pipeline_version_reference?.pipeline_version_id || ''; - const cloneRunExperimentId = cloneRun?.experiment_id || ''; + const isSchedule = runType === RunTypeOption.SCHEDULED; - const [cloneRunPipelineVersion] = usePipelineVersionById(cloneRunPipelineId, cloneRunVersionId); - const [cloneRunPipeline] = usePipelineById(cloneRunPipelineId); - const [runExperiment] = useExperimentById(cloneRunExperimentId || experimentId); const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; const jumpToSections = Object.values(CreateRunPageSections).filter( @@ -65,7 +75,7 @@ const RunPage: React.FC = ({ cloneRun, contextPath, testId }) => { const runTypeData: RunType = React.useMemo( () => - runType === PipelineRunType.SCHEDULED + isSchedule ? { type: RunTypeOption.SCHEDULED, data: { @@ -74,28 +84,19 @@ const RunPage: React.FC = ({ cloneRun, contextPath, testId }) => { }, } : { type: RunTypeOption.ONE_TRIGGER }, - [runType, triggerType], + [isSchedule, triggerType], ); const [formData, setFormDataValue] = useRunFormData(cloneRun, { + nameDesc: cloneRun + ? { name: `Duplicate of ${cloneRun.display_name}`, description: cloneRun.description } + : locationNameDesc || { name: '', description: '' }, runType: runTypeData, - pipeline: location.state?.lastPipeline || cloneRunPipeline, - version: location.state?.lastVersion || cloneRunPipelineVersion, - experiment: runExperiment, + pipeline: locationPipeline || contextPipeline, + version: locationVersion || contextPipelineVersion, + experiment: locationExperiment || contextExperiment, }); - // need to correctly set runType after switching between run types as the form data is not updated automatically - React.useEffect(() => { - // set the data if the url run type is different from the form data run type - if ( - (runType === PipelineRunType.SCHEDULED && - formData.runType.type === RunTypeOption.ONE_TRIGGER) || - (runType !== PipelineRunType.SCHEDULED && formData.runType.type === RunTypeOption.SCHEDULED) - ) { - setFormDataValue('runType', runTypeData); - } - }, [formData.runType.type, runType, runTypeData, setFormDataValue]); - const onValueChange = React.useCallback( (key: keyof RunFormData, value: ValueOf) => setFormDataValue(key, value), [setFormDataValue], @@ -116,18 +117,11 @@ const RunPage: React.FC = ({ cloneRun, contextPath, testId }) => { titles={runPageSectionTitlesEdited} maxWidth={175} > - + - + ); diff --git a/frontend/src/concepts/pipelines/content/createRun/RunPageFooter.tsx b/frontend/src/concepts/pipelines/content/createRun/RunPageFooter.tsx index 776d4ad79c..a0d33ac5ec 100644 --- a/frontend/src/concepts/pipelines/content/createRun/RunPageFooter.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/RunPageFooter.tsx @@ -1,19 +1,15 @@ import * as React from 'react'; import { Alert, Button, Split, SplitItem, Stack, StackItem } from '@patternfly/react-core'; -import { useNavigate, useParams } from 'react-router-dom'; -import { RunFormData } from '~/concepts/pipelines/content/createRun/types'; +import { useNavigate } from 'react-router-dom'; +import { RunFormData, RunTypeOption } from '~/concepts/pipelines/content/createRun/types'; import { isFilledRunFormData, isFilledRunFormDataExperiment, } from '~/concepts/pipelines/content/createRun/utils'; import { handleSubmit } from '~/concepts/pipelines/content/createRun/submitUtils'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; -import { useGetSearchParamValues } from '~/utilities/useGetSearchParamValues'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; import { isRunSchedule } from '~/concepts/pipelines/utils'; -import { routePipelineRunDetails, routePipelineRunJobDetails } from '~/routes'; type RunPageFooterProps = { data: RunFormData; @@ -21,9 +17,8 @@ type RunPageFooterProps = { }; const RunPageFooter: React.FC = ({ data, contextPath }) => { - const { experimentId } = useParams(); const { api } = usePipelinesAPI(); - const { runType } = useGetSearchParamValues([PipelineRunSearchParam.RunType]); + const runType = data.runType.type; const navigate = useNavigate(); const [isSubmitting, setSubmitting] = React.useState(false); const [error, setError] = React.useState(null); @@ -54,15 +49,9 @@ const RunPageFooter: React.FC = ({ data, contextPath }) => { setError(null); handleSubmit(data, api) .then((resource) => { - let detailsPath = isRunSchedule(resource) - ? routePipelineRunJobDetails(resource.recurring_run_id) - : routePipelineRunDetails(resource.run_id); - - if (isExperimentsAvailable && experimentId) { - detailsPath = isRunSchedule(resource) - ? resource.recurring_run_id - : resource.run_id; - } + const detailsPath = isRunSchedule(resource) + ? resource.recurring_run_id + : resource.run_id; navigate(`${contextPath}/${detailsPath}`); }) @@ -72,19 +61,11 @@ const RunPageFooter: React.FC = ({ data, contextPath }) => { }); }} > - {`${runType === PipelineRunType.SCHEDULED ? 'Schedule' : 'Create'} run`} + {`Create ${runType === RunTypeOption.SCHEDULED ? 'schedule' : 'run'}`} - diff --git a/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineSection.tsx b/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineSection.tsx index 6aa2fedaf0..70b5dc7bde 100644 --- a/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineSection.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineSection.tsx @@ -18,6 +18,7 @@ type PipelineSectionProps = Pick, 'onValueC pipeline: PipelineKFv2 | null; version: PipelineVersionKFv2 | null; updateInputParams: (version: PipelineVersionKFv2 | undefined) => void; + setInitialLoadedState: (isInitial: boolean) => void; }; const PipelineSection: React.FC = ({ @@ -25,13 +26,15 @@ const PipelineSection: React.FC = ({ version, onValueChange, updateInputParams, + setInitialLoadedState, }) => { const onPipelineChange = React.useCallback( (value: PipelineKFv2) => { onValueChange('pipeline', value); onValueChange('version', undefined); + setInitialLoadedState(false); }, - [onValueChange], + [onValueChange, setInitialLoadedState], ); const onVersionChange = React.useCallback( diff --git a/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSection.tsx b/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSection.tsx index d29b4d6ad4..862ee17c5f 100644 --- a/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSection.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/contentSections/RunTypeSection.tsx @@ -3,32 +3,37 @@ import React from 'react'; import { Alert, AlertActionCloseButton, FormSection } from '@patternfly/react-core'; import { useParams, Link } from 'react-router-dom'; -import { PipelineRunTabTitle, PipelineRunType } from '~/pages/pipelines/global/runs'; +import { PipelineRunTabTitle } from '~/pages/pipelines/global/runs'; import { CreateRunPageSections, runPageSectionTitles, } from '~/concepts/pipelines/content/createRun/const'; -import { createRunRoute, scheduleRunRoute } from '~/routes'; +import { createRunRoute, createScheduleRoute } from '~/routes'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; +import { RunFormData, RunTypeOption } from '~/concepts/pipelines/content/createRun/types'; interface RunTypeSectionProps { - runType: PipelineRunType; + data: RunFormData; + isCloned: boolean; } -export const RunTypeSection: React.FC = ({ runType }) => { - const { namespace, experimentId } = useParams(); +export const RunTypeSection: React.FC = ({ data, isCloned }) => { + const { namespace, experimentId, pipelineId, pipelineVersionId } = useParams(); const [isAlertOpen, setIsAlertOpen] = React.useState(true); const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; - const runTypeValue = 'Run once immediately after creation'; + let runTypeValue = 'Run once immediately after creation'; let alertTitle = ( <> To create a schedule that executes recurring runs,{' '} go to the {PipelineRunTabTitle.SCHEDULES} tab @@ -37,15 +42,19 @@ export const RunTypeSection: React.FC = ({ runType }) => { ); - if (runType === PipelineRunType.SCHEDULED) { + if (data.runType.type === RunTypeOption.SCHEDULED) { + runTypeValue = 'Schedule recurring run'; alertTitle = ( <> To create a non-recurring run,{' '} go to the {PipelineRunTabTitle.ACTIVE} tab @@ -62,7 +71,7 @@ export const RunTypeSection: React.FC = ({ runType }) => { > {runTypeValue} - {isAlertOpen && ( + {isAlertOpen && !isCloned && ( { - const { runId, recurringRunId } = useParams(); - const [run, runLoaded, runError] = usePipelineRunById(runId); - const [job, jobLoaded, jobError] = usePipelineRunJobById(recurringRunId); - - if (jobLoaded || jobError) { - return [job, jobLoaded, jobError]; - } - if (runLoaded || runError) { - return [run ?? null, runLoaded, runError]; - } - - return [null, false, undefined]; -}; - -export default useCloneRunData; diff --git a/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts b/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts index 2a36c49ee0..a2b9238f2f 100644 --- a/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts +++ b/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts @@ -11,10 +11,9 @@ import { import { DateTimeKF, ExperimentKFv2, - PipelineKFv2, PipelineRunJobKFv2, PipelineRunKFv2, - PipelineVersionKFv2, + RuntimeConfigParameters, StorageStateKF, } from '~/concepts/pipelines/kfTypes'; @@ -27,6 +26,10 @@ import { } from '~/concepts/pipelines/content/createRun/const'; import { convertDateToTimeString, convertSecondsToPeriodicTime } from '~/utilities/time'; import { isPipelineRunJob } from '~/concepts/pipelines/content/utils'; +import { getInputDefinitionParams } from '~/concepts/pipelines/content/createRun/utils'; +import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; +import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; +import useExperimentById from '~/concepts/pipelines/apiHooks/useExperimentById'; const parseKFTime = (kfTime?: DateTimeKF): RunDateTime | undefined => { if (!kfTime) { @@ -120,22 +123,25 @@ const useUpdateExperimentFormData = ( }, [formData.experiment, setFormValue, experiment, formData.runType.type]); }; -const useUpdatePipelineFormData = ( - formState: GenericObjectState, - pipeline: PipelineKFv2 | null | undefined, - version: PipelineVersionKFv2 | null | undefined, +const useUpdateCloneData = ( + setFunction: UpdateObjectAtPropAndValue, + initialData?: PipelineRunKFv2 | PipelineRunJobKFv2 | null, ) => { - const [formData, setFormValue] = formState; + const cloneRunPipelineId = initialData?.pipeline_version_reference?.pipeline_id || ''; + const cloneRunVersionId = initialData?.pipeline_version_reference?.pipeline_version_id || ''; + const cloneRunExperimentId = initialData?.experiment_id || ''; + const [cloneRunPipelineVersion] = usePipelineVersionById(cloneRunPipelineId, cloneRunVersionId); + const [cloneRunPipeline] = usePipelineById(cloneRunPipelineId); + const [cloneExperiment] = useExperimentById(cloneRunExperimentId); React.useEffect(() => { - if (!formData.pipeline && pipeline) { - setFormValue('pipeline', pipeline); - } - - if (!formData.version && version && formData.pipeline?.pipeline_id === pipeline?.pipeline_id) { - setFormValue('version', version); + if (!initialData) { + return; } - }, [formData.pipeline, formData.version, pipeline, setFormValue, version]); + setFunction('experiment', cloneExperiment); + setFunction('pipeline', cloneRunPipeline); + setFunction('version', cloneRunPipelineVersion); + }, [setFunction, initialData, cloneExperiment, cloneRunPipeline, cloneRunPipelineVersion]); }; const useRunFormData = ( @@ -143,26 +149,31 @@ const useRunFormData = ( initialFormData?: Partial, ): GenericObjectState => { const { project } = usePipelinesAPI(); - const { pipeline, version, experiment } = initialFormData || {}; + const { pipeline, version, experiment, nameDesc } = initialFormData || {}; const formState = useGenericObjectState({ project, - nameDesc: { - name: run?.display_name ? `Duplicate of ${run.display_name}` : '', - description: run?.description ?? '', - }, + nameDesc: nameDesc ?? { name: '', description: '' }, pipeline: pipeline ?? null, version: version ?? null, experiment: experiment ?? null, runType: { type: RunTypeOption.ONE_TRIGGER }, - params: run?.runtime_config?.parameters || {}, + params: + run?.runtime_config?.parameters || + Object.entries(getInputDefinitionParams(version) || {}).reduce( + (acc: RuntimeConfigParameters, [paramKey, paramValue]) => { + acc[paramKey] = paramValue.defaultValue ?? ''; + return acc; + }, + {}, + ), ...initialFormData, }); const [, setFormValue] = formState; useUpdateExperimentFormData(formState, experiment); - useUpdatePipelineFormData(formState, pipeline, version); useUpdateRunType(setFormValue, run); + useUpdateCloneData(setFormValue, run); return formState; }; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx index 915fcdec68..b837b67d33 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx @@ -26,7 +26,7 @@ import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVer import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; import PipelineVersionSelector from '~/concepts/pipelines/content/pipelineSelector/PipelineVersionSelector'; import DeletePipelinesModal from '~/concepts/pipelines/content/DeletePipelinesModal'; -import { routePipelineDetailsNamespace, routePipelinesNamespace } from '~/routes'; +import { pipelineVersionDetailsRoute, pipelinesBaseRoute } from '~/routes'; import { getCorePipelineSpec } from '~/concepts/pipelines/getCorePipelineSpec'; import PipelineDetailsActions from './PipelineDetailsActions'; import SelectedTaskDrawerContent from './SelectedTaskDrawerContent'; @@ -69,7 +69,7 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = - {breadcrumbPath()} + {breadcrumbPath} {title} } @@ -97,7 +97,7 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = - {breadcrumbPath()} + {breadcrumbPath} {/* TODO: Remove the custom className after upgrading to PFv6 */} navigate( - routePipelineDetailsNamespace( + pipelineVersionDetailsRoute( namespace, version.pipeline_id, version.pipeline_version_id, @@ -260,7 +260,7 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = onClose={(deleted) => { setDeletionOpen(false); if (deleted) { - navigate(routePipelinesNamespace(namespace)); + navigate(pipelinesBaseRoute(namespace)); } }} /> diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx index 90707a224f..12e2574e5e 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx @@ -11,12 +11,12 @@ import { import { usePipelinesAPI } from '~/concepts/pipelines/context'; import PipelineVersionImportModal from '~/concepts/pipelines/content/import/PipelineVersionImportModal'; import { PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; import { - routePipelineDetailsNamespace, - routePipelineRunCreateNamespacePipelinesPage, - routePipelineVersionRunsNamespace, + pipelineVersionCreateRunRoute, + pipelineVersionCreateScheduleRoute, + pipelineVersionDetailsRoute, + pipelineVersionRunsRoute, + pipelineVersionSchedulesRoute, } from '~/routes'; type PipelineDetailsActionsProps = { @@ -55,21 +55,29 @@ const PipelineDetailsActions: React.FC = ({ - navigate(routePipelineRunCreateNamespacePipelinesPage(namespace), { - state: { lastPipeline: pipeline, lastVersion: pipelineVersion }, - }) + navigate( + pipelineVersionCreateRunRoute( + namespace, + pipeline?.pipeline_id, + pipelineVersion?.pipeline_version_id, + ), + { + state: { lastPipeline: pipeline, lastVersion: pipelineVersion }, + }, + ) } > Create run , navigate( - { - pathname: routePipelineRunCreateNamespacePipelinesPage(namespace), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }, + pipelineVersionCreateScheduleRoute( + namespace, + pipeline?.pipeline_id, + pipelineVersion?.pipeline_version_id, + ), { state: { lastPipeline: pipeline, lastVersion: pipelineVersion }, }, @@ -85,17 +93,11 @@ const PipelineDetailsActions: React.FC = ({ key="view-runs" onClick={() => navigate( - { - pathname: routePipelineVersionRunsNamespace( - namespace, - pipeline.pipeline_id, - pipelineVersion.pipeline_version_id, - ), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.ACTIVE}`, - }, - { - state: { lastVersion: pipelineVersion }, - }, + pipelineVersionRunsRoute( + namespace, + pipeline.pipeline_id, + pipelineVersion.pipeline_version_id, + ), ) } > @@ -105,17 +107,11 @@ const PipelineDetailsActions: React.FC = ({ key="view-schedules" onClick={() => navigate( - { - pathname: routePipelineVersionRunsNamespace( - namespace, - pipeline.pipeline_id, - pipelineVersion.pipeline_version_id, - ), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }, - { - state: { lastVersion: pipelineVersion }, - }, + pipelineVersionSchedulesRoute( + namespace, + pipeline.pipeline_id, + pipelineVersion.pipeline_version_id, + ), ) } > @@ -138,7 +134,7 @@ const PipelineDetailsActions: React.FC = ({ if (resource) { refreshAllAPI(); navigate( - routePipelineDetailsNamespace( + pipelineVersionDetailsRoute( namespace, resource.pipeline_id, resource.pipeline_version_id, diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx index b17a7f2ad6..829ed1ce4c 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx @@ -11,7 +11,7 @@ import { } from '@patternfly/react-core'; import { CubesIcon } from '@patternfly/react-icons'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import { routePipelinesNamespace } from '~/routes'; +import { pipelinesBaseRoute } from '~/routes'; const PipelineNotFound: React.FC = () => { const { namespace } = usePipelinesAPI(); @@ -26,7 +26,7 @@ const PipelineNotFound: React.FC = () => { To see more pipelines navigate to the pipelines page - diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx index ebd7bb8098..2a82b785ee 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx @@ -13,35 +13,35 @@ import { Truncate, EmptyStateHeader, } from '@patternfly/react-core'; -import { Link, useNavigate, useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import ApplicationsPage from '~/pages/ApplicationsPage'; import MarkdownView from '~/components/MarkdownView'; -import usePipelineRunById from '~/concepts/pipelines/apiHooks/usePipelineRunById'; -import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { PathProps } from '~/concepts/pipelines/content/types'; import PipelineRunDetailsActions from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions'; import PipelineRunDrawerRightContent from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightContent'; import { ArchiveRunModal } from '~/pages/pipelines/global/runs/ArchiveRunModal'; import DeletePipelineRunsModal from '~/concepts/pipelines/content/DeletePipelineRunsModal'; -import { usePipelinesAPI } from '~/concepts/pipelines/context'; import PipelineDetailsTitle from '~/concepts/pipelines/content/pipelinesDetails/PipelineDetailsTitle'; import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; import { usePipelineTaskTopology } from '~/concepts/pipelines/topology'; import { PipelineRunType } from '~/pages/pipelines/global/runs/types'; -import { routePipelineRunsNamespace, routePipelineVersionRunsNamespace } from '~/routes'; import PipelineJobReferenceName from '~/concepts/pipelines/content/PipelineJobReferenceName'; import useExecutionsForPipelineRun from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/useExecutionsForPipelineRun'; import { useGetEventsByExecutionIds } from '~/concepts/pipelines/apiHooks/mlmd/useGetEventsByExecutionId'; import { PipelineTopology } from '~/concepts/topology'; -import { StorageStateKF } from '~/concepts/pipelines/kfTypes'; +import { FetchState } from '~/utilities/useFetchState'; +import { PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes'; import { usePipelineRunArtifacts } from './artifacts'; import { PipelineRunDetailsTabs } from './PipelineRunDetailsTabs'; -const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, contextPath }) => { - const { runId } = useParams(); +const PipelineRunDetails: React.FC< + PathProps & { + fetchedRun: FetchState; + } +> = ({ fetchedRun, breadcrumbPath, contextPath }) => { const navigate = useNavigate(); - const { namespace } = usePipelinesAPI(); - const [run, runLoaded, runError] = usePipelineRunById(runId, true); + const [run, runLoaded, runError] = fetchedRun; const [version, versionLoaded, versionError] = usePipelineVersionById( run?.pipeline_version_reference?.pipeline_id, run?.pipeline_version_reference?.pipeline_version_id, @@ -93,9 +93,6 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, ); } - const runType = - run?.storage_state === StorageStateKF.ARCHIVED ? PipelineRunType.ARCHIVED : undefined; - return ( <> @@ -132,24 +129,7 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, loadError={error} breadcrumb={ - {breadcrumbPath(runType)} - - {version ? ( - - {/* TODO: Remove the custom className after upgrading to PFv6 */} - - - ) : ( - 'Loading...' - )} - + {breadcrumbPath} {/* TODO: Remove the custom className after upgrading to PFv6 */} { if (deleteComplete) { - navigate(contextPath ?? routePipelineRunsNamespace(namespace)); + navigate(contextPath); } else { setDeleting(false); } diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx index 9c32c7e16f..766caf929c 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx @@ -33,7 +33,7 @@ const PipelineRunDetailsActions: React.FC = ({ const isRunActive = run?.storage_state === StorageStateKF.AVAILABLE; const [experiment] = useExperimentById(run?.experiment_id); const isExperimentActive = experiment?.storage_state === StorageStateKF.AVAILABLE; - const { experimentId } = useParams(); + const { experimentId, pipelineId, pipelineVersionId } = useParams(); const RestoreDropdownItem = ( = ({ namespace, run.run_id, isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, ), ) } diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx index abb5335da2..f35f0f7c33 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx @@ -12,7 +12,7 @@ import { Bullseye, Spinner, } from '@patternfly/react-core'; -import { Link, useNavigate, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import ApplicationsPage from '~/pages/ApplicationsPage'; import { usePipelineTaskTopology } from '~/concepts/pipelines/topology'; @@ -20,11 +20,9 @@ import { PipelineTopology } from '~/concepts/topology'; import MarkdownView from '~/components/MarkdownView'; import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; import DeletePipelineRunsModal from '~/concepts/pipelines/content/DeletePipelineRunsModal'; -import { usePipelinesAPI } from '~/concepts/pipelines/context'; import usePipelineRunJobById from '~/concepts/pipelines/apiHooks/usePipelineRunJobById'; import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; import { PipelineRunType } from '~/pages/pipelines/global/runs'; -import { routePipelineRunsNamespace, routePipelineVersionRunsNamespace } from '~/routes'; import SelectedTaskDrawerContent from '~/concepts/pipelines/content/pipelinesDetails/pipeline/SelectedTaskDrawerContent'; import { PipelineRunDetailsTabs } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsTabs'; import PipelineRunJobDetailsActions from './PipelineRunJobDetailsActions'; @@ -35,7 +33,6 @@ const PipelineRunJobDetails: PipelineCoreDetailsPageComponent = ({ }) => { const { recurringRunId } = useParams(); const navigate = useNavigate(); - const { namespace } = usePipelinesAPI(); const [job, jobLoaded, jobError] = usePipelineRunJobById(recurringRunId); const [version, versionLoaded, versionError] = usePipelineVersionById( job?.pipeline_version_reference.pipeline_id, @@ -94,23 +91,7 @@ const PipelineRunJobDetails: PipelineCoreDetailsPageComponent = ({ loadError={error} breadcrumb={ - {breadcrumbPath(PipelineRunType.SCHEDULED)} - - {version ? ( - - {version.display_name} - - ) : ( - 'Loading...' - )} - + {breadcrumbPath} {job?.display_name ?? 'Loading...'} } @@ -151,7 +132,7 @@ const PipelineRunJobDetails: PipelineCoreDetailsPageComponent = ({ toDeleteResources={deleting && job ? [job] : []} onClose={(deleteComplete) => { if (deleteComplete) { - navigate(contextPath ?? routePipelineRunsNamespace(namespace)); + navigate(contextPath); } else { setDeleting(false); } diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetailsActions.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetailsActions.tsx index 6ec286d0a2..4649af8491 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetailsActions.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetailsActions.tsx @@ -9,8 +9,6 @@ import { useNavigate, useParams } from 'react-router-dom'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { PipelineRunJobKFv2 } from '~/concepts/pipelines/kfTypes'; import { cloneScheduleRoute } from '~/routes'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; import { useIsAreaAvailable, SupportedArea } from '~/concepts/areas'; type PipelineRunJobDetailsActionsProps = { @@ -25,7 +23,7 @@ const PipelineRunJobDetailsActions: React.FC const navigate = useNavigate(); const { namespace } = usePipelinesAPI(); const [open, setOpen] = React.useState(false); - const { experimentId } = useParams(); + const { experimentId, pipelineId, pipelineVersionId } = useParams(); const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; return ( @@ -46,14 +44,15 @@ const PipelineRunJobDetailsActions: React.FC - navigate({ - pathname: cloneScheduleRoute( + navigate( + cloneScheduleRoute( namespace, job.recurring_run_id, isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, ), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }) + ) } > Duplicate diff --git a/frontend/src/concepts/pipelines/content/tables/columns.ts b/frontend/src/concepts/pipelines/content/tables/columns.ts index 46925056c6..2bab7bfe52 100644 --- a/frontend/src/concepts/pipelines/content/tables/columns.ts +++ b/frontend/src/concepts/pipelines/content/tables/columns.ts @@ -183,7 +183,12 @@ export const pipelineRunJobColumns: SortableData[] = [ label: 'Schedule', field: 'name', sortable: true, - width: 20, + }, + { + label: 'Experiment', + field: 'experiment', + sortable: false, + width: 15, }, { label: 'Pipeline version', diff --git a/frontend/src/concepts/pipelines/content/tables/experiment/ExperimentTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/experiment/ExperimentTableRow.tsx index dc1a4e31c4..178ad80c64 100644 --- a/frontend/src/concepts/pipelines/content/tables/experiment/ExperimentTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/experiment/ExperimentTableRow.tsx @@ -4,7 +4,7 @@ import { ActionsColumn, IAction, Td, Tr } from '@patternfly/react-table'; import { Truncate } from '@patternfly/react-core'; import { ExperimentKFv2, StorageStateKF } from '~/concepts/pipelines/kfTypes'; import { CheckboxTd } from '~/components/table'; -import { experimentRunsRoute } from '~/routes'; +import { experimentArchivedRunsRoute, experimentRunsRoute } from '~/routes'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { ExperimentCreated, LastExperimentRuns, LastExperimentRunsStarted } from './renderUtils'; @@ -30,9 +30,11 @@ const ExperimentTableRow: React.FC = ({ {/* TODO: Remove the custom className after upgrading to PFv6 */} diff --git a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTable.tsx b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTable.tsx index 9701370ee7..abb1a45fe3 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTable.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTable.tsx @@ -9,7 +9,6 @@ import usePipelinesCheckboxTable from '~/concepts/pipelines/content/tables/pipel type PipelinesTableProps = { pipelines: PipelineKFv2[]; - pipelineDetailsPath: (namespace: string, pipelineId: string, pipelineVersionId: string) => string; refreshPipelines: () => Promise; loading?: boolean; totalSize?: number; @@ -35,7 +34,6 @@ const PipelinesTable: React.FC = ({ pageSize, setPage, setPageSize, - pipelineDetailsPath, enablePagination, emptyTableView, toolbarContent, @@ -85,7 +83,6 @@ const PipelinesTable: React.FC = ({ onToggleCheck={() => toggleSelection(pipeline)} onDeletePipeline={() => setDeletePipelines([pipeline])} refreshPipelines={refreshPipelines} - pipelineDetailsPath={pipelineDetailsPath} disableCheck={disableCheck} /> )} diff --git a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableExpandedRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableExpandedRow.tsx index 9f10667b5f..258141c414 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableExpandedRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableExpandedRow.tsx @@ -17,13 +17,9 @@ import ImportPipelineVersionButton from '~/concepts/pipelines/content/import/Imp type PipelinesTableExpandedRowProps = { pipeline: PipelineKFv2; - pipelineDetailsPath: (namespace: string, pipelineId: string, pipelineVersionId: string) => string; }; -const PipelinesTableExpandedRow: React.FC = ({ - pipeline, - pipelineDetailsPath, -}) => { +const PipelinesTableExpandedRow: React.FC = ({ pipeline }) => { const [ [{ items: initialVersions, totalSize, nextPageToken }, loaded], { initialLoaded, ...tableProps }, @@ -76,7 +72,6 @@ const PipelinesTableExpandedRow: React.FC = ({ initialVersions={initialVersions} loading={!loaded} totalSize={totalSize} - pipelineDetailsPath={pipelineDetailsPath} nextPageToken={nextPageToken} pipeline={pipeline} /> diff --git a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx index 816010e6a0..9d3b464f58 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx @@ -10,9 +10,11 @@ import PipelineVersionUploadModal from '~/concepts/pipelines/content/import/Pipe import PipelinesTableRowTime from '~/concepts/pipelines/content/tables/PipelinesTableRowTime'; import usePipelineTableRowData from '~/concepts/pipelines/content/tables/pipeline/usePipelineTableRowData'; import { PipelineAndVersionContext } from '~/concepts/pipelines/content/PipelineAndVersionContext'; -import { routePipelineRunCreateNamespacePipelinesPage } from '~/routes'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; +import { + pipelineVersionCreateRunRoute, + pipelineVersionCreateScheduleRoute, + pipelineVersionDetailsRoute, +} from '~/routes'; const DISABLE_TOOLTIP = 'All child pipeline versions must be deleted before deleting the parent pipeline'; @@ -24,7 +26,6 @@ type PipelinesTableRowProps = { rowIndex: number; onDeletePipeline: () => void; refreshPipelines: () => Promise; - pipelineDetailsPath: (namespace: string, pipelineId: string, pipelineVersionId: string) => string; disableCheck: (id: PipelineKFv2, disabled: boolean) => void; }; @@ -35,7 +36,6 @@ const PipelinesTableRow: React.FC = ({ rowIndex, onDeletePipeline, refreshPipelines, - pipelineDetailsPath, disableCheck, }) => { const navigate = useNavigate(); @@ -90,9 +90,9 @@ const PipelinesTableRow: React.FC = ({ ) : version?.pipeline_version_id ? ( @@ -126,22 +126,24 @@ const PipelinesTableRow: React.FC = ({ { title: 'Create run', onClick: () => { - navigate(routePipelineRunCreateNamespacePipelinesPage(namespace), { - state: { lastPipeline: pipeline }, - }); + navigate( + pipelineVersionCreateRunRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + ), + ); }, }, { title: 'Create schedule', onClick: () => { navigate( - { - pathname: routePipelineRunCreateNamespacePipelinesPage(namespace), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }, - { - state: { lastPipeline: pipeline }, - }, + pipelineVersionCreateScheduleRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + ), ); }, }, @@ -170,7 +172,6 @@ const PipelinesTableRow: React.FC = ({ // Which will trigger a re-render of the versions table key={`${pipeline.pipeline_id}-expanded-${totalSize}`} pipeline={pipeline} - pipelineDetailsPath={pipelineDetailsPath} /> )} diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx index a024c85d4e..84ddba2111 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx @@ -25,12 +25,10 @@ import { RestoreRunModal } from '~/pages/pipelines/global/runs/RestoreRunModal'; import { useSetVersionFilter } from '~/concepts/pipelines/content/tables/useSetVersionFilter'; import { createRunRoute, experimentsCompareRunsRoute } from '~/routes'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; -import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; +import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentContext'; import { getArtifactProperties } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/artifacts/utils'; import { useGetArtifactsByRuns } from '~/concepts/pipelines/apiHooks/mlmd/useGetArtifactsByRuns'; import { ArtifactProperty } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/artifacts/types'; -import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; -import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; import { CustomMetricsColumnsModal } from './CustomMetricsColumnsModal'; import { RunWithMetrics } from './types'; import { UnavailableMetricValue } from './UnavailableMetricValue'; @@ -76,8 +74,6 @@ const PipelineRunTableInternal: React.FC = ({ }) => { const navigate = useNavigate(); const { experimentId, pipelineVersionId, pipelineId } = useParams(); - const [pipeline] = usePipelineById(pipelineId); - const [pipelineVersion] = usePipelineVersionById(pipelineId, pipelineVersionId); const { namespace, refreshAllAPI } = usePipelinesAPI(); const filterToolbarProps = usePipelineFilter(setFilter); const { @@ -136,9 +132,14 @@ const PipelineRunTableInternal: React.FC = ({ data-testid="create-run-button" variant="primary" onClick={() => - navigate(createRunRoute(namespace, isExperimentsAvailable ? experimentId : undefined), { - state: { lastPipeline: pipeline, lastVersion: pipelineVersion }, - }) + navigate( + createRunRoute( + namespace, + isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, + ), + ) } > Create run @@ -152,8 +153,8 @@ const PipelineRunTableInternal: React.FC = ({ namespace, isExperimentsAvailable, experimentId, - pipeline, - pipelineVersion, + pipelineId, + pipelineVersionId, ]); const compareRunsAction = @@ -205,7 +206,7 @@ const PipelineRunTableInternal: React.FC = ({ let columns = isExperimentsEnabled ? getExperimentRunColumns(metricsColumnNames) : pipelineRunColumns; - if (isExperimentsAvailable && experimentId) { + if (isExperimentsEnabled) { columns = columns.filter((column) => column.field !== 'experiment'); } if (pipelineVersionId) { @@ -297,6 +298,7 @@ const PipelineRunTableInternal: React.FC = ({ )} ))} + runType={runType} /> )} variant={TableVariant.compact} @@ -358,8 +360,8 @@ const PipelineRunTable: React.FC = ({ runs, page, ...prop const runsWithMetrics = runs.map((run) => ({ ...run, - metrics: runArtifacts.reduce((acc: ArtifactProperty[], runArtifactseMap) => { - const artifacts = Object.entries(runArtifactseMap).find( + metrics: runArtifacts.reduce((acc: ArtifactProperty[], runArtifactsMap) => { + const artifacts = Object.entries(runArtifactsMap).find( ([runId]) => run.run_id === runId, )?.[1]; diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx index 77c011734c..e66171931f 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx @@ -13,15 +13,13 @@ import PipelineRunTableRowTitle from '~/concepts/pipelines/content/tables/pipeli import useNotification from '~/utilities/useNotification'; import usePipelineRunVersionInfo from '~/concepts/pipelines/content/tables/usePipelineRunVersionInfo'; import { PipelineVersionLink } from '~/concepts/pipelines/content/PipelineVersionLink'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; import { PipelineRunType } from '~/pages/pipelines/global/runs'; import { RestoreRunModal } from '~/pages/pipelines/global/runs/RestoreRunModal'; -import { useGetSearchParamValues } from '~/utilities/useGetSearchParamValues'; import { cloneRunRoute } from '~/routes'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; import { ArchiveRunModal } from '~/pages/pipelines/global/runs/ArchiveRunModal'; import PipelineRunTableRowExperiment from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowExperiment'; -import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; +import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentContext'; import { getDashboardMainContainer } from '~/utilities/utils'; type PipelineRunTableRowProps = { @@ -31,6 +29,7 @@ type PipelineRunTableRowProps = { customCells?: React.ReactNode; hasExperiments?: boolean; hasRowActions?: boolean; + runType?: PipelineRunType; }; const PipelineRunTableRow: React.FC = ({ @@ -40,9 +39,9 @@ const PipelineRunTableRow: React.FC = ({ customCells, onDelete, run, + runType, }) => { - const { runType } = useGetSearchParamValues([PipelineRunSearchParam.RunType]); - const { experimentId, pipelineVersionId } = useParams(); + const { experimentId, pipelineId, pipelineVersionId } = useParams(); const { namespace, api, refreshAllAPI } = usePipelinesAPI(); const notification = useNotification(); const navigate = useNavigate(); @@ -53,11 +52,18 @@ const PipelineRunTableRow: React.FC = ({ const isExperimentArchived = useContextExperimentArchived(); const actions: IAction[] = React.useMemo(() => { + const isExperimentContext = experimentId && isExperimentsAvailable; const cloneAction: IAction = { title: 'Duplicate', onClick: () => { navigate( - cloneRunRoute(namespace, run.run_id, isExperimentsAvailable ? experimentId : undefined), + cloneRunRoute( + namespace, + run.run_id, + isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, + ), ); }, }; @@ -75,7 +81,7 @@ const PipelineRunTableRow: React.FC = ({ }, }), }, - ...(version ? [cloneAction] : []), + ...(!version && isExperimentContext ? [] : [cloneAction]), { isSeparator: true, }, @@ -99,7 +105,7 @@ const PipelineRunTableRow: React.FC = ({ .catch((e) => notification.error('Unable to stop the pipeline run.', e.message)); }, }, - ...(version ? [cloneAction] : []), + ...(!version && isExperimentContext ? [] : [cloneAction]), { isSeparator: true, }, @@ -122,6 +128,8 @@ const PipelineRunTableRow: React.FC = ({ namespace, isExperimentsAvailable, experimentId, + pipelineId, + pipelineVersionId, ]); return ( diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx index b388358e50..999a42789c 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx @@ -15,7 +15,7 @@ type PipelineRunTableRowTitleProps = { const PipelineRunTableRowTitle: React.FC = ({ run }) => { const { namespace } = usePipelinesAPI(); - const { experimentId } = useParams(); + const { experimentId, pipelineId, pipelineVersionId } = useParams(); const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; return ( @@ -26,6 +26,8 @@ const PipelineRunTableRowTitle: React.FC = ({ run namespace, run.run_id, isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, )} > {run.display_name} diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx index 51eeda568c..c615a7e37e 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx @@ -14,6 +14,7 @@ import { PipelinesFilter } from '~/concepts/pipelines/types'; import usePipelineFilter from '~/concepts/pipelines/content/tables/usePipelineFilter'; import SimpleMenuActions from '~/components/SimpleMenuActions'; import { useSetVersionFilter } from '~/concepts/pipelines/content/tables/useSetVersionFilter'; +import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; type PipelineRunTableProps = { jobs: PipelineRunJobKFv2[]; @@ -42,8 +43,9 @@ const PipelineRunJobTable: React.FC = ({ ...tableProps }) => { const { refreshAllAPI } = usePipelinesAPI(); - const { pipelineVersionId } = useParams(); + const { experimentId, pipelineVersionId } = useParams(); const filterToolbarProps = usePipelineFilter(setFilter); + const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; const { selections, tableProps: checkboxTableProps, @@ -57,6 +59,11 @@ const PipelineRunJobTable: React.FC = ({ const getColumns = () => { let columns = pipelineRunJobColumns; + + if (isExperimentsAvailable && experimentId) { + columns = columns.filter((column) => column.field !== 'experiment'); + } + if (pipelineVersionId) { columns = columns.filter((column) => column.field !== 'pipeline_version'); } diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx index db38d95685..75b66e15c7 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTableRow.tsx @@ -12,10 +12,9 @@ import { import { usePipelinesAPI } from '~/concepts/pipelines/context'; import usePipelineRunVersionInfo from '~/concepts/pipelines/content/tables/usePipelineRunVersionInfo'; import { PipelineVersionLink } from '~/concepts/pipelines/content/PipelineVersionLink'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; import { cloneScheduleRoute, scheduleDetailsRoute } from '~/routes'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; +import PipelineRunTableRowExperiment from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowExperiment'; type PipelineRunJobTableRowProps = { isChecked: boolean; @@ -31,10 +30,11 @@ const PipelineRunJobTableRow: React.FC = ({ job, }) => { const navigate = useNavigate(); - const { experimentId, pipelineVersionId } = useParams(); + const { experimentId, pipelineId, pipelineVersionId } = useParams(); const { namespace, api, refreshAllAPI } = usePipelinesAPI(); const { version, loaded, error } = usePipelineRunVersionInfo(job); const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; + const isExperimentsContext = isExperimentsAvailable && experimentId; return ( @@ -47,6 +47,8 @@ const PipelineRunJobTableRow: React.FC = ({ namespace, job.recurring_run_id, isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, )} > {job.display_name} @@ -56,6 +58,11 @@ const PipelineRunJobTableRow: React.FC = ({ descriptionAsMarkdown /> + {!isExperimentsContext && ( + + + + )} {!pipelineVersionId && ( = ({ { - navigate({ - pathname: cloneScheduleRoute( + navigate( + cloneScheduleRoute( namespace, job.recurring_run_id, isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, ), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }); + ); }, }, { isSeparator: true, }, - ] - : []), + ]), { title: 'Delete', onClick: () => { diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTable.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTable.tsx index 94e8f4d569..c701c15a6b 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTable.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTable.tsx @@ -21,7 +21,6 @@ type PipelineVersionTableProps = { sortDirection?: 'asc' | 'desc'; setSortField: (field: string) => void; setSortDirection: (dir: 'asc' | 'desc') => void; - pipelineDetailsPath: (namespace: string, pipelineId: string, pipelineVersionId: string) => string; }; const PipelineVersionTable: React.FC = ({ @@ -32,7 +31,6 @@ const PipelineVersionTable: React.FC = ({ totalSize, sortField, sortDirection, - pipelineDetailsPath, ...tableProps }) => { const pipelineId = pipeline.pipeline_id; @@ -71,7 +69,6 @@ const PipelineVersionTable: React.FC = ({ isChecked={pipelineChecked || isSelected(version)} onToggleCheck={() => toggleSelection(version)} version={version} - pipelineVersionDetailsPath={pipelineDetailsPath} isDisabled={pipelineChecked} pipeline={pipeline} onDeleteVersion={() => diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTableRow.tsx index e2a323e975..d6891f2cbd 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineVersion/PipelineVersionTableRow.tsx @@ -5,23 +5,17 @@ import { PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes' import { CheckboxTd, TableRowTitleDescription } from '~/components/table'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import PipelinesTableRowTime from '~/concepts/pipelines/content/tables/PipelinesTableRowTime'; -import { PipelineRunType } from '~/pages/pipelines/global/runs/types'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; import { - routePipelineRunCreateNamespacePipelinesPage, - routePipelineRunsNamespace, - routePipelineVersionRunsNamespace, + pipelineVersionCreateRunRoute, + pipelineVersionCreateScheduleRoute, + pipelineVersionDetailsRoute, + pipelineVersionRunsRoute, + pipelineVersionSchedulesRoute, } from '~/routes'; -import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; type PipelineVersionTableRowProps = { isChecked: boolean; onToggleCheck: () => void; - pipelineVersionDetailsPath: ( - namespace: string, - pipelineId: string, - pipelineVersionId: string, - ) => string; version: PipelineVersionKFv2; isDisabled: boolean; pipeline: PipelineKFv2; @@ -31,7 +25,6 @@ type PipelineVersionTableRowProps = { const PipelineVersionTableRow: React.FC = ({ isChecked, onToggleCheck, - pipelineVersionDetailsPath, version, isDisabled, pipeline, @@ -40,7 +33,6 @@ const PipelineVersionTableRow: React.FC = ({ const navigate = useNavigate(); const { namespace } = usePipelinesAPI(); const createdDate = new Date(version.created_at); - const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; return ( @@ -55,7 +47,7 @@ const PipelineVersionTableRow: React.FC = ({ title={ = ({ { title: 'Create run', onClick: () => { - navigate(routePipelineRunCreateNamespacePipelinesPage(namespace), { - state: { lastPipeline: pipeline, lastVersion: version }, - }); + navigate( + pipelineVersionCreateRunRoute( + namespace, + pipeline.pipeline_id, + version.pipeline_version_id, + ), + ); }, }, { title: 'Create schedule', onClick: () => { navigate( - { - pathname: routePipelineRunCreateNamespacePipelinesPage(namespace), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }, - { - state: { lastPipeline: pipeline, lastVersion: version }, - }, + pipelineVersionCreateScheduleRoute( + namespace, + pipeline.pipeline_id, + version.pipeline_version_id, + ), ); }, }, @@ -104,19 +98,11 @@ const PipelineVersionTableRow: React.FC = ({ title: 'View runs', onClick: () => { navigate( - { - pathname: isExperimentsAvailable - ? routePipelineVersionRunsNamespace( - namespace, - pipeline.pipeline_id, - version.pipeline_version_id, - ) - : routePipelineRunsNamespace(namespace), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.ACTIVE}`, - }, - { - state: { lastVersion: version }, - }, + pipelineVersionRunsRoute( + namespace, + pipeline.pipeline_id, + version.pipeline_version_id, + ), ); }, }, @@ -124,19 +110,11 @@ const PipelineVersionTableRow: React.FC = ({ title: 'View schedules', onClick: () => { navigate( - { - pathname: isExperimentsAvailable - ? routePipelineVersionRunsNamespace( - namespace, - pipeline.pipeline_id, - version.pipeline_version_id, - ) - : routePipelineRunsNamespace(namespace), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }, - { - state: { lastVersion: version }, - }, + pipelineVersionSchedulesRoute( + namespace, + pipeline.pipeline_id, + version.pipeline_version_id, + ), ); }, }, diff --git a/frontend/src/concepts/pipelines/content/tables/renderUtils.tsx b/frontend/src/concepts/pipelines/content/tables/renderUtils.tsx index 3cbd17ca0a..593994a68f 100644 --- a/frontend/src/concepts/pipelines/content/tables/renderUtils.tsx +++ b/frontend/src/concepts/pipelines/content/tables/renderUtils.tsx @@ -26,7 +26,7 @@ import { } from '~/concepts/pipelines/content/tables/utils'; import { computeRunStatus } from '~/concepts/pipelines/content/utils'; import PipelinesTableRowTime from '~/concepts/pipelines/content/tables/PipelinesTableRowTime'; -import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; +import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentContext'; export const NoRunContent = (): React.JSX.Element => <>-; diff --git a/frontend/src/concepts/pipelines/content/types.ts b/frontend/src/concepts/pipelines/content/types.ts index 039e040579..ee0c359a41 100644 --- a/frontend/src/concepts/pipelines/content/types.ts +++ b/frontend/src/concepts/pipelines/content/types.ts @@ -1,16 +1,14 @@ import * as React from 'react'; import { BreadcrumbItem } from '@patternfly/react-core'; -import { PipelineRunType } from '~/pages/pipelines/global/runs'; export type PathProps = { - breadcrumbPath: (runType?: PipelineRunType | null) => React.ReactElement[]; - contextPath?: string; + breadcrumbPath: React.ReactElement[]; + contextPath: string; }; export type PipelineCoreDetailsPageComponent = React.FC; export enum PipelineRunSearchParam { - RunType = 'runType', TriggerType = 'triggerType', } diff --git a/frontend/src/pages/pipelines/GlobalPipelineExperimentsRoutes.tsx b/frontend/src/pages/pipelines/GlobalPipelineExperimentsRoutes.tsx index 27765915c6..aba4b1a97b 100644 --- a/frontend/src/pages/pipelines/GlobalPipelineExperimentsRoutes.tsx +++ b/frontend/src/pages/pipelines/GlobalPipelineExperimentsRoutes.tsx @@ -8,15 +8,21 @@ import { experimentsPageTitle, } from '~/pages/pipelines/global/experiments/const'; import GlobalExperiments from '~/pages/pipelines/global/experiments/GlobalExperiments'; -import PipelineRunDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails'; -import PipelineRunJobDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails'; import { experimentsBaseRoute } from '~/routes'; -import CreateRunPage from '~/concepts/pipelines/content/createRun/CreateRunPage'; -import CloneRunPage from '~/concepts/pipelines/content/createRun/CloneRunPage'; -import ExperimentRunsContextProvider from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; -import { GlobalExperimentDetails } from './global/GlobalPipelineCoreDetails'; -import GlobalPipelineRuns from './global/runs/GlobalPipelineRuns'; -import { ExperimentRunsListBreadcrumb } from './global/experiments/ExperimentRunsListBreadcrumb'; +import ExperimentContextProvider from '~/pages/pipelines/global/experiments/ExperimentContext'; +import ExperimentPipelineRuns from '~/pages/pipelines/global/experiments/ExperimentPipelineRuns'; +import ExperimentPipelineRunsTabs from '~/pages/pipelines/global/experiments/ExperimentPipelineRunsTabs'; +import { PipelineRunType } from '~/pages/pipelines/global/runs'; +import ExperimentPipelineRunDetails from '~/pages/pipelines/global/experiments/ExperimentPipelineRunDetails'; +import ExperimentPipelineRunJobDetails from '~/pages/pipelines/global/experiments/ExperimentPipelineRunJobDetails'; +import { + ExperimentCreateRunPage, + ExperimentCreateSchedulePage, +} from '~/pages/pipelines/global/experiments/ExperimentCreateRunPage'; +import PipelineAvailabilityLoader from '~/pages/pipelines/global/pipelines/PipelineAvailabilityLoader'; +import ExperimentCloneRunPage from '~/pages/pipelines/global/experiments/ExperimentCloneRunPage'; +import ExperimentCloneSchedulePage from '~/pages/pipelines/global/experiments/ExperimentCloneSchedulePage'; +import { ExperimentCoreDetails } from './global/GlobalPipelineCoreDetails'; import GlobalComparePipelineRunsLoader from './global/experiments/compareRuns/GlobalComparePipelineRunsLoader'; import CompareRunsPage from './global/experiments/compareRuns/CompareRunsPage'; import { ManageRunsPage } from './global/experiments/compareRuns/ManageRunsPage'; @@ -36,58 +42,82 @@ const GlobalPipelineExperimentsRoutes: React.FC = () => ( } /> } /> } /> - }> - } - description="Manage your experiment runs and schedules." - getRedirectPath={experimentsBaseRoute} + }> + }> + } + > + } /> - } - /> - } - /> - } /> - } - /> - } - /> - - } - /> - } - /> - } - /> - }> + } + /> + } + /> + + + + } + /> + + } + /> + + } + /> + + + + } + /> + + } + /> + + } + /> + + }> + } + /> + } + path="compareRuns/add" + element={} /> + } /> - } - /> } /> diff --git a/frontend/src/pages/pipelines/GlobalPipelineRunsRoutes.tsx b/frontend/src/pages/pipelines/GlobalPipelineRunsRoutes.tsx deleted file mode 100644 index 6651122a6e..0000000000 --- a/frontend/src/pages/pipelines/GlobalPipelineRunsRoutes.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from 'react'; -import { Navigate, Route } from 'react-router-dom'; -import ProjectsRoutes from '~/concepts/projects/ProjectsRoutes'; -import GlobalPipelineCoreLoader from '~/pages/pipelines/global/GlobalPipelineCoreLoader'; -import { - pipelineRunsPageDescription, - pipelineRunsPageTitle, -} from '~/pages/pipelines/global/runs/const'; -import GlobalPipelineCoreDetails from '~/pages/pipelines/global/GlobalPipelineCoreDetails'; -import PipelineDetails from '~/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails'; -import PipelineRunDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails'; -import CreateRunPage from '~/concepts/pipelines/content/createRun/CreateRunPage'; -import CloneRunPage from '~/concepts/pipelines/content/createRun/CloneRunPage'; -import PipelineRunJobDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails'; -import { - globNamespaceAll, - globPipelineDetails, - globPipelineRunClone, - globPipelineRunCreate, - globPipelineRunDetails, - globPipelineRunJobClone, - globPipelineRunJobDetails, - routePipelineRunsNamespace, -} from '~/routes'; -import GlobalPipelineRuns from './global/runs/GlobalPipelineRuns'; - -const GlobalPipelineRunsRoutes: React.FC = () => ( - - - } - > - } /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } /> - - - } /> - -); - -export default GlobalPipelineRunsRoutes; diff --git a/frontend/src/pages/pipelines/GlobalPipelinesRoutes.tsx b/frontend/src/pages/pipelines/GlobalPipelinesRoutes.tsx index 436f1ce239..c5b37cabe0 100644 --- a/frontend/src/pages/pipelines/GlobalPipelinesRoutes.tsx +++ b/frontend/src/pages/pipelines/GlobalPipelinesRoutes.tsx @@ -6,25 +6,23 @@ import { pipelinesPageDescription, pipelinesPageTitle, } from '~/pages/pipelines/global/pipelines/const'; -import GlobalPipelineCoreDetails from '~/pages/pipelines/global/GlobalPipelineCoreDetails'; +import { PipelineVersionCoreDetails } from '~/pages/pipelines/global/GlobalPipelineCoreDetails'; import PipelineDetails from '~/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails'; -import PipelineRunDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails'; -import CreateRunPage from '~/concepts/pipelines/content/createRun/CreateRunPage'; -import CloneRunPage from '~/concepts/pipelines/content/createRun/CloneRunPage'; -import PipelineRunJobDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails'; +import { globNamespaceAll, pipelinesBaseRoute } from '~/routes'; +import PipelineVersionContextProvider from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; +import { PipelineRunType } from '~/pages/pipelines/global/runs'; +import PipelineVersionRunsTabs from '~/pages/pipelines/global/pipelines/PipelineVersionRunsTabs'; +import PipelineVersionRuns from '~/pages/pipelines/global/pipelines/PipelineVersionRuns'; +import PipelineVersionRunDetails from '~/pages/pipelines/global/pipelines/PipelineVersionRunDetails'; +import PipelineVersionRunJobDetails from '~/pages/pipelines/global/pipelines/PipelineVersionRunJobDetails'; +import PipelineAvailabilityLoader from '~/pages/pipelines/global/pipelines/PipelineAvailabilityLoader'; import { - globNamespaceAll, - globPipelineDetails, - globPipelineRunClone, - globPipelineRunCreate, - globPipelineRunDetails, - globPipelineRunJobClone, - globPipelineRunJobDetails, - globPipelineVersionRuns, - routePipelinesNamespace, -} from '~/routes'; + PipelineVersionCreateRunPage, + PipelineVersionCreateSchedulePage, +} from '~/pages/pipelines/global/pipelines/PipelineVersionCreateRunPage'; +import PipelineVersionCloneSchedulePage from '~/pages/pipelines/global/pipelines/PipelineVersionCloneSchedulePage'; +import PipelineVersionCloneRunPage from '~/pages/pipelines/global/pipelines/PipelineVersionCloneRunPage'; import GlobalPipelines from './global/pipelines/GlobalPipelines'; -import GlobalPipelineVersionRuns from './global/runs/GlobalPipelineVersionRuns'; const GlobalPipelinesRoutes: React.FC = () => ( @@ -34,85 +32,93 @@ const GlobalPipelinesRoutes: React.FC = () => ( } > } /> - }> + }> + + } + > + } /> + } + /> + } + /> + } + /> + + + + } + /> + + } + /> + + } + /> + + + + } + /> + + } + /> + + } + /> + + } /> - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } /> + {/* All the other paths fall back to the pipeline version details view */} + } /> + + - } /> ); diff --git a/frontend/src/pages/pipelines/global/GlobalPipelineCoreDetails.tsx b/frontend/src/pages/pipelines/global/GlobalPipelineCoreDetails.tsx index fe7925f8e2..e76eef2633 100644 --- a/frontend/src/pages/pipelines/global/GlobalPipelineCoreDetails.tsx +++ b/frontend/src/pages/pipelines/global/GlobalPipelineCoreDetails.tsx @@ -4,9 +4,8 @@ import { BreadcrumbItem } from '@patternfly/react-core'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; import EnsureAPIAvailability from '~/concepts/pipelines/EnsureAPIAvailability'; -import { experimentRunsRoute, experimentSchedulesRoute, experimentsBaseRoute } from '~/routes'; +import { experimentsBaseRoute, pipelinesBaseRoute } from '~/routes'; import EnsureCompatiblePipelineServer from '~/concepts/pipelines/EnsureCompatiblePipelineServer'; -import { ExperimentRunsContext } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; type GlobalPipelineCoreDetailsProps = { @@ -15,7 +14,15 @@ type GlobalPipelineCoreDetailsProps = { BreadcrumbDetailsComponent: PipelineCoreDetailsPageComponent; }; -const GlobalPipelineCoreDetails: React.FC = ({ +const GlobalPipelineCoreDetails: React.FC = (props) => ( + + + + + +); + +const GlobalPipelineCoreDetailsInner: React.FC = ({ pageName, redirectPath, BreadcrumbDetailsComponent, @@ -23,64 +30,40 @@ const GlobalPipelineCoreDetails: React.FC = ({ const { namespace, project } = usePipelinesAPI(); return ( - - - [ - ( - - {pageName} - {getDisplayNameFromK8sResource(project)} - - )} - />, - ]} - contextPath={redirectPath(namespace)} - /> - - + ( + + {pageName} - {getDisplayNameFromK8sResource(project)} + + )} + />, + ]} + contextPath={redirectPath(namespace)} + /> ); }; -export const GlobalExperimentDetails: React.FC< - Pick & { - isSchedule?: boolean; - } -> = ({ BreadcrumbDetailsComponent, isSchedule }) => { - const { experiment } = React.useContext(ExperimentRunsContext); - const experimentId = experiment?.experiment_id; - const { namespace, project } = usePipelinesAPI(); +export const PipelineVersionCoreDetails: React.FC< + Pick +> = ({ BreadcrumbDetailsComponent }) => ( + +); - return ( - - - [ - - - Experiments - {getDisplayNameFromK8sResource(project)} - - , - - {experiment?.display_name ? ( - - {experiment.display_name} - - ) : ( - 'Loading...' - )} - , - ]} - contextPath={ - isSchedule - ? experimentSchedulesRoute(namespace, experimentId) - : experimentRunsRoute(namespace, experimentId) - } - /> - - - ); -}; +export const ExperimentCoreDetails: React.FC< + Pick +> = ({ BreadcrumbDetailsComponent }) => ( + +); export default GlobalPipelineCoreDetails; diff --git a/frontend/src/pages/pipelines/global/PipelineCoreNoProjects.tsx b/frontend/src/pages/pipelines/global/PipelineCoreNoProjects.tsx index 700c8b1a51..dc78ebe0e6 100644 --- a/frontend/src/pages/pipelines/global/PipelineCoreNoProjects.tsx +++ b/frontend/src/pages/pipelines/global/PipelineCoreNoProjects.tsx @@ -9,7 +9,7 @@ import { import { WrenchIcon } from '@patternfly/react-icons/dist/esm/icons/wrench-icon'; import { useNavigate } from 'react-router-dom'; import NewProjectButton from '~/pages/projects/screens/projects/NewProjectButton'; -import { routePipelinesNamespace } from '~/routes'; +import { pipelinesBaseRoute } from '~/routes'; const PipelineCoreNoProjects: React.FC = () => { const navigate = useNavigate(); @@ -27,7 +27,7 @@ const PipelineCoreNoProjects: React.FC = () => { navigate(routePipelinesNamespace(projectName))} + onProjectCreated={(projectName) => navigate(pipelinesBaseRoute(projectName))} /> diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentCloneRunPage.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentCloneRunPage.tsx new file mode 100644 index 0000000000..e4ff55678e --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentCloneRunPage.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import { PathProps } from '~/concepts/pipelines/content/types'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; +import { experimentRunDetailsRoute, experimentRunsRoute } from '~/routes'; +import CloneRunPage from '~/concepts/pipelines/content/createRun/CloneRunPage'; + +const ExperimentCloneRunPage: React.FC = ({ breadcrumbPath }) => { + const { experiment } = React.useContext(ExperimentContext); + const { namespace } = usePipelinesAPI(); + + return ( + + {experiment ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={experimentRunsRoute(namespace, experiment?.experiment_id)} + contextExperiment={experiment} + detailsRedirect={(runId) => + experimentRunDetailsRoute(namespace, experiment?.experiment_id, runId) + } + /> + ); +}; + +export default ExperimentCloneRunPage; diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentCloneSchedulePage.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentCloneSchedulePage.tsx new file mode 100644 index 0000000000..7f842a530f --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentCloneSchedulePage.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import { PathProps } from '~/concepts/pipelines/content/types'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; +import { experimentScheduleDetailsRoute, experimentSchedulesRoute } from '~/routes'; +import CloneSchedulePage from '~/concepts/pipelines/content/createRun/CloneSchedulePage'; + +const ExperimentCloneSchedulePage: React.FC = ({ breadcrumbPath }) => { + const { experiment } = React.useContext(ExperimentContext); + const { namespace } = usePipelinesAPI(); + + return ( + + {experiment ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={experimentSchedulesRoute(namespace, experiment?.experiment_id)} + contextExperiment={experiment} + detailsRedirect={(jobId) => + experimentScheduleDetailsRoute(namespace, experiment?.experiment_id, jobId) + } + /> + ); +}; + +export default ExperimentCloneSchedulePage; diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentRunsContext.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentContext.tsx similarity index 50% rename from frontend/src/pages/pipelines/global/experiments/ExperimentRunsContext.tsx rename to frontend/src/pages/pipelines/global/experiments/ExperimentContext.tsx index 00d71c582f..f0d829f6d9 100644 --- a/frontend/src/pages/pipelines/global/experiments/ExperimentRunsContext.tsx +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentContext.tsx @@ -3,19 +3,27 @@ import { Bullseye, Spinner } from '@patternfly/react-core'; import { Outlet } from 'react-router-dom'; import { ExperimentKFv2, StorageStateKF } from '~/concepts/pipelines/kfTypes'; import { useExperimentByParams } from '~/pages/pipelines/global/experiments/useExperimentByParams'; +import { experimentRoute } from '~/routes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; -type ExperimentRunsContextState = { +type ExperimentContextState = { experiment: ExperimentKFv2 | null; + basePath: string; }; -export const ExperimentRunsContext = React.createContext({ +export const ExperimentContext = React.createContext({ experiment: null, + basePath: '', }); -const ExperimentRunsContextProvider: React.FC = () => { +const ExperimentContextProvider: React.FC = () => { const { experiment, isExperimentLoaded } = useExperimentByParams(); + const { namespace } = usePipelinesAPI(); - const contextValue = React.useMemo(() => ({ experiment }), [experiment]); + const contextValue = React.useMemo( + () => ({ experiment, basePath: experimentRoute(namespace, experiment?.experiment_id) }), + [experiment, namespace], + ); if (!isExperimentLoaded) { return ( @@ -26,15 +34,15 @@ const ExperimentRunsContextProvider: React.FC = () => { } return ( - + - + ); }; export const useContextExperimentArchived = (): boolean => { - const { experiment } = React.useContext(ExperimentRunsContext); + const { experiment } = React.useContext(ExperimentContext); return experiment?.storage_state === StorageStateKF.ARCHIVED; }; -export default ExperimentRunsContextProvider; +export default ExperimentContextProvider; diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentCreateRunPage.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentCreateRunPage.tsx new file mode 100644 index 0000000000..645284f21c --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentCreateRunPage.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import CreateRunPage from '~/concepts/pipelines/content/createRun/CreateRunPage'; +import { RunTypeOption } from '~/concepts/pipelines/content/createRun/types'; +import { PathProps, PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; +import { experimentRunsRoute, experimentSchedulesRoute } from '~/routes'; + +const ExperimentCreateRunPageInner: React.FC = ({ + breadcrumbPath, + runType, +}) => { + const { experiment } = React.useContext(ExperimentContext); + const { namespace } = usePipelinesAPI(); + + const redirectLink = + runType === RunTypeOption.SCHEDULED + ? experimentSchedulesRoute(namespace, experiment?.experiment_id) + : experimentRunsRoute(namespace, experiment?.experiment_id); + return ( + + {experiment ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={redirectLink} + runType={runType} + contextExperiment={experiment} + /> + ); +}; + +export const ExperimentCreateRunPage: PipelineCoreDetailsPageComponent = (props) => ( + +); + +export const ExperimentCreateSchedulePage: PipelineCoreDetailsPageComponent = (props) => ( + +); diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunDetails.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunDetails.tsx new file mode 100644 index 0000000000..d81b4b8448 --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunDetails.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link, useParams } from 'react-router-dom'; +import PipelineRunDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails'; +import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; +import { experimentArchivedRunsRoute, experimentRunsRoute } from '~/routes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import usePipelineRunById from '~/concepts/pipelines/apiHooks/usePipelineRunById'; +import { StorageStateKF } from '~/concepts/pipelines/kfTypes'; + +const ExperimentPipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { + const { runId } = useParams(); + const fetchedRun = usePipelineRunById(runId, true); + const { experiment } = React.useContext(ExperimentContext); + const { namespace } = usePipelinesAPI(); + const [run] = fetchedRun; + const isRunArchived = run?.storage_state === StorageStateKF.ARCHIVED; + return ( + + {experiment ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={experimentRunsRoute(namespace, experiment?.experiment_id)} + fetchedRun={fetchedRun} + /> + ); +}; + +export default ExperimentPipelineRunDetails; diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunJobDetails.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunJobDetails.tsx new file mode 100644 index 0000000000..4e0aea7e42 --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunJobDetails.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; +import { experimentSchedulesRoute } from '~/routes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import PipelineRunJobDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails'; + +const ExperimentPipelineRunJobDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { + const { experiment } = React.useContext(ExperimentContext); + const { namespace } = usePipelinesAPI(); + return ( + + {experiment ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={experimentSchedulesRoute(namespace, experiment?.experiment_id)} + /> + ); +}; + +export default ExperimentPipelineRunJobDetails; diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRuns.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRuns.tsx new file mode 100644 index 0000000000..b2c477e861 --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRuns.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { Breadcrumb, BreadcrumbItem, Label, Truncate } from '@patternfly/react-core'; +import { Outlet } from 'react-router'; +import { pipelineRunsPageTitle } from '~/pages/pipelines/global/runs/const'; +import PipelineCoreApplicationPage from '~/pages/pipelines/global/PipelineCoreApplicationPage'; +import PipelineRunVersionsContextProvider from '~/pages/pipelines/global/runs/PipelineRunVersionsContext'; +import { ProjectObjectType } from '~/concepts/design/utils'; +import TitleWithIcon from '~/concepts/design/TitleWithIcon'; +import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { + ExperimentContext, + useContextExperimentArchived, +} from '~/pages/pipelines/global/experiments/ExperimentContext'; +import { experimentsBaseRoute } from '~/routes'; + +const ExperimentPipelineRuns: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { + const { experiment } = React.useContext(ExperimentContext); + const isArchived = useContextExperimentArchived(); + + return ( + + } + description="Manage your experiment runs and schedules." + getRedirectPath={experimentsBaseRoute} + overrideChildPadding + breadcrumb={ + + {breadcrumbPath} + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + {isArchived && } + + } + > + + + + + ); +}; + +export default ExperimentPipelineRuns; diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunsTabs.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunsTabs.tsx new file mode 100644 index 0000000000..f1eaf40142 --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/ExperimentPipelineRunsTabs.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { PipelineRunType } from '~/pages/pipelines/global/runs'; +import GlobalPipelineRunsTabs from '~/pages/pipelines/global/runs/GlobalPipelineRunsTabs'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; + +type ExperimentPipelineRunsTabsProps = { + tab: PipelineRunType; +}; + +const ExperimentPipelineRunsTabs: React.FC = ({ tab }) => { + const { basePath } = React.useContext(ExperimentContext); + + return ; +}; + +export default ExperimentPipelineRunsTabs; diff --git a/frontend/src/pages/pipelines/global/experiments/ExperimentRunsListBreadcrumb.tsx b/frontend/src/pages/pipelines/global/experiments/ExperimentRunsListBreadcrumb.tsx deleted file mode 100644 index 1b99b7143d..0000000000 --- a/frontend/src/pages/pipelines/global/experiments/ExperimentRunsListBreadcrumb.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { Breadcrumb, BreadcrumbItem, Label, Truncate } from '@patternfly/react-core'; - -import { experimentsRootPath } from '~/routes'; -import { StorageStateKF } from '~/concepts/pipelines/kfTypes'; -import { ExperimentRunsContext } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; - -export const ExperimentRunsListBreadcrumb: React.FC = () => { - const { experiment } = React.useContext(ExperimentRunsContext); - - const displayName = experiment?.display_name || 'Loading...'; - - return ( - - - Experiments - - - - {/* A hack solution to get rid of the minWidth set on PF Truncate component - So we can show correct spacing between the title and the label - The min width is set to 12 characters: - https://github.com/patternfly/patternfly/blob/9499f0a70a18f51474285752a04928958d901829/src/patternfly/components/Truncate/truncate.scss#L4 */} - {displayName.length > 12 ? : <>{displayName}} - - {experiment?.storage_state === StorageStateKF.ARCHIVED && } - - ); -}; diff --git a/frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactDetails/ArtifactDetails.tsx b/frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactDetails/ArtifactDetails.tsx index f43a652aab..1ae310c802 100644 --- a/frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactDetails/ArtifactDetails.tsx +++ b/frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactDetails/ArtifactDetails.tsx @@ -61,7 +61,7 @@ export const ArtifactDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPa loadError={artifactError} breadcrumb={ - {breadcrumbPath()} + {breadcrumbPath} diff --git a/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsPage.tsx b/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsPage.tsx index 5f48db21af..4493785d41 100644 --- a/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsPage.tsx +++ b/frontend/src/pages/pipelines/global/experiments/compareRuns/CompareRunsPage.tsx @@ -1,15 +1,21 @@ import React from 'react'; -import { Breadcrumb, BreadcrumbItem, Stack, StackItem } from '@patternfly/react-core'; +import { Breadcrumb, BreadcrumbItem, Stack, StackItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; import ApplicationsPage from '~/pages/ApplicationsPage'; import { PathProps } from '~/concepts/pipelines/content/types'; import { useCompareRuns } from '~/concepts/pipelines/content/compareRuns/CompareRunsContext'; import { CompareRunsInvalidRunCount } from '~/concepts/pipelines/content/compareRuns/CompareRunInvalidRunCount'; import CompareRunsRunList from '~/concepts/pipelines/content/compareRuns/CompareRunsRunList'; -import { CompareRunParamsSection } from './CompareRunParamsSection'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { experimentRunsRoute } from '~/routes'; import { CompareRunMetricsSection } from './CompareRunsMetricsSection'; +import { CompareRunParamsSection } from './CompareRunParamsSection'; const CompareRunsPage: React.FC = ({ breadcrumbPath }) => { const { runs, loaded } = useCompareRuns(); + const { experiment } = React.useContext(ExperimentContext); + const { namespace } = usePipelinesAPI(); if (loaded && (runs.length > 10 || runs.length === 0)) { return ; @@ -21,7 +27,17 @@ const CompareRunsPage: React.FC = ({ breadcrumbPath }) => { title="" breadcrumb={ - {breadcrumbPath()} + {breadcrumbPath} + + {experiment?.display_name ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + Compare runs } diff --git a/frontend/src/pages/pipelines/global/experiments/compareRuns/ManageRunsPage.tsx b/frontend/src/pages/pipelines/global/experiments/compareRuns/ManageRunsPage.tsx index 3c508395b5..bd8a6c7d63 100644 --- a/frontend/src/pages/pipelines/global/experiments/compareRuns/ManageRunsPage.tsx +++ b/frontend/src/pages/pipelines/global/experiments/compareRuns/ManageRunsPage.tsx @@ -13,11 +13,12 @@ import { Button, Breadcrumb, BreadcrumbItem, + Truncate, } from '@patternfly/react-core'; import { ExclamationCircleIcon, PlusCircleIcon } from '@patternfly/react-icons'; import { usePipelineActiveRunsTable } from '~/concepts/pipelines/content/tables/pipelineRun/usePipelineRunTable'; -import { CompareRunsSearchParam, PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; +import { CompareRunsSearchParam } from '~/concepts/pipelines/content/types'; import { experimentRunsRoute, experimentsBaseRoute, @@ -30,9 +31,9 @@ import usePipelineFilter, { FilterOptions, } from '~/concepts/pipelines/content/tables/usePipelineFilter'; import { ExperimentKFv2 } from '~/concepts/pipelines/kfTypes'; -import { PipelineRunTabTitle, PipelineRunType } from '~/pages/pipelines/global/runs'; +import { PipelineRunTabTitle } from '~/pages/pipelines/global/runs'; import PipelineRunVersionsContextProvider from '~/pages/pipelines/global/runs/PipelineRunVersionsContext'; -import { ExperimentRunsContext } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; +import { ExperimentContext } from '~/pages/pipelines/global/experiments/ExperimentContext'; import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; import { ManageRunsTable } from './ManageRunsTable'; @@ -98,10 +99,7 @@ export const ManageRunsPageInternal: React.FC = ({ data-testid="create-run-button" variant="primary" onClick={() => - navigate({ - pathname: experimentsCreateRunRoute(namespace, experiment.experiment_id), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.ACTIVE}`, - }) + navigate(experimentsCreateRunRoute(namespace, experiment.experiment_id)) } > Create run @@ -128,7 +126,8 @@ export const ManageRunsPageInternal: React.FC = ({ {experiment.display_name ? ( - {experiment.display_name} + {/* TODO: Remove the custom className after upgrading to PFv6 */} + ) : ( 'Loading...' @@ -165,6 +164,6 @@ export const ManageRunsPageInternal: React.FC = ({ }; export const ManageRunsPage: React.FC = () => { - const { experiment } = React.useContext(ExperimentRunsContext); + const { experiment } = React.useContext(ExperimentContext); return experiment ? : null; }; diff --git a/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx b/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx index 63a3c4533b..bfef24a9c8 100644 --- a/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx +++ b/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetails.tsx @@ -19,7 +19,6 @@ import { useGetArtifactTypes } from '~/concepts/pipelines/apiHooks/mlmd/useGetAr import { useGetEventsByExecutionId } from '~/concepts/pipelines/apiHooks/mlmd/useGetEventsByExecutionId'; import { useGetExecutionById } from '~/concepts/pipelines/apiHooks/mlmd/useGetExecutionById'; import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; -import { usePipelinesAPI } from '~/concepts/pipelines/context'; import ApplicationsPage from '~/pages/ApplicationsPage'; import { inputOutputSectionTitle } from '~/pages/pipelines/global/experiments/executions/const'; import ExecutionDetailsCustomPropertiesSection from '~/pages/pipelines/global/experiments/executions/details/ExecutionDetailsCustomPropertiesSection'; @@ -32,13 +31,11 @@ import { getExecutionDisplayName, parseEventsByType, } from '~/pages/pipelines/global/experiments/executions/utils'; -import { executionsBaseRoute } from '~/routes'; import { Event } from '~/third_party/mlmd'; const ExecutionDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, contextPath }) => { const { executionId } = useParams(); const navigate = useNavigate(); - const { namespace } = usePipelinesAPI(); const [execution, executionLoaded, executionError] = useGetExecutionById(executionId); const [events, eventsLoaded, eventsError] = useGetEventsByExecutionId(Number(executionId)); const [artifactTypes, artifactTypesLoaded] = useGetArtifactTypes(); @@ -75,7 +72,7 @@ const ExecutionDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, co } if (!execution) { - navigate(contextPath ?? executionsBaseRoute(namespace)); + navigate(contextPath); return; } @@ -96,7 +93,7 @@ const ExecutionDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, co loaded breadcrumb={ - {breadcrumbPath()} + {breadcrumbPath} {displayName} } diff --git a/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetailsReferenceSection.tsx b/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetailsReferenceSection.tsx index b291eb8531..5dd103662b 100644 --- a/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetailsReferenceSection.tsx +++ b/frontend/src/pages/pipelines/global/experiments/executions/details/ExecutionDetailsReferenceSection.tsx @@ -4,8 +4,9 @@ import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { Link } from 'react-router-dom'; import { Execution } from '~/third_party/mlmd'; import { useGetPipelineRunContextByExecution } from '~/concepts/pipelines/apiHooks/mlmd/useGetMlmdContextByExecution'; -import { executionDetailsRoute, routePipelineRunDetailsNamespacePipelinesPage } from '~/routes'; +import { executionDetailsRoute, experimentRunDetailsRoute } from '~/routes'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import usePipelineRunById from '~/concepts/pipelines/apiHooks/usePipelineRunById'; type ExecutionDetailsReferenceSectionProps = { execution: Execution; @@ -15,7 +16,8 @@ const ExecutionDetailsReferenceSection: React.FC { const { namespace } = usePipelinesAPI(); - const [context, contextLoaded] = useGetPipelineRunContextByExecution(execution); + const [context] = useGetPipelineRunContextByExecution(execution); + const [run, runLoaded, runError] = usePipelineRunById(context?.getName()); const originalExecutionId = execution .getCustomPropertiesMap() @@ -39,16 +41,13 @@ const ExecutionDetailsReferenceSection: React.FC Pipeline run - {contextLoaded ? ( - context ? ( - - {`runs/details/${context.getName()}`} + {runLoaded && !runError ? ( + run ? ( + + {`runs/details/${run.run_id}`} + ) : context?.getName() ? ( + `runs/details/${context.getName()}` ) : ( 'Unknown' ) diff --git a/frontend/src/pages/pipelines/global/experiments/useExperimentByParams.ts b/frontend/src/pages/pipelines/global/experiments/useExperimentByParams.ts index e908e38332..90d5d969f5 100644 --- a/frontend/src/pages/pipelines/global/experiments/useExperimentByParams.ts +++ b/frontend/src/pages/pipelines/global/experiments/useExperimentByParams.ts @@ -14,10 +14,10 @@ export const useExperimentByParams = (): { // Redirect users to the Experiments list page when failing to retrieve the experiment from route params. React.useEffect(() => { - if (isExperimentLoaded && experimentError) { + if (experimentError) { navigate(experimentsRootPath); } - }, [experimentError, isExperimentLoaded, navigate]); + }, [experimentError, navigate]); return { experiment, isExperimentLoaded }; }; diff --git a/frontend/src/pages/pipelines/global/pipelines/GlobalPipelines.tsx b/frontend/src/pages/pipelines/global/pipelines/GlobalPipelines.tsx index c44e3ecfff..5a5c12e387 100644 --- a/frontend/src/pages/pipelines/global/pipelines/GlobalPipelines.tsx +++ b/frontend/src/pages/pipelines/global/pipelines/GlobalPipelines.tsx @@ -10,7 +10,7 @@ import PipelinesView from '~/pages/pipelines/global/pipelines/PipelinesView'; import EnsureAPIAvailability from '~/concepts/pipelines/EnsureAPIAvailability'; import PipelineAndVersionContextProvider from '~/concepts/pipelines/content/PipelineAndVersionContext'; import EnsureCompatiblePipelineServer from '~/concepts/pipelines/EnsureCompatiblePipelineServer'; -import { routePipelinesNamespace } from '~/routes'; +import { pipelinesBaseRoute } from '~/routes'; import { ProjectObjectType } from '~/concepts/design/utils'; import TitleWithIcon from '~/concepts/design/TitleWithIcon'; @@ -22,7 +22,7 @@ const GlobalPipelines: React.FC = () => { title={} description={pipelinesPageDescription} headerAction={} - getRedirectPath={routePipelinesNamespace} + getRedirectPath={pipelinesBaseRoute} > diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineAvailabilityLoader.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineAvailabilityLoader.tsx new file mode 100644 index 0000000000..e4ac711730 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineAvailabilityLoader.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Outlet } from 'react-router-dom'; +import EnsureAPIAvailability from '~/concepts/pipelines/EnsureAPIAvailability'; +import EnsureCompatiblePipelineServer from '~/concepts/pipelines/EnsureCompatiblePipelineServer'; + +const PipelineAvailabilityLoader: React.FC = () => ( + + + + + +); + +export default PipelineAvailabilityLoader; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCloneRunPage.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCloneRunPage.tsx new file mode 100644 index 0000000000..2c869d4848 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCloneRunPage.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { PathProps } from '~/concepts/pipelines/content/types'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { pipelineVersionRunDetailsRoute, pipelineVersionRunsRoute } from '~/routes'; +import CloneRunPage from '~/concepts/pipelines/content/createRun/CloneRunPage'; +import { PipelineVersionContext } from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; + +const PipelineVersionCloneRunPage: React.FC = ({ breadcrumbPath }) => { + const { pipeline, version } = React.useContext(PipelineVersionContext); + const { namespace } = usePipelinesAPI(); + + return ( + + pipelineVersionRunDetailsRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + runId, + ) + } + /> + ); +}; + +export default PipelineVersionCloneRunPage; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCloneSchedulePage.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCloneSchedulePage.tsx new file mode 100644 index 0000000000..c64f0cfb15 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCloneSchedulePage.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import { PathProps } from '~/concepts/pipelines/content/types'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { + pipelineVersionDetailsRoute, + pipelineVersionScheduleDetailsRoute, + pipelineVersionSchedulesRoute, +} from '~/routes'; +import CloneSchedulePage from '~/concepts/pipelines/content/createRun/CloneSchedulePage'; +import { PipelineVersionContext } from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; + +const PipelineVersionCloneSchedulePage: React.FC = ({ breadcrumbPath }) => { + const { pipeline, version } = React.useContext(PipelineVersionContext); + const { namespace } = usePipelinesAPI(); + + return ( + + {version ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + + {version ? ( + + Runs + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={pipelineVersionSchedulesRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + )} + contextPipeline={pipeline} + contextPipelineVersion={version} + detailsRedirect={(jobId) => + pipelineVersionScheduleDetailsRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + jobId, + ) + } + /> + ); +}; + +export default PipelineVersionCloneSchedulePage; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionContext.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionContext.tsx new file mode 100644 index 0000000000..27fa731fc4 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionContext.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Bullseye, Spinner } from '@patternfly/react-core'; +import { Outlet } from 'react-router-dom'; +import { PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; +import { usePipelineVersionByParams } from '~/pages/pipelines/global/pipelines/usePipelineVersionByParams'; +import { pipelineVersionsBaseRoute } from '~/routes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; + +type PipelineVersionContextState = { + pipeline: PipelineKFv2 | null; + version: PipelineVersionKFv2 | null; + basePath: string; +}; + +export const PipelineVersionContext = React.createContext({ + pipeline: null, + version: null, + basePath: '', +}); + +const PipelineVersionContextProvider: React.FC = () => { + const { namespace } = usePipelinesAPI(); + const { pipeline, version, isLoaded } = usePipelineVersionByParams(); + + const contextValue = React.useMemo( + () => ({ + pipeline, + version, + basePath: pipelineVersionsBaseRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + ), + }), + [namespace, pipeline, version], + ); + + if (!isLoaded) { + return ( + + + + ); + } + + return ( + + + + ); +}; + +export default PipelineVersionContextProvider; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCreateRunPage.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCreateRunPage.tsx new file mode 100644 index 0000000000..8621586ad7 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionCreateRunPage.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import CreateRunPage from '~/concepts/pipelines/content/createRun/CreateRunPage'; +import { RunTypeOption } from '~/concepts/pipelines/content/createRun/types'; +import { PathProps, PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { + pipelineVersionDetailsRoute, + pipelineVersionRunsRoute, + pipelineVersionSchedulesRoute, +} from '~/routes'; +import { PipelineVersionContext } from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; + +const PipelineVersionCreateRunPageInner: React.FC = ({ + breadcrumbPath, + runType, +}) => { + const { version, pipeline } = React.useContext(PipelineVersionContext); + const { namespace } = usePipelinesAPI(); + + const redirectLink = + runType === RunTypeOption.SCHEDULED + ? pipelineVersionSchedulesRoute(namespace, version?.pipeline_id, version?.pipeline_version_id) + : pipelineVersionRunsRoute(namespace, version?.pipeline_id, version?.pipeline_version_id); + return ( + + {version ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + + {version ? Runs : 'Loading...'} + , + ]} + contextPath={redirectLink} + runType={runType} + contextPipeline={pipeline} + contextPipelineVersion={version} + /> + ); +}; + +export const PipelineVersionCreateRunPage: PipelineCoreDetailsPageComponent = (props) => ( + +); + +export const PipelineVersionCreateSchedulePage: PipelineCoreDetailsPageComponent = (props) => ( + +); diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunDetails.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunDetails.tsx new file mode 100644 index 0000000000..1686f80e68 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunDetails.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link, useParams } from 'react-router-dom'; +import PipelineRunDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails'; +import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { + pipelineVersionArchivedRunsRoute, + pipelineVersionDetailsRoute, + pipelineVersionRunsRoute, +} from '~/routes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { PipelineVersionContext } from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; +import usePipelineRunById from '~/concepts/pipelines/apiHooks/usePipelineRunById'; +import { StorageStateKF } from '~/concepts/pipelines/kfTypes'; + +const PipelineVersionRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { + const { runId } = useParams(); + const fetchedRun = usePipelineRunById(runId, true); + const { version } = React.useContext(PipelineVersionContext); + const { namespace } = usePipelinesAPI(); + const [run] = fetchedRun; + const isRunArchived = run?.storage_state === StorageStateKF.ARCHIVED; + return ( + + {version ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + + {version ? ( + + Runs + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={pipelineVersionRunsRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + )} + fetchedRun={fetchedRun} + /> + ); +}; + +export default PipelineVersionRunDetails; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunJobDetails.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunJobDetails.tsx new file mode 100644 index 0000000000..545d467d69 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunJobDetails.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { pipelineVersionDetailsRoute, pipelineVersionSchedulesRoute } from '~/routes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { PipelineVersionContext } from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; +import PipelineRunJobDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails'; + +const PipelineVersionRunJobDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { + const { version } = React.useContext(PipelineVersionContext); + const { namespace } = usePipelinesAPI(); + return ( + + {version ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + , + + {version ? ( + + Runs + + ) : ( + 'Loading...' + )} + , + ]} + contextPath={pipelineVersionSchedulesRoute( + namespace, + version?.pipeline_id, + version?.pipeline_version_id, + )} + /> + ); +}; + +export default PipelineVersionRunJobDetails; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRuns.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRuns.tsx new file mode 100644 index 0000000000..23aa768dc9 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRuns.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { Breadcrumb, BreadcrumbItem, Truncate } from '@patternfly/react-core'; +import { Outlet, Link } from 'react-router-dom'; +import { + pipelineRunsPageDescription, + pipelineRunsPageTitle, +} from '~/pages/pipelines/global/runs/const'; +import PipelineCoreApplicationPage from '~/pages/pipelines/global/PipelineCoreApplicationPage'; +import { pipelinesBaseRoute, pipelineVersionDetailsRoute } from '~/routes'; +import { ProjectObjectType } from '~/concepts/design/utils'; +import TitleWithIcon from '~/concepts/design/TitleWithIcon'; +import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; +import { PipelineVersionContext } from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; + +const PipelineVersionRuns: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { + const { namespace } = usePipelinesAPI(); + const { version } = React.useContext(PipelineVersionContext); + + return ( + + } + description={pipelineRunsPageDescription} + getRedirectPath={pipelinesBaseRoute} + overrideChildPadding + breadcrumb={ + + {breadcrumbPath} + + {version?.display_name ? ( + + {/* TODO: Remove the custom className after upgrading to PFv6 */} + + + ) : ( + 'Loading...' + )} + + Runs + + } + > + + + ); +}; + +export default PipelineVersionRuns; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunsTabs.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunsTabs.tsx new file mode 100644 index 0000000000..9c3eb909a8 --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/PipelineVersionRunsTabs.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { PipelineRunType } from '~/pages/pipelines/global/runs'; +import GlobalPipelineRunsTabs from '~/pages/pipelines/global/runs/GlobalPipelineRunsTabs'; +import { PipelineVersionContext } from '~/pages/pipelines/global/pipelines/PipelineVersionContext'; + +type PipelineVersionRunsTabsProps = { + tab: PipelineRunType; +}; + +const PipelineVersionRunsTabs: React.FC = ({ tab }) => { + const { basePath } = React.useContext(PipelineVersionContext); + + return ; +}; + +export default PipelineVersionRunsTabs; diff --git a/frontend/src/pages/pipelines/global/pipelines/PipelinesView.tsx b/frontend/src/pages/pipelines/global/pipelines/PipelinesView.tsx index 9f6a16e772..71372ca007 100644 --- a/frontend/src/pages/pipelines/global/pipelines/PipelinesView.tsx +++ b/frontend/src/pages/pipelines/global/pipelines/PipelinesView.tsx @@ -11,7 +11,6 @@ import { getTablePagingProps, getTableSortProps, } from '~/concepts/pipelines/content/tables/usePipelineTable'; -import { routePipelineDetailsNamespace } from '~/routes'; const PipelinesView: React.FC = () => { const [ @@ -49,7 +48,6 @@ const PipelinesView: React.FC = () => { pipelines={pipelines} enablePagination="compact" refreshPipelines={refresh} - pipelineDetailsPath={routePipelineDetailsNamespace} toolbarContent={} emptyTableView={ diff --git a/frontend/src/pages/pipelines/global/pipelines/usePipelineVersionByParams.ts b/frontend/src/pages/pipelines/global/pipelines/usePipelineVersionByParams.ts new file mode 100644 index 0000000000..4c7b5d287f --- /dev/null +++ b/frontend/src/pages/pipelines/global/pipelines/usePipelineVersionByParams.ts @@ -0,0 +1,29 @@ +import React from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; +import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; +import { PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; +import { pipelinesBaseRoute } from '~/routes'; + +export const usePipelineVersionByParams = (): { + pipeline: PipelineKFv2 | null; + version: PipelineVersionKFv2 | null; + isLoaded: boolean; +} => { + const navigate = useNavigate(); + const { pipelineId, pipelineVersionId } = useParams(); + const [pipeline, isPipelineLoaded, pipelineError] = usePipelineById(pipelineId); + const [version, isVersionLoaded, versionError] = usePipelineVersionById( + pipelineId, + pipelineVersionId, + ); + + // Redirect users to the Pipeline list page when failing to retrieve the version from route params. + React.useEffect(() => { + if (pipelineError || versionError) { + navigate(pipelinesBaseRoute()); + } + }, [versionError, navigate, pipelineError]); + + return { pipeline, version, isLoaded: isPipelineLoaded && isVersionLoaded }; +}; diff --git a/frontend/src/pages/pipelines/global/runs/ActiveRuns.tsx b/frontend/src/pages/pipelines/global/runs/ActiveRuns.tsx index 1244f7a7a4..7857fcbae4 100644 --- a/frontend/src/pages/pipelines/global/runs/ActiveRuns.tsx +++ b/frontend/src/pages/pipelines/global/runs/ActiveRuns.tsx @@ -16,12 +16,9 @@ import { CubesIcon, ExclamationCircleIcon, PlusCircleIcon } from '@patternfly/re import PipelineRunTable from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable'; import { usePipelineActiveRunsTable } from '~/concepts/pipelines/content/tables/pipelineRun/usePipelineRunTable'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; import { createRunRoute } from '~/routes'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; -import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; -import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; -import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; +import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentContext'; import { PipelineRunTabTitle, PipelineRunType } from './types'; export const ActiveRuns: React.FC = () => { @@ -31,8 +28,6 @@ export const ActiveRuns: React.FC = () => { usePipelineActiveRunsTable({ experimentId, pipelineVersionId }); const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; const isExperimentArchived = useContextExperimentArchived(); - const [pipeline] = usePipelineById(pipelineId); - const [pipelineVersion] = usePipelineVersionById(pipelineId, pipelineVersionId); if (isExperimentArchived) { return ( @@ -97,14 +92,12 @@ export const ActiveRuns: React.FC = () => { variant="primary" onClick={() => navigate( - { - pathname: createRunRoute( - namespace, - isExperimentsAvailable ? experimentId : undefined, - ), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.ACTIVE}`, - }, - { state: { lastPipeline: pipeline, lastVersion: pipelineVersion } }, + createRunRoute( + namespace, + isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, + ), ) } > diff --git a/frontend/src/pages/pipelines/global/runs/CreateScheduleButton.tsx b/frontend/src/pages/pipelines/global/runs/CreateScheduleButton.tsx index 6c6d080c9b..54f09cbbb5 100644 --- a/frontend/src/pages/pipelines/global/runs/CreateScheduleButton.tsx +++ b/frontend/src/pages/pipelines/global/runs/CreateScheduleButton.tsx @@ -2,12 +2,8 @@ import React from 'react'; import { Button, Tooltip } from '@patternfly/react-core'; import { useNavigate, useParams } from 'react-router-dom'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; -import { PipelineRunType } from '~/pages/pipelines/global/runs/types'; -import { scheduleRunRoute } from '~/routes'; -import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentRunsContext'; -import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; -import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; +import { createScheduleRoute } from '~/routes'; +import { useContextExperimentArchived } from '~/pages/pipelines/global/experiments/ExperimentContext'; const CreateScheduleButton: React.FC = () => { const navigate = useNavigate(); @@ -15,8 +11,6 @@ const CreateScheduleButton: React.FC = () => { const isExperimentsAvailable = useIsAreaAvailable(SupportedArea.PIPELINE_EXPERIMENTS).status; const isExperimentArchived = useContextExperimentArchived(); const tooltipRef = React.useRef(null); - const [pipeline] = usePipelineById(pipelineId); - const [pipelineVersion] = usePipelineVersionById(pipelineId, pipelineVersionId); return ( <> @@ -31,14 +25,12 @@ const CreateScheduleButton: React.FC = () => { variant="primary" onClick={() => navigate( - { - pathname: scheduleRunRoute( - namespace, - isExperimentsAvailable ? experimentId : undefined, - ), - search: `?${PipelineRunSearchParam.RunType}=${PipelineRunType.SCHEDULED}`, - }, - { state: { lastPipeline: pipeline, lastVersion: pipelineVersion } }, + createScheduleRoute( + namespace, + isExperimentsAvailable ? experimentId : undefined, + pipelineId, + pipelineVersionId, + ), ) } isAriaDisabled={isExperimentArchived} diff --git a/frontend/src/pages/pipelines/global/runs/GlobalPipelineRuns.tsx b/frontend/src/pages/pipelines/global/runs/GlobalPipelineRuns.tsx deleted file mode 100644 index 85a2468218..0000000000 --- a/frontend/src/pages/pipelines/global/runs/GlobalPipelineRuns.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from 'react'; -import { - pipelineRunsPageDescription, - pipelineRunsPageTitle, -} from '~/pages/pipelines/global/runs/const'; -import PipelineCoreApplicationPage from '~/pages/pipelines/global/PipelineCoreApplicationPage'; -import EnsureAPIAvailability from '~/concepts/pipelines/EnsureAPIAvailability'; -import PipelineRunVersionsContextProvider from '~/pages/pipelines/global/runs/PipelineRunVersionsContext'; -import EnsureCompatiblePipelineServer from '~/concepts/pipelines/EnsureCompatiblePipelineServer'; -import { routePipelineRunsNamespace } from '~/routes'; -import { ProjectObjectType } from '~/concepts/design/utils'; -import TitleWithIcon from '~/concepts/design/TitleWithIcon'; -import GlobalPipelineRunsTabs from './GlobalPipelineRunsTabs'; - -type GlobalPipelineRunsProps = Partial< - Pick< - React.ComponentProps, - 'breadcrumb' | 'description' | 'getRedirectPath' - > ->; - -const GlobalPipelineRuns: React.FC = ({ - breadcrumb, - description = pipelineRunsPageDescription, - getRedirectPath = routePipelineRunsNamespace, -}) => ( - - } - description={description} - getRedirectPath={getRedirectPath} - overrideChildPadding - breadcrumb={breadcrumb} - > - - - - - - - - -); - -export default GlobalPipelineRuns; diff --git a/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx b/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx index 62ddbacdfd..f7593e9270 100644 --- a/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx +++ b/frontend/src/pages/pipelines/global/runs/GlobalPipelineRunsTabs.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { PageSection, Tab, Tabs, TabTitleText } from '@patternfly/react-core'; import { asEnumMember } from '~/utilities/utils'; import { @@ -9,32 +9,27 @@ import { PipelineRunType, PipelineRunTabTitle, } from '~/pages/pipelines/global/runs'; -import { PipelineRunSearchParam } from '~/concepts/pipelines/content/types'; import './GlobalPipelineRunsTabs.scss'; -const GlobalPipelineRunsTab: React.FC = () => { - const [searchParams, setSearchParams] = useSearchParams(); - const runType = asEnumMember( - searchParams.get(PipelineRunSearchParam.RunType), - PipelineRunType, - ); +type GlobalPipelineRunsTabsProps = { + basePath: string; + tab: PipelineRunType; +}; - React.useEffect(() => { - if (runType && !Object.values(PipelineRunType).includes(runType)) { - searchParams.delete(PipelineRunSearchParam.RunType); - setSearchParams(searchParams); - } - }, [runType, searchParams, setSearchParams]); +const GlobalPipelineRunsTabs: React.FC = ({ tab, basePath }) => { + const navigate = useNavigate(); return ( { const enumValue = asEnumMember(tabId, PipelineRunType); - if (enumValue !== null) { - setSearchParams({ runType: enumValue }); - } + navigate( + `${basePath}/${ + enumValue === PipelineRunType.SCHEDULED ? 'schedules' : `runs/${enumValue}` + }`, + ); }} aria-label="Pipeline run page tabs" role="region" @@ -80,4 +75,4 @@ const GlobalPipelineRunsTab: React.FC = () => { ); }; -export default GlobalPipelineRunsTab; +export default GlobalPipelineRunsTabs; diff --git a/frontend/src/pages/pipelines/global/runs/GlobalPipelineVersionRuns.tsx b/frontend/src/pages/pipelines/global/runs/GlobalPipelineVersionRuns.tsx deleted file mode 100644 index 7a756cd3fc..0000000000 --- a/frontend/src/pages/pipelines/global/runs/GlobalPipelineVersionRuns.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { Breadcrumb, BreadcrumbItem, Truncate } from '@patternfly/react-core'; -import ApplicationsPage from '~/pages/ApplicationsPage'; -import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; -import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; -import PipelineNotFound from '~/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound'; -import { routePipelineDetailsNamespace } from '~/routes'; -import { ProjectObjectType } from '~/concepts/design/utils'; -import TitleWithIcon from '~/concepts/design/TitleWithIcon'; -import GlobalPipelineRunsTab from './GlobalPipelineRunsTabs'; -import { pipelineRunsPageDescription, pipelineRunsPageTitle } from './const'; - -const GlobalPipelineVersionRuns: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { - const { namespace } = usePipelinesAPI(); - - // get pipeline and version from url - const { pipelineId, pipelineVersionId } = useParams(); - const [pipelineVersion, pipelineVersionLoaded, pipelineVersionLoadError] = usePipelineVersionById( - pipelineId, - pipelineVersionId, - ); - - if (pipelineVersionLoadError || !pipelineVersionId || !pipelineId) { - const title = 'Pipeline version not found'; - - return ( - - {breadcrumbPath()} - {title} - - } - title={title} - empty={false} - loaded - > - - - ); - } - - return ( - - {breadcrumbPath()} - - - {/* TODO: Remove the custom className after upgrading to PFv6 */} - - - - Runs - - } - title={ - - } - description={pipelineRunsPageDescription} - empty={false} - loaded={pipelineVersionLoaded} - > - - - ); -}; - -export default GlobalPipelineVersionRuns; diff --git a/frontend/src/pages/projects/ProjectViewRoutes.tsx b/frontend/src/pages/projects/ProjectViewRoutes.tsx index 494af7b340..f2624b6fe3 100644 --- a/frontend/src/pages/projects/ProjectViewRoutes.tsx +++ b/frontend/src/pages/projects/ProjectViewRoutes.tsx @@ -4,23 +4,10 @@ import ProjectModelMetricsWrapper from '~/pages/modelServing/screens/projects/Pr import ProjectServerMetricsWrapper from '~/pages/modelServing/screens/projects/ProjectServerMetricsWrapper'; import useModelMetricsEnabled from '~/pages/modelServing/useModelMetricsEnabled'; import ProjectsRoutes from '~/concepts/projects/ProjectsRoutes'; -import ProjectPipelineBreadcrumbPage from '~/pages/projects/screens/detail/pipelines/ProjectPipelineBreadcrumbPage'; -import PipelineDetails from '~/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails'; -import PipelineRunDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails'; -import CreateRunPage from '~/concepts/pipelines/content/createRun/CreateRunPage'; -import CloneRunPage from '~/concepts/pipelines/content/createRun/CloneRunPage'; -import PipelineRunJobDetails from '~/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails'; import ProjectModelMetricsConfigurationPage from '~/pages/modelServing/screens/projects/ProjectModelMetricsConfigurationPage'; import ProjectModelMetricsPage from '~/pages/modelServing/screens/projects/ProjectModelMetricsPage'; import ProjectInferenceExplainabilityWrapper from '~/pages/modelServing/screens/projects/ProjectInferenceExplainabilityWrapper'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; -import { - globPipelineDetails, - globPipelineRunClone, - globPipelineRunCreate, - globPipelineRunDetails, - globPipelineRunJobDetails, -} from '~/routes'; import ProjectDetails from './screens/detail/ProjectDetails'; import ProjectView from './screens/projects/ProjectView'; import ProjectDetailsContextProvider from './ProjectDetailsContext'; @@ -61,31 +48,6 @@ const ProjectViewRoutes: React.FC = () => { )} )} - } - /> - - } - /> - - } - /> - } - /> - } - /> - } /> } /> diff --git a/frontend/src/pages/projects/screens/detail/pipelines/PipelinesList.tsx b/frontend/src/pages/projects/screens/detail/pipelines/PipelinesList.tsx index 3f648cec39..58cd074b5f 100644 --- a/frontend/src/pages/projects/screens/detail/pipelines/PipelinesList.tsx +++ b/frontend/src/pages/projects/screens/detail/pipelines/PipelinesList.tsx @@ -8,7 +8,7 @@ import { usePipelinesAPI } from '~/concepts/pipelines/context'; import EmptyStateErrorMessage from '~/components/EmptyStateErrorMessage'; import { TABLE_CONTENT_LIMIT } from '~/concepts/pipelines/const'; import usePipelinesTable from '~/concepts/pipelines/content/tables/pipeline/usePipelinesTable'; -import { routePipelinesNamespace, routeProjectPipelineDetailsNamespace } from '~/routes'; +import { pipelinesBaseRoute } from '~/routes'; import NoPipelineServer from '~/concepts/pipelines/NoPipelineServer'; type PipelinesListProps = { @@ -56,7 +56,6 @@ const PipelinesList: React.FC = ({ setIsPipelinesEmpty }) => loading={!loaded} pipelines={pipelines} aria-label="pipelines table" - pipelineDetailsPath={routeProjectPipelineDetailsNamespace} refreshPipelines={refresh} variant={TableVariant.compact} /> @@ -64,7 +63,7 @@ const PipelinesList: React.FC = ({ setIsPipelinesEmpty }) => {totalSize > TABLE_CONTENT_LIMIT && ( - diff --git a/frontend/src/pages/projects/screens/detail/pipelines/ProjectPipelineBreadcrumbPage.tsx b/frontend/src/pages/projects/screens/detail/pipelines/ProjectPipelineBreadcrumbPage.tsx index 4f51b1606d..86057f3a3b 100644 --- a/frontend/src/pages/projects/screens/detail/pipelines/ProjectPipelineBreadcrumbPage.tsx +++ b/frontend/src/pages/projects/screens/detail/pipelines/ProjectPipelineBreadcrumbPage.tsx @@ -19,7 +19,7 @@ const ProjectPipelineBreadcrumbPage: React.FC = ({ return ( [ + breadcrumbPath={[ Data Science Projects} diff --git a/frontend/src/routes/pipelines/experiments.ts b/frontend/src/routes/pipelines/experiments.ts index 56c8566320..5224b0f999 100644 --- a/frontend/src/routes/pipelines/experiments.ts +++ b/frontend/src/routes/pipelines/experiments.ts @@ -1,9 +1,7 @@ -import { PipelineRunType } from '~/pages/pipelines/global/runs'; - export const experimentsRootPath = '/experiments'; export const globExperimentsAll = `${experimentsRootPath}/*`; -export const experimentsBaseRoute = (namespace: string | undefined): string => +export const experimentsBaseRoute = (namespace?: string): string => !namespace ? experimentsRootPath : `${experimentsRootPath}/${namespace}`; export const experimentsTabRoute = ( @@ -16,7 +14,7 @@ export const experimentsCreateRunRoute = ( experimentId: string, ): string => `${experimentRunsRoute(namespace, experimentId)}/create`; -export const experimentsScheduleRunRoute = ( +export const experimentsCreateScheduleRoute = ( namespace: string | undefined, experimentId: string, ): string => `${experimentSchedulesRoute(namespace, experimentId)}/create`; @@ -33,16 +31,23 @@ export const experimentsCloneScheduleRoute = ( recurringRunId: string, ): string => `${experimentSchedulesRoute(namespace, experimentId)}/clone/${recurringRunId}`; -export const experimentRunsRoute = ( +export const experimentRoute = ( namespace: string | undefined, experimentId: string | undefined, - runType?: PipelineRunType | null, ): string => !experimentId ? experimentsBaseRoute(namespace) - : `${experimentsBaseRoute(namespace)}/${experimentId}/runs${ - runType ? `?runType=${runType}` : '' - }`; + : `${experimentsBaseRoute(namespace)}/${experimentId}`; + +export const experimentRunsRoute = ( + namespace: string | undefined, + experimentId: string | undefined, +): string => `${experimentRoute(namespace, experimentId)}/runs`; + +export const experimentArchivedRunsRoute = ( + namespace: string | undefined, + experimentId: string | undefined, +): string => `${experimentRunsRoute(namespace, experimentId)}/archived`; export const experimentSchedulesRoute = ( namespace: string | undefined, diff --git a/frontend/src/routes/pipelines/global.ts b/frontend/src/routes/pipelines/global.ts index 32fa3276b2..5621694f86 100644 --- a/frontend/src/routes/pipelines/global.ts +++ b/frontend/src/routes/pipelines/global.ts @@ -1,92 +1,99 @@ -import { PipelineRunType } from '~/pages/pipelines/global/runs'; - const globNamespace = ':namespace'; export const globNamespaceAll = `/${globNamespace}?/*`; +export const pipelinesRootPath = '/pipelines'; const globPipelineId = ':pipelineId'; const globPipelineVersionId = ':pipelineVersionId'; -const globPipelineRunId = ':runId'; -const globPipelineRunJobId = ':recurringRunId'; // pipelines and versions -const globPipeline = 'pipeline'; -const globPipelines = `${globPipeline}s`; export const routePipelineDetails = (pipelineId: string, versionId: string): string => - `${globPipeline}/view/${pipelineId}/${versionId}`; + `${pipelineId}/${versionId}/view`; export const routePipelineVersionRuns = (pipelineId: string, versionId: string): string => - `${globPipeline}/runs/${pipelineId}/${versionId}`; -export const routePipelines = (): string => `/${globPipelines}`; -export const globPipelinesAll = `${routePipelines()}/*`; -export const globPipelineDetails = routePipelineDetails(globPipelineId, globPipelineVersionId); -export const globPipelineVersionRuns = routePipelineVersionRuns( + `${pipelineId}/${versionId}/runs`; +export const globPipelinesAll = `${pipelinesRootPath}/*`; +export const globPipelineDetails = `pipelines/${routePipelineDetails( globPipelineId, globPipelineVersionId, -); -export const routePipelinesNamespace = (namespace?: string): string => - namespace ? `/${globPipelines}/${namespace}` : routePipelines(); -export const routePipelineDetailsNamespace = ( - namespace: string, - pipelineId: string, - versionId: string, -): string => `${routePipelinesNamespace(namespace)}/${routePipelineDetails(pipelineId, versionId)}`; -export const routePipelineVersionRunsNamespace = ( - namespace: string, - pipelineId: string, - versionId: string, - runType?: PipelineRunType, +)}`; + +export const pipelinesBaseRoute = (namespace?: string): string => + !namespace ? pipelinesRootPath : `${pipelinesRootPath}/${namespace}`; + +export const pipelineVersionsBaseRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, ): string => - `${routePipelinesNamespace(namespace)}/${routePipelineVersionRuns(pipelineId, versionId)}${ - runType ? `?runType=${runType}` : '' - }`; -export const routePipelineRunCreateNamespacePipelinesPage = (namespace?: string): string => - `${routePipelinesNamespace(namespace)}/${globPipelineRunCreate}`; -export const routePipelineRunCloneNamespacePipelinesPage = ( - namespace: string, + !pipelineId || !pipelineVersionId + ? pipelinesBaseRoute(namespace) + : `${pipelinesBaseRoute(namespace)}/${pipelineId}/${pipelineVersionId}`; + +export const pipelineVersionRunsRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, +): string => `${pipelineVersionsBaseRoute(namespace, pipelineId, pipelineVersionId)}/runs`; + +export const pipelineVersionArchivedRunsRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, +): string => `${pipelineVersionRunsRoute(namespace, pipelineId, pipelineVersionId)}/archived`; + +export const pipelineVersionSchedulesRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, +): string => `${pipelineVersionsBaseRoute(namespace, pipelineId, pipelineVersionId)}/schedules`; + +export const pipelineVersionDetailsRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, +): string => `${pipelineVersionsBaseRoute(namespace, pipelineId, pipelineVersionId)}/view`; + +export const pipelineVersionCreateRunRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId?: string, +): string => `${pipelineVersionRunsRoute(namespace, pipelineId, pipelineVersionId)}/create`; + +export const pipelineVersionCreateScheduleRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId?: string, +): string => `${pipelineVersionSchedulesRoute(namespace, pipelineId, pipelineVersionId)}/create`; + +export const pipelineVersionCloneRunRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, runId: string, -): string => `${routePipelinesNamespace(namespace)}/${routePipelineRunClone(runId)}`; -export const routePipelineRunJobCloneNamespacePipelinesPage = ( - namespace: string, - jobId: string, -): string => `${routePipelinesNamespace(namespace)}/${routePipelineRunJobClone(jobId)}`; -export const routePipelineRunDetailsNamespacePipelinesPage = ( +): string => `${pipelineVersionRunsRoute(namespace, pipelineId, pipelineVersionId)}/clone/${runId}`; + +export const pipelineVersionCloneScheduleRoute = ( + namespace: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, + recurringRunId: string, +): string => + `${pipelineVersionSchedulesRoute( + namespace, + pipelineId, + pipelineVersionId, + )}/clone/${recurringRunId}`; + +export const pipelineVersionRunDetailsRoute = ( namespace: string, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, runId: string, -): string => `${routePipelinesNamespace(namespace)}/${routePipelineRunDetails(runId)}`; -export const routePipelineRunJobDetailsNamespacePipelinesPage = ( - namespace: string, - jobId: string, -): string => `${routePipelinesNamespace(namespace)}/${routePipelineRunJobDetails(jobId)}`; - -// pipeline runs -const globPipelineRun = 'pipelineRun'; -const globPipelineRuns = `${globPipelineRun}s`; -export const routePipelineRuns = (): string => `/${globPipelineRuns}`; -export const globPipelineRunsAll = `${routePipelineRuns()}/*`; -export const routePipelineRunDetails = (runId: string): string => - `${globPipelineRun}/view/${runId}`; -export const routePipelineRunsNamespace = (namespace?: string): string => - namespace ? `${routePipelineRuns()}/${namespace}` : routePipelineRuns(); -export const globPipelineRunDetails = routePipelineRunDetails(globPipelineRunId); -export const routePipelineRunDetailsNamespace = (namespace: string, runId: string): string => - `${routePipelineRunsNamespace(namespace)}/${routePipelineRunDetails(runId)}`; -export const globPipelineRunCreate = `${globPipelineRun}/create`; -export const routePipelineRunCreateNamespace = (namespace?: string): string => - namespace - ? `${routePipelineRunsNamespace(namespace)}/${globPipelineRunCreate}` - : routePipelineRunsNamespace(namespace); -const routePipelineRunClone = (runId: string): string => `${globPipelineRun}/clone/${runId}`; -export const globPipelineRunClone = routePipelineRunClone(globPipelineRunId); -export const routePipelineRunCloneNamespace = (namespace: string, runId: string): string => - `${routePipelineRunsNamespace(namespace)}/${routePipelineRunClone(runId)}`; +): string => `${pipelineVersionRunsRoute(namespace, pipelineId, pipelineVersionId)}/${runId}`; -// pipeline run jobs -const globPipelineRunJob = 'pipelineRunJob'; -export const routePipelineRunJobDetails = (jobId: string): string => - `${globPipelineRunJob}/view/${jobId}`; -export const globPipelineRunJobDetails = routePipelineRunJobDetails(globPipelineRunJobId); -export const routePipelineRunJobDetailsNamespace = (namespace: string, jobId: string): string => - `${routePipelineRunsNamespace(namespace)}/${routePipelineRunJobDetails(jobId)}`; -const routePipelineRunJobClone = (jobId: string): string => `${globPipelineRun}/cloneJob/${jobId}`; -export const globPipelineRunJobClone = routePipelineRunJobClone(globPipelineRunJobId); -export const routePipelineRunJobCloneNamespace = (namespace: string, jobId: string): string => - `${routePipelineRunsNamespace(namespace)}/${routePipelineRunJobClone(jobId)}`; +export const pipelineVersionScheduleDetailsRoute = ( + namespace: string, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, + recurringRunId: string, +): string => + `${pipelineVersionSchedulesRoute(namespace, pipelineId, pipelineVersionId)}/${recurringRunId}`; diff --git a/frontend/src/routes/pipelines/index.ts b/frontend/src/routes/pipelines/index.ts index 67212914a0..0c7dc24a0c 100644 --- a/frontend/src/routes/pipelines/index.ts +++ b/frontend/src/routes/pipelines/index.ts @@ -1,5 +1,4 @@ export * from './global'; -export * from './project'; export * from './experiments'; export * from './artifacts'; export * from './runs'; diff --git a/frontend/src/routes/pipelines/project.ts b/frontend/src/routes/pipelines/project.ts deleted file mode 100644 index 7c3f8054bc..0000000000 --- a/frontend/src/routes/pipelines/project.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { routePipelineDetails } from '~/routes/pipelines/global'; -import { routeProjectsNamespace } from '~/routes/projects'; - -export const routeProjectPipelineDetailsNamespace = ( - namespace: string, - pipelineId: string, - versionId: string, -): string => `${routeProjectsNamespace(namespace)}/${routePipelineDetails(pipelineId, versionId)}`; diff --git a/frontend/src/routes/pipelines/runs.ts b/frontend/src/routes/pipelines/runs.ts index d86f555e1a..f57ff1ed45 100644 --- a/frontend/src/routes/pipelines/runs.ts +++ b/frontend/src/routes/pipelines/runs.ts @@ -1,9 +1,10 @@ import { - routePipelineRunCloneNamespacePipelinesPage, - routePipelineRunCreateNamespacePipelinesPage, - routePipelineRunDetailsNamespacePipelinesPage, - routePipelineRunJobCloneNamespacePipelinesPage, - routePipelineRunJobDetailsNamespacePipelinesPage, + pipelineVersionCloneRunRoute, + pipelineVersionCloneScheduleRoute, + pipelineVersionCreateRunRoute, + pipelineVersionCreateScheduleRoute, + pipelineVersionRunDetailsRoute, + pipelineVersionScheduleDetailsRoute, } from './global'; import { experimentRunDetailsRoute, @@ -11,57 +12,69 @@ import { experimentsCloneRunRoute, experimentsCloneScheduleRoute, experimentsCreateRunRoute, - experimentsScheduleRunRoute, + experimentsCreateScheduleRoute, } from './experiments'; export const cloneScheduleRoute = ( namespace: string, recurringRunId: string, experimentId: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, ): string => experimentId ? experimentsCloneScheduleRoute(namespace, experimentId, recurringRunId) - : routePipelineRunJobCloneNamespacePipelinesPage(namespace, recurringRunId); + : pipelineVersionCloneScheduleRoute(namespace, pipelineId, pipelineVersionId, recurringRunId); -export const scheduleRunRoute = ( +export const createScheduleRoute = ( namespace: string | undefined, experimentId: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, ): string => experimentId - ? experimentsScheduleRunRoute(namespace, experimentId) - : routePipelineRunCreateNamespacePipelinesPage(namespace); + ? experimentsCreateScheduleRoute(namespace, experimentId) + : pipelineVersionCreateScheduleRoute(namespace, pipelineId, pipelineVersionId); export const createRunRoute = ( namespace: string | undefined, experimentId: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, ): string => experimentId ? experimentsCreateRunRoute(namespace, experimentId) - : routePipelineRunCreateNamespacePipelinesPage(namespace); + : pipelineVersionCreateRunRoute(namespace, pipelineId, pipelineVersionId); export const scheduleDetailsRoute = ( namespace: string, recurringRunId: string, experimentId: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, ): string => experimentId ? experimentScheduleDetailsRoute(namespace, experimentId, recurringRunId) - : routePipelineRunJobDetailsNamespacePipelinesPage(namespace, recurringRunId); + : pipelineVersionScheduleDetailsRoute(namespace, pipelineId, pipelineVersionId, recurringRunId); export const runDetailsRoute = ( namespace: string, runId: string, experimentId: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, ): string => experimentId ? experimentRunDetailsRoute(namespace, experimentId, runId) - : routePipelineRunDetailsNamespacePipelinesPage(namespace, runId); + : pipelineVersionRunDetailsRoute(namespace, pipelineId, pipelineVersionId, runId); export const cloneRunRoute = ( namespace: string, runId: string, experimentId: string | undefined, + pipelineId: string | undefined, + pipelineVersionId: string | undefined, ): string => experimentId ? experimentsCloneRunRoute(namespace, experimentId, runId) - : routePipelineRunCloneNamespacePipelinesPage(namespace, runId); + : pipelineVersionCloneRunRoute(namespace, pipelineId, pipelineVersionId, runId); diff --git a/frontend/src/utilities/NavData.tsx b/frontend/src/utilities/NavData.tsx index d8522e0b59..c26ea93ee8 100644 --- a/frontend/src/utilities/NavData.tsx +++ b/frontend/src/utilities/NavData.tsx @@ -5,8 +5,7 @@ import { artifactsRootPath, executionsRootPath, experimentsRootPath, - routePipelineRuns, - routePipelines, + pipelinesRootPath, } from '~/routes'; type NavDataCommon = { @@ -67,18 +66,7 @@ const useDSPipelinesNav = (): NavDataItem[] => { } return [ - ...(isExperimentsAvailable - ? [{ id: 'pipelines', label: 'Data Science Pipelines', href: routePipelines() }] - : [ - { - id: 'pipelines', - group: { id: 'pipelines', title: 'Data Science Pipelines' }, - children: [ - { id: 'global-pipelines', label: 'Pipelines', href: routePipelines() }, - { id: 'global-pipeline-runs', label: 'Runs', href: routePipelineRuns() }, - ], - }, - ]), + { id: 'pipelines', label: 'Data Science Pipelines', href: pipelinesRootPath }, ...(isExperimentsAvailable ? [ {