From cf7548e633ead251d67e62a3dfe1e8ba8cee733f Mon Sep 17 00:00:00 2001 From: Ashique Ansari Date: Tue, 25 Jul 2023 19:20:39 +0530 Subject: [PATCH 1/2] Fix Pipeline Job titles are partially handled --- .../table/TableRowTitleDescription.tsx | 6 + .../DeletePipelineCoreResourceModal.tsx | 109 ++++++++++++------ .../content/PipelineJobReferenceName.tsx | 29 +++++ .../content/PipelineRunTypeLabel.tsx | 41 +++++++ .../content/createRun/useRunFormData.ts | 41 ++++++- .../pipelinesDetails/PipelineDetailsTitle.tsx | 39 +++++++ .../pipelineRun/PipelineRunDetails.tsx | 20 +++- .../pipelineRun/PipelineRunTabDetails.tsx | 60 +++++++++- .../tables/pipelineRun/PipelineRunTable.tsx | 7 +- .../pipelineRun/PipelineRunTableRow.tsx | 28 +---- .../pipelineRun/PipelineRunTableRowTitle.tsx | 38 ++++++ .../pipelineRun/usePipelineRunFilter.ts | 10 ++ .../pipelineRunJob/PipelineRunJobTable.tsx | 3 +- .../pipelines/content/tables/utils.ts | 31 +++-- .../pipelines/context/PipelinesContext.tsx | 20 +++- .../useJobRelatedInformation.ts | 12 +- frontend/src/pages/ApplicationsPage.tsx | 3 + .../pages/projects/components/DeleteModal.tsx | 2 +- 18 files changed, 410 insertions(+), 89 deletions(-) create mode 100644 frontend/src/concepts/pipelines/content/PipelineJobReferenceName.tsx create mode 100644 frontend/src/concepts/pipelines/content/PipelineRunTypeLabel.tsx create mode 100644 frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsTitle.tsx create mode 100644 frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx rename frontend/src/concepts/pipelines/{content/tables/pipelineRun => context}/useJobRelatedInformation.ts (83%) diff --git a/frontend/src/components/table/TableRowTitleDescription.tsx b/frontend/src/components/table/TableRowTitleDescription.tsx index 29185933f0..ae9a199ee2 100644 --- a/frontend/src/components/table/TableRowTitleDescription.tsx +++ b/frontend/src/components/table/TableRowTitleDescription.tsx @@ -7,15 +7,19 @@ import ResourceNameTooltip from '~/components/ResourceNameTooltip'; type TableRowTitleDescriptionProps = { title: React.ReactNode; resource?: K8sResourceCommon; + subtitle?: React.ReactNode; description?: string; descriptionAsMarkdown?: boolean; + label?: React.ReactNode; }; const TableRowTitleDescription: React.FC = ({ title, description, resource, + subtitle, descriptionAsMarkdown, + label, }) => { let descriptionNode: React.ReactNode; if (description) { @@ -31,7 +35,9 @@ const TableRowTitleDescription: React.FC = ({ {resource ? <ResourceNameTooltip resource={resource}>{title}</ResourceNameTooltip> : title} + {subtitle && {subtitle}} {descriptionNode} + {label} ); }; diff --git a/frontend/src/concepts/pipelines/content/DeletePipelineCoreResourceModal.tsx b/frontend/src/concepts/pipelines/content/DeletePipelineCoreResourceModal.tsx index fb22a1cbd4..24552e9036 100644 --- a/frontend/src/concepts/pipelines/content/DeletePipelineCoreResourceModal.tsx +++ b/frontend/src/concepts/pipelines/content/DeletePipelineCoreResourceModal.tsx @@ -1,11 +1,26 @@ import * as React from 'react'; -import { Icon, List, ListItem, Stack, StackItem, Text, Tooltip } from '@patternfly/react-core'; +import { + Icon, + List, + ListItem, + Stack, + StackItem, + TextContent, + Text, + TextVariants, + Tooltip, + ExpandableSection, + Badge, +} from '@patternfly/react-core'; import { CheckCircleIcon, ExclamationCircleIcon, PendingIcon } from '@patternfly/react-icons'; import DeleteModal from '~/pages/projects/components/DeleteModal'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { PipelineCoreResourceKF } from '~/concepts/pipelines/kfTypes'; import { K8sAPIOptions } from '~/k8sTypes'; import useNotification from '~/utilities/useNotification'; +import { PipelineType } from '~/concepts/pipelines/content/tables/utils'; +import PipelineJobReferenceName from './PipelineJobReferenceName'; +import PipelineRunTypeLabel from './PipelineRunTypeLabel'; type DeletePipelineCoreResourceModalProps = { type: 'triggered run' | 'scheduled run' | 'pipeline'; @@ -24,7 +39,7 @@ const DeletePipelineCoreResourceModal: React.FC([]); const abortControllerRef = React.useRef(new AbortController()); const notification = useNotification(); - + const [isExpanded, setIsExpanded] = React.useState(true); const resourceCount = toDeleteResources.length; const onBeforeCloseRef = React.useRef<(v: boolean) => void>(() => undefined); @@ -54,10 +69,14 @@ const DeletePipelineCoreResourceModal: React.FC { + setIsExpanded(isExpanded); + }; return ( 1 ? 's' : ''}`} + title={`Delete ${resourceCount > 1 ? resourceCount : ''} ${type}${ + resourceCount > 1 ? 's' : '' + }?`} isOpen={resourceCount !== 0} onClose={() => onBeforeCloseRef.current(false)} deleting={deleting} @@ -118,7 +137,7 @@ const DeletePipelineCoreResourceModal: React.FC 1 ? 's' : ''}`} + submitButtonLabel="Delete" deleteName={ resourceCount === 1 ? toDeleteResources[0].name : `Delete ${resourceCount} ${type}s` } @@ -133,39 +152,61 @@ const DeletePipelineCoreResourceModal: React.FC - - {toDeleteResources.map((resource, i) => { - let icon: React.ReactNode; - if (!deleting) { - icon = null; - } else { - const state = deleteStatuses[i]; - if (state === undefined) { - icon = ; - } else if (state === true) { - icon = ( - - - - ); + + Selected {resourceCount > 1 ? 'runs' : 'run'} + {resourceCount > 0 && {resourceCount}} + + } + onToggle={onToggle} + isExpanded={isExpanded} + > + + {toDeleteResources.map((resource, i) => { + let icon: React.ReactNode; + if (!deleting) { + icon = null; } else { - icon = ( - - - + const state = deleteStatuses[i]; + if (state === undefined) { + icon = ; + } else if (state === true) { + icon = ( + + - - ); + ); + } else { + icon = ( + + + + + + ); + } } - } - return ( - - {resource.name} - - ); - })} - + return ( + + + + {resource.name}{' '} + {type === PipelineType.TRIGGERED_RUN && ( + + )} + + + {type === PipelineType.TRIGGERED_RUN && ( + + )} + + + ); + })} + + )} diff --git a/frontend/src/concepts/pipelines/content/PipelineJobReferenceName.tsx b/frontend/src/concepts/pipelines/content/PipelineJobReferenceName.tsx new file mode 100644 index 0000000000..580f60e0f8 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/PipelineJobReferenceName.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Text, TextVariants } from '@patternfly/react-core'; +import { getPipelineJobExecutionCount } from '~/concepts/pipelines/content/tables/utils'; +import { PipelineCoreResourceKF } from '~/concepts/pipelines/kfTypes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; + +type PipelineJobReferenceNameProps = { + resource: PipelineCoreResourceKF; +}; + +const PipelineJobReferenceName: React.FC = ({ resource }) => { + const { getJobInformation } = usePipelinesAPI(); + const { data, loading } = getJobInformation(resource); + + return ( + <> + {loading ? ( + 'loading...' + ) : data ? ( + + Run {getPipelineJobExecutionCount(resource.name)} of {data?.name} + + ) : ( + '' + )} + + ); +}; +export default PipelineJobReferenceName; diff --git a/frontend/src/concepts/pipelines/content/PipelineRunTypeLabel.tsx b/frontend/src/concepts/pipelines/content/PipelineRunTypeLabel.tsx new file mode 100644 index 0000000000..0452031cc3 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/PipelineRunTypeLabel.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Label, Tooltip } from '@patternfly/react-core'; +import { + PipelineRunLabels, + getPipelineCoreResourceJobReference, + getPipelineCoreResourcePipelineReference, +} from '~/concepts/pipelines/content/tables/utils'; +import { PipelineCoreResourceKF } from '~/concepts/pipelines/kfTypes'; + +type PipelineRunTypeLabelProps = { + resource: PipelineCoreResourceKF; +}; +const PipelineRunTypeLabel: React.FC = ({ resource }) => { + const jobReference = getPipelineCoreResourceJobReference(resource); + const pipelineReference = getPipelineCoreResourcePipelineReference(resource); + + return ( + <> + {jobReference ? ( + <> + + + + + ) : !pipelineReference ? ( + <> + Created by a scheduled run that was deleted}> + + + + ) : ( + <> + Run once immediately after creation}> + + + + )} + + ); +}; +export default PipelineRunTypeLabel; diff --git a/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts b/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts index 337a5a731b..71e3522a4b 100644 --- a/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts +++ b/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts @@ -18,8 +18,12 @@ import { PipelineRunKF, ResourceReferenceKF, } from '~/concepts/pipelines/kfTypes'; -import { getPipelineCoreResourcePipelineReference } from '~/concepts/pipelines/content/tables/utils'; +import { + getPipelineCoreResourcePipelineReference, + getPipelineCoreResourceJobReference, +} from '~/concepts/pipelines/content/tables/utils'; import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; +import usePipelineRunJobById from '~/concepts/pipelines/apiHooks/usePipelineRunJobById'; import { UpdateObjectAtPropAndValue } from '~/pages/projects/types'; import { FetchState } from '~/utilities/useFetchState'; import { ValueOf } from '~/typeHelpers'; @@ -45,6 +49,7 @@ const useUpdateData = ( const [resource, loaded] = useFetchHookFnc(reference?.key.id); const resourceRef = React.useRef>(resource); resourceRef.current = resource; + const setData = React.useCallback(() => { if (loaded && resourceRef.current) { setFunction(fieldId, resourceRef.current); @@ -55,6 +60,33 @@ const useUpdateData = ( }, [setData]); }; +const useUpdatePipelineRun = ( + setFunction: UpdateObjectAtPropAndValue, + initialData?: PipelineRunKF | PipelineRunJobKF, +) => { + const reference = getPipelineCoreResourceJobReference(initialData); + const [pipelineRunJob] = usePipelineRunJobById(reference?.key.id); + const updatedSetFunction = React.useCallback>( + (key, resource) => { + setFunction(key, resource); + const nameDesc: ValueOf = { + name: initialData?.name ? `Duplicate of ${initialData.name}` : '', + description: pipelineRunJob?.description ?? initialData?.description ?? '', + }; + setFunction('nameDesc', nameDesc); + }, + [setFunction, initialData, pipelineRunJob], + ); + + return useUpdateData( + updatedSetFunction, + pipelineRunJob, + 'pipeline', + getPipelineCoreResourcePipelineReference, + usePipelineById, + ); +}; + const useUpdatePipeline = ( setFunction: UpdateObjectAtPropAndValue, initialData?: PipelineRunKF | PipelineRunJobKF, @@ -72,13 +104,16 @@ const useUpdatePipeline = ( }, [setFunction, initialData?.pipeline_spec.parameters], ); - return useUpdateData( + + const updatedData = useUpdateData( updatedSetFunction, initialData, 'pipeline', getPipelineCoreResourcePipelineReference, usePipelineById, ); + + return updatedData; }; // const useUpdateExperiment = ( @@ -180,9 +215,9 @@ const useRunFormData = ( ? (lastPipeline.parameters || []).map((p) => ({ label: p.name, value: p.value ?? '' })) : undefined, }); - const setFunction = objState[1]; useUpdatePipeline(setFunction, initialData); + useUpdatePipelineRun(setFunction, initialData); // useUpdateExperiment(setFunction, initialData); useUpdateRunType(setFunction, initialData); diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsTitle.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsTitle.tsx new file mode 100644 index 0000000000..1b769f22d5 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsTitle.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Label, Split, SplitItem } from '@patternfly/react-core'; +import { PipelineRunKF } from '~/concepts/pipelines/kfTypes'; +import { computeRunStatus } from '~/concepts/pipelines/content/utils'; +import PipelineRunTypeLabel from '~/concepts/pipelines/content/PipelineRunTypeLabel'; + +type RunJobTitleProps = { + run: PipelineRunKF; + statusIcon?: boolean; + hasJobReference?: boolean; + pipelineRunLabel?: boolean; +}; + +const PipelineDetailsTitle: React.FC = ({ + run, + statusIcon, + pipelineRunLabel, +}) => { + const { icon, label } = computeRunStatus(run); + + return ( + <> + + {run.name} + {pipelineRunLabel && ( + + + + )} + {statusIcon && ( + + + + )} + + + ); +}; +export default PipelineDetailsTitle; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx index 322dc1f223..84a83754ae 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetails.tsx @@ -32,7 +32,8 @@ import { } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerBottomTabs'; import DeletePipelineCoreResourceModal from '~/concepts/pipelines/content/DeletePipelineCoreResourceModal'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import PipelineRunTitle from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTitle'; +import PipelineDetailsTitle from '~/concepts/pipelines/content/pipelinesDetails/PipelineDetailsTitle'; +import PipelineJobReferenceName from '~/concepts/pipelines/content/PipelineJobReferenceName'; const getPipelineRunKind = ( pipelineRuntime?: PipelineRunResourceKF['pipeline_runtime'], @@ -113,7 +114,14 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, } > } + title={ + run && !error ? ( + + ) : ( + 'Error loading run' + ) + } + jobReferenceName={run && } description={ run ? : '' } @@ -161,8 +169,12 @@ const PipelineRunDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath, { - navigate(contextPath ?? `/pipelineRuns/${namespace}`); + onClose={(deleteComplete) => { + if (deleteComplete) { + navigate(contextPath ?? `/pipelineRuns/${namespace}`); + } else { + setDeleting(false); + } }} /> diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx index ac4bb06fe5..a5242840ce 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunTabDetails.tsx @@ -1,5 +1,12 @@ import * as React from 'react'; -import { Spinner, EmptyStateVariant, EmptyState, Title } from '@patternfly/react-core'; +import { + Spinner, + EmptyStateVariant, + EmptyState, + Title, + Split, + SplitItem, +} from '@patternfly/react-core'; import { Link } from 'react-router-dom'; import { PipelineRunKF } from '~/concepts/pipelines/kfTypes'; import { @@ -14,6 +21,10 @@ import { DetailItem, renderDetailItems, } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/utils'; +import PipelineDetailsTitle from '~/concepts/pipelines/content/pipelinesDetails/PipelineDetailsTitle'; +import PipelineRunTypeLabel from '~/concepts/pipelines/content/PipelineRunTypeLabel'; +import PipelineJobReferenceName from '~/concepts/pipelines/content/PipelineJobReferenceName'; + type PipelineRunTabDetailsProps = { pipelineRunKF?: PipelineRunKF; workflowName?: string; @@ -23,7 +34,7 @@ const PipelineRunTabDetails: React.FC = ({ pipelineRunKF, workflowName, }) => { - const { namespace, project } = usePipelinesAPI(); + const { namespace, project, getJobInformation } = usePipelinesAPI(); if (!pipelineRunKF || !workflowName) { return ( @@ -36,7 +47,11 @@ const PipelineRunTabDetails: React.FC = ({ ); } - const pipelineReference = getPipelineCoreResourcePipelineReference(pipelineRunKF); + const { data } = getJobInformation(pipelineRunKF); + const pipelineReference = + getPipelineCoreResourcePipelineReference(pipelineRunKF) ?? + getPipelineCoreResourcePipelineReference(data ?? undefined); + const pipelineRef = pipelineReference ? [ { @@ -51,8 +66,45 @@ const PipelineRunTabDetails: React.FC = ({ ] : []; + const jobRefDescription = data + ? [ + { + key: 'Description', + value: ( + <> + + {data.description ?? ''} + + ), + }, + ] + : null; + const pipelineDescription = pipelineRunKF.description + ? [ + { + key: 'Description', + value: pipelineRunKF.description, + }, + ] + : null; + const description = (pipelineDescription || jobRefDescription) ?? []; + + const renderRunName = { + key: 'Name', + value: ( + + + {' '} + + + + + + ), + }; const details: DetailItem[] = [ - { key: 'Name', value: pipelineRunKF.name }, + renderRunName, + ...description, ...pipelineRef, { key: 'Project', diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx index e323982143..fbb1064cb1 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTable.tsx @@ -10,20 +10,19 @@ import usePipelineRunFilter from '~/concepts/pipelines/content/tables/pipelineRu import PipelineRunTableToolbar from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableToolbar'; import DeletePipelineCoreResourceModal from '~/concepts/pipelines/content/DeletePipelineCoreResourceModal'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import useJobRelatedInformation from '~/concepts/pipelines/content/tables/pipelineRun/useJobRelatedInformation'; +import { PipelineType } from '~/concepts/pipelines/content/tables/utils'; type PipelineRunTableProps = { runs: PipelineRunKF[]; }; const PipelineRunTable: React.FC = ({ runs }) => { - const { refreshAllAPI } = usePipelinesAPI(); + const { refreshAllAPI, getJobInformation } = usePipelinesAPI(); const [filteredRuns, toolbarProps] = usePipelineRunFilter(runs); const { selections, tableProps, toggleSelection, isSelected } = useCheckboxTable( filteredRuns.map(({ id }) => id), ); const [deleteResources, setDeleteResources] = React.useState([]); - const { getJobInformation } = useJobRelatedInformation(); return ( <> @@ -62,7 +61,7 @@ const PipelineRunTable: React.FC = ({ runs }) => { /> { if (deleted) { refreshAllAPI(); diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx index 69791b702d..5c447aa931 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRow.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; import { ActionsColumn, Td, Tr } from '@patternfly/react-table'; -import { Link } from 'react-router-dom'; import { Skeleton } from '@patternfly/react-core'; import { PipelineRunKF } from '~/concepts/pipelines/kfTypes'; -import TableRowTitleDescription from '~/components/table/TableRowTitleDescription'; + import { RunCreated, RunDuration, @@ -13,7 +12,8 @@ import { } from '~/concepts/pipelines/content/tables/renderUtils'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import CheckboxTd from '~/components/table/CheckboxTd'; -import { GetJobInformation } from '~/concepts/pipelines/content/tables/pipelineRun/useJobRelatedInformation'; +import { GetJobInformation } from '~/concepts/pipelines/context/useJobRelatedInformation'; +import PipelineRunTableRowTitle from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle'; type PipelineRunTableRowProps = { isChecked: boolean; @@ -33,36 +33,18 @@ const PipelineRunTableRow: React.FC = ({ const { namespace } = usePipelinesAPI(); const { loading, data } = getJobInformation(run); - const loadingState = ; - return ( - {loading ? ( - loadingState - ) : ( - - {data ? `Run of ${data.name}` : run.name} - - } - description={ - data - ? `${run.name}\n${run.description ?? ''}\n${data.description ?? ''}` - : run.description - } - descriptionAsMarkdown - /> - )} + {loading ? ( - loadingState + ) : ( )} diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx new file mode 100644 index 0000000000..fdf02d15cf --- /dev/null +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableRowTitle.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Skeleton } from '@patternfly/react-core'; +import { Link } from 'react-router-dom'; +import TableRowTitleDescription from '~/components/table/TableRowTitleDescription'; +import { PipelineCoreResourceKF } from '~/concepts/pipelines/kfTypes'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import PipelineJobReferenceName from '~/concepts/pipelines/content/PipelineJobReferenceName'; +import PipelineRunTypeLabel from '~/concepts/pipelines/content/PipelineRunTypeLabel'; + +type PipelineRunTableRowTitleProps = { + resource: PipelineCoreResourceKF; +}; + +const PipelineRunTableRowTitle: React.FC = ({ resource }) => { + const { namespace, getJobInformation } = usePipelinesAPI(); + const { data, loading } = getJobInformation(resource); + + return ( +
+ {loading ? ( + + ) : ( + + {resource.name} + + } + subtitle={} + description={data ? data.description : resource.description} + descriptionAsMarkdown + label={} + /> + )} +
+ ); +}; +export default PipelineRunTableRowTitle; diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/usePipelineRunFilter.ts b/frontend/src/concepts/pipelines/content/tables/pipelineRun/usePipelineRunFilter.ts index 5023f00059..17854a5107 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/usePipelineRunFilter.ts +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRun/usePipelineRunFilter.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { getPipelineCoreResourceExperimentName, getPipelineCoreResourcePipelineName, + getPipelineCoreResourcePipelineReference, } from '~/concepts/pipelines/content/tables/utils'; import { PipelineRunKF, @@ -9,6 +10,7 @@ import { PipelineRunStatusUnknown, } from '~/concepts/pipelines/kfTypes'; import { computeRunStatus } from '~/concepts/pipelines/content/utils'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { FilterOptions, FilterProps } from './PipelineRunTableToolbar'; type FilterData = Record; @@ -24,6 +26,7 @@ const usePipelineRunFilter = ( runs: PipelineRunKF[], ): [filterJobs: PipelineRunKF[], toolbarProps: FilterProps] => { const [filterData, setFilterData] = React.useState(DEFAULT_FILTER_DATA); + const { getJobInformation } = usePipelinesAPI(); const filteredRuns = runs.filter((run) => { const runValue = filterData[FilterOptions.NAME]; @@ -31,10 +34,17 @@ const usePipelineRunFilter = ( const pipelineValue = filterData[FilterOptions.PIPELINE]; const startedValue = filterData[FilterOptions.STARTED]; const statusValue = filterData[FilterOptions.STATUS]; + const { data } = getJobInformation(run); if (runValue && !run.name.toLowerCase().includes(runValue.toLowerCase())) { return false; } + if (data && pipelineValue && !getPipelineCoreResourcePipelineName(run)) { + const pipelineName = getPipelineCoreResourcePipelineReference(data) + ?.name?.toLowerCase() + .includes(pipelineValue.toLowerCase()); + return pipelineName; + } if ( experimentValue && !getPipelineCoreResourceExperimentName(run) diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx index 94c4938ab3..a037311725 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipelineRunJob/PipelineRunJobTable.tsx @@ -10,6 +10,7 @@ import usePipelineRunJobFilter from '~/concepts/pipelines/content/tables/pipelin import EmptyTableView from '~/concepts/pipelines/content/tables/EmptyTableView'; import DeletePipelineCoreResourceModal from '~/concepts/pipelines/content/DeletePipelineCoreResourceModal'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { PipelineType } from '~/concepts/pipelines/content/tables/utils'; type PipelineRunTableProps = { jobs: PipelineRunJobKF[]; @@ -59,7 +60,7 @@ const PipelineRunJobTable: React.FC = ({ jobs }) => { /> { if (deleted) { refreshAllAPI(); diff --git a/frontend/src/concepts/pipelines/content/tables/utils.ts b/frontend/src/concepts/pipelines/content/tables/utils.ts index ac20b775ce..3bb3095fec 100644 --- a/frontend/src/concepts/pipelines/content/tables/utils.ts +++ b/frontend/src/concepts/pipelines/content/tables/utils.ts @@ -34,28 +34,28 @@ export const getStatusWeight = (run: PipelineRunKF): number => { }; export const getRunResourceReference = ( - resource: PipelineCoreResourceKF, - type: ResourceTypeKF, + resource?: PipelineCoreResourceKF, + type?: ResourceTypeKF, ): ResourceReferenceKF | undefined => - resource.resource_references?.find((ref) => ref.key.type === type); + resource?.resource_references?.find((ref) => ref.key.type === type); export const getPipelineCoreResourceJobReference = ( - resource: PipelineCoreResourceKF, + resource?: PipelineCoreResourceKF, ): ResourceReferenceKF | undefined => getRunResourceReference(resource, ResourceTypeKF.JOB); export const getPipelineCoreResourcePipelineReference = ( - resource: PipelineCoreResourceKF, + resource?: PipelineCoreResourceKF, ): ResourceReferenceKF | undefined => getRunResourceReference(resource, ResourceTypeKF.PIPELINE_VERSION); export const getPipelineCoreResourceExperimentReference = ( - resource: PipelineCoreResourceKF, + resource?: PipelineCoreResourceKF, ): ResourceReferenceKF | undefined => getRunResourceReference(resource, ResourceTypeKF.EXPERIMENT); -export const getPipelineCoreResourceExperimentName = (resource: PipelineCoreResourceKF): string => +export const getPipelineCoreResourceExperimentName = (resource?: PipelineCoreResourceKF): string => getPipelineCoreResourceExperimentReference(resource)?.name || 'Default'; -export const getPipelineCoreResourcePipelineName = (resource: PipelineCoreResourceKF): string => +export const getPipelineCoreResourcePipelineName = (resource?: PipelineCoreResourceKF): string => getPipelineCoreResourcePipelineReference(resource)?.name || ''; export const getPipelineRunJobStartTime = (job: PipelineRunJobKF): Date | null => { @@ -77,7 +77,14 @@ export enum ScheduledState { STARTED_NOT_ENDED, ENDED, } - +export enum PipelineRunLabels { + RECURRING = 'Recurring', + ONEOFF = 'One-off', +} +export enum PipelineType { + SCHEDULED_RUN = 'scheduled run', + TRIGGERED_RUN = 'triggered run', +} const inPast = (date: Date | null): boolean => (date ? date.getTime() - Date.now() <= 0 : false); export const getPipelineRunJobScheduledState = ( job: PipelineRunJobKF, @@ -139,3 +146,9 @@ export const isJobWithinDateRange = ( (endNumber >= jobStartNumber && endNumber <= jobEndNumber) ); }; + +export const getPipelineJobExecutionCount = (resourceName: string) => { + const regex = /(\w+)(?:-[^-]*)?$/; + const match = resourceName?.match(regex); + return match ? match[1] : null; +}; diff --git a/frontend/src/concepts/pipelines/context/PipelinesContext.tsx b/frontend/src/concepts/pipelines/context/PipelinesContext.tsx index 415cb6e85b..388f0e1671 100644 --- a/frontend/src/concepts/pipelines/context/PipelinesContext.tsx +++ b/frontend/src/concepts/pipelines/context/PipelinesContext.tsx @@ -9,6 +9,7 @@ import { StackItem, } from '@patternfly/react-core'; import { ProjectKind } from '~/k8sTypes'; +import { PipelineCoreResourceKF, PipelineRunJobKF } from '~/concepts/pipelines/kfTypes'; import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext'; import DeletePipelineServerModal from '~/concepts/pipelines/content/DeletePipelineServerModal'; import { ConfigurePipelinesServerModal } from '~/concepts/pipelines/content/configurePipelinesServer/ConfigurePipelinesServerModal'; @@ -16,10 +17,17 @@ import ViewPipelineServerModal from '~/concepts/pipelines/content/ViewPipelineSe import useSyncPreferredProject from '~/concepts/projects/useSyncPreferredProject'; import useManageElyraSecret from '~/concepts/pipelines/context/useManageElyraSecret'; import { deleteServer } from '~/concepts/pipelines/utils'; +import useJobRelatedInformation from '~/concepts/pipelines/context/useJobRelatedInformation'; import useAPIState, { APIState } from './useAPIState'; import usePipelineNamespaceCR, { dspaLoaded, hasServerTimedOut } from './usePipelineNamespaceCR'; import usePipelinesAPIRoute from './usePipelinesAPIRoute'; +type JobStatus = { + loading: boolean; + data: PipelineRunJobKF | null; +}; + +type GetJobInformation = (resource: PipelineCoreResourceKF) => JobStatus; type PipelineContext = { hasCR: boolean; crInitializing: boolean; @@ -30,6 +38,7 @@ type PipelineContext = { refreshState: () => Promise; refreshAPIState: () => void; apiState: APIState; + getJobInformation: GetJobInformation; }; const PipelinesContext = React.createContext({ @@ -41,6 +50,10 @@ const PipelinesContext = React.createContext({ project: null as unknown as ProjectKind, refreshState: async () => undefined, refreshAPIState: () => undefined, + getJobInformation: () => ({ + loading: false, + data: null, + }), apiState: { apiAvailable: false, api: null as unknown as APIState['api'] }, }); @@ -70,6 +83,7 @@ export const PipelineContextProvider: React.FC = ( isCRReady, namespace, ); + const hostPath = routeLoaded && pipelineAPIRouteHost ? pipelineAPIRouteHost : null; useManageElyraSecret(namespace, pipelineNamespaceCR, hostPath); @@ -79,7 +93,7 @@ export const PipelineContextProvider: React.FC = ( ); const [apiState, refreshAPIState] = useAPIState(hostPath); - + const { getJobInformation } = useJobRelatedInformation(apiState); let error = crLoadError || routeLoadError; if (error || !project) { error = error || new Error('Project not found'); @@ -104,6 +118,7 @@ export const PipelineContextProvider: React.FC = ( namespace, refreshState, refreshAPIState, + getJobInformation, }} > {children} @@ -122,6 +137,7 @@ type UsePipelinesAPI = APIState & { * Allows agnostic functionality to request all watched API to be reacquired. * Triggering this will invalidate the memo for API - pay attention to only calling it once per need. */ + getJobInformation: GetJobInformation; refreshAllAPI: () => void; }; @@ -134,6 +150,7 @@ export const usePipelinesAPI = (): UsePipelinesAPI => { namespace, project, refreshAPIState: refreshAllAPI, + getJobInformation, } = React.useContext(PipelinesContext); const pipelinesServer: UsePipelinesAPI['pipelinesServer'] = { @@ -147,6 +164,7 @@ export const usePipelinesAPI = (): UsePipelinesAPI => { namespace, project, refreshAllAPI, + getJobInformation, ...apiState, }; }; diff --git a/frontend/src/concepts/pipelines/content/tables/pipelineRun/useJobRelatedInformation.ts b/frontend/src/concepts/pipelines/context/useJobRelatedInformation.ts similarity index 83% rename from frontend/src/concepts/pipelines/content/tables/pipelineRun/useJobRelatedInformation.ts rename to frontend/src/concepts/pipelines/context/useJobRelatedInformation.ts index c8c15e012a..1589c015ad 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipelineRun/useJobRelatedInformation.ts +++ b/frontend/src/concepts/pipelines/context/useJobRelatedInformation.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { PipelineCoreResourceKF, PipelineRunJobKF } from '~/concepts/pipelines/kfTypes'; -import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { getPipelineCoreResourceJobReference } from '~/concepts/pipelines/content/tables/utils'; +import { APIState } from '~/concepts/pipelines/context/useAPIState'; type JobStatus = { loading: boolean; @@ -10,14 +10,16 @@ type JobStatus = { export type GetJobInformation = (resource: PipelineCoreResourceKF) => JobStatus; -const useJobRelatedInformation = (): { getJobInformation: GetJobInformation } => { - const { api } = usePipelinesAPI(); +const useJobRelatedInformation = (apiState: APIState): { getJobInformation: GetJobInformation } => { const [jobStorage, setJobStorage] = React.useState<{ [jobId: string]: JobStatus }>({}); const loadedIds = React.useRef([]); return { getJobInformation: React.useCallback( (resource) => { + if (!apiState.apiAvailable) { + return { loading: false, data: null }; + } const jobReference = getPipelineCoreResourceJobReference(resource); if (!jobReference) { return { loading: false, data: null }; @@ -31,7 +33,7 @@ const useJobRelatedInformation = (): { getJobInformation: GetJobInformation } => } loadedIds.current.push(jobId); - api + apiState.api .getPipelineRunJob({}, jobId) .then((job) => { setJobStorage((jobState) => ({ ...jobState, [jobId]: { loading: false, data: job } })); @@ -45,7 +47,7 @@ const useJobRelatedInformation = (): { getJobInformation: GetJobInformation } => return { loading: true, data: null }; }, - [api, jobStorage], + [apiState, jobStorage], ), }; }; diff --git a/frontend/src/pages/ApplicationsPage.tsx b/frontend/src/pages/ApplicationsPage.tsx index 132e97fd71..7a06ff8daa 100644 --- a/frontend/src/pages/ApplicationsPage.tsx +++ b/frontend/src/pages/ApplicationsPage.tsx @@ -33,6 +33,7 @@ type ApplicationsPageProps = { headerAction?: React.ReactNode; headerContent?: React.ReactNode; provideChildrenPadding?: boolean; + jobReferenceName?: React.ReactNode; }; const ApplicationsPage: React.FC = ({ @@ -50,6 +51,7 @@ const ApplicationsPage: React.FC = ({ headerAction, headerContent, provideChildrenPadding, + jobReferenceName, }) => { const renderHeader = () => ( @@ -59,6 +61,7 @@ const ApplicationsPage: React.FC = ({ {title} + {jobReferenceName} {description && {description}} diff --git a/frontend/src/pages/projects/components/DeleteModal.tsx b/frontend/src/pages/projects/components/DeleteModal.tsx index 777c414bae..587a2afabb 100644 --- a/frontend/src/pages/projects/components/DeleteModal.tsx +++ b/frontend/src/pages/projects/components/DeleteModal.tsx @@ -70,7 +70,7 @@ const DeleteModal: React.FC = ({ {children} - Confirm deletion by typing {deleteNameSanitized} below: + Type {deleteNameSanitized} to confirm deletion: Date: Thu, 14 Sep 2023 23:32:46 +0530 Subject: [PATCH 2/2] fix pipeline run to show jobs details --- .../content/createRun/useRunFormData.ts | 44 +++++++++++-------- .../pipelines/context/PipelinesContext.tsx | 11 ++--- .../context/useJobRelatedInformation.ts | 2 +- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts b/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts index 71e3522a4b..09f6b95bec 100644 --- a/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts +++ b/frontend/src/concepts/pipelines/content/createRun/useRunFormData.ts @@ -9,6 +9,7 @@ import { RunType, RunTypeOption, ScheduledType, + RunParam, } from '~/concepts/pipelines/content/createRun/types'; import { DateTimeKF, @@ -18,12 +19,9 @@ import { PipelineRunKF, ResourceReferenceKF, } from '~/concepts/pipelines/kfTypes'; -import { - getPipelineCoreResourcePipelineReference, - getPipelineCoreResourceJobReference, -} from '~/concepts/pipelines/content/tables/utils'; + +import { getPipelineCoreResourcePipelineReference } from '~/concepts/pipelines/content/tables/utils'; import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; -import usePipelineRunJobById from '~/concepts/pipelines/apiHooks/usePipelineRunJobById'; import { UpdateObjectAtPropAndValue } from '~/pages/projects/types'; import { FetchState } from '~/utilities/useFetchState'; import { ValueOf } from '~/typeHelpers'; @@ -38,6 +36,9 @@ const isPipelineRunJob = ( runOrJob?: PipelineRunJobKF | PipelineRunKF, ): runOrJob is PipelineRunJobKF => !!(runOrJob as PipelineRunJobKF)?.trigger; +const isPipeline = (pipeline?: unknown): pipeline is PipelineKF => + !!(pipeline as PipelineKF)?.default_version; + const useUpdateData = ( setFunction: UpdateObjectAtPropAndValue, run: T | null = null, @@ -64,18 +65,22 @@ const useUpdatePipelineRun = ( setFunction: UpdateObjectAtPropAndValue, initialData?: PipelineRunKF | PipelineRunJobKF, ) => { - const reference = getPipelineCoreResourceJobReference(initialData); - const [pipelineRunJob] = usePipelineRunJobById(reference?.key.id); + const { getJobInformation } = usePipelinesAPI(); + const { data: pipelineRunJob, loading } = getJobInformation(initialData); + const updatedSetFunction = React.useCallback>( - (key, resource) => { - setFunction(key, resource); - const nameDesc: ValueOf = { - name: initialData?.name ? `Duplicate of ${initialData.name}` : '', - description: pipelineRunJob?.description ?? initialData?.description ?? '', - }; - setFunction('nameDesc', nameDesc); + (key, pipeline) => { + if (!loading && isPipeline(pipeline)) { + setFunction(key, pipeline); + setFunction('params', getParams(pipeline)); + const nameDesc: ValueOf = { + name: initialData?.name ? `Duplicate of ${initialData.name}` : '', + description: pipelineRunJob?.description ?? initialData?.description ?? '', + }; + setFunction('nameDesc', nameDesc); + } }, - [setFunction, initialData, pipelineRunJob], + [setFunction, initialData, pipelineRunJob, loading], ); return useUpdateData( @@ -195,6 +200,11 @@ export const useUpdateRunType = ( }, [setFunction, initialData]); }; +const getParams = (pipeline?: PipelineKF): RunParam[] | undefined => + pipeline + ? (pipeline.parameters || []).map((p) => ({ label: p.name, value: p.value ?? '' })) + : undefined; + const useRunFormData = ( initialData?: PipelineRunKF | PipelineRunJobKF, lastPipeline?: PipelineKF, @@ -211,9 +221,7 @@ const useRunFormData = ( pipeline: lastPipeline ?? null, // experiment: null, runType: { type: RunTypeOption.ONE_TRIGGER }, - params: lastPipeline - ? (lastPipeline.parameters || []).map((p) => ({ label: p.name, value: p.value ?? '' })) - : undefined, + params: getParams(lastPipeline), }); const setFunction = objState[1]; useUpdatePipeline(setFunction, initialData); diff --git a/frontend/src/concepts/pipelines/context/PipelinesContext.tsx b/frontend/src/concepts/pipelines/context/PipelinesContext.tsx index 388f0e1671..0a9003de5f 100644 --- a/frontend/src/concepts/pipelines/context/PipelinesContext.tsx +++ b/frontend/src/concepts/pipelines/context/PipelinesContext.tsx @@ -9,7 +9,6 @@ import { StackItem, } from '@patternfly/react-core'; import { ProjectKind } from '~/k8sTypes'; -import { PipelineCoreResourceKF, PipelineRunJobKF } from '~/concepts/pipelines/kfTypes'; import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext'; import DeletePipelineServerModal from '~/concepts/pipelines/content/DeletePipelineServerModal'; import { ConfigurePipelinesServerModal } from '~/concepts/pipelines/content/configurePipelinesServer/ConfigurePipelinesServerModal'; @@ -22,12 +21,8 @@ import useAPIState, { APIState } from './useAPIState'; import usePipelineNamespaceCR, { dspaLoaded, hasServerTimedOut } from './usePipelineNamespaceCR'; import usePipelinesAPIRoute from './usePipelinesAPIRoute'; -type JobStatus = { - loading: boolean; - data: PipelineRunJobKF | null; -}; +type GetJobInformationType = ReturnType['getJobInformation']; -type GetJobInformation = (resource: PipelineCoreResourceKF) => JobStatus; type PipelineContext = { hasCR: boolean; crInitializing: boolean; @@ -38,7 +33,7 @@ type PipelineContext = { refreshState: () => Promise; refreshAPIState: () => void; apiState: APIState; - getJobInformation: GetJobInformation; + getJobInformation: GetJobInformationType; }; const PipelinesContext = React.createContext({ @@ -137,7 +132,7 @@ type UsePipelinesAPI = APIState & { * Allows agnostic functionality to request all watched API to be reacquired. * Triggering this will invalidate the memo for API - pay attention to only calling it once per need. */ - getJobInformation: GetJobInformation; + getJobInformation: GetJobInformationType; refreshAllAPI: () => void; }; diff --git a/frontend/src/concepts/pipelines/context/useJobRelatedInformation.ts b/frontend/src/concepts/pipelines/context/useJobRelatedInformation.ts index 1589c015ad..7db2a2f9ef 100644 --- a/frontend/src/concepts/pipelines/context/useJobRelatedInformation.ts +++ b/frontend/src/concepts/pipelines/context/useJobRelatedInformation.ts @@ -8,7 +8,7 @@ type JobStatus = { data: PipelineRunJobKF | null; }; -export type GetJobInformation = (resource: PipelineCoreResourceKF) => JobStatus; +export type GetJobInformation = (resource?: PipelineCoreResourceKF) => JobStatus; const useJobRelatedInformation = (apiState: APIState): { getJobInformation: GetJobInformation } => { const [jobStorage, setJobStorage] = React.useState<{ [jobId: string]: JobStatus }>({});