diff --git a/console-extensions.json b/console-extensions.json index b6e4b792..616eb9e4 100644 --- a/console-extensions.json +++ b/console-extensions.json @@ -1033,6 +1033,34 @@ "required": ["HIDE_STATIC_PIPELINE_PLUGIN_PIPELINERUN_DETAILS"] } }, + { + "type": "console.page/resource/details", + "properties": { + "model": { + "group": "tekton.dev", + "version": "v1beta1", + "kind": "Task" + }, + "component": { "$codeRef": "taskDetails.TaskDetailsPage" } + }, + "flags": { + "required": ["HIDE_STATIC_PIPELINE_PLUGIN_TASK_DETAILS"] + } + }, + { + "type": "console.page/resource/details", + "properties": { + "model": { + "group": "tekton.dev", + "version": "v1", + "kind": "Task" + }, + "component": { "$codeRef": "taskDetails.TaskDetailsPage" } + }, + "flags": { + "required": ["HIDE_STATIC_PIPELINE_PLUGIN_TASK_DETAILS"] + } + }, { "type": "console.tab/horizontalNav", "properties": { diff --git a/locales/en/plugin__pipelines-console-plugin.json b/locales/en/plugin__pipelines-console-plugin.json index b9d42578..da950850 100644 --- a/locales/en/plugin__pipelines-console-plugin.json +++ b/locales/en/plugin__pipelines-console-plugin.json @@ -18,6 +18,7 @@ "{{min}}m": "{{min}}m", "{{resourceName}} results": "{{resourceName}} results", "{{sec}}s": "{{sec}}s", + "{{taskLabel}} details": "{{taskLabel}} details", "{{taskRunLabel}} details": "{{taskRunLabel}} details", "{{version}} (latest)": "{{version}} (latest)", "<0>{{eventCount}} times in the last <3>": "<0>{{eventCount}} times in the last <3>", @@ -451,6 +452,7 @@ "Tags": "Tags", "Task": "Task", "Task approval required": "Task approval required", + "Task details": "Task details", "Task does not exist": "Task does not exist", "Task status": "Task status", "Task version will be updated across all instances": "Task version will be updated across all instances", diff --git a/package.json b/package.json index 79cf9e24..304aaed3 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,8 @@ "toastContext": "./components/toast", "pipelineApprovalContext": "./components/approval-tasks/approval-notification", "pipelineBuilder": "./components/pipeline-builder", - "catalog": "./components/catalog" + "catalog": "./components/catalog", + "taskDetails": "./components/tasks" }, "dependencies": { "@console/pluginAPI": ">=4.15" diff --git a/src/components/pipeline-topology/TaskList.tsx b/src/components/pipeline-topology/TaskList.tsx index b0c30bc6..c2de76df 100644 --- a/src/components/pipeline-topology/TaskList.tsx +++ b/src/components/pipeline-topology/TaskList.tsx @@ -10,7 +10,7 @@ import RemoveNodeDecorator from './RemoveNodeDecorator'; import { KebabOption, NewTaskNodeCallback } from './types'; import { TaskKind } from '../../types'; import { getReferenceForModel } from '../pipelines-overview/utils'; -import { getResourceModelFromTaskKind } from '../utils/pipeline-augment'; +import { getResourceModelFromTaskKind, getTaskName } from '../utils/pipeline-augment'; import { ResourceIcon } from '@openshift-console/dynamic-plugin-sdk'; import { truncateMiddle } from './truncate-middle'; @@ -20,10 +20,8 @@ const taskToOption = ( task: TaskKind, callback: NewTaskNodeCallback, ): KeyedKebabOption => { - const { - kind, - metadata: { name }, - } = task; + const { kind } = task; + const name = getTaskName(task) return { key: `${name}-${kind}`, diff --git a/src/components/pipelines-tasks/TasksRow.tsx b/src/components/pipelines-tasks/TasksRow.tsx index 24766a0a..3fb7499e 100644 --- a/src/components/pipelines-tasks/TasksRow.tsx +++ b/src/components/pipelines-tasks/TasksRow.tsx @@ -18,6 +18,8 @@ import { } from '@patternfly/react-core'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; import { K8sCommonKebabMenu } from '../utils/k8s-common-kebab-menu'; +import { TaskKind } from '../../types'; +import { getTaskName } from '../utils/pipeline-augment'; type TasksKebabProps = { obj: K8sResourceCommon; @@ -62,10 +64,7 @@ const TaskKebab: React.FC = ({ obj }) => { ); }; -const TaskRow: React.FC> = ({ - activeColumnIDs, - obj, -}) => { +const TaskRow: React.FC> = ({ activeColumnIDs, obj }) => { const { t } = useTranslation('plugin__pipelines-console-plugin'); return ( @@ -74,6 +73,7 @@ const TaskRow: React.FC> = ({ diff --git a/src/components/tasks/TaskDetails.tsx b/src/components/tasks/TaskDetails.tsx new file mode 100644 index 00000000..6b664511 --- /dev/null +++ b/src/components/tasks/TaskDetails.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { TaskModel } from '../../models'; +import { TaskKind } from '../../types'; +import { PageSection } from '@patternfly/react-core'; +import { SectionHeading } from '../pipelines-tasks/tasks-details-pages/headings'; +import { ResourceSummary } from '../details-page/details-page'; +import { WorkspaceDefinitionList } from '../pipelines-tasks'; + +export interface TaskDetailsProps { + obj: TaskKind; +} + +const TaskDetails: React.FC = ({ obj: task }) => { + const { t } = useTranslation('plugin__pipelines-console-plugin'); + return ( + + +
+
+ +
+
+ +
+
+
+ ); +}; + +export default TaskDetails; diff --git a/src/components/tasks/TaskDetailsPage.tsx b/src/components/tasks/TaskDetailsPage.tsx new file mode 100644 index 00000000..c6bfc2a0 --- /dev/null +++ b/src/components/tasks/TaskDetailsPage.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { BreadcrumbItem, Text, TextVariants } from '@patternfly/react-core'; +import { Link, useNavigate, useParams } from 'react-router-dom-v5-compat'; +import { + getGroupVersionKindForModel, + useAccessReview, + useAnnotationsModal, + useDeleteModal, + useK8sWatchResource, + useLabelsModal, +} from '@openshift-console/dynamic-plugin-sdk'; +import TaskDetails from './TaskDetails'; +import DetailsPage from '../details-page/DetailsPage'; +import { navFactory } from '../utils/horizontal-nav'; +import { TaskModel } from '../../models'; +import { TaskKind } from '../../types'; +import { getReferenceForModel } from '../pipelines-overview/utils'; +import { getTaskName } from '../utils/pipeline-augment'; +import { ErrorPage404 } from '../common/error'; +import { LoadingBox } from '../status/status-box'; +import ResourceYAMLEditorTab from '../yaml-editor/ResourceYAMLEditorTab'; + +type TaskDetailsPageProps = { + name: string; + namespace: string; +}; + +const TaskDetailsPage: React.FC = () => { + const { t } = useTranslation('plugin__pipelines-console-plugin'); + const params = useParams(); + const navigate = useNavigate(); + const { name, ns: namespace } = params; + const [task, loaded, loadError] = useK8sWatchResource({ + groupVersionKind: getGroupVersionKindForModel(TaskModel), + namespace, + name, + }); + const launchAnnotationsModal = useAnnotationsModal(task); + const launchLabelsModal = useLabelsModal(task); + const launchDeleteModal = useDeleteModal(task); + const canEditResource = useAccessReview({ + group: TaskModel.apiGroup, + resource: TaskModel.plural, + verb: 'update', + name, + namespace, + }); + const canDeleteResource = useAccessReview({ + group: TaskModel.apiGroup, + resource: TaskModel.plural, + verb: 'delete', + name, + namespace, + }); + + const editURL = `/k8s/ns/${namespace}/${getReferenceForModel( + TaskModel, + )}/${encodeURIComponent(name)}/yaml`; + + const resourceTitleFunc = React.useMemo(() => { + return
{getTaskName(task)}
; + }, [task]); + + if (!loaded) { + return loadError ? : ; + } + + return ( + {resourceTitleFunc}} + actions={[ + { + key: 'edit-labels', + label: t('Edit labels'), + onClick: () => launchLabelsModal(), + disabled: !canEditResource[0], + }, + { + key: 'edit-annotations', + label: t('Edit annotations'), + onClick: () => launchAnnotationsModal(), + disabled: !canEditResource[0], + }, + { + key: 'edit-task', + label: t('Edit {{resourceKind}}', { + resourceKind: TaskModel.kind, + }), + onClick: () => navigate(editURL), + disabled: !canEditResource[0], + }, + { + key: 'delete-task', + label: t('Delete {{resourceKind}}', { + resourceKind: TaskModel.kind, + }), + onClick: () => launchDeleteModal(), + disabled: !canDeleteResource[0], + }, + ]} + pages={[ + navFactory.details(TaskDetails), + navFactory.editYaml(ResourceYAMLEditorTab), + ]} + breadcrumbs={[ + + + {t('Tasks')} + + , + { + path: `/tasks/ns/${namespace}/`, + name: t('Task details'), + }, + ]} + /> + ); +}; + +export default TaskDetailsPage; diff --git a/src/components/tasks/index.ts b/src/components/tasks/index.ts new file mode 100644 index 00000000..4a18fe9d --- /dev/null +++ b/src/components/tasks/index.ts @@ -0,0 +1,2 @@ +export { default as TaskDetails } from './TaskDetails'; +export { default as TaskDetailsPage } from './TaskDetailsPage'; diff --git a/src/components/utils/pipeline-augment.ts b/src/components/utils/pipeline-augment.ts index c8fb99b6..2c8ecae0 100644 --- a/src/components/utils/pipeline-augment.ts +++ b/src/components/utils/pipeline-augment.ts @@ -22,6 +22,7 @@ import { PipelineKind, PipelineRunKind, PipelineTask, + TaskKind, TaskRunKind, } from '../../types'; import { getReferenceForModel } from '../pipelines-overview/utils'; @@ -309,6 +310,10 @@ export const getTaskStatus = ( return taskStatus; }; +export const getTaskName = (task: TaskKind): string => { + return task?.spec?.displayName || task?.metadata?.name || 'anonymous-task'; +}; + export const getResourceModelFromTaskKind = (kind: string): K8sKind => { if (kind === TaskModel.kind || kind === undefined) { return TaskModel; diff --git a/src/types/coreTekton.ts b/src/types/coreTekton.ts index 08895636..68b457ee 100644 --- a/src/types/coreTekton.ts +++ b/src/types/coreTekton.ts @@ -30,6 +30,7 @@ export type TaskResult = { export type TektonTaskSpec = { metadata?: ObjectMetadata; + displayName?: string; description?: string; steps: TektonTaskSteps[]; params?: TektonParam[];