diff --git a/packages/components/src/components/Actions/Actions.jsx b/packages/components/src/components/Actions/Actions.jsx index 70b169e8e2..39b6e98213 100644 --- a/packages/components/src/components/Actions/Actions.jsx +++ b/packages/components/src/components/Actions/Actions.jsx @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useState } from 'react'; +import { Fragment, useState } from 'react'; import { useIntl } from 'react-intl'; import { Button, @@ -105,7 +105,7 @@ export default function Actions({ items, kind, resource }) { } = item; const disabled = disable && disable(resource); return ( - <> + {hasDivider && } itemAction(resource), modalProperties) } /> - + ); })} diff --git a/packages/components/src/components/DetailsHeader/DetailsHeader.jsx b/packages/components/src/components/DetailsHeader/DetailsHeader.jsx index 41727cfd1d..e2808b4353 100644 --- a/packages/components/src/components/DetailsHeader/DetailsHeader.jsx +++ b/packages/components/src/components/DetailsHeader/DetailsHeader.jsx @@ -65,6 +65,13 @@ export default function DetailsHeader({ function getStatusLabel() { const { reason: taskReason, status: taskStatus } = getStatus(taskRun); + if (stepStatus?.terminationReason === 'Skipped') { + return intl.formatMessage({ + id: 'dashboard.taskRun.status.skipped', + defaultMessage: 'Skipped' + }); + } + if ( status === 'cancelled' || (status === 'terminated' && @@ -126,11 +133,16 @@ export default function DetailsHeader({ if (type === 'taskRun') { ({ reason: reasonToUse, status: statusToUse } = getStatus(taskRun)); statusLabel = - reasonToUse || - intl.formatMessage({ - id: 'dashboard.taskRun.status.pending', - defaultMessage: 'Pending' - }); + reason === 'tkn-dashboard:skipped' + ? intl.formatMessage({ + id: 'dashboard.taskRun.status.skipped', + defaultMessage: 'Skipped' + }) + : reasonToUse || + intl.formatMessage({ + id: 'dashboard.taskRun.status.pending', + defaultMessage: 'Pending' + }); } else { statusLabel = getStatusLabel(); } @@ -140,14 +152,20 @@ export default function DetailsHeader({ className="tkn--step-details-header" data-status={statusToUse} data-reason={reasonToUse} + data-termination-reason={stepStatus?.terminationReason} >

} hasWarning={hasWarning} - reason={reasonToUse} + reason={reason === 'tkn-dashboard:skipped' ? reason : reasonToUse} status={statusToUse} - {...(type === 'step' ? { type: 'inverse' } : null)} + {...(type === 'step' + ? { + terminationReason: stepStatus?.terminationReason, + type: 'inverse' + } + : null)} /> {displayName} diff --git a/packages/components/src/components/DetailsHeader/DetailsHeader.stories.js b/packages/components/src/components/DetailsHeader/DetailsHeader.stories.js index 93edccc867..8eefadfb0d 100644 --- a/packages/components/src/components/DetailsHeader/DetailsHeader.stories.js +++ b/packages/components/src/components/DetailsHeader/DetailsHeader.stories.js @@ -13,12 +13,13 @@ limitations under the License. import DetailsHeader from './DetailsHeader'; -const getTaskRun = ({ reason, status }) => ({ +const getTaskRun = ({ reason, status, terminationReason }) => ({ status: { conditions: [ { reason, status, + terminationReason, type: 'Succeeded' } ] @@ -71,6 +72,35 @@ export const CompletedWithWarning = { name: 'Completed with warning' }; +export const SkippedTask = { + args: { + reason: 'tkn-dashboard:skipped', + displayName: 'build', + taskRun: {}, + type: 'taskRun' + }, + argTypes: { + type: { + control: false + } + } +}; + +export const SkippedStep = { + args: { + reason: 'Completed', + status: 'terminated', + stepStatus: { terminationReason: 'Skipped' }, + displayName: 'build', + type: 'step' + }, + argTypes: { + type: { + control: false + } + } +}; + export const Failed = { args: { displayName: 'build', diff --git a/packages/components/src/components/DetailsHeader/DetailsHeader.test.jsx b/packages/components/src/components/DetailsHeader/DetailsHeader.test.jsx index 81e932f867..f23793a4da 100644 --- a/packages/components/src/components/DetailsHeader/DetailsHeader.test.jsx +++ b/packages/components/src/components/DetailsHeader/DetailsHeader.test.jsx @@ -104,6 +104,43 @@ describe('DetailsHeader', () => { expect(queryByText(/pending/i)).toBeTruthy(); }); + it('renders the skipped state for a step', () => { + const taskRun = { + status: { + conditions: [ + { + reason: 'Completed', + status: 'True', + type: 'Succeeded' + } + ] + } + }; + + const { queryByText } = render( + + ); + expect(queryByText(/skipped/i)).toBeTruthy(); + }); + + it('renders the skipped state for a TaskRun', () => { + const taskRun = {}; + + const { queryByText } = render( + + ); + expect(queryByText(/skipped/i)).toBeTruthy(); + }); + it('renders no duration for a running step', () => { const stepStatus = { running: { diff --git a/packages/components/src/components/DetailsHeader/_DetailsHeader.scss b/packages/components/src/components/DetailsHeader/_DetailsHeader.scss index dbe5ee18ae..6ccfeba376 100644 --- a/packages/components/src/components/DetailsHeader/_DetailsHeader.scss +++ b/packages/components/src/components/DetailsHeader/_DetailsHeader.scss @@ -86,7 +86,7 @@ header.tkn--step-details-header { color: $support-info; } } - &[data-status='terminated'][data-reason='Completed'], + &[data-status='terminated'][data-reason='Completed']:not([data-termination-reason='Skipped']), &[data-status='True'] { .tkn--status-label { color: $support-success; diff --git a/packages/components/src/components/Log/Log.jsx b/packages/components/src/components/Log/Log.jsx index a405115224..b3348b63b4 100644 --- a/packages/components/src/components/Log/Log.jsx +++ b/packages/components/src/components/Log/Log.jsx @@ -276,9 +276,16 @@ export class LogContainer extends Component { ); }; - getTrailerMessage = ({ exitCode, reason }) => { + getTrailerMessage = ({ exitCode, reason, terminationReason }) => { const { forcePolling, intl } = this.props; + if (terminationReason === 'Skipped') { + return intl.formatMessage({ + id: 'dashboard.pipelineRun.stepSkipped', + defaultMessage: 'Step skipped' + }); + } + if (reason && forcePolling) { return ( <> @@ -397,7 +404,11 @@ export class LogContainer extends Component { logTrailer = () => { const { forcePolling, stepStatus } = this.props; const { exitCode, reason } = (stepStatus && stepStatus.terminated) || {}; - const trailer = this.getTrailerMessage({ exitCode, reason }); + const trailer = this.getTrailerMessage({ + exitCode, + reason, + terminationReason: stepStatus?.terminationReason + }); if (!trailer) { return null; } diff --git a/packages/components/src/components/Log/Log.stories.jsx b/packages/components/src/components/Log/Log.stories.jsx index 8a1eb1f09a..20b0c543a0 100644 --- a/packages/components/src/components/Log/Log.stories.jsx +++ b/packages/components/src/components/Log/Log.stories.jsx @@ -97,6 +97,16 @@ export const Performance = { name: 'performance test (<20,000 lines with ANSI)' }; +export const Skipped = { + args: { + fetchLogs: () => 'This step was skipped', + stepStatus: { + terminated: { reason: 'Completed', exitCode: 0 }, + terminationReason: 'Skipped' + } + } +}; + export const Toolbar = { args: { fetchLogs: () => 'A log message', diff --git a/packages/components/src/components/Log/Log.test.jsx b/packages/components/src/components/Log/Log.test.jsx index aefe896caf..725fc7b6d6 100644 --- a/packages/components/src/components/Log/Log.test.jsx +++ b/packages/components/src/components/Log/Log.test.jsx @@ -61,6 +61,19 @@ describe('Log', () => { await waitFor(() => getByText(/step failed/i)); }); + it('renders skipped trailer', async () => { + const { getByText } = render( + 'testing'} + /> + ); + await waitFor(() => getByText(/step skipped/i)); + }); + it('renders pending trailer when step complete and forcePolling is true', async () => { const { getByText, queryByText } = render( skipped.name === selectedTaskId + ); + return ( <> {(selectedStepId && ( @@ -331,6 +337,7 @@ export default /* istanbul ignore next */ function PipelineRun({ definition={definition} logContainer={logContainer} onViewChange={onViewChange} + skippedTask={skippedTask} stepName={selectedStepId} stepStatus={stepStatus} taskRun={taskRun} @@ -341,6 +348,7 @@ export default /* istanbul ignore next */ function PipelineRun({ { }} onViewChange={selectedView => updateArgs({ view: selectedView })} pipelineRun={pipelineRun} - taskRuns={[taskRun, taskRunWithWarning]} + taskRuns={[ + taskRun, + taskRunWithWarning, + taskRunSkipped, + taskRunWithSkippedStep + ]} tasks={[task]} /> ); diff --git a/packages/components/src/components/StatusIcon/StatusIcon.jsx b/packages/components/src/components/StatusIcon/StatusIcon.jsx index cbaf912f11..f95071e277 100644 --- a/packages/components/src/components/StatusIcon/StatusIcon.jsx +++ b/packages/components/src/components/StatusIcon/StatusIcon.jsx @@ -18,6 +18,8 @@ import { CloseFilled, CloseOutline, Time as Pending, + Undefined, + UndefinedFilled, WarningAltFilled as WarningFilled } from '@carbon/react/icons'; import { classNames, isRunning } from '@tektoncd/dashboard-utils'; @@ -30,6 +32,7 @@ const icons = { error: CloseOutline, pending: Pending, running: Spinner, + skipped: Undefined, success: CheckmarkOutline, warning: WarningFilled }, @@ -38,6 +41,7 @@ const icons = { error: CloseFilled, pending: Pending, running: Spinner, + skipped: UndefinedFilled, success: CheckmarkFilled, warning: CheckmarkFilledWarning } @@ -63,11 +67,17 @@ export default function StatusIcon({ isCustomTask, reason, status, + terminationReason, title, type = 'normal' }) { let statusClass; if ( + (!status && reason === 'tkn-dashboard:skipped') || + terminationReason === 'Skipped' + ) { + statusClass = 'skipped'; + } else if ( (!status && !DefaultIcon) || (status === 'Unknown' && reason === 'Pending') ) { diff --git a/packages/components/src/components/StatusIcon/StatusIcon.stories.jsx b/packages/components/src/components/StatusIcon/StatusIcon.stories.jsx index 97814d7054..e121301f56 100644 --- a/packages/components/src/components/StatusIcon/StatusIcon.stories.jsx +++ b/packages/components/src/components/StatusIcon/StatusIcon.stories.jsx @@ -15,7 +15,7 @@ limitations under the License. import { Pending as DefaultStepIcon, PendingFilled as DefaultTaskIcon, - UndefinedFilled as UndefinedIcon + UnknownFilled as UnknownIcon } from '@carbon/react/icons'; import StatusIcon from './StatusIcon'; @@ -90,6 +90,10 @@ export const SucceededWithWarning = { name: 'Succeeded with warning' }; +export const Skipped = { + args: { status: 'True', terminationReason: 'Skipped' } +}; + export const DefaultTask = { args: { DefaultIcon: DefaultTaskIcon }, name: 'Task default - no status received yet' @@ -101,7 +105,7 @@ export const DefaultStep = { }; export const CustomRun = { - args: { DefaultIcon: UndefinedIcon }, + args: { DefaultIcon: UnknownIcon }, name: 'CustomRun (unknown status)' }; @@ -171,6 +175,10 @@ export const AllIcons = { Succeeded with warning +
  • + + Skipped +
  • Default - no status received yet @@ -199,6 +207,10 @@ export const AllIcons = { Succeeded with warning
  • +
  • + + Skipped +
  • Default - no status received yet diff --git a/packages/components/src/components/StatusIcon/StatusIcon.test.jsx b/packages/components/src/components/StatusIcon/StatusIcon.test.jsx index 67e07b7ce2..184c2a7833 100644 --- a/packages/components/src/components/StatusIcon/StatusIcon.test.jsx +++ b/packages/components/src/components/StatusIcon/StatusIcon.test.jsx @@ -23,6 +23,18 @@ describe('StatusIcon', () => { expect(queryByText(title)).toBeTruthy(); }); + it('renders success state', () => { + render(); + }); + + it('renders success with warning state', () => { + render(); + }); + + it('renders step success state', () => { + render(); + }); + it('renders PipelineRunCancelled state', () => { render(); }); @@ -39,6 +51,20 @@ describe('StatusIcon', () => { render(); }); + it('renders Skipped state', () => { + render( + + ); + }); + + it('renders Custom Task running state', () => { + render(); + }); + it('gracefully handles unsupported state', () => { render(); }); diff --git a/packages/components/src/components/Step/Step.jsx b/packages/components/src/components/Step/Step.jsx index c3fa96a764..f60d933ee1 100644 --- a/packages/components/src/components/Step/Step.jsx +++ b/packages/components/src/components/Step/Step.jsx @@ -23,7 +23,8 @@ export default function Step({ reason, selected, status, - stepName = 'unknown' + stepName = 'unknown', + terminationReason }) { const intl = useIntl(); @@ -33,6 +34,12 @@ export default function Step({ } function getStatusLabel() { + if (terminationReason === 'Skipped') { + return intl.formatMessage({ + id: 'dashboard.taskRun.status.skipped', + defaultMessage: 'Skipped' + }); + } if ( status === 'cancelled' || (status === 'terminated' && @@ -93,6 +100,7 @@ export default function Step({ data-status={status} data-reason={reason} data-selected={selected || undefined} + data-termination-reason={terminationReason} > diff --git a/packages/components/src/components/Step/Step.stories.js b/packages/components/src/components/Step/Step.stories.js index 63cafbb50d..0a7affc6c8 100644 --- a/packages/components/src/components/Step/Step.stories.js +++ b/packages/components/src/components/Step/Step.stories.js @@ -42,4 +42,12 @@ export const CompletedWithWarning = { name: 'Completed with warning' }; +export const Skipped = { + args: { + reason: 'Completed', + status: 'terminated', + terminationReason: 'Skipped' + } +}; + export const Error = { args: { reason: 'Error', status: 'terminated' } }; diff --git a/packages/components/src/components/Step/Step.test.jsx b/packages/components/src/components/Step/Step.test.jsx index f53c496899..c7cfdbe485 100644 --- a/packages/components/src/components/Step/Step.test.jsx +++ b/packages/components/src/components/Step/Step.test.jsx @@ -74,6 +74,18 @@ it('Step renders completed with warning state', () => { expect(queryByText(/Completed with exit code 1/i)).toBeTruthy(); }); +it('Step renders skipped state', () => { + const { queryByText } = render( + + ); + expect(queryByText(/Skipped/i)).toBeTruthy(); +}); + it('Step renders error state', () => { const { queryByText } = render(); expect(queryByText(/Failed/i)).toBeTruthy(); diff --git a/packages/components/src/components/StepDetails/StepDetails.jsx b/packages/components/src/components/StepDetails/StepDetails.jsx index faea392934..34af8b9f0a 100644 --- a/packages/components/src/components/StepDetails/StepDetails.jsx +++ b/packages/components/src/components/StepDetails/StepDetails.jsx @@ -17,6 +17,7 @@ import { getStatus, getStepStatusReason } from '@tektoncd/dashboard-utils'; import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react'; import DetailsHeader from '../DetailsHeader'; +import Log from '../Log'; import StepDefinition from '../StepDefinition'; const tabs = ['logs', 'details']; @@ -30,6 +31,7 @@ const StepDetails = ({ definition, logContainer, onViewChange = defaults.onViewChange, + skippedTask, stepName, stepStatus, taskRun = defaults.taskRun, @@ -77,7 +79,21 @@ const StepDetails = ({ - {selectedTabIndex === 0 && logContainer} + + {selectedTabIndex === 0 && skippedTask ? ( + + intl.formatMessage({ + id: 'dashboard.taskRun.logs.skipped', + defaultMessage: + 'This step did not run as the task was skipped. See task status for more details.' + }) + } + /> + ) : ( + logContainer + )} + {selectedTabIndex === 1 && ( diff --git a/packages/components/src/components/StepDetails/StepDetails.stories.jsx b/packages/components/src/components/StepDetails/StepDetails.stories.jsx index b7cf5e9188..80657cd04c 100644 --- a/packages/components/src/components/StepDetails/StepDetails.stories.jsx +++ b/packages/components/src/components/StepDetails/StepDetails.stories.jsx @@ -16,16 +16,23 @@ import { useArgs } from '@storybook/preview-api'; import Log from '../Log'; import StepDetails from './StepDetails'; -function getStepStatus({ exitCode = 0 } = {}) { - return { terminated: { exitCode, reason: 'Completed' } }; +function getStepStatus({ exitCode = 0, terminationReason } = {}) { + return { terminated: { exitCode, reason: 'Completed' }, terminationReason }; } const ansiLog = '\n=== demo-pipeline-run-1-build-skaffold-app-2mrdg-pod-59e217: build-step-git-source-skaffold-git-ml8j4 ===\n{"level":"info","ts":1553865693.943092,"logger":"fallback-logger","caller":"git-init/main.go:100","msg":"Successfully cloned https://github.com/GoogleContainerTools/skaffold @ \\"master\\" in path \\"/workspace\\""}\n\n=== demo-pipeline-run-1-build-skaffold-app-2mrdg-pod-59e217: build-step-build-and-push ===\n\u001b[36mINFO\u001b[0m[0000] Downloading base image golang:1.10.1-alpine3.7\n2019/03/29 13:21:34 No matching credentials were found, falling back on anonymous\n\u001b[36mINFO\u001b[0m[0001] Executing 0 build triggers\n\u001b[36mINFO\u001b[0m[0001] Unpacking rootfs as cmd RUN go build -o /app . requires it.\n\u001b[36mINFO\u001b[0m[0010] Taking snapshot of full filesystem...\n\u001b[36mINFO\u001b[0m[0015] Using files from context: [/workspace/examples/microservices/leeroy-app/app.go]\n\u001b[36mINFO\u001b[0m[0015] COPY app.go .\n\u001b[36mINFO\u001b[0m[0015] Taking snapshot of files...\n\u001b[36mINFO\u001b[0m[0015] RUN go build -o /app .\n\u001b[36mINFO\u001b[0m[0015] cmd: /bin/sh\n\u001b[36mINFO\u001b[0m[0015] args: [-c go build -o /app .]\n\u001b[36mINFO\u001b[0m[0016] Taking snapshot of full filesystem...\n\u001b[36mINFO\u001b[0m[0036] CMD ["./app"]\n\u001b[36mINFO\u001b[0m[0036] COPY --from=builder /app .\n\u001b[36mINFO\u001b[0m[0036] Taking snapshot of files...\nerror pushing image: failed to push to destination gcr.io/christiewilson-catfactory/leeroy-app:latest: Get https://gcr.io/v2/token?scope=repository%3Achristiewilson-catfactory%2Fleeroy-app%3Apush%2Cpull\u0026scope=repository%3Alibrary%2Falpine%3Apull\u0026service=gcr.io exit status 1\n\n=== demo-pipeline-run-1-build-skaffold-app-2mrdg-pod-59e217: nop ===\nBuild successful\n'; -function getLogContainer({ exitCode = 0 } = {}) { +function getLogContainer({ + exitCode = 0, + logContent = ansiLog, + terminationReason +} = {}) { return ( - ansiLog} stepStatus={getStepStatus({ exitCode })} /> + logContent} + stepStatus={getStepStatus({ exitCode, terminationReason })} + /> ); } @@ -70,3 +77,42 @@ export const WithWarning = { ); } }; + +export const SkippedTask = { + args: { + logContainer: getLogContainer(), + skippedTask: {} + }, + render: args => { + const [, updateArgs] = useArgs(); + return ( + updateArgs({ view: selectedView })} + /> + ); + } +}; + +export const SkippedStep = { + args: { + logContainer: getLogContainer({ + logContent: + 'Step was skipped due to when expressions were evaluated to false.', + terminationReason: 'Skipped' + }), + stepStatus: { + terminated: { exitCode: 0, reason: 'Completed' }, + terminationReason: 'Skipped' + } + }, + render: args => { + const [, updateArgs] = useArgs(); + return ( + updateArgs({ view: selectedView })} + /> + ); + } +}; diff --git a/packages/components/src/components/StepDetails/StepDetails.test.jsx b/packages/components/src/components/StepDetails/StepDetails.test.jsx index b65d52dbef..7cef088806 100644 --- a/packages/components/src/components/StepDetails/StepDetails.test.jsx +++ b/packages/components/src/components/StepDetails/StepDetails.test.jsx @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { fireEvent } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import { renderWithRouter } from '../../utils/test'; import StepDetails from './StepDetails'; @@ -53,4 +53,12 @@ describe('StepDetails', () => { fireEvent.click(getByText(/logs/i)); }); + + it('renders skipped Task state', async () => { + const { getByText } = renderWithRouter( + + ); + + await waitFor(() => getByText(/task was skipped/i)); + }); }); diff --git a/packages/components/src/components/Task/Task.jsx b/packages/components/src/components/Task/Task.jsx index 72002bcfa7..412d8412f0 100644 --- a/packages/components/src/components/Task/Task.jsx +++ b/packages/components/src/components/Task/Task.jsx @@ -45,7 +45,7 @@ class Task extends Component { const { reason, selectedStepId, steps } = this.props; let hasWarning = false; const stepData = updateUnexecutedSteps(steps).map(step => { - const { name } = step; + const { name, terminationReason } = step; const { exitCode, status, @@ -62,7 +62,14 @@ class Task extends Component { ? 'cancelled' : status; - return { exitCode, name, selected, stepReason, stepStatus }; + return { + exitCode, + name, + selected, + stepReason, + stepStatus, + terminationReason + }; }); if (propagateWarning) { @@ -237,7 +244,14 @@ class Task extends Component { {expanded && (
      {this.getStepData().map(step => { - const { exitCode, name, selected, stepReason, stepStatus } = step; + const { + exitCode, + name, + selected, + stepReason, + stepStatus, + terminationReason + } = step; return ( ); })} diff --git a/packages/components/src/components/Task/Task.stories.jsx b/packages/components/src/components/Task/Task.stories.jsx index 6b24cda1b1..56b6f1908c 100644 --- a/packages/components/src/components/Task/Task.stories.jsx +++ b/packages/components/src/components/Task/Task.stories.jsx @@ -51,6 +51,8 @@ export const Pending = { args: { ...Unknown.args, reason: 'Pending' } }; export const Running = { args: { ...Unknown.args, reason: 'Running' } }; +export const Skipped = { args: { reason: 'tkn-dashboard:skipped' } }; + export const Expanded = args => { const [, updateArgs] = useArgs(); @@ -64,6 +66,11 @@ export const Expanded = args => { reason="Running" steps={[ { name: 'lint', terminated: { exitCode: 0, reason: 'Completed' } }, + { + name: 'check', + terminated: { exitCode: 0, reason: 'Completed' }, + terminationReason: 'Skipped' + }, { name: 'test', terminated: { exitCode: 1, reason: 'Completed' } }, { name: 'build', running: {} }, { name: 'deploy', running: {} } diff --git a/packages/components/src/components/Task/Task.test.jsx b/packages/components/src/components/Task/Task.test.jsx index d2e3ad4ab4..bae6bf0a9f 100644 --- a/packages/components/src/components/Task/Task.test.jsx +++ b/packages/components/src/components/Task/Task.test.jsx @@ -147,6 +147,10 @@ describe('Task', () => { render(); }); + it('renders skipped state', () => { + render(); + }); + it('renders cancelled state', () => { render( + {intl.formatMessage({ id: 'dashboard.taskRun.params', defaultMessage: 'Parameters' @@ -180,7 +181,7 @@ const TaskRunDetails = ({ ); tabPanels.push( - + {selectedTabIndex === tabPanels.length && (
      {paramsTable}
      )} @@ -189,7 +190,7 @@ const TaskRunDetails = ({ } if (resultsTable) { tabList.push( - + {intl.formatMessage({ id: 'dashboard.taskRun.results', defaultMessage: 'Results' @@ -197,7 +198,7 @@ const TaskRunDetails = ({ ); tabPanels.push( - + {selectedTabIndex === tabPanels.length && (
      {resultsTable}
      )} @@ -205,7 +206,7 @@ const TaskRunDetails = ({ ); } tabList.push( - + {intl.formatMessage({ id: 'dashboard.taskRun.status', defaultMessage: 'Status' @@ -213,13 +214,14 @@ const TaskRunDetails = ({ ); tabPanels.push( - + {selectedTabIndex === tabPanels.length && (
      ); - if (pod) { - tabList.push(Pod); + if (!skippedTask && pod) { + tabList.push(Pod); tabPanels.push( - + {selectedTabIndex === tabPanels.length && (
      {hasEvents ? ( @@ -272,6 +274,7 @@ const TaskRunDetails = ({ diff --git a/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx b/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx index 77bff4649c..d5d36b16fb 100644 --- a/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx +++ b/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx @@ -194,3 +194,13 @@ export const Pod = { ); } }; + +export const Skipped = { + args: { + ...Pod.args, + skippedTask: { + reason: 'When Expressions evaluated to false', + whenExpressions: [{ cel: `'yes'=='missing'` }] + } + } +}; diff --git a/packages/components/src/components/TaskRunDetails/TaskRunDetails.test.jsx b/packages/components/src/components/TaskRunDetails/TaskRunDetails.test.jsx index 607377e371..c510574c1a 100644 --- a/packages/components/src/components/TaskRunDetails/TaskRunDetails.test.jsx +++ b/packages/components/src/components/TaskRunDetails/TaskRunDetails.test.jsx @@ -247,4 +247,31 @@ describe('TaskRunDetails', () => { expect(queryByText('Resource')).toBeFalsy(); expect(queryByText(waitingMessage)).toBeTruthy(); }); + + it('renders skipped task', () => { + const reason = 'When Expressions evaluated to false'; + const podName = 'fake_pod'; + const pod = { metadata: { name: podName } }; + const taskRun = { + metadata: { name: 'task-run-name' }, + spec: {}, + status: {} + }; + const { queryByText } = render( + + ); + expect(queryByText('Pod')).toBeFalsy(); + expect(queryByText(reason)).toBeTruthy(); + }); }); diff --git a/packages/components/src/components/TaskTree/TaskTree.jsx b/packages/components/src/components/TaskTree/TaskTree.jsx index a38393f50d..09e6944803 100644 --- a/packages/components/src/components/TaskTree/TaskTree.jsx +++ b/packages/components/src/components/TaskTree/TaskTree.jsx @@ -15,6 +15,7 @@ import { getStatus, labels as labelConstants } from '@tektoncd/dashboard-utils'; import Task from '../Task'; const defaults = { + skippedTasks: [], taskRuns: [] }; @@ -26,6 +27,7 @@ const TaskTree = ({ selectedStepId, selectedTaskId, selectedTaskRunName, + skippedTasks = defaults.skippedTasks, taskRuns = defaults.taskRuns }) => { if (!taskRuns) { @@ -62,7 +64,9 @@ const TaskTree = ({ }; } - const { reason, status } = getStatus(taskRunToUse); + const taskRunStatus = getStatus(taskRunToUse); + let { reason } = taskRunStatus; + const { status } = taskRunStatus; const { steps } = taskRunToUse.status || {}; const expanded = // should only have 1 expanded task at a time (may change in a future design) @@ -82,6 +86,15 @@ const TaskTree = ({ hasExpandedTask = true; } + if ( + !reason && + skippedTasks.find( + skippedTask => skippedTask.name === pipelineTaskName + ) + ) { + reason = 'tkn-dashboard:skipped'; + } + const selectDefaultStep = !selectedTaskId || (isSelectedTaskMatrix && !selectedTaskRunName); return ( diff --git a/packages/components/src/components/TaskTree/TaskTree.stories.jsx b/packages/components/src/components/TaskTree/TaskTree.stories.jsx index 3f1a7fa1ea..2edeb995a9 100644 --- a/packages/components/src/components/TaskTree/TaskTree.stories.jsx +++ b/packages/components/src/components/TaskTree/TaskTree.stories.jsx @@ -19,6 +19,7 @@ export default { args: { selectedStepId: undefined, selectedTaskId: undefined, + skippedTasks: [{ name: 'Task 2' }], taskRuns: [ { metadata: { @@ -30,8 +31,8 @@ export default { { reason: 'Completed', status: 'True', type: 'Succeeded' } ], steps: [ - { name: 'build', terminated: { reason: 'Completed' } }, - { name: 'test', terminated: { reason: 'Completed' } } + { name: 'build', terminated: { exitCode: 0, reason: 'Completed' } }, + { name: 'test', terminated: { exitCode: 1, reason: 'Completed' } } ] } }, @@ -40,6 +41,16 @@ export default { labels: { 'tekton.dev/pipelineTask': 'Task 2' }, uid: 'task2' }, + status: { + conditions: [], + steps: [{ name: 'build' }, { name: 'test' }] + } + }, + { + metadata: { + labels: { 'tekton.dev/pipelineTask': 'Task 3' }, + uid: 'task3' + }, status: { conditions: [ { reason: 'Failed', status: 'False', type: 'Succeeded' } @@ -53,10 +64,10 @@ export default { }, { metadata: { - labels: { 'tekton.dev/pipelineTask': 'Task 3' }, - uid: 'task3' + labels: { 'tekton.dev/pipelineTask': 'Task 4' }, + uid: 'task4' }, - pipelineTaskName: 'Task 3', + pipelineTaskName: 'Task 4', status: { conditions: [ { reason: 'Running', status: 'Unknown', type: 'Succeeded' } diff --git a/packages/components/src/components/TaskTree/TaskTree.test.jsx b/packages/components/src/components/TaskTree/TaskTree.test.jsx index c0e67f78b2..55e68bf369 100644 --- a/packages/components/src/components/TaskTree/TaskTree.test.jsx +++ b/packages/components/src/components/TaskTree/TaskTree.test.jsx @@ -84,7 +84,7 @@ it('TaskTree renders when taskRuns contains a falsy run', () => { render(); }); -it('TaskTree renders and expands first Task in TaskRun with no error', () => { +it('TaskTree renders and expands first Task in run with no error', () => { const { queryByText } = render(); // Selected Task should have two child elements. The anchor and ordered list // of steps in expanded task @@ -99,7 +99,7 @@ it('TaskTree renders and expands first Task in TaskRun with no error', () => { ).toHaveLength(1); }); -it('TaskTree renders and expands error Task in TaskRun', () => { +it('TaskTree renders and expands error Task', () => { const props = getProps(); props.taskRuns[1].status.conditions[0].status = 'False'; @@ -117,7 +117,7 @@ it('TaskTree renders and expands error Task in TaskRun', () => { ).toHaveLength(1); }); -it('TaskTree renders and expands first error Task in TaskRun', () => { +it('TaskTree renders and expands first error Task', () => { const props = getProps(); props.taskRuns[1].status.conditions[0].status = 'False'; props.taskRuns[2].status.conditions[0].status = 'False'; @@ -136,6 +136,23 @@ it('TaskTree renders and expands first error Task in TaskRun', () => { ).toHaveLength(1); }); +it('TaskTree renders skipped Task', () => { + const { queryByText } = render( + + ); + // Selected Task should have two child elements. The anchor and ordered list + // of steps in expanded task + expect(queryByText('A Task').parentNode.parentNode.childNodes).toHaveLength( + 2 + ); + expect( + queryByText('A Second Task').parentNode.parentNode.childNodes + ).toHaveLength(1); + expect( + queryByText('A Third Task').parentNode.parentNode.childNodes + ).toHaveLength(1); +}); + it('TaskTree handles click event on Task', () => { const onSelect = vi.fn(); const { getByText } = render( diff --git a/src/containers/CustomRun/CustomRun.jsx b/src/containers/CustomRun/CustomRun.jsx index 63e1afcddd..9fbe9ba096 100644 --- a/src/containers/CustomRun/CustomRun.jsx +++ b/src/containers/CustomRun/CustomRun.jsx @@ -16,7 +16,7 @@ import { useState } from 'react'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { useIntl } from 'react-intl'; import { InlineNotification } from '@carbon/react'; -import { UndefinedFilled as UndefinedIcon } from '@carbon/react/icons'; +import { UnknownFilled as UnknownIcon } from '@carbon/react/icons'; import { ActionableNotification, Actions, @@ -69,7 +69,7 @@ function getRunStatusIcon(run) { const { reason, status } = getStatus(run); return ( } + DefaultIcon={props => } isCustomTask reason={reason} status={status} diff --git a/src/nls/messages_de.json b/src/nls/messages_de.json index 38d1aa16b4..6b9f57148d 100644 --- a/src/nls/messages_de.json +++ b/src/nls/messages_de.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "Schritt abgeschlossen", "dashboard.pipelineRun.stepCompleted.exitCode": "", "dashboard.pipelineRun.stepFailed": "Schritt fehlgeschlagen", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "Fehler beim Laden von PipelineRuns", "dashboard.pipelines.errorLoading": "", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "", "dashboard.tableHeader.value": "", "dashboard.taskRun.logs": "Protokolle", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "", "dashboard.taskRun.results": "", "dashboard.taskRun.status": "Status", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "Nicht ausgeführt", "dashboard.taskRun.status.pending": "Anstehend", "dashboard.taskRun.status.running": "Wird ausgeführt", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "Abgeschlossen", "dashboard.taskRun.status.succeeded.warning": "", "dashboard.taskRun.status.waiting": "Wartestatus", diff --git a/src/nls/messages_en.json b/src/nls/messages_en.json index 11f6381cdd..50d6368748 100644 --- a/src/nls/messages_en.json +++ b/src/nls/messages_en.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "Step completed successfully", "dashboard.pipelineRun.stepCompleted.exitCode": "Step completed with exit code {exitCode}", "dashboard.pipelineRun.stepFailed": "Step failed", + "dashboard.pipelineRun.stepSkipped": "Step skipped", "dashboard.pipelineRuns.error": "Error loading PipelineRuns", "dashboard.pipelines.errorLoading": "Error loading Pipelines", "dashboard.pipelines.v1Resources.label": "Use Tekton Pipelines API version v1", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "Task", "dashboard.tableHeader.value": "Value", "dashboard.taskRun.logs": "Logs", + "dashboard.taskRun.logs.skipped": "This step did not run as the task was skipped. See task status for more details.", "dashboard.taskRun.params": "Parameters", "dashboard.taskRun.results": "Results", "dashboard.taskRun.status": "Status", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "Not run", "dashboard.taskRun.status.pending": "Pending", "dashboard.taskRun.status.running": "Running", + "dashboard.taskRun.status.skipped": "Skipped", "dashboard.taskRun.status.succeeded": "Completed", "dashboard.taskRun.status.succeeded.warning": "Completed with exit code {exitCode}", "dashboard.taskRun.status.waiting": "Waiting", diff --git a/src/nls/messages_es.json b/src/nls/messages_es.json index db5d7f4666..75bd5f9726 100644 --- a/src/nls/messages_es.json +++ b/src/nls/messages_es.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "Paso completado", "dashboard.pipelineRun.stepCompleted.exitCode": "", "dashboard.pipelineRun.stepFailed": "Paso fallido", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "Error al cargar PipelineRuns", "dashboard.pipelines.errorLoading": "", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "", "dashboard.tableHeader.value": "", "dashboard.taskRun.logs": "Anotaciones", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "", "dashboard.taskRun.results": "", "dashboard.taskRun.status": "Estado", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "No ejecutado", "dashboard.taskRun.status.pending": "Pendiente", "dashboard.taskRun.status.running": "En ejecución", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "Completado", "dashboard.taskRun.status.succeeded.warning": "", "dashboard.taskRun.status.waiting": "En espera", diff --git a/src/nls/messages_fr.json b/src/nls/messages_fr.json index 7585031b32..b3d4046d53 100644 --- a/src/nls/messages_fr.json +++ b/src/nls/messages_fr.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "Etape terminée", "dashboard.pipelineRun.stepCompleted.exitCode": "", "dashboard.pipelineRun.stepFailed": "Echec de l'étape", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "Une erreur s'est produite lors du chargement des ressources PipelineRun", "dashboard.pipelines.errorLoading": "", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "", "dashboard.tableHeader.value": "", "dashboard.taskRun.logs": "Journaux", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "", "dashboard.taskRun.results": "", "dashboard.taskRun.status": "Statut", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "Non exécuté", "dashboard.taskRun.status.pending": "En attente", "dashboard.taskRun.status.running": "En cours d'exécution", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "Terminé", "dashboard.taskRun.status.succeeded.warning": "", "dashboard.taskRun.status.waiting": "En attente", diff --git a/src/nls/messages_it.json b/src/nls/messages_it.json index 2f31225ade..a591aca33a 100644 --- a/src/nls/messages_it.json +++ b/src/nls/messages_it.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "Passo completato", "dashboard.pipelineRun.stepCompleted.exitCode": "", "dashboard.pipelineRun.stepFailed": "Passo non riuscito", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "Errore nel caricamento delle esecuzioni pipeline", "dashboard.pipelines.errorLoading": "", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "", "dashboard.tableHeader.value": "", "dashboard.taskRun.logs": "Log", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "", "dashboard.taskRun.results": "", "dashboard.taskRun.status": "Stato", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "Non eseguito", "dashboard.taskRun.status.pending": "In attesa", "dashboard.taskRun.status.running": "In esecuzione", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "Completato", "dashboard.taskRun.status.succeeded.warning": "", "dashboard.taskRun.status.waiting": "In attesa", diff --git a/src/nls/messages_ja.json b/src/nls/messages_ja.json index 6eaa312401..0b40b3095c 100644 --- a/src/nls/messages_ja.json +++ b/src/nls/messages_ja.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "ステップが完了しました", "dashboard.pipelineRun.stepCompleted.exitCode": "", "dashboard.pipelineRun.stepFailed": "ステップが失敗しました", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "PipelineRunのロード中にエラーが発生しました", "dashboard.pipelines.errorLoading": "Pipelineのロード中にエラーが発生しました", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "", "dashboard.tableHeader.value": "値", "dashboard.taskRun.logs": "ログ", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "パラメータ", "dashboard.taskRun.results": "結果", "dashboard.taskRun.status": "ステータス", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "未実行", "dashboard.taskRun.status.pending": "保留中", "dashboard.taskRun.status.running": "実行中", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "完了", "dashboard.taskRun.status.succeeded.warning": "", "dashboard.taskRun.status.waiting": "待機中", diff --git a/src/nls/messages_ko.json b/src/nls/messages_ko.json index 5e2eb56542..5e519bfc9b 100644 --- a/src/nls/messages_ko.json +++ b/src/nls/messages_ko.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "단계 완료", "dashboard.pipelineRun.stepCompleted.exitCode": "{exitCode} 종료 코드와 함께 단계가 완료됨", "dashboard.pipelineRun.stepFailed": "단계 실패", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "PipelineRun 로드 중 오류 발생", "dashboard.pipelines.errorLoading": "Pipelines 로드하는 중 오류가 발생했습니다.", "dashboard.pipelines.v1Resources.label": "Tekton Pipelines API 버전 v1 사용", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "작업", "dashboard.tableHeader.value": "값", "dashboard.taskRun.logs": "로그", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "매개변수", "dashboard.taskRun.results": "결과", "dashboard.taskRun.status": "상태", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "실행하지 않음", "dashboard.taskRun.status.pending": "보류 중", "dashboard.taskRun.status.running": "실행 중", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "완료됨", "dashboard.taskRun.status.succeeded.warning": "종료 코드 {exitCode}로 완료됨", "dashboard.taskRun.status.waiting": "대기 중", diff --git a/src/nls/messages_pt.json b/src/nls/messages_pt.json index b60bd1df2f..babfcffb32 100644 --- a/src/nls/messages_pt.json +++ b/src/nls/messages_pt.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "Etapa concluída", "dashboard.pipelineRun.stepCompleted.exitCode": "", "dashboard.pipelineRun.stepFailed": "Etapa com falha", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "Erro ao carregar os PipelineRuns", "dashboard.pipelines.errorLoading": "", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "", "dashboard.tableHeader.value": "", "dashboard.taskRun.logs": "Logs", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "", "dashboard.taskRun.results": "", "dashboard.taskRun.status": "Status", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "Não executado", "dashboard.taskRun.status.pending": "Pendente", "dashboard.taskRun.status.running": "Executando", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "Concluído", "dashboard.taskRun.status.succeeded.warning": "Completado com exit code {exitCode}", "dashboard.taskRun.status.waiting": "Aguardando", diff --git a/src/nls/messages_zh-Hans.json b/src/nls/messages_zh-Hans.json index 6138fa5f59..13612239d5 100644 --- a/src/nls/messages_zh-Hans.json +++ b/src/nls/messages_zh-Hans.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "步骤已完成", "dashboard.pipelineRun.stepCompleted.exitCode": "步骤已完成,退出代码为 {exitCode}", "dashboard.pipelineRun.stepFailed": "步骤失败", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "加载 PipelineRun 时出错", "dashboard.pipelines.errorLoading": "加载 Pipelines 时出错", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "Task", "dashboard.tableHeader.value": "值", "dashboard.taskRun.logs": "日志", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "参数", "dashboard.taskRun.results": "结果", "dashboard.taskRun.status": "状态", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "未运行", "dashboard.taskRun.status.pending": "暂挂中", "dashboard.taskRun.status.running": "运行中", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "已完成", "dashboard.taskRun.status.succeeded.warning": "已完成,退出代码 {exitCode}", "dashboard.taskRun.status.waiting": "等待中", diff --git a/src/nls/messages_zh-Hant.json b/src/nls/messages_zh-Hant.json index 8807eb19b6..7dd0f66d9c 100644 --- a/src/nls/messages_zh-Hant.json +++ b/src/nls/messages_zh-Hant.json @@ -187,6 +187,7 @@ "dashboard.pipelineRun.stepCompleted": "步驟已完成", "dashboard.pipelineRun.stepCompleted.exitCode": "", "dashboard.pipelineRun.stepFailed": "步驟失敗", + "dashboard.pipelineRun.stepSkipped": "", "dashboard.pipelineRuns.error": "載入 PipelineRuns 時發生錯誤", "dashboard.pipelines.errorLoading": "", "dashboard.pipelines.v1Resources.label": "", @@ -234,6 +235,7 @@ "dashboard.tableHeader.task": "", "dashboard.tableHeader.value": "", "dashboard.taskRun.logs": "日誌", + "dashboard.taskRun.logs.skipped": "", "dashboard.taskRun.params": "", "dashboard.taskRun.results": "", "dashboard.taskRun.status": "狀態", @@ -242,6 +244,7 @@ "dashboard.taskRun.status.notRun": "未執行", "dashboard.taskRun.status.pending": "擱置中", "dashboard.taskRun.status.running": "執行中", + "dashboard.taskRun.status.skipped": "", "dashboard.taskRun.status.succeeded": "已完成", "dashboard.taskRun.status.succeeded.warning": "", "dashboard.taskRun.status.waiting": "等待中",