diff --git a/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts b/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts index cfe9e26f1c..83d58ce10d 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts @@ -77,10 +77,20 @@ class PipelineRunRightDrawer extends Contextual { } class RunDetails extends PipelinesTopology { - findBottomDrawer() { - return new PipelineRunBottomDrawer(() => - cy.findByTestId('pipeline-run-drawer-bottom').parent(), - ); + findGraphTab() { + return cy.findByTestId('pipeline-run-tab-graph'); + } + + findDetailsTab() { + return cy.findByTestId('pipeline-run-tab-details'); + } + + findPipelineSpecTab() { + return cy.findByTestId('pipeline-run-tab-spec'); + } + + findDetailItem(key: string) { + return new DetailsItem(() => cy.findByTestId(`detail-item-${key}`)); } findRightDrawer() { @@ -247,24 +257,6 @@ class PipelineRunDetails extends RunDetails { } } -class PipelineRunBottomDrawer extends Contextual { - findBottomDrawerDetailsTab() { - return this.find().findByTestId('bottom-drawer-tab-details'); - } - - findBottomDrawerYamlTab() { - return this.find().findByTestId('bottom-drawer-tab-run-output'); - } - - findBottomDrawerInputTab() { - return this.find().findByTestId('bottom-drawer-tab-input-parameters'); - } - - findBottomDrawerDetailItem(key: string) { - return new DetailsItem(() => this.find().findByTestId(`detail-item-${key}`)); - } -} - export const pipelineDetails = new PipelineDetails(); export const pipelineRunDetails = new PipelineRunDetails(); export const pipelineRunJobDetails = new PipelineRunJobDetails(); 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 cd7c324691..67f017d1ad 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 @@ -341,85 +341,45 @@ describe('Pipeline topology', () => { cy.wait('@deletepipelineRunJob'); }); - it('Test pipeline job bottom drawer project navigation', () => { + it('Test pipeline job details project navigation', () => { pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); - pipelineRunJobDetails.findBottomDrawer().findBottomDrawerDetailsTab().click(); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Project') - .findValue() - .find('a') - .click(); + pipelineRunJobDetails.findDetailsTab().click(); + pipelineRunJobDetails.findDetailItem('Project').findValue().find('a').click(); verifyRelativeURL(`/projects/${projectId}?section=overview`); }); - it('Test pipeline job bottom drawer pipeline version navigation', () => { + it('Test pipeline job details pipeline version navigation', () => { pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); - pipelineRunJobDetails.findBottomDrawer().findBottomDrawerDetailsTab().click(); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Pipeline version') - .findValue() - .find('a') - .click(); + 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}`, ); }); }); - it('Test pipeline job bottom drawer details', () => { + it('Test pipeline job tab details', () => { initIntercepts(); pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); - pipelineRunJobDetails.findBottomDrawer().findBottomDrawerDetailsTab().click(); + pipelineRunJobDetails.findDetailsTab().click(); + pipelineRunJobDetails.findDetailItem('Name').findValue().contains(mockJob.display_name); + pipelineRunJobDetails.findDetailItem('Project').findValue().contains('Test Project'); + pipelineRunJobDetails.findDetailItem('Run ID').findValue().contains(mockJob.display_name); pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Name') + .findDetailItem('Pipeline version') .findValue() - .contains(mockJob.display_name); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Project') - .findValue() - .contains('Test Project'); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Run ID') - .findValue() - .contains(mockJob.display_name); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Workflow name') - .findValue() - .contains('test-pipeline'); + .contains('test-version-name'); + pipelineRunJobDetails.findDetailItem('Pipeline').findValue().contains('test-pipeline'); + pipelineRunJobDetails.findDetailItem('Workflow name').findValue().contains('test-pipeline'); + pipelineRunJobDetails.findDetailItem('Created').findValue().contains('February 8, 2024'); + pipelineRunJobDetails.findDetailItem('Run trigger enabled').findValue().contains('Yes'); + pipelineRunJobDetails.findDetailItem('Trigger').findValue().contains('Every 1 minute'); }); - it('Test pipeline job bottom drawer parameters', () => { - initIntercepts(); - - pipelineRunJobDetails.visit(projectId, mockJob.recurring_run_id); - - pipelineRunJobDetails.findBottomDrawer().findBottomDrawerInputTab().click(); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('min_max_scaler') - .findValue() - .contains('False'); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('neighbors') - .findValue() - .contains('0'); - pipelineRunJobDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('standard_scaler') - .findValue() - .contains('yes'); - }); it('Ensure that clicking on a node will open a right-side drawer', () => { initIntercepts(); @@ -438,63 +398,32 @@ describe('Pipeline topology', () => { taskDrawer.find().should('not.exist'); }); - it('Test pipeline triggered run bottom drawer details', () => { + it('Test pipeline triggered run tab details', () => { initIntercepts(); pipelineRunDetails.visit(projectId, mockRun.run_id); - pipelineRunJobDetails.findBottomDrawer().findBottomDrawerYamlTab(); - pipelineRunDetails.findBottomDrawer().findBottomDrawerDetailsTab().click(); - pipelineRunDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Name') - .findValue() - .contains(mockJob.display_name); - pipelineRunDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Project') - .findValue() - .contains('Test Project'); - pipelineRunDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Run ID') - .findValue() - .contains(mockJob.display_name); - pipelineRunDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('Workflow name') - .findValue() - .contains('test-pipeline'); - }); - - it('Test pipeline triggered run bottom drawer parameters', () => { - initIntercepts(); - - pipelineRunDetails.visit(projectId, mockRun.run_id); - - pipelineRunDetails.findBottomDrawer().findBottomDrawerInputTab().click(); - pipelineRunDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('min_max_scaler') - .findValue() - .contains('False'); - pipelineRunDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('neighbors') - .findValue() - .contains('1'); + pipelineRunJobDetails.findPipelineSpecTab(); + pipelineRunDetails.findDetailsTab().click(); + pipelineRunDetails.findDetailItem('Name').findValue().contains(mockJob.display_name); pipelineRunDetails - .findBottomDrawer() - .findBottomDrawerDetailItem('standard_scaler') + .findDetailItem('Pipeline version') .findValue() - .contains('False'); + .contains('test-version-name'); + pipelineRunDetails.findDetailItem('Pipeline').findValue().contains('test-pipeline'); + pipelineRunDetails.findDetailItem('Project').findValue().contains('Test Project'); + pipelineRunDetails.findDetailItem('Run ID').findValue().contains(mockJob.display_name); + pipelineRunDetails.findDetailItem('Workflow name').findValue().contains('test-pipeline'); + pipelineRunDetails.findDetailItem('Started').findValue().contains('March 15, 2024'); + pipelineRunDetails.findDetailItem('Finished').findValue().contains('March 15, 2024'); + pipelineRunDetails.findDetailItem('Duration').findValue().contains('0:50'); }); - it('Test pipeline triggered run bottom drawer output', () => { + it('Test pipeline triggered run YAML output', () => { initIntercepts(); pipelineRunDetails.visit(projectId, mockRun.run_id); - pipelineRunDetails.findBottomDrawer().findBottomDrawerYamlTab().click(); + pipelineRunDetails.findPipelineSpecTab().click(); pipelineRunDetails.findYamlOutput().click(); const pipelineDashboardCodeEditor = pipelineDetails.getPipelineDashboardCodeEditor(); pipelineDashboardCodeEditor.findInput().should('not.be.empty'); diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx index 67f92aa08d..78bf3552b5 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx @@ -10,6 +10,7 @@ import { FlexItem, Tab, TabContent, + TabContentBody, Tabs, TabTitleText, Truncate, @@ -30,9 +31,11 @@ import { getCorePipelineSpec } from '~/concepts/pipelines/getCorePipelineSpec'; import PipelineDetailsActions from './PipelineDetailsActions'; import SelectedTaskDrawerContent from './SelectedTaskDrawerContent'; import PipelineNotFound from './PipelineNotFound'; +import { PipelineSummaryDescriptionList } from './PipelineSummaryDescriptionList'; enum PipelineDetailsTab { GRAPH, + SUMMARY, YAML, } @@ -163,9 +166,20 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = aria-label="Pipeline Graph Tab" tabContentId={`tabContent-${PipelineDetailsTab.GRAPH}`} /> + + Summary} + aria-label="Pipeline Summary Tab" + > + + + + + YAML} + title={Pipeline spec} data-testid="pipeline-yaml-tab" aria-label="Pipeline YAML Tab" tabContentId={`tabContent-${PipelineDetailsTab.YAML}`} @@ -202,15 +216,17 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = eventKey={PipelineDetailsTab.YAML} activeKey={activeTabKey} hidden={PipelineDetailsTab.YAML !== activeTabKey} - style={{ height: '100%' }} + className="pf-v5-u-h-100" > - + + + diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineSummaryDescriptionList.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineSummaryDescriptionList.tsx new file mode 100644 index 0000000000..b20fc14190 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineSummaryDescriptionList.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { Truncate } from '@patternfly/react-core'; + +import { PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; +import { + asTimestamp, + DetailItem, + renderDetailItems, +} from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/utils'; + +type PipelineSummaryDescriptionListProps = { + pipeline: PipelineKFv2 | null; + version: PipelineVersionKFv2 | null; +}; + +export const PipelineSummaryDescriptionList: React.FC = ({ + pipeline, + version, +}) => { + if (!pipeline) { + return null; + } + + const details: DetailItem[] = [ + { key: 'Pipeline ID', value: }, + ...(version + ? [ + { + key: 'Version ID', + value: version.pipeline_version_id, + }, + ] + : []), + ...(version?.code_source_url + ? [ + { + key: 'Version source', + value: {version.code_source_url}, + }, + ] + : []), + { key: 'Uploaded on', value: asTimestamp(new Date(pipeline.created_at)) }, + ...(pipeline.description ? [{ key: 'Pipeline description', value: pipeline.description }] : []), + ]; + + return renderDetailItems(details); +}; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx index 0f7e1a4ab8..178aaa9246 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx @@ -4,7 +4,6 @@ import { BreadcrumbItem, Drawer, DrawerContent, - DrawerContentBody, EmptyState, EmptyStateIcon, EmptyStateVariant, @@ -20,18 +19,12 @@ 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 PipelineRunDrawerBottomContent from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomContent'; import PipelineRunDetailsActions from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions'; import PipelineRunDrawerRightContent from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightContent'; -import { - RunDetailsTabs, - RunDetailsTabSelection, -} from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomTabs'; 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 { PipelineTopology, PipelineTopologyEmpty } from '~/concepts/topology'; import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; import { usePipelineTaskTopology } from '~/concepts/pipelines/topology'; import { PipelineRunType } from '~/pages/pipelines/global/runs/types'; @@ -39,7 +32,9 @@ import { routePipelineRunsNamespace } 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 { usePipelineRunArtifacts } from './artifacts'; +import { PipelineRunDetailsTabs } from './PipelineRunDetailsTabs'; const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, contextPath }) => { const { runId } = useParams(); @@ -53,10 +48,6 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, const pipelineSpec = version?.pipeline_spec ?? run?.pipeline_spec; const [deleting, setDeleting] = React.useState(false); const [archiving, setArchiving] = React.useState(false); - - const [detailsTab, setDetailsTab] = React.useState( - RunDetailsTabs.DETAILS, - ); const [selectedId, setSelectedId] = React.useState(null); const [executions, executionsLoaded, executionsError] = useExecutionsForPipelineRun(run); @@ -77,8 +68,6 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, [selectedId, nodes], ); - const getFirstNode = (firstId: string) => nodes.find((n) => n.id === firstId); - const loaded = runLoaded && (versionLoaded || !!run?.pipeline_spec); const error = versionError || runError; @@ -116,83 +105,62 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, /> } > - - - { - setDetailsTab(selection); + + ) : ( + 'Error loading run' + ) + } + subtext={ + run && ( + + ) + } + description={ + run?.description ? : '' + } + loaded={loaded} + loadError={error} + breadcrumb={ + + {breadcrumbPath} + + + + + } + headerAction={ + setDeleting(true)} + onArchive={() => setArchiving(true)} + /> + } + empty={false} + > + { + const firstId = ids[0]; + if (ids.length === 0) { setSelectedId(null); - }} - pipelineRunDetails={run && pipelineSpec ? run : undefined} - /> - } - > - - ) : ( - 'Error loading run' - ) - } - subtext={ - run && ( - - ) - } - description={ - run?.description ? ( - - ) : ( - '' - ) - } - loaded={loaded} - loadError={error} - breadcrumb={ - - {breadcrumbPath} - - - - - } - headerAction={ - setDeleting(true)} - onArchive={() => setArchiving(true)} - /> - } - empty={false} - > - {nodes.length === 0 ? ( - - ) : ( - { - const firstId = ids[0]; - if (ids.length === 0) { - setSelectedId(null); - } else if (getFirstNode(firstId)) { - setDetailsTab(null); - setSelectedId(firstId); - } - }} - /> - )} - - - - + } else if (nodes.find((node) => node.id === firstId)) { + setSelectedId(firstId); + } + }} + /> + } + /> + = ({ + run, + graphContent, +}) => { + const [activeKey, setActiveKey] = React.useState(DetailsTabKey.Graph); + const isJob = run && isPipelineRunJob(run); + + return ( + <> + setActiveKey(eventKey)} + aria-label="Pipeline run details tabs" + > + Graph} + aria-label="Run graph tab" + data-testid="pipeline-run-tab-graph" + /> + + Details} + aria-label="Run details tab" + data-testid="pipeline-run-tab-details" + > + + + + + + {!isJob && ( + Pipeline spec} + aria-label="Run spec tab" + data-testid="pipeline-run-tab-spec" + /> + )} + + + + + + + ); +}; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomContent.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomContent.tsx deleted file mode 100644 index b8e4142899..0000000000 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomContent.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { DrawerPanelContent } from '@patternfly/react-core'; -import PipelineRunDrawerBottomTabs, { - RunDetailsTabSelection, -} from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomTabs'; - -type PipelineRunBottomDrawerContentProps = { - detailsTab: RunDetailsTabSelection; - onSelectionChange: (newSelection: RunDetailsTabSelection) => void; -} & Omit, 'selection' | 'onSelection'>; - -const CLOSED_MIN_SIZE = '40px'; -const OPEN_MIN_SIZE_THRESHOLD = 115; -const OPEN_MIN_SIZE = `${OPEN_MIN_SIZE_THRESHOLD - 15}px`; - -const PipelineRunDrawerBottomContent: React.FC = ({ - detailsTab, - onSelectionChange, - ...drawerProps -}) => ( - { - if (size < OPEN_MIN_SIZE_THRESHOLD) { - onSelectionChange(null); - } - }} - > - onSelectionChange(tabId)} - /> - -); - -export default PipelineRunDrawerBottomContent; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomTabs.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomTabs.tsx deleted file mode 100644 index 6259f9fdf0..0000000000 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomTabs.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import * as React from 'react'; -import { Tabs, Tab, TabContent, DrawerPanelBody } from '@patternfly/react-core'; -import PipelineDetailsYAML from '~/concepts/pipelines/content/pipelinesDetails/PipelineDetailsYAML'; -import { PipelineRunJobKFv2, PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes'; -import { isPipelineRunJob } from '~/concepts/pipelines/content/utils'; -import PipelineRunTabDetails from './PipelineRunTabDetails'; -import PipelineRunTabParameters from './PipelineRunTabParameters'; - -export enum RunDetailsTabs { - DETAILS = 'details', - PARAMETERS = 'input-parameters', - YAML = 'run-output', -} - -const RunDetailsTabTitles = { - [RunDetailsTabs.DETAILS]: 'Details', - [RunDetailsTabs.PARAMETERS]: 'Input parameters', - [RunDetailsTabs.YAML]: 'Run output', -}; - -export type RunDetailsTabSelection = RunDetailsTabs | null; - -type PipelineRunBottomDrawerProps = { - selection: RunDetailsTabSelection; - onSelection: (id: RunDetailsTabs) => void; - pipelineRunDetails?: PipelineRunKFv2 | PipelineRunJobKFv2; -}; - -const PipelineRunDrawerBottomTabs: React.FC = ({ - selection, - onSelection, - pipelineRunDetails, -}) => { - const isJob = pipelineRunDetails && isPipelineRunJob(pipelineRunDetails); - - return ( - <> - - {Object.values(RunDetailsTabs) - .filter((key) => (isJob ? key !== RunDetailsTabs.YAML : true)) // do not include yaml tab for jobs - .map((tab) => ( - onSelection(tab)} - /> - ))} - - {selection && ( - - - - {!isJob && ( // do not include yaml tab for jobs - - )} - - )} - - ); -}; - -export default PipelineRunDrawerBottomTabs; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx index c0acf78813..f1f5d6ad4d 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx @@ -7,7 +7,11 @@ import { Truncate, } from '@patternfly/react-core'; import { Link } from 'react-router-dom'; -import { PipelineRunJobKFv2, PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes'; +import { + PipelineRunJobKFv2, + PipelineRunKFv2, + RecurringRunStatus, +} from '~/concepts/pipelines/kfTypes'; import { getRunDuration } from '~/concepts/pipelines/content/tables/utils'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { getProjectDisplayName } from '~/concepts/projects/utils'; @@ -21,23 +25,23 @@ import { import { isPipelineRun, isPipelineRunJob } from '~/concepts/pipelines/content/utils'; import { PipelineVersionLink } from '~/concepts/pipelines/content/PipelineVersionLink'; import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; +import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; +import { RunJobTrigger } from '~/concepts/pipelines/content/tables/renderUtils'; type PipelineRunTabDetailsProps = { - pipelineRunKF?: PipelineRunKFv2 | PipelineRunJobKFv2; + run?: PipelineRunKFv2 | PipelineRunJobKFv2 | null; workflowName?: string; }; -const PipelineRunTabDetails: React.FC = ({ - pipelineRunKF, - workflowName, -}) => { +const PipelineRunTabDetails: React.FC = ({ run, workflowName }) => { const { namespace, project } = usePipelinesAPI(); - const [version, loaded, error] = usePipelineVersionById( - pipelineRunKF?.pipeline_version_reference?.pipeline_id, - pipelineRunKF?.pipeline_version_reference?.pipeline_version_id, + const [version, versionLoaded, versionError] = usePipelineVersionById( + run?.pipeline_version_reference?.pipeline_id, + run?.pipeline_version_reference?.pipeline_version_id, ); + const [pipeline] = usePipelineById(run?.pipeline_version_reference?.pipeline_id); - if (!pipelineRunKF || !workflowName) { + if (!run || !workflowName) { return ( @@ -46,12 +50,14 @@ const PipelineRunTabDetails: React.FC = ({ ); } - const runId = isPipelineRun(pipelineRunKF) - ? pipelineRunKF.run_id - : pipelineRunKF.recurring_run_id; + const runId = isPipelineRun(run) ? run.run_id : run.recurring_run_id; const details: DetailItem[] = [ - { key: 'Name', value: }, + { key: 'Name', value: }, + { + key: 'Project', + value: {getProjectDisplayName(project)}, + }, ...(version ? [ { @@ -60,32 +66,41 @@ const PipelineRunTabDetails: React.FC = ({ } - loaded={loaded} + loaded={versionLoaded} version={version} - error={error} + error={versionError} /> ), }, ] : []), - { - key: 'Project', - value: {getProjectDisplayName(project)}, - }, + ...(pipeline + ? [ + { + key: 'Pipeline', + value: pipeline.display_name, + }, + ] + : []), { key: 'Run ID', value: runId }, { key: 'Workflow name', value: workflowName }, - ...(!isPipelineRunJob(pipelineRunKF) + ...(!isPipelineRunJob(run) ? [ - { key: 'Started', value: asTimestamp(new Date(pipelineRunKF.created_at)) }, + { key: 'Started', value: asTimestamp(new Date(run.created_at)) }, { key: 'Finished', - value: isEmptyDateKF(pipelineRunKF.finished_at) - ? 'N/A' - : asTimestamp(new Date(pipelineRunKF.finished_at)), + value: isEmptyDateKF(run.finished_at) ? 'N/A' : asTimestamp(new Date(run.finished_at)), }, - { key: 'Duration', value: relativeDuration(getRunDuration(pipelineRunKF)) }, + { key: 'Duration', value: relativeDuration(getRunDuration(run)) }, ] - : []), + : [ + { key: 'Created', value: asTimestamp(new Date(run.created_at)) }, + { + key: 'Run trigger enabled', + value: run.status === RecurringRunStatus.ENABLED ? 'Yes' : 'No', + }, + { key: 'Trigger', value: }, + ]), ]; return <>{renderDetailItems(details)}; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabParameters.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabParameters.tsx deleted file mode 100644 index 787aaa3fbb..0000000000 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabParameters.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from 'react'; -import { - Spinner, - EmptyStateVariant, - EmptyState, - EmptyStateBody, - EmptyStateHeader, -} from '@patternfly/react-core'; -import { PipelineRunJobKFv2, PipelineRunKFv2 } from '~/concepts/pipelines/kfTypes'; -import { - DetailItem, - normalizeInputParamValue, - renderDetailItems, -} from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/utils'; - -type PipelineRunTabParametersProps = { - run?: PipelineRunJobKFv2 | PipelineRunKFv2; -}; - -const PipelineRunTabParameters: React.FC = ({ run }) => { - if (!run) { - return ( - - - - - ); - } - - const parameters = run.runtime_config?.parameters - ? Object.entries(run.runtime_config.parameters) - : []; - - if (parameters.length === 0) { - return ( - - - This pipeline run does not have any parameters defined. - - ); - } - - const details: DetailItem[] = parameters.map(([key, value]) => ({ - key, - value: normalizeInputParamValue(value), - })); - - return <>{renderDetailItems(details)}; -}; - -export default PipelineRunTabParameters; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx index 4bd5677f00..e3d19e4472 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRunJob/PipelineRunJobDetails.tsx @@ -11,21 +11,14 @@ import { Title, Bullseye, Spinner, - DrawerContentBody, } from '@patternfly/react-core'; 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'; -import { PipelineTopology, PipelineTopologyEmpty } from '~/concepts/topology'; +import { PipelineTopology } from '~/concepts/topology'; import MarkdownView from '~/components/MarkdownView'; import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; - -import PipelineRunDrawerBottomContent from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomContent'; -import { - RunDetailsTabs, - RunDetailsTabSelection, -} from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomTabs'; import DeletePipelineRunsModal from '~/concepts/pipelines/content/DeletePipelineRunsModal'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import usePipelineRunJobById from '~/concepts/pipelines/apiHooks/usePipelineRunJobById'; @@ -33,6 +26,7 @@ import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVer import { PipelineRunType } from '~/pages/pipelines/global/runs'; import { routePipelineRunsNamespace } from '~/routes'; import SelectedTaskDrawerContent from '~/concepts/pipelines/content/pipelinesDetails/pipeline/SelectedTaskDrawerContent'; +import { PipelineRunDetailsTabs } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsTabs'; import PipelineRunJobDetailsActions from './PipelineRunJobDetailsActions'; const PipelineRunJobDetails: PipelineCoreDetailsPageComponent = ({ @@ -48,9 +42,6 @@ const PipelineRunJobDetails: PipelineCoreDetailsPageComponent = ({ job?.pipeline_version_reference.pipeline_version_id, ); const [deleting, setDeleting] = React.useState(false); - const [detailsTab, setDetailsTab] = React.useState( - RunDetailsTabs.DETAILS, - ); const [selectedId, setSelectedId] = React.useState(null); const nodes = usePipelineTaskTopology(version?.pipeline_spec); @@ -96,64 +87,45 @@ const PipelineRunJobDetails: PipelineCoreDetailsPageComponent = ({ /> } > - - - { - setDetailsTab(selection); + : ''} + loaded={loaded} + loadError={error} + breadcrumb={ + + {breadcrumbPath} + {job?.display_name ?? 'Loading...'} + + } + headerAction={ + loaded && ( + setDeleting(true)} + /> + ) + } + empty={false} + > + { + const firstId = ids[0]; + if (ids.length === 0) { setSelectedId(null); - }} - pipelineRunDetails={job && version?.pipeline_spec ? job : undefined} - /> - } - > - : '' - } - loaded={loaded} - loadError={error} - breadcrumb={ - - {breadcrumbPath} - {job?.display_name ?? 'Loading...'} - - } - headerAction={ - loaded && ( - setDeleting(true)} - /> - ) - } - empty={false} - > - {nodes.length === 0 ? ( - - ) : ( - { - const firstId = ids[0]; - if (ids.length === 0) { - setSelectedId(null); - } else if (getFirstNode(firstId)) { - setDetailsTab(null); - setSelectedId(firstId); - } - }} - /> - )} - - - - + } else if (getFirstNode(firstId)) { + setSelectedId(firstId); + } + }} + /> + } + /> + diff --git a/frontend/src/concepts/topology/PipelineTopology.tsx b/frontend/src/concepts/topology/PipelineTopology.tsx index bfa6f3bf7b..aba5f9e56f 100644 --- a/frontend/src/concepts/topology/PipelineTopology.tsx +++ b/frontend/src/concepts/topology/PipelineTopology.tsx @@ -7,6 +7,7 @@ import { import { Bullseye, Spinner } from '@patternfly/react-core'; import useTopologyController from './useTopologyController'; import PipelineVisualizationSurface from './PipelineVisualizationSurface'; +import PipelineTopologyEmpty from './PipelineTopologyEmpty'; type PipelineTopologyProps = { selectedIds?: string[]; @@ -36,6 +37,10 @@ const PipelineTopology: React.FC = ({ return undefined; }, [controller, onSelectionChange]); + if (!nodes.length) { + return ; + } + if (!controller) { return (