From 1f4e00f92b3adc1b446265a1de277045344ae60f Mon Sep 17 00:00:00 2001 From: Lucifergene <47265560+Lucifergene@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:02:47 +0530 Subject: [PATCH] Added ApprovalTask Node to the Pipeline Visualization --- .../locales/en/pipelines-plugin.json | 11 + .../pipeline-details/StatusIcon.tsx | 33 ++- .../pipeline-topology/ApprovalTaskNode.tsx | 220 ++++++++++++++++++ .../pipeline-topology/CustomTaskNode.scss | 37 +++ .../pipeline-topology/CustomTaskNode.tsx | 197 ++++++++++++++++ .../pipelines/pipeline-topology/const.ts | 7 + .../pipelines/pipeline-topology/factories.ts | 6 + .../pipelines/pipeline-topology/utils.ts | 23 +- .../src/images/FailedApprovalTaskIcon.tsx | 29 +++ .../src/images/SuccessApprovalTaskIcon.tsx | 29 +++ .../src/images/TimeoutApprovalTaskIcon.tsx | 18 ++ .../pipelines-plugin/src/models/pipelines.ts | 36 +++ .../src/types/computedStatus.ts | 12 + .../pipelines-plugin/src/types/task.ts | 28 +++ .../src/utils/pipeline-augment.ts | 24 +- 15 files changed, 706 insertions(+), 4 deletions(-) create mode 100644 frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/ApprovalTaskNode.tsx create mode 100644 frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.scss create mode 100644 frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.tsx create mode 100644 frontend/packages/pipelines-plugin/src/images/FailedApprovalTaskIcon.tsx create mode 100644 frontend/packages/pipelines-plugin/src/images/SuccessApprovalTaskIcon.tsx create mode 100644 frontend/packages/pipelines-plugin/src/images/TimeoutApprovalTaskIcon.tsx diff --git a/frontend/packages/pipelines-plugin/locales/en/pipelines-plugin.json b/frontend/packages/pipelines-plugin/locales/en/pipelines-plugin.json index 5ca80b9d054d..7489ffb48a8d 100644 --- a/frontend/packages/pipelines-plugin/locales/en/pipelines-plugin.json +++ b/frontend/packages/pipelines-plugin/locales/en/pipelines-plugin.json @@ -323,11 +323,13 @@ "Represents the location of the blob storage i.e gs://some-private-bucket": "Represents the location of the blob storage i.e gs://some-private-bucket", "Directory": "Directory", "Represents whether the blob storage is a directory or not": "Represents whether the blob storage is a directory or not", + "Approval Task": "Approval Task", "Task does not exist": "Task does not exist", "Add finally task": "Add finally task", "Add a sequential task after this task": "Add a sequential task after this task", "Add a sequential task before this task": "Add a sequential task before this task", "Add a parallel task": "Add a parallel task", + "Custom Task": "Custom Task", "Installing": "Installing", "Add task": "Add task", "No tasks": "No tasks", @@ -436,6 +438,10 @@ "TaskRun log": "TaskRun log", "Pod not found": "Pod not found", "{{taskLabel}} details": "{{taskLabel}} details", + "CustomRun": "CustomRun", + "CustomRuns": "CustomRuns", + "ApprovalTask": "ApprovalTask", + "ApprovalTasks": "ApprovalTasks", "TektonConfig": "TektonConfig", "TektonConfigs": "TektonConfigs", "TektonHub": "TektonHub", @@ -465,5 +471,10 @@ "Cancelling": "Cancelling", "Pending": "Pending", "PipelineRun not started yet": "PipelineRun not started yet", + "Request sent": "Request sent", + "Approved": "Approved", + "Rejected": "Rejected", + "Timed out": "Timed out", + "Idle": "Idle", "Other": "Other" } \ No newline at end of file diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/detail-page-tabs/pipeline-details/StatusIcon.tsx b/frontend/packages/pipelines-plugin/src/components/pipelines/detail-page-tabs/pipeline-details/StatusIcon.tsx index 7b603ea4b72a..5b7681df724f 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelines/detail-page-tabs/pipeline-details/StatusIcon.tsx +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/detail-page-tabs/pipeline-details/StatusIcon.tsx @@ -4,10 +4,18 @@ import { CheckCircleIcon } from '@patternfly/react-icons/dist/esm/icons/check-ci import { CircleIcon } from '@patternfly/react-icons/dist/esm/icons/circle-icon'; import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import { HourglassHalfIcon } from '@patternfly/react-icons/dist/esm/icons/hourglass-half-icon'; +import NewProcessIcon from '@patternfly/react-icons/dist/esm/icons/new-process-icon'; +import { QuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/question-circle-icon'; +import { ResourcesAlmostEmptyIcon } from '@patternfly/react-icons/dist/esm/icons/resources-almost-empty-icon'; +import { ResourcesAlmostFullIcon } from '@patternfly/react-icons/dist/esm/icons/resources-almost-full-icon'; +import { ResourcesEmptyIcon } from '@patternfly/react-icons/dist/esm/icons/resources-empty-icon'; import { SyncAltIcon } from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon'; import * as cx from 'classnames'; -import { YellowExclamationTriangleIcon } from '@console/dynamic-plugin-sdk'; -import { ComputedStatus } from '../../../../types'; +import { YellowExclamationTriangleIcon } from '@console/dynamic-plugin-sdk/src/app/components/status/icons'; +import FailedApprovalTaskIcon from '../../../../images/FailedApprovalTaskIcon'; +import SuccessApprovalTaskIcon from '../../../../images/SuccessApprovalTaskIcon'; +import TimeoutApprovalTaskIcon from '../../../../images/TimeoutApprovalTaskIcon'; +import { ApprovalStatus, ComputedStatus } from '../../../../types'; import { getRunStatusColor } from '../../../../utils/pipeline-augment'; interface StatusIconProps { @@ -44,6 +52,27 @@ export const StatusIcon: React.FC = ({ status, disableSpin, ... } }; +export const ApprovalStatusIcon: React.FC = ({ status, ...others }) => { + switch (status) { + case ApprovalStatus.Idle: + return ; + case ApprovalStatus.RequestSent: + return ; + case 'partially approved (1)': + return ; + case 'partially approved (2)': + return ; + case ApprovalStatus.Accepted: + return ; + case ApprovalStatus.Rejected: + return ; + case ApprovalStatus.TimedOut: + return ; + default: + return ; + } +}; + export const ColoredStatusIcon: React.FC = ({ status, ...others }) => { return (
; + disableTooltip?: boolean; +}; + +type WatchResource = { + [key: string]: K8sResourceKind[] | K8sResourceKind; +}; + +interface ApprovalTaskComponentProps { + pipelineRunName?: string; + name: string; + loaded?: boolean; + task?: { + data: TaskKind; + }; + status: string; + namespace: string; + disableVisualizationTooltip?: boolean; + width: number; + height: number; + customTask?: K8sResourceKind; +} + +const FILTER_ID = 'SvgTaskDropShadowFilterId'; + +const ApprovalTaskComponent: React.FC = ({ + pipelineRunName, + namespace, + task, + status, + name, + disableVisualizationTooltip, + width, + height, + customTask, +}) => { + const { t } = useTranslation(); + const showStatusState: boolean = !!pipelineRunName; + const visualName = name || _.get(task, ['metadata', 'name'], ''); + const nameRef = React.useRef(); + const pillRef = React.useRef(); + + const path = `${resourcePathFromModel(ApprovalTaskModel, customTask?.metadata?.name, namespace)}`; + + const enableLogLink = status !== ApprovalStatus.Idle && !!path; + const taskStatusColor = status + ? getApprovalStatusColor(status).pftoken.value + : getApprovalStatusColor(ApprovalStatus.Idle).pftoken.value; + + const [hover, hoverRef] = useHover(); + const truncatedVisualName = React.useMemo( + () => truncateMiddle(visualName, { length: showStatusState ? 11 : 14, truncateEnd: true }), + [visualName, showStatusState], + ); + + const renderVisualName = ( + + {truncatedVisualName} + + ); + + let taskPill = ( + + + + {visualName !== truncatedVisualName && disableVisualizationTooltip ? ( + + {renderVisualName} + + ) : ( + renderVisualName + )} + + {showStatusState && ( + + + + )} + + ); + + if (!disableVisualizationTooltip) { + taskPill = ( + + {taskPill} + + ); + } + return ( + + {enableLogLink ? {taskPill} : taskPill} + + ); +}; + +const ApprovalTaskNode: React.FC = ({ element, disableTooltip }) => { + const { height, width } = element.getBounds(); + + const { pipeline, pipelineRun, task } = element.getData(); + + const customTaskName = `${pipelineRun?.metadata?.name}-${task?.name}`; + const pipelineRunStatus = pipelineRun && pipelineRunFilterReducer(pipelineRun); + let customTaskStatus: string = ''; + + const watchedResources = { + customRun: { + groupVersionKind: getGroupVersionKindForModel(CustomRunModelV1Beta1), + name: customTaskName, + namespace: pipeline?.metadata?.namespace, + prop: 'task', + }, + approvalTask: { + groupVersionKind: getGroupVersionKindForModel(ApprovalTaskModel), + name: customTaskName, + namespace: pipeline?.metadata?.namespace, + prop: 'task', + }, + }; + + const resourcesData: WatchK8sResults = useK8sWatchResources( + watchedResources, + ); + + let approvalStatus = (resourcesData?.approvalTask?.data as ApprovalTaskKind)?.status + ?.approvalState; + if (pipelineRunStatus === ComputedStatus.Running && !approvalStatus) { + approvalStatus = ApprovalStatus.Idle; + } + if ( + (resourcesData?.customRun?.data as CustomRunKind)?.spec?.status === CustomRunStatus.RunCancelled + ) { + approvalStatus = ApprovalStatus.TimedOut; + } + customTaskStatus = approvalStatus; + + const taskComponent: JSX.Element = ( + + ); + return taskComponent; +}; + +export default React.memo(observer(ApprovalTaskNode)); diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.scss b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.scss new file mode 100644 index 000000000000..10687079e707 --- /dev/null +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.scss @@ -0,0 +1,37 @@ +.odc-pipeline-topology__task-node { + &.is-link { + > a:hover { + text-decoration: none; + } + } + &:focus { + outline: -webkit-focus-ring-color auto 5px; + } +} + +.odc-pipeline-vis-task { + fill: var(--pf-v5-global--BackgroundColor--light-100); + stroke-width: 1; + cursor: default; + + &.is-selected { + stroke-width: 2; + } + &.is-linked { + cursor: pointer; + } +} + +.odc-pipeline-vis-task-text { + cursor: default; + dominant-baseline: middle; + fill: var(--pf-v5-global--Color--100); + font-size: var(--pf-global--FontSize--sm); + + &.is-text-center { + text-anchor: middle; + } + &.is-linked { + cursor: pointer; + } +} diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.tsx b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.tsx new file mode 100644 index 000000000000..cff7c49f7f1a --- /dev/null +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/CustomTaskNode.tsx @@ -0,0 +1,197 @@ +import * as React from 'react'; +import { Tooltip } from '@patternfly/react-core'; +import QuestionCircleIcon from '@patternfly/react-icons/dist/js/icons/question-circle-icon'; +import { observer, Node, NodeModel, useHover, createSvgIdUrl } from '@patternfly/react-topology'; +import * as cx from 'classnames'; +import * as _ from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom-v5-compat'; +import { + K8sResourceKind, + WatchK8sResults, + getGroupVersionKindForModel, +} from '@console/dynamic-plugin-sdk/src/lib-core'; +import { useK8sWatchResources } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks'; +import { resourcePathFromModel, truncateMiddle } from '@console/internal/components/utils'; +import { CustomRunModelV1Beta1 } from '@console/pipelines-plugin/src/models'; +import { SvgDropShadowFilter } from '@console/topology/src/components/svg'; +import { TaskKind, ApprovalStatus, CustomRunKind } from '../../../types'; +import { getApprovalStatusColor } from '../../../utils/pipeline-augment'; +import { TaskNodeModelData } from './types'; + +import './CustomTaskNode.scss'; + +type CustomTaskNodeProps = { + element: Node; + disableTooltip?: boolean; +}; + +type WatchResource = { + [key: string]: K8sResourceKind[] | K8sResourceKind; +}; + +interface CustomTaskProps { + pipelineRunName?: string; + name: string; + loaded?: boolean; + task?: { + data: TaskKind; + }; + status: string; + namespace: string; + disableVisualizationTooltip?: boolean; + width: number; + height: number; + customTask?: K8sResourceKind; +} + +const FILTER_ID = 'SvgTaskDropShadowFilterId'; + +const CustomTaskComponent: React.FC = ({ + pipelineRunName, + namespace, + task, + status, + name, + disableVisualizationTooltip, + width, + height, + customTask, +}) => { + const { t } = useTranslation(); + const showStatusState: boolean = !!pipelineRunName; + const visualName = name || _.get(task, ['metadata', 'name'], ''); + const nameRef = React.useRef(); + const pillRef = React.useRef(); + + const path = `${resourcePathFromModel( + CustomRunModelV1Beta1, + customTask?.metadata?.name, + namespace, + )}`; + + const enableLogLink = !!path; + const taskStatusColor = status + ? getApprovalStatusColor(status).pftoken.value + : getApprovalStatusColor(ApprovalStatus.Idle).pftoken.value; + + const [hover, hoverRef] = useHover(); + const truncatedVisualName = React.useMemo( + () => truncateMiddle(visualName, { length: showStatusState ? 11 : 14, truncateEnd: true }), + [visualName, showStatusState], + ); + + const renderVisualName = ( + + {truncatedVisualName} + + ); + + let taskPill = ( + + + + {visualName !== truncatedVisualName && disableVisualizationTooltip ? ( + + {renderVisualName} + + ) : ( + renderVisualName + )} + + {showStatusState && ( + + {} + + )} + + ); + + if (!disableVisualizationTooltip) { + taskPill = ( + + {taskPill} + + ); + } + return ( + + {enableLogLink ? {taskPill} : taskPill} + + ); +}; + +const CustomTaskNode: React.FC = ({ element, disableTooltip }) => { + const { height, width } = element.getBounds(); + + const { pipeline, pipelineRun, task } = element.getData(); + + const customTaskName = `${pipelineRun?.metadata?.name}-${task?.name}`; + const customTaskStatus: string = ''; + + const watchedResources = { + customRun: { + groupVersionKind: getGroupVersionKindForModel(CustomRunModelV1Beta1), + name: customTaskName, + namespace: pipeline?.metadata?.namespace, + prop: 'task', + }, + }; + + const resourcesData: WatchK8sResults = useK8sWatchResources( + watchedResources, + ); + + const taskComponent: JSX.Element = ( + + ); + return taskComponent; +}; + +export default React.memo(observer(CustomTaskNode)); diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/const.ts b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/const.ts index 8a23f2603e26..9fbfce66a0d5 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/const.ts +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/const.ts @@ -28,6 +28,8 @@ export const GRAPH_MAX_HEIGHT_PERCENT = 45; export enum NodeType { TASK_NODE = 'task', + CUSTOM_TASK_NODE = 'custom-task', + APPROVAL_TASK_NODE = 'approval-task', SPACER_NODE = 'spacer', LOADING_NODE = 'loading', TASK_LIST_NODE = 'task-list', @@ -100,3 +102,8 @@ export const DAGRE_BUILDER_SPACED_PROPS: dagre.GraphLabel = { ...DAGRE_BUILDER_PROPS, ranksep: NODE_SEPARATION_HORIZONTAL + WHEN_EXPRESSION_SPACING + BUILDER_NODE_ADD_RADIUS * 2, }; + +export enum CustomTask { + APPROVAL_TASK = 'ApprovalTask', + CUSTOM_TASK = 'CustomTask', +} diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/factories.ts b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/factories.ts index 50669ace6bd4..56c17ad835a0 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/factories.ts +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/factories.ts @@ -11,9 +11,11 @@ import { SpacerNode, DefaultTaskGroup, } from '@patternfly/react-topology'; +import ApprovalTaskNode from './ApprovalTaskNode'; import BuilderFinallyNode from './BuilderFinallyNode'; import BuilderNode from './BuilderNode'; import { NodeType, PipelineLayout } from './const'; +import CustomTaskNode from './CustomTaskNode'; import FinallyNode from './FinallyNode'; import InvalidTaskListNode from './InvalidTaskListNode'; import LoadingNode from './LoadingNode'; @@ -66,6 +68,10 @@ export const dagreViewerComponentFactory: ComponentFactory = (kind: ModelKind, t case NodeType.TASK_NODE: case NodeType.FINALLY_NODE: return PipelineTaskNode; + case NodeType.CUSTOM_TASK_NODE: + return CustomTaskNode; + case NodeType.APPROVAL_TASK_NODE: + return ApprovalTaskNode; case NodeType.FINALLY_GROUP: return DefaultTaskGroup; case NodeType.SPACER_NODE: diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/utils.ts b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/utils.ts index a80349048672..acae01a6964e 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/utils.ts +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-topology/utils.ts @@ -76,6 +76,14 @@ const createGenericNode: NodeCreatorSetup = (type, width?, height?) => (name, da // Node variations export const createTaskNode: NodeCreator = createGenericNode(NodeType.TASK_NODE); + +export const createCustomTaskNode: NodeCreator = createGenericNode( + NodeType.CUSTOM_TASK_NODE, +); + +export const createApprovalTaskNode: NodeCreator = createGenericNode( + NodeType.APPROVAL_TASK_NODE, +); export const createSpacerNode: NodeCreator = createGenericNode( NodeType.SPACER_NODE, 0, @@ -122,6 +130,10 @@ export const getNodeCreator = (type: NodeType): NodeCreator 0 ? DEFAULT_BADGE_WIDTH : 0; const isTaskSkipped = pipelineRun?.status?.skippedTasks?.some((t) => t.name === task.name); + const getNodeType = (taskKind: string) => { + if (taskKind === 'Task' || taskKind === 'ClusterTask') { + return NodeType.TASK_NODE; + } + if (taskKind === 'ApprovalTask') { + return NodeType.APPROVAL_TASK_NODE; + } + return NodeType.CUSTOM_TASK_NODE; + }; nodes.push( - createPipelineTaskNode(NodeType.TASK_NODE, { + createPipelineTaskNode(getNodeType(task?.taskRef?.kind), { id: vertex.name, label: vertex.name, width: diff --git a/frontend/packages/pipelines-plugin/src/images/FailedApprovalTaskIcon.tsx b/frontend/packages/pipelines-plugin/src/images/FailedApprovalTaskIcon.tsx new file mode 100644 index 000000000000..837e57e8ff64 --- /dev/null +++ b/frontend/packages/pipelines-plugin/src/images/FailedApprovalTaskIcon.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const FailedApprovalTaskIcon: React.FC> = (props): React.ReactElement => { + return ( + + + + + + ); +}; + +export default FailedApprovalTaskIcon; diff --git a/frontend/packages/pipelines-plugin/src/images/SuccessApprovalTaskIcon.tsx b/frontend/packages/pipelines-plugin/src/images/SuccessApprovalTaskIcon.tsx new file mode 100644 index 000000000000..340725245aac --- /dev/null +++ b/frontend/packages/pipelines-plugin/src/images/SuccessApprovalTaskIcon.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const SuccessApprovalTaskIcon: React.FC> = (props): React.ReactElement => { + return ( + + + + + + ); +}; + +export default SuccessApprovalTaskIcon; diff --git a/frontend/packages/pipelines-plugin/src/images/TimeoutApprovalTaskIcon.tsx b/frontend/packages/pipelines-plugin/src/images/TimeoutApprovalTaskIcon.tsx new file mode 100644 index 000000000000..4114d9f730d4 --- /dev/null +++ b/frontend/packages/pipelines-plugin/src/images/TimeoutApprovalTaskIcon.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const TimeoutApprovalTaskIcon: React.FC> = (props): React.ReactElement => { + return ( + + + + + ); +}; + +export default TimeoutApprovalTaskIcon; diff --git a/frontend/packages/pipelines-plugin/src/models/pipelines.ts b/frontend/packages/pipelines-plugin/src/models/pipelines.ts index c9bf542469cf..735a243df23a 100644 --- a/frontend/packages/pipelines-plugin/src/models/pipelines.ts +++ b/frontend/packages/pipelines-plugin/src/models/pipelines.ts @@ -147,6 +147,42 @@ export const TaskRunModelV1Beta1: K8sKind = { color, }; +export const CustomRunModelV1Beta1: K8sKind = { + apiGroup: 'tekton.dev', + apiVersion: 'v1beta1', + label: 'CustomRun', + // t('pipelines-plugin~CustomRun') + labelKey: 'pipelines-plugin~CustomRun', + // t('pipelines-plugin~CustomRuns') + labelPluralKey: 'pipelines-plugin~CustomRuns', + plural: 'customruns', + abbr: 'CR', + namespaced: true, + kind: 'CustomRun', + id: 'customrun', + labelPlural: 'CustomRuns', + crd: true, + color, +}; + +export const ApprovalTaskModel: K8sKind = { + apiGroup: 'openshift-pipelines.org', + apiVersion: 'v1alpha1', + label: 'ApprovalTask', + // t('pipelines-plugin~ApprovalTask') + labelKey: 'pipelines-plugin~ApprovalTask', + // t('pipelines-plugin~ApprovalTasks') + labelPluralKey: 'pipelines-plugin~ApprovalTasks', + plural: 'approvaltasks', + abbr: 'AT', + namespaced: true, + kind: 'ApprovalTask', + id: 'approvaltask', + labelPlural: 'ApprovalTasks', + crd: true, + color, +}; + export const PipelineResourceModel: K8sKind = { apiGroup: 'tekton.dev', apiVersion: 'v1alpha1', diff --git a/frontend/packages/pipelines-plugin/src/types/computedStatus.ts b/frontend/packages/pipelines-plugin/src/types/computedStatus.ts index 0302d7f162f8..2bab4e7f021e 100644 --- a/frontend/packages/pipelines-plugin/src/types/computedStatus.ts +++ b/frontend/packages/pipelines-plugin/src/types/computedStatus.ts @@ -13,3 +13,15 @@ export enum ComputedStatus { Idle = 'Idle', Other = '-', } + +export enum ApprovalStatus { + Idle = 'idle', + RequestSent = 'wait', + Accepted = 'true', + Rejected = 'false', + TimedOut = 'timeout', +} + +export enum CustomRunStatus { + RunCancelled = 'RunCancelled', +} diff --git a/frontend/packages/pipelines-plugin/src/types/task.ts b/frontend/packages/pipelines-plugin/src/types/task.ts index 7de297102eee..1755e486c085 100644 --- a/frontend/packages/pipelines-plugin/src/types/task.ts +++ b/frontend/packages/pipelines-plugin/src/types/task.ts @@ -1,6 +1,34 @@ import { K8sResourceCommon } from '@console/internal/module/k8s'; +import { CustomRunStatus } from './computedStatus'; import { TektonTaskSpec } from './coreTekton'; export type TaskKind = K8sResourceCommon & { spec: TektonTaskSpec; }; + +export type CustomRunKind = K8sResourceCommon & { + spec: { + customRef: { + apiVersion: string; + kind: string; + }; + serviceAccountName?: string; + status?: CustomRunStatus; + statusMessage?: string; + }; +}; + +export type ApprovalTaskKind = K8sResourceCommon & { + spec?: { + approved?: string; + name?: string; + }; + status?: { + approvalState: string; + approvals: string[]; + approvedBy: { + name: string; + approved: string; + }; + }; +}; diff --git a/frontend/packages/pipelines-plugin/src/utils/pipeline-augment.ts b/frontend/packages/pipelines-plugin/src/utils/pipeline-augment.ts index ce3316cea54a..172c15719907 100644 --- a/frontend/packages/pipelines-plugin/src/utils/pipeline-augment.ts +++ b/frontend/packages/pipelines-plugin/src/utils/pipeline-augment.ts @@ -19,7 +19,14 @@ import { TriggerBindingModel, PipelineModel, } from '../models'; -import { ComputedStatus, PipelineKind, PipelineRunKind, PipelineTask, TaskRunKind } from '../types'; +import { + ApprovalStatus, + ComputedStatus, + PipelineKind, + PipelineRunKind, + PipelineTask, + TaskRunKind, +} from '../types'; import { pipelineRunFilterReducer, SucceedConditionReason } from './pipeline-filter-reducer'; interface Metadata { @@ -131,6 +138,21 @@ export const getRunStatusColor = (status: string): StatusMessage => { } }; +export const getApprovalStatusColor = (status: string): StatusMessage => { + switch (status) { + case ApprovalStatus.RequestSent: + return { message: i18next.t('pipelines-plugin~Request sent'), pftoken: skippedColor }; + case ApprovalStatus.Accepted: + return { message: i18next.t('pipelines-plugin~Approved'), pftoken: successColor }; + case ApprovalStatus.Rejected: + return { message: i18next.t('pipelines-plugin~Rejected'), pftoken: failureColor }; + case ApprovalStatus.TimedOut: + return { message: i18next.t('pipelines-plugin~Timed out'), pftoken: cancelledColor }; + default: + return { message: i18next.t('pipelines-plugin~Idle'), pftoken: skippedColor }; + } +}; + export const truncateName = (name: string, length: number): string => name.length < length ? name : `${name.slice(0, length - 1)}...`;