diff --git a/.storybook/Container.scss b/.storybook/Container.scss
index 3efe68e8b..b10c91aef 100644
--- a/.storybook/Container.scss
+++ b/.storybook/Container.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2024 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -28,3 +28,9 @@ limitations under the License.
background-color: inherit !important;
}
}
+
+[role="main"] > .tkn--task-logs {
+ .cds--accordion__heading {
+ inset-block-start: 0; // demonstrate the sticky header behaviour when displayed in isolation
+ }
+}
diff --git a/packages/components/src/components/TaskTree/_TaskTree.scss b/packages/components/src/components/AccordionItem/AccordionItem.jsx
similarity index 62%
rename from packages/components/src/components/TaskTree/_TaskTree.scss
rename to packages/components/src/components/AccordionItem/AccordionItem.jsx
index 126b47f04..5ab0f1763 100644
--- a/packages/components/src/components/TaskTree/_TaskTree.scss
+++ b/packages/components/src/components/AccordionItem/AccordionItem.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2024 The Tekton Authors
+Copyright 2025-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -11,7 +11,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.tkn--task-tree {
- inline-size: 21%;
- min-inline-size: 15rem;
+import { AccordionItem as CarbonAccordionItem } from '@carbon/react';
+
+function AccordionItem({ children, open, ...rest }) {
+ return (
+
+ {open ? children : null}
+
+ );
}
+
+export default AccordionItem;
diff --git a/packages/components/src/components/AccordionItem/AccordionItem.stories.jsx b/packages/components/src/components/AccordionItem/AccordionItem.stories.jsx
new file mode 100644
index 000000000..76803e0e5
--- /dev/null
+++ b/packages/components/src/components/AccordionItem/AccordionItem.stories.jsx
@@ -0,0 +1,75 @@
+/*
+Copyright 2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { Accordion } from '@carbon/react';
+
+import AccordionItem from './AccordionItem';
+
+export default {
+ component: AccordionItem,
+ title: 'AccordionItem'
+};
+
+export const Closed = {
+ args: {
+ open: false,
+ title: 'Closed Accordion Item',
+ children:
This content is not rendered when closed
+ },
+ decorators: [
+ Story => (
+
+
+
+ )
+ ]
+};
+
+export const Open = {
+ args: {
+ open: true,
+ title: 'Open Accordion Item',
+ children: (
+
+
This content is rendered when open
+
+ The AccordionItem optimizes rendering by only rendering children when
+ open
+
+
+ )
+ },
+ decorators: [
+ Story => (
+
+
+
+ )
+ ]
+};
+
+export const MultipleItems = {
+ render: () => (
+
+
+ Content of first item
+
+
+ Content of second item (not rendered)
+
+
+ Content of third item
+
+
+ )
+};
diff --git a/packages/components/src/components/AccordionItem/AccordionItem.test.jsx b/packages/components/src/components/AccordionItem/AccordionItem.test.jsx
new file mode 100644
index 000000000..4cfd5246c
--- /dev/null
+++ b/packages/components/src/components/AccordionItem/AccordionItem.test.jsx
@@ -0,0 +1,51 @@
+/*
+Copyright 2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { render } from '../../utils/test';
+
+import AccordionItem from './AccordionItem';
+
+describe('AccordionItem', () => {
+ it('should render children when open is true', () => {
+ const { queryByText } = render(
+
+ Test Content
+
+ );
+ expect(queryByText('Test Content')).toBeTruthy();
+ });
+
+ it('should not render children when open is false', () => {
+ const { queryByText } = render(
+
+ Test Content
+
+ );
+ expect(queryByText('Test Content')).toBeFalsy();
+ });
+
+ it('should pass through other props to CarbonAccordionItem', () => {
+ const { container } = render(
+
+ Test Content
+
+ );
+ const accordionItem = container.querySelector('.custom-class');
+ expect(accordionItem).toBeTruthy();
+ });
+});
diff --git a/packages/components/src/components/TaskTree/index.js b/packages/components/src/components/AccordionItem/index.js
similarity index 87%
rename from packages/components/src/components/TaskTree/index.js
rename to packages/components/src/components/AccordionItem/index.js
index 1655a5b0c..665a5e1ad 100644
--- a/packages/components/src/components/TaskTree/index.js
+++ b/packages/components/src/components/AccordionItem/index.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2021 The Tekton Authors
+Copyright 2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -12,4 +12,4 @@ limitations under the License.
*/
/* istanbul ignore file */
-export { default } from './TaskTree';
+export { default } from './AccordionItem';
diff --git a/packages/components/src/components/DetailsHeader/_DetailsHeader.scss b/packages/components/src/components/DetailsHeader/_DetailsHeader.scss
index ca1a7ad71..df994919e 100644
--- a/packages/components/src/components/DetailsHeader/_DetailsHeader.scss
+++ b/packages/components/src/components/DetailsHeader/_DetailsHeader.scss
@@ -42,12 +42,6 @@ header.tkn--step-details-header {
.#{$prefix}--btn-set {
margin-inline: 0.5rem;
align-self: center;
- align-items: baseline;
-
- .cds--btn--icon-only {
- inline-size: 2rem;
- block-size: 2rem;
- }
}
.tkn--run-details-name {
diff --git a/packages/components/src/components/Log/_Log.scss b/packages/components/src/components/Log/_Log.scss
index 4f8168594..75c2ae35f 100644
--- a/packages/components/src/components/Log/_Log.scss
+++ b/packages/components/src/components/Log/_Log.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2025 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -72,37 +72,7 @@ pre.tkn--log {
.#{$prefix}--btn-set {
position: absolute;
inset-block-start: 0;
- inset-inline-end: var(--tkn-log-inline-padding);
- align-items: center;
- }
-
- .button-container {
- position: absolute;
- clip-path: inset(0); // ensure the children with position:fixed are not shown outside this element.
- inset-block-start: 3.125rem; //equals the maximum between padding-block-start of pre.tkn--log and between the page header height
- inset-block-end: 0;
- inset-inline-end: 0;
- inline-size: var(--tkn-log-inline-padding);
- }
-
- .#{$prefix}--btn--ghost,
- .#{$prefix}--copy-btn {
- inline-size: 2rem;
- block-size: 2rem;
- min-block-size: 2rem;
- background-color: $background;
-
- &:hover {
- background-color: $layer-hover;
- }
-
- &:focus {
- outline-color: white;
- }
-
- svg {
- fill: $icon-primary;
- }
+ inset-inline-end: 0.5rem; // var(--tkn-log-inline-padding);
}
.tkn--log-trailer {
diff --git a/packages/components/src/components/LogsToolbar/LogsToolbar.jsx b/packages/components/src/components/LogsToolbar/LogsToolbar.jsx
index 3df54b781..bf6538e33 100644
--- a/packages/components/src/components/LogsToolbar/LogsToolbar.jsx
+++ b/packages/components/src/components/LogsToolbar/LogsToolbar.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2020-2025 The Tekton Authors
+Copyright 2020-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -12,98 +12,51 @@ limitations under the License.
*/
/* istanbul ignore file */
import { useIntl } from 'react-intl';
+import { Maximize, Minimize, Settings } from '@carbon/react/icons';
import {
- Download,
- Launch,
- Maximize,
- Minimize,
- Settings
-} from '@carbon/react/icons';
-import {
+ Button,
+ ButtonSet,
Checkbox,
CheckboxGroup,
Popover,
- PopoverContent,
- usePrefix
+ PopoverContent
} from '@carbon/react';
import { useState } from 'react';
const LogsToolbar = ({
id,
isMaximized,
- name,
logLevels,
onToggleShowTimestamps,
onToggleLogLevel,
onToggleMaximized,
- showTimestamps,
- url
+ showTimestamps
}) => {
- const carbonPrefix = usePrefix();
const intl = useIntl();
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
return (
-
+
);
};
diff --git a/packages/components/src/components/LogsToolbar/LogsToolbar.stories.jsx b/packages/components/src/components/LogsToolbar/LogsToolbar.stories.jsx
index d0daec77f..7f51445c3 100644
--- a/packages/components/src/components/LogsToolbar/LogsToolbar.stories.jsx
+++ b/packages/components/src/components/LogsToolbar/LogsToolbar.stories.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2025 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -97,27 +97,3 @@ export const WithMaximize = {
);
}
};
-
-export const WithURL = {
- args: {
- ...WithMaximize.args,
- name: 'some_filename.txt',
- url: '/some/logs/url'
- },
- render: args => {
- const [, updateArgs] = useArgs();
-
- return (
-
- updateArgs({ logLevels: { ...args.logLevels, ...logLevel } })
- }
- onToggleMaximized={() => updateArgs({ isMaximized: !args.isMaximized })}
- onToggleShowTimestamps={showTimestamps =>
- updateArgs({ showTimestamps })
- }
- />
- );
- }
-};
diff --git a/packages/components/src/components/PipelineRun/PipelineRun.jsx b/packages/components/src/components/PipelineRun/PipelineRun.jsx
index ef35ce7fd..13978c76d 100644
--- a/packages/components/src/components/PipelineRun/PipelineRun.jsx
+++ b/packages/components/src/components/PipelineRun/PipelineRun.jsx
@@ -11,25 +11,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { Fragment, useState } from 'react';
+import { useState } from 'react';
import { InlineNotification, SkeletonText, TabsVertical } from '@carbon/react';
import { useIntl } from 'react-intl';
import {
getErrorMessage,
getStatus,
- getStepDefinition,
- getStepStatus,
labels as labelConstants
} from '@tektoncd/dashboard-utils';
import Log from '../Log';
-import Portal from '../Portal';
import RunHeader from '../RunHeader';
-import StepDetails from '../StepDetails';
-import TaskRunDetails from '../TaskRunDetails';
import TaskRunTabPanels from '../TaskRunTabPanels';
import TaskRunTabs from '../TaskRunTabs';
-import TaskTree from '../TaskTree';
function getPipelineTask({ pipeline, pipelineRun, selectedTaskId, taskRun }) {
const memberOf = taskRun?.metadata?.labels?.[labelConstants.MEMBER_OF];
@@ -48,17 +42,16 @@ export default /* istanbul ignore next */ function PipelineRun({
duration,
enableLogAutoScroll,
enableLogScrollButtons,
- enableTabLayout,
error,
fetchLogs,
forceLogPolling,
getLogsToolbar,
+ getStepLogToolbar,
handlePipelineRunInfo = () => {},
handleTaskSelected = /* istanbul ignore next */ () => {},
ignoredSidecars = {},
loading,
logLevels,
- maximizedLogsContainer,
onRetryChange,
onViewChange = /* istanbul ignore next */ () => {},
pipeline,
@@ -79,7 +72,7 @@ export default /* istanbul ignore next */ function PipelineRun({
view = null
}) {
const intl = useIntl();
- const [isLogsMaximized, setIsLogsMaximized] = useState(false);
+ const [isLogsMaximized] = useState(false);
const [isTaskRunMaximized, setIsTaskRunMaximized] = useState(false);
const [expandedSteps, setExpandedSteps] = useState(() =>
selectedStepId ? { [selectedStepId]: true } : {}
@@ -106,60 +99,35 @@ export default /* istanbul ignore next */ function PipelineRun({
return status === 'False' && reason !== 'Cancelled';
}
- function onToggleLogsMaximized() {
- setIsLogsMaximized(prevIsLogsMaximized => !prevIsLogsMaximized);
- }
-
function onToggleTaskRunMaximized() {
setIsTaskRunMaximized(prevIsTaskRunMaximized => !prevIsTaskRunMaximized);
}
- function getLogContainer({
- disableLogsToolbar,
- isSidecar,
- stepName,
- stepStatus,
- taskRun
- }) {
+ function getLogContainer({ isSidecar, stepName, stepStatus, taskRun }) {
if ((!selectedStepId && !stepName) || !stepStatus) {
return null;
}
- const LogsRoot =
- isLogsMaximized && maximizedLogsContainer ? Portal : Fragment;
-
return (
-
- fetchLogs({ stepName, stepStatus, taskRun })}
- forcePolling={forceLogPolling}
- isLogsMaximized={isLogsMaximized}
- isSidecar={isSidecar}
- key={`${selectedTaskId}:${stepName}:${selectedRetry}`}
- logLevels={logLevels}
- pollingInterval={pollingInterval}
- showLevels={showLogLevels}
- showTimestamps={showLogTimestamps}
- stepStatus={stepStatus}
- toolbar={
- !disableLogsToolbar &&
- getLogsToolbar &&
- stepStatus &&
- getLogsToolbar({
- id: `${selectedTaskId}-${stepName}-${selectedRetry}-logs-toolbar`,
- isMaximized: isLogsMaximized,
- onToggleMaximized:
- !!maximizedLogsContainer && onToggleLogsMaximized,
- stepStatus,
- taskRun
- })
- }
- />
-
+ fetchLogs({ stepName, stepStatus, taskRun })}
+ forcePolling={forceLogPolling}
+ isLogsMaximized={isLogsMaximized}
+ isSidecar={isSidecar}
+ key={`${selectedTaskId}:${stepName}:${selectedRetry}`}
+ logLevels={logLevels}
+ pollingInterval={pollingInterval}
+ showLevels={showLogLevels}
+ showTimestamps={showLogTimestamps}
+ stepStatus={stepStatus}
+ toolbar={
+ getStepLogToolbar &&
+ stepStatus &&
+ getStepLogToolbar({ stepStatus, taskRun })
+ }
+ />
);
}
@@ -338,33 +306,12 @@ export default /* istanbul ignore next */ function PipelineRun({
tasks?.find(t => t.metadata.name === taskRun.spec.taskRef.name)) ||
{};
- const stepStatus = getStepStatus({
- selectedStepId,
- taskRun
- });
-
- let definition;
- let logContainer;
- if (!enableTabLayout) {
- definition = getStepDefinition({
- selectedStepId,
- task,
- taskRun
- });
-
- logContainer = getLogContainer({
- stepName: selectedStepId,
- stepStatus,
- taskRun
- });
- }
-
const skippedTasks = pipelineRun.status?.skippedTasks || [];
const skippedTask = skippedTasks.find(
skipped => skipped.name === selectedTaskId
);
- if (enableTabLayout && !selectedTaskId) {
+ if (!selectedTaskId) {
const selectedTaskRun = taskRunsToUse[0];
const { labels = {} } = selectedTaskRun?.metadata || {};
const { [labelConstants.PIPELINE_TASK]: pipelineTaskName } = labels;
@@ -399,93 +346,55 @@ export default /* istanbul ignore next */ function PipelineRun({
{(taskRunsToUse.length > 0 || preTaskRun) && (
- {enableTabLayout ? (
- {
+ if (preTaskRun && newSelectedIndex === 0) {
+ onTaskSelected({});
+ return;
+ }
+
+ const selectedTaskRun =
+ taskRunsToUse[newSelectedIndex - preTaskRunOffset];
+ const { labels } = selectedTaskRun.metadata;
+ const { [labelConstants.PIPELINE_TASK]: pipelineTaskName } =
+ labels;
+
+ setExpandedSteps({});
+ onTaskSelected({
+ selectedTaskId: pipelineTaskName,
+ taskRunName: selectedTaskRun.metadata?.name
+ });
+ }}
+ >
+
+ {
- if (preTaskRun && newSelectedIndex === 0) {
- onTaskSelected({});
- return;
- }
-
- const selectedTaskRun =
- taskRunsToUse[newSelectedIndex - preTaskRunOffset];
- const { labels } = selectedTaskRun.metadata;
- const { [labelConstants.PIPELINE_TASK]: pipelineTaskName } =
- labels;
-
- setExpandedSteps({});
- onTaskSelected({
- selectedTaskId: pipelineTaskName,
- taskRunName: selectedTaskRun.metadata?.name
- });
- }}
- >
-
-
-
- ) : (
- <>
-
- {(selectedStepId && (
-
- )) ||
- (selectedTaskId && (
-
- ))}
- >
- )}
+ selectedRetry={selectedRetry}
+ selectedTaskId={selectedTaskId}
+ selectedStepId={selectedStepId}
+ skippedTask={skippedTask}
+ task={task}
+ taskRun={taskRun}
+ taskRuns={taskRunsToUse}
+ view={view}
+ />
+
)}
>
diff --git a/packages/components/src/components/PipelineRun/PipelineRun.stories.jsx b/packages/components/src/components/PipelineRun/PipelineRun.stories.jsx
index 20519603c..66387f79f 100644
--- a/packages/components/src/components/PipelineRun/PipelineRun.stories.jsx
+++ b/packages/components/src/components/PipelineRun/PipelineRun.stories.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2025 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -231,7 +231,6 @@ const pipelineRunWithMinimalStatus = {
export default {
args: {
- enableTabLayout: false,
selectedRetry: '',
selectedStepId: undefined,
selectedTaskId: undefined,
diff --git a/packages/components/src/components/PipelineRun/PipelineRun.test.jsx b/packages/components/src/components/PipelineRun/PipelineRun.test.jsx
index d94c23fb5..01e416c4f 100644
--- a/packages/components/src/components/PipelineRun/PipelineRun.test.jsx
+++ b/packages/components/src/components/PipelineRun/PipelineRun.test.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2024 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -30,11 +30,15 @@ it('PipelineRunContainer handles init step failures', async () => {
const initStepName = 'my-failed-init-step';
const pipelineRunName = 'fake_pipelineRunName';
const taskRunName = 'fake_taskRunName';
+ const pipelineTaskName = 'fake_pipelineTaskName';
+ const handleTaskSelected = vi.fn();
const taskRun = {
metadata: {
name: taskRunName,
- labels: {},
+ labels: {
+ 'tekton.dev/pipelineTask': pipelineTaskName
+ },
uid: '41deea80-53bc-4500-a20e-3b18549e23ab'
},
spec: {
@@ -60,15 +64,22 @@ it('PipelineRunContainer handles init step failures', async () => {
}
};
- const { getByText } = render(
+ render(
{}}
+ handleTaskSelected={handleTaskSelected}
pipelineRun={pipelineRun}
taskRuns={[taskRun]}
tasks={[]}
/>
);
- await waitFor(() => getByText(initStepName));
+ await waitFor(() =>
+ expect(handleTaskSelected).toHaveBeenCalledWith({
+ selectedRetry: undefined,
+ selectedStepId: undefined,
+ selectedTaskId: pipelineTaskName,
+ taskRunName: undefined
+ })
+ );
});
it('PipelineRunContainer handles init step failures for retry', async () => {
@@ -78,7 +89,9 @@ it('PipelineRunContainer handles init step failures for retry', async () => {
const taskRun = {
metadata: {
- labels: {},
+ labels: {
+ 'tekton.dev/pipelineTask': 'task-1'
+ },
name: taskRunName,
uid: 'b4402feb-69fe-4a5a-a0c2-5e390aa58894'
},
@@ -115,7 +128,6 @@ it('PipelineRunContainer handles init step failures for retry', async () => {
]
}
};
-
const pipelineRun = {
metadata: {
name: pipelineRunName
@@ -123,7 +135,8 @@ it('PipelineRunContainer handles init step failures for retry', async () => {
status: {
childReferences: [
{
- name: taskRunName
+ name: taskRunName,
+ pipelineTaskName: 'task-1'
}
]
}
@@ -132,21 +145,21 @@ it('PipelineRunContainer handles init step failures for retry', async () => {
class TestWrapper extends ReactComponent {
state = {
selectedStepId: null,
- selectedTaskId: null
+ selectedTaskId: 'task-1'
};
- handleTaskSelected = ({ selectedStepId, selectedTaskId }) => {
- this.setState({ selectedStepId, selectedTaskId });
+ handleTaskSelected = ({ selectedTaskId }) => {
+ this.setState({
+ selectedTaskId: selectedTaskId ?? this.state.selectedTaskId
+ });
};
render() {
const { children: Component } = this.props;
- const { selectedStepId, selectedTaskId } = this.state;
return (
);
}
@@ -154,12 +167,11 @@ it('PipelineRunContainer handles init step failures for retry', async () => {
const { getByText } = render(
- {({ handleTaskSelected, selectedStepId, selectedTaskId }) => (
+ {({ handleTaskSelected, selectedTaskId }) => (
{}}
handleTaskSelected={handleTaskSelected}
pipelineRun={pipelineRun}
- selectedStepId={selectedStepId}
+ selectedRetry="0"
selectedTaskId={selectedTaskId}
taskRuns={[taskRun]}
tasks={[]}
@@ -167,6 +179,5 @@ it('PipelineRunContainer handles init step failures for retry', async () => {
)}
);
- await waitFor(() => getByText(/(retry 1)/));
- await waitFor(() => getByText(initStepName, { selector: '.tkn--step-name' }));
+ await waitFor(() => getByText('task-1'));
});
diff --git a/packages/components/src/components/Step/Step.jsx b/packages/components/src/components/Step/Step.jsx
deleted file mode 100644
index 2a705f1ff..000000000
--- a/packages/components/src/components/Step/Step.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
-Copyright 2019-2025 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { useIntl } from 'react-intl';
-import { Pending as DefaultIcon } from '@carbon/react/icons';
-
-import StatusIcon from '../StatusIcon';
-
-export default function Step({
- exitCode,
- id,
- onSelect,
- reason,
- selected,
- status,
- stepName = 'unknown',
- terminationReason
-}) {
- const intl = useIntl();
-
- function handleClick(event) {
- event.preventDefault();
- onSelect(id);
- }
-
- function getStatusLabel() {
- if (terminationReason === 'Skipped') {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.skipped',
- defaultMessage: 'Skipped'
- });
- }
- if (
- status === 'cancelled' ||
- (status === 'terminated' &&
- (reason === 'TaskRunCancelled' || reason === 'TaskRunTimeout'))
- ) {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.cancelled',
- defaultMessage: 'Cancelled'
- });
- }
-
- if (status === 'running') {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.running',
- defaultMessage: 'Running'
- });
- }
-
- if (status === 'terminated') {
- if (reason === 'Completed') {
- return exitCode !== 0
- ? intl.formatMessage(
- {
- id: 'dashboard.taskRun.status.succeeded.warning',
- defaultMessage: 'Completed with exit code {exitCode}'
- },
- { exitCode }
- )
- : intl.formatMessage({
- id: 'dashboard.taskRun.status.succeeded',
- defaultMessage: 'Completed'
- });
- }
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.failed',
- defaultMessage: 'Failed'
- });
- }
-
- if (status === 'waiting') {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.waiting',
- defaultMessage: 'Waiting'
- });
- }
-
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.notRun',
- defaultMessage: 'Not run'
- });
- }
-
- const statusLabel = getStatusLabel();
-
- return (
-
- e.key === 'Enter' && handleClick(e)}
- role="button"
- >
- }
- hasWarning={exitCode !== 0}
- reason={reason}
- status={status}
- terminationReason={terminationReason}
- title={statusLabel}
- type="inverse"
- />
-
- {stepName}
-
-
-
- );
-}
diff --git a/packages/components/src/components/Step/Step.stories.js b/packages/components/src/components/Step/Step.stories.js
deleted file mode 100644
index 6fe032753..000000000
--- a/packages/components/src/components/Step/Step.stories.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-Copyright 2019-2025 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { action } from 'storybook/actions';
-
-import Step from './Step';
-
-export default {
- args: {
- exitCode: 0,
- onSelect: action('selected'),
- stepName: 'build'
- },
- component: Step,
- title: 'Step'
-};
-
-export const Default = {};
-export const Selected = { args: { selected: true } };
-export const Waiting = { args: { status: 'waiting' } };
-export const Running = { args: { status: 'running' } };
-
-export const Completed = {
- args: { reason: 'Completed', status: 'terminated' }
-};
-
-export const CompletedWithWarning = {
- args: {
- ...Completed.args,
- exitCode: 1
- },
- 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
deleted file mode 100644
index c7cfdbe48..000000000
--- a/packages/components/src/components/Step/Step.test.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
-Copyright 2019-2024 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { fireEvent } from '@testing-library/react';
-import Step from './Step';
-import { render } from '../../utils/test';
-
-it('Step renders default content', () => {
- const { queryByText } = render( );
-
- expect(queryByText(/Unknown/i)).toBeTruthy();
-});
-
-it('Step renders the provided content', () => {
- const stepName = 'build';
- const { queryByText } = render( );
- expect(queryByText(/build/i)).toBeTruthy();
-});
-
-it('Step renders selected state', () => {
- render( );
-});
-
-it('Step renders waiting state', () => {
- const { queryByText } = render( );
- expect(queryByText(/waiting/i)).toBeTruthy();
-});
-
-it('Step renders running state', () => {
- const { queryByText } = render( );
- expect(queryByText(/running/i)).toBeTruthy();
-});
-
-it('Step renders cancelled state', () => {
- const { queryByText } = render( );
- expect(queryByText(/Cancelled/i)).toBeTruthy();
-});
-
-it('Step renders cancelled state for TaskRunCancelled', () => {
- const { queryByText } = render(
-
- );
- expect(queryByText(/Cancelled/i)).toBeTruthy();
-});
-
-it('Step renders cancelled state for TaskRunTimeout', () => {
- const { queryByText } = render(
-
- );
- expect(queryByText(/Cancelled/i)).toBeTruthy();
-});
-
-it('Step renders completed state', () => {
- const { queryByText } = render(
-
- );
- expect(queryByText(/Completed/i)).toBeTruthy();
-});
-
-it('Step renders completed with warning state', () => {
- const { queryByText } = render(
-
- );
- 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();
-});
-
-it('Step handles click event', () => {
- const onSelect = vi.fn();
- const { getByText } = render( );
- fireEvent.click(getByText(/build/i));
- expect(onSelect).toHaveBeenCalledTimes(1);
-});
diff --git a/packages/components/src/components/Step/_Step.scss b/packages/components/src/components/Step/_Step.scss
index 1b17a505a..2d7eb27f4 100644
--- a/packages/components/src/components/Step/_Step.scss
+++ b/packages/components/src/components/Step/_Step.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2024 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -12,42 +12,8 @@ limitations under the License.
*/
@use '@carbon/react/scss/theme' as *;
-@use '@carbon/react/scss/type' as *;
.tkn--step {
- list-style-type: none;
-
- &:hover > .tkn--step-link,
- &:hover > .tkn--step-link:hover,
- &[data-selected] > .tkn--step-link:hover {
- background-color: $layer-hover;
- text-decoration: none;
- }
-
- &:hover > .tkn--step-link,
- > .tkn--step-link:hover {
- border-inline-start-color: $layer-hover;
- }
-
- &[data-selected] > .tkn--step-link {
- border-inline-start: 3px solid $border-interactive;
- }
-
- > .tkn--step-link {
- cursor: pointer;
- display: flex;
- align-items: baseline;
- position: relative;
- padding-block: 0;
- padding-inline-start: 0.75rem;
- padding-inline-end: 1rem;
- line-height: 2.2rem;
- font-size: 0.78rem;
- letter-spacing: 0.02rem;
- text-decoration: none;
- border-inline-start: 3px solid $layer;
- }
-
.tkn--status-icon {
flex-shrink: 0;
align-self: center;
diff --git a/packages/components/src/components/StepDetails/StepDetails.jsx b/packages/components/src/components/StepDetails/StepDetails.jsx
deleted file mode 100644
index 3d5b1e1e4..000000000
--- a/packages/components/src/components/StepDetails/StepDetails.jsx
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
-Copyright 2019-2024 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import PropTypes from 'prop-types';
-import { useIntl } from 'react-intl';
-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'];
-
-const defaults = {
- onViewChange: /* istanbul ignore next */ () => {},
- taskRun: {}
-};
-
-const StepDetails = ({
- definition,
- logContainer,
- onViewChange = defaults.onViewChange,
- skippedTask,
- stepName,
- stepStatus,
- taskRun = defaults.taskRun,
- view
-}) => {
- const intl = useIntl();
- const { exitCode, reason, status } = getStepStatusReason(stepStatus);
- const statusValue =
- getStatus(taskRun).reason === 'TaskRunCancelled' && status !== 'terminated'
- ? 'cancelled'
- : status;
-
- let selectedTabIndex = tabs.indexOf(view);
- if (selectedTabIndex === -1) {
- selectedTabIndex = 0;
- }
-
- return (
-
-
- onViewChange(tabs[event.selectedIndex])}
- selectedIndex={selectedTabIndex}
- >
-
-
- {intl.formatMessage({
- id: 'dashboard.taskRun.logs',
- defaultMessage: 'Logs'
- })}
-
-
- {intl.formatMessage({
- id: 'dashboard.resource.detailsTab',
- defaultMessage: 'Details'
- })}
-
-
-
-
- {selectedTabIndex === 0 && skippedTask ? (
-
- intl.formatMessage({
- id: 'dashboard.taskRun.logs.skipped',
- defaultMessage:
- 'This step did not run as the task was skipped. See status for more details.'
- })
- }
- />
- ) : (
- logContainer
- )}
-
-
- {selectedTabIndex === 1 && (
-
- )}
-
-
-
-
- );
-};
-
-StepDetails.propTypes = {
- onViewChange: PropTypes.func,
- taskRun: PropTypes.shape({})
-};
-
-export default StepDetails;
diff --git a/packages/components/src/components/StepDetails/StepDetails.stories.jsx b/packages/components/src/components/StepDetails/StepDetails.stories.jsx
deleted file mode 100644
index b1f22a12c..000000000
--- a/packages/components/src/components/StepDetails/StepDetails.stories.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
-Copyright 2019-2025 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { useArgs } from 'storybook/preview-api';
-
-import Log from '../Log';
-import StepDetails from './StepDetails';
-
-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,
- logContent = ansiLog,
- terminationReason
-} = {}) {
- return (
- logContent}
- stepStatus={getStepStatus({ exitCode, terminationReason })}
- />
- );
-}
-
-export default {
- args: {
- definition: 'this will show the Task.spec or TaskRun.spec.taskSpec',
- stepName: 'build',
- taskRun: {}
- },
- component: StepDetails,
- subcomponents: { Log },
- title: 'StepDetails'
-};
-
-export const Default = {
- args: {
- logContainer: getLogContainer(),
- stepStatus: getStepStatus()
- },
- render: args => {
- const [, updateArgs] = useArgs();
- return (
- updateArgs({ view: selectedView })}
- />
- );
- }
-};
-
-export const WithWarning = {
- args: {
- logContainer: getLogContainer({ exitCode: 1 }),
- stepStatus: getStepStatus({ exitCode: 1 })
- },
- render: args => {
- const [, updateArgs] = useArgs();
- return (
- updateArgs({ view: selectedView })}
- />
- );
- }
-};
-
-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
deleted file mode 100644
index 7cef08880..000000000
--- a/packages/components/src/components/StepDetails/StepDetails.test.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-Copyright 2019-2024 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { fireEvent, waitFor } from '@testing-library/react';
-import { renderWithRouter } from '../../utils/test';
-
-import StepDetails from './StepDetails';
-
-describe('StepDetails', () => {
- it('renders', () => {
- renderWithRouter( );
- });
-
- it('renders terminated state', () => {
- renderWithRouter(
-
- );
- });
-
- it('renders cancelled state', () => {
- renderWithRouter(
-
- );
- });
-
- it('renders with selected view', () => {
- const { getByText } = renderWithRouter(
-
- );
-
- 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/StepLogToolbar/StepLogToolbar.jsx b/packages/components/src/components/StepLogToolbar/StepLogToolbar.jsx
new file mode 100644
index 000000000..34d59b725
--- /dev/null
+++ b/packages/components/src/components/StepLogToolbar/StepLogToolbar.jsx
@@ -0,0 +1,55 @@
+/*
+Copyright 2020-2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+/* istanbul ignore file */
+
+import { useIntl } from 'react-intl';
+import { Download, Launch } from '@carbon/react/icons';
+import { Button, ButtonSet } from '@carbon/react';
+
+const StepLogToolbar = ({ name, url }) => {
+ const intl = useIntl();
+
+ return (
+
+
+
+
+ );
+};
+
+export default StepLogToolbar;
diff --git a/packages/components/src/components/StepDetails/_StepDetails.scss b/packages/components/src/components/StepLogToolbar/StepLogToolbar.stories.jsx
similarity index 57%
rename from packages/components/src/components/StepDetails/_StepDetails.scss
rename to packages/components/src/components/StepLogToolbar/StepLogToolbar.stories.jsx
index 19df9ecbe..497e3a9f7 100644
--- a/packages/components/src/components/StepDetails/_StepDetails.scss
+++ b/packages/components/src/components/StepLogToolbar/StepLogToolbar.stories.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2024 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -11,18 +11,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-@use '@carbon/react/scss/config' as *;
-@use '@carbon/react/scss/theme' as *;
+import StepLogToolbar from './StepLogToolbar';
-.tkn--step-details {
- background-color: $layer;
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- align-items: stretch;
- overflow: hidden;
+export default {
+ component: StepLogToolbar,
+ decorators: [
+ Story => (
+
+
+
+ )
+ ],
+ title: 'StepLogToolbar'
+};
- .#{$prefix}--tab-content {
- padding-inline-start: 0;
+export const Default = {
+ args: {
+ name: 'some_filename.txt',
+ url: '/some/logs/url'
}
-}
+};
diff --git a/packages/components/src/components/Task/index.js b/packages/components/src/components/StepLogToolbar/index.js
similarity index 86%
rename from packages/components/src/components/Task/index.js
rename to packages/components/src/components/StepLogToolbar/index.js
index a170d29c5..2a1748625 100644
--- a/packages/components/src/components/Task/index.js
+++ b/packages/components/src/components/StepLogToolbar/index.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2023 The Tekton Authors
+Copyright 2020-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -11,4 +11,4 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/* istanbul ignore file */
-export { default } from './Task';
+export { default } from './StepLogToolbar';
diff --git a/packages/components/src/components/Task/Task.jsx b/packages/components/src/components/Task/Task.jsx
deleted file mode 100644
index 5bc6ebb9b..000000000
--- a/packages/components/src/components/Task/Task.jsx
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
-Copyright 2019-2025 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { Component } from 'react';
-import { injectIntl } from 'react-intl';
-import { OverflowMenu, OverflowMenuItem } from '@carbon/react';
-import {
- PendingFilled as DefaultIcon,
- ChevronDown as ExpandIcon
-} from '@carbon/react/icons';
-import {
- getStepStatusReason,
- updateUnexecutedSteps
-} from '@tektoncd/dashboard-utils';
-
-import StatusIcon from '../StatusIcon';
-import Step from '../Step';
-
-class Task extends Component {
- state = { hasWarning: false, selectedStepId: null };
-
- componentDidMount() {
- this.selectDefaultStep();
- this.getStepData({ propagateWarning: true });
- }
-
- componentDidUpdate(prevProps) {
- const { reason } = this.props;
- if (prevProps.reason !== reason && reason === 'Succeeded') {
- this.getStepData({ propagateWarning: true });
- }
- }
-
- getStepData({ propagateWarning = false } = {}) {
- const { reason, selectedStepId, steps } = this.props;
- let hasWarning = false;
- const stepData = updateUnexecutedSteps(steps).map(step => {
- const { name, terminationReason } = step;
- const {
- exitCode,
- status,
- reason: stepReason
- } = getStepStatusReason(step);
-
- if (stepReason === 'Completed') {
- hasWarning = hasWarning || exitCode !== 0;
- }
-
- const selected = selectedStepId === name;
- const stepStatus =
- reason === 'TaskRunCancelled' && status !== 'terminated'
- ? 'cancelled'
- : status;
-
- return {
- exitCode,
- name,
- selected,
- stepReason,
- stepStatus,
- terminationReason
- };
- });
-
- if (propagateWarning) {
- this.setState({ hasWarning });
- }
-
- return stepData;
- }
-
- handleClick = () => {
- const { id, selectedRetry, taskRun } = this.props;
- const { selectedStepId } = this.state;
- this.props.onSelect({
- selectedRetry,
- selectedStepId,
- selectedTaskId: id,
- taskRunName: taskRun.metadata?.name
- });
- };
-
- handleStepSelected = selectedStepId => {
- this.setState({ selectedStepId }, () => {
- this.handleClick();
- });
- };
-
- handleTaskSelected = event => {
- event?.preventDefault();
- this.setState({ selectedStepId: null }, () => {
- this.handleClick();
- });
- };
-
- selectDefaultStep() {
- const { expanded, selectDefaultStep, selectedStepId, steps } = this.props;
- if (!selectDefaultStep) {
- return;
- }
- if (expanded && !selectedStepId) {
- const erroredStep = steps.find(
- step => step.terminated?.reason === 'Error' || !step.terminated
- );
- const { name } = erroredStep || steps[0] || {};
- this.handleStepSelected(name);
- }
- }
-
- render() {
- const {
- displayName,
- expanded,
- intl,
- onRetryChange,
- reason,
- selectedRetry,
- selectedStepId,
- succeeded,
- taskRun
- } = this.props;
- const { hasWarning } = this.state;
-
- const expandIcon = expanded ? null : (
-
- );
-
- let retryName;
- let retryMenuTitle;
- if (selectedRetry || taskRun.status?.retriesStatus) {
- retryMenuTitle = intl.formatMessage({
- id: 'dashboard.pipelineRun.retries.view',
- defaultMessage: 'View retries'
- });
-
- if (selectedRetry === '0') {
- retryName = intl.formatMessage(
- {
- id: 'dashboard.pipelineRun.pipelineTaskName.firstAttempt',
- defaultMessage: '{pipelineTaskName} (first attempt)'
- },
- { pipelineTaskName: displayName }
- );
- } else {
- retryName = intl.formatMessage(
- {
- id: 'dashboard.pipelineRun.pipelineTaskName.retry',
- defaultMessage: '{pipelineTaskName} (retry {retryNumber, number})'
- },
- {
- pipelineTaskName: displayName,
- retryNumber: selectedRetry || taskRun.status.retriesStatus.length
- }
- );
- }
- }
-
- return (
-
- e.key === 'Enter' && this.handleTaskSelected(e)}
- role="button"
- >
- }
- hasWarning={hasWarning}
- reason={reason}
- status={succeeded}
- />
-
- {retryName || displayName}
-
- {expanded && taskRun.status?.retriesStatus ? (
-
- {taskRun.status.retriesStatus
- .map((_retryStatus, index) => {
- return {
- id: index,
- text:
- index === 0
- ? intl.formatMessage({
- id: 'dashboard.pipelineRun.retries.viewFirstAttempt',
- defaultMessage: 'View first attempt'
- })
- : intl.formatMessage(
- {
- id: 'dashboard.pipelineRun.retries.viewRetry',
- defaultMessage: 'View retry {retryNumber, number}'
- },
- { retryNumber: index }
- )
- };
- })
- .concat([
- {
- id: '',
- text: intl.formatMessage({
- id: 'dashboard.pipelineRun.retries.viewLatestRetry',
- defaultMessage: 'View latest retry'
- })
- }
- ])
- .map(item => (
- onRetryChange(item.id)}
- requireTitle
- />
- ))}
-
- ) : null}
- {expandIcon}
-
- {expanded && (
-
- {this.getStepData().map(step => {
- const {
- exitCode,
- name,
- selected,
- stepReason,
- stepStatus,
- terminationReason
- } = step;
- return (
-
- );
- })}
-
- )}
-
- );
- }
-}
-
-Task.defaultProps = {
- steps: []
-};
-
-export default injectIntl(Task);
diff --git a/packages/components/src/components/Task/Task.stories.jsx b/packages/components/src/components/Task/Task.stories.jsx
deleted file mode 100644
index 468005c86..000000000
--- a/packages/components/src/components/Task/Task.stories.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
-Copyright 2019-2025 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { dashboardReasonSkipped } from '@tektoncd/dashboard-utils';
-import { action } from 'storybook/actions';
-import { useArgs } from 'storybook/preview-api';
-
-import Task from './Task';
-
-export default {
- args: {
- displayName: 'A Task',
- onSelect: action('selected'),
- selectedRetry: '',
- selectedStepId: undefined,
- taskRun: {}
- },
- component: Task,
- decorators: [
- Story => (
-
-
-
- )
- ],
- title: 'Task'
-};
-
-export const Succeeded = { args: { succeeded: 'True' } };
-
-export const SucceededWithWarning = {
- args: {
- ...Succeeded.args,
- steps: [{ terminated: { exitCode: 1, reason: 'Completed' } }]
- },
- name: 'Succeeded with warning'
-};
-
-export const Failed = { args: { succeeded: 'False' } };
-export const Unknown = { args: { succeeded: 'Unknown' } };
-
-export const Pending = { args: { ...Unknown.args, reason: 'Pending' } };
-
-export const Running = { args: { ...Unknown.args, reason: 'Running' } };
-
-export const Skipped = { args: { reason: dashboardReasonSkipped } };
-
-export const Expanded = args => {
- const [, updateArgs] = useArgs();
-
- return (
-
- updateArgs({ selectedStepId: stepId })
- }
- 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: {} }
- ]}
- succeeded="Unknown"
- />
- );
-};
-
-export const Retries = args => {
- const [, updateArgs] = useArgs();
-
- return (
- {
- updateArgs({ selectedRetry: `${selectedRetry}` });
- }}
- reason="Running"
- succeeded="Unknown"
- taskRun={{ status: { retriesStatus: [{}, {}] } }}
- />
- );
-};
diff --git a/packages/components/src/components/Task/Task.test.jsx b/packages/components/src/components/Task/Task.test.jsx
deleted file mode 100644
index 9d1040a8a..000000000
--- a/packages/components/src/components/Task/Task.test.jsx
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
-Copyright 2019-2024 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { dashboardReasonSkipped } from '@tektoncd/dashboard-utils';
-import { fireEvent } from '@testing-library/react';
-
-import Task from './Task';
-import { render } from '../../utils/test';
-
-const props = {
- displayName: 'A Task',
- id: 'fake_task_id',
- onSelect: () => {},
- taskRun: {}
-};
-
-describe('Task', () => {
- it('renders default content', () => {
- const { queryByText } = render( );
- expect(queryByText(/a task/i)).toBeTruthy();
- });
-
- it('does not render steps in collapsed state', () => {
- const steps = [{ name: 'a step' }];
- const { queryByText } = render( );
- expect(queryByText(/a step/i)).toBeFalsy();
- });
-
- it('renders steps in expanded state', () => {
- const steps = [{ name: 'a step' }];
- const { queryByText } = render( );
- expect(queryByText(/a step/i)).toBeTruthy();
- });
-
- it('automatically selects first step in expanded Task with no error', () => {
- const firstStepName = 'a step';
- const steps = [
- { name: firstStepName, terminated: { reason: 'Completed' } },
- { name: 'a step two', terminated: { reason: 'Completed' } }
- ];
-
- const onSelect = vi.fn();
-
- render(
-
- );
-
- expect(onSelect).toHaveBeenCalledWith({
- selectedRetry: undefined,
- selectedStepId: firstStepName,
- selectedTaskId: props.id,
- taskRunName: undefined
- });
- });
-
- it('automatically selects first error step in expanded Task', () => {
- const errorStepName = 'a step two';
- const steps = [
- { name: 'a step', terminated: { reason: 'Completed' } },
- { name: errorStepName, terminated: { reason: 'Error' } }
- ];
-
- const onSelect = vi.fn();
-
- render(
-
- );
-
- expect(onSelect).toHaveBeenCalledWith({
- selectedRetry: undefined,
- selectedStepId: errorStepName,
- selectedTaskId: props.id,
- taskRunName: undefined
- });
- });
-
- it('handles no steps', () => {
- const onSelect = vi.fn();
-
- render(
-
- );
-
- expect(onSelect).toHaveBeenCalledWith({
- selectedRetry: undefined,
- selectedStepId: undefined,
- selectedTaskId: props.id,
- taskRunName: undefined
- });
- });
-
- it('renders completed steps in expanded state', () => {
- const steps = [
- { name: 'step 1', terminated: { reason: 'Completed' } },
- { name: 'step 2', terminated: { reason: 'Error' } },
- { name: 'step 3', terminated: { reason: 'Completed' } }
- ];
- const { queryByText } = render( );
- expect(queryByText(/step 1/i)).toBeTruthy();
- expect(queryByText(/step 2/i)).toBeTruthy();
- expect(queryByText(/step 3/i)).toBeTruthy();
- });
-
- it('renders success state', () => {
- render( );
- });
-
- it('renders failure state', () => {
- render( );
- });
-
- it('renders unknown state', () => {
- render( );
- });
-
- it('renders pending state', () => {
- render( );
- });
-
- it('renders running state', () => {
- render( );
- });
-
- it('renders skipped state', () => {
- render( );
- });
-
- it('renders cancelled state', () => {
- render(
-
- );
- });
-
- it('renders selected step state', () => {
- const stepName = 'a step';
- const steps = [{ name: stepName }];
- const { getByText } = render(
-
- );
-
- expect(
- getByText(
- (content, element) =>
- content === stepName &&
- element.parentNode.parentNode.getAttribute('data-selected')
- )
- ).toBeTruthy();
- });
-
- it('handles click event', () => {
- const onSelect = vi.fn();
- const { getByText } = render( );
- expect(onSelect).not.toHaveBeenCalled();
- fireEvent.click(getByText(props.displayName));
- expect(onSelect).toHaveBeenCalledTimes(1);
- expect(onSelect).toHaveBeenCalledWith({
- selectedRetry: undefined,
- selectedStepId: null,
- selectedTaskId: props.id,
- taskRunName: undefined
- });
- });
-
- it('handles click event with retries', () => {
- const selectedRetry = 2;
- const onSelect = vi.fn();
- const { getByText } = render(
-
- );
- expect(onSelect).not.toHaveBeenCalled();
- fireEvent.click(getByText(`${props.displayName} (retry ${selectedRetry})`));
- expect(onSelect).toHaveBeenCalledTimes(1);
- expect(onSelect).toHaveBeenCalledWith({
- selectedRetry,
- selectedStepId: null,
- selectedTaskId: props.id,
- taskRunName: undefined
- });
- });
-
- it('handles click event on Step', () => {
- const onSelect = vi.fn();
- const stepName = 'build';
- const steps = [{ name: stepName }];
- const { getByText } = render(
-
- );
- expect(onSelect).not.toHaveBeenCalled();
- fireEvent.click(getByText(stepName));
- expect(onSelect).toHaveBeenCalledTimes(1);
- expect(onSelect).toHaveBeenCalledWith({
- selectedRetry: undefined,
- selectedStepId: stepName,
- selectedTaskId: props.id,
- taskRunName: undefined
- });
- });
-
- it('handles click event on Step with retries', () => {
- const selectedRetry = 2;
- const onSelect = vi.fn();
- const stepName = 'build';
- const steps = [{ name: stepName }];
- const { getByText } = render(
-
- );
- expect(onSelect).not.toHaveBeenCalled();
- fireEvent.click(getByText(stepName));
- expect(onSelect).toHaveBeenCalledTimes(1);
- expect(onSelect).toHaveBeenCalledWith({
- selectedRetry,
- selectedStepId: stepName,
- selectedTaskId: props.id,
- taskRunName: undefined
- });
- });
-});
diff --git a/packages/components/src/components/Task/_Task.scss b/packages/components/src/components/Task/_Task.scss
index c56cfe88f..cf29e7083 100644
--- a/packages/components/src/components/Task/_Task.scss
+++ b/packages/components/src/components/Task/_Task.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2025 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -13,112 +13,6 @@ limitations under the License.
@use '@carbon/react/scss/config' as *;
@use '@carbon/react/scss/theme' as *;
-@use '@carbon/react/scss/type' as *;
-
-.tkn--task {
- list-style-type: none;
-
- > .tkn--task-link {
- cursor: pointer;
- display: flex;
- align-items: center;
- position: relative;
- padding-block: 0;
- padding-inline-start: 0.75rem;
- padding-inline-end: 1rem;
- font-size: 0.76rem;
- letter-spacing: 0.06rem;
- font-weight: bold;
- line-height: 2.2rem;
- text-decoration: none;
- color: inherit;
- margin-block-start: 1rem;
- margin-inline-end: 1rem;
- margin-block-end: 0;
- margin-inline-start: 0;
- white-space: nowrap;
- background-color: $layer;
- border-inline-start: 3px solid transparent;
-
- > .tkn--status-icon {
- margin-inline-end: 0.75rem;
-
- &.tkn--status-icon--warning {
- inline-size: 24px;
- block-size: 24px;
- margin-block-start: 2px;
- margin-inline-end: 0.5rem;
- }
- }
-
- &:focus, &:hover {
- background-color: $layer-hover;
- text-decoration: none;
- border-inline-start-color: $layer-hover;
- }
-
- .tkn--task-link--name {
- flex-grow: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- > .tkn--status-icon,
- > .tkn--task--expand-icon {
- flex-shrink: 0;
- }
-
- > .#{$prefix}--overflow-menu {
- flex-shrink: 0;
- margin-inline-end: 10px;
- }
- }
-
- &[data-selected] > .tkn--task-link {
- border-inline-start: 3px solid $border-interactive;
- margin-inline-end: 0;
- }
-
- &[data-active] > .tkn--task-link {
- margin-inline-end: 0;
-
- .tkn--task--expand-icon {
- /* needed when we support expanding multiple tasks */
- margin-inline-end: 1rem;
- }
- }
-
- .#{$prefix}--dropdown__wrapper.#{$prefix}--dropdown__wrapper--inline {
- position: relative;
- inset-inline-start: -1.5rem;
- grid-gap: 0;
- margin-inline-start: 0.5rem;
- }
-
- &:not([data-succeeded]) > .tkn--task-link {
- &:hover {
- color: inherit;
- background-color: $layer-hover;
- }
- }
-
- &:first-child > .tkn--task-link {
- margin-block-start: 0;
- }
-
- .tkn--step-list {
- background-color: $layer;
- }
-}
-
-.tkn--task--retries-menu-options {
- max-block-size: 350px;
- overflow: auto;
-
- .#{$prefix}--overflow-menu-options__option {
- flex-shrink: 0;
- }
-}
.#{$prefix}--tabs__nav-item.tkn--task {
.#{$prefix}--tabs__nav-item-label-wrapper {
diff --git a/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx b/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx
index 33d350f1f..33e956b7e 100644
--- a/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx
+++ b/packages/components/src/components/TaskRunDetails/TaskRunDetails.stories.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2020-2025 The Tekton Authors
+Copyright 2020-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -25,6 +25,128 @@ const params = [
];
const results = [{ name: 'message', value: 'hello' }];
+const pod = {
+ events: [
+ {
+ metadata: {
+ name: 'guarded-pr-vkm6w-check-file-pod.1721f00ca1846de4',
+ namespace: 'test',
+ uid: '0f4218f0-270a-408d-b5bd-56fc35dda853',
+ resourceVersion: '2047658',
+ creationTimestamp: '2022-10-27T13:27:54Z'
+ },
+ involvedObject: {
+ kind: 'Pod',
+ namespace: 'test',
+ name: 'guarded-pr-vkm6w-check-file-pod',
+ uid: '939a4823-2203-4b5a-8c00-6a2c9f15549d',
+ apiVersion: 'v1',
+ resourceVersion: '2047624'
+ },
+ reason: 'Scheduled',
+ message:
+ 'Successfully assigned test/guarded-pr-vkm6w-check-file-pod to tekton-dashboard-control-plane',
+ '…': ''
+ },
+ {
+ metadata: {
+ name: 'guarded-pr-vkm6w-check-file-pod.1721f00cb6ef6ea7',
+ namespace: 'test',
+ uid: 'd1c8e367-66d1-4cd7-a04b-e49bdf9f322e',
+ resourceVersion: '2047664',
+ creationTimestamp: '2022-10-27T13:27:54Z'
+ },
+ involvedObject: {
+ kind: 'Pod',
+ namespace: 'test',
+ name: 'guarded-pr-vkm6w-check-file-pod',
+ uid: '939a4823-2203-4b5a-8c00-6a2c9f15549d',
+ apiVersion: 'v1',
+ resourceVersion: '2047657',
+ fieldPath: 'spec.initContainers{prepare}'
+ },
+ reason: 'Pulled',
+ message:
+ 'Container image "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/entrypoint:v0.40.0@sha256:ee6c81fa567c97b4dba0fb315fa038c671a0250ac3a5d43e6ccf8a91e86e6352" already present on machine',
+ '…': ''
+ }
+ ],
+ resource: {
+ kind: 'Pod',
+ apiVersion: 'v1',
+ metadata: {
+ name: 'some-pod-name',
+ namespace: 'test',
+ uid: '939a4823-2203-4b5a-8c00-6a2c9f15549d',
+ resourceVersion: '2047732',
+ creationTimestamp: '2022-10-27T13:27:49Z'
+ },
+ spec: {
+ '…': ''
+ }
+ }
+};
+
+const taskRun = {
+ metadata: { name: 'my-task', namespace: 'my-namespace', uid: 'my-task' },
+ spec: {
+ params,
+ taskSpec: {
+ params: [
+ {
+ name: params[0].name,
+ description: 'A useful description of the param…'
+ }
+ ],
+ results: [
+ {
+ name: results[0].name,
+ description: 'A useful description of the result…'
+ }
+ ]
+ }
+ },
+ status: {
+ completionTime: '2021-03-03T15:25:34Z',
+ podName: 'my-task-h7d6j-pod-pdtb7',
+ startTime: '2021-03-03T15:25:27Z',
+ results
+ }
+};
+
+const taskRunWithWarning = {
+ ...taskRun,
+ status: {
+ ...taskRun.status,
+ conditions: [
+ {
+ reason: 'Succeeded',
+ status: 'True',
+ type: 'Succeeded'
+ }
+ ],
+ steps: [
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 1,
+ reason: 'Completed'
+ }
+ }
+ ]
+ }
+};
+
+function renderStory(args) {
+ const [, updateArgs] = useArgs();
+ return (
+ updateArgs({ view: selectedView })}
+ />
+ );
+}
+
export default {
component: TaskRunDetails,
title: 'TaskRunDetails'
@@ -32,175 +154,48 @@ export default {
export const Default = {
args: {
- taskRun: {
- metadata: { name: 'my-task', namespace: 'my-namespace' },
- spec: {
- params,
- taskSpec: {
- params: [
- {
- name: params[0].name,
- description: 'A useful description of the param…'
- }
- ],
- results: [
- {
- name: results[0].name,
- description: 'A useful description of the result…'
- }
- ]
- }
- },
- status: {
- completionTime: '2021-03-03T15:25:34Z',
- podName: 'my-task-h7d6j-pod-pdtb7',
- startTime: '2021-03-03T15:25:27Z',
- results
- }
- }
+ logs: 'Sample log output',
+ taskRun,
+ view: 'logs'
},
- render: args => {
- const [, updateArgs] = useArgs();
- return (
- updateArgs({ view: selectedView })}
- />
- );
- }
+ render: renderStory
};
export const WithWarning = {
args: {
- taskRun: {
- metadata: { name: 'my-task', namespace: 'my-namespace' },
- spec: {
- params
- },
- status: {
- completionTime: '2021-03-03T15:25:34Z',
- podName: 'my-task-h7d6j-pod-pdtb7',
- conditions: [
- {
- reason: 'Succeeded',
- status: 'True',
- type: 'Succeeded'
- }
- ],
- steps: [
- {
- terminated: {
- exitCode: 1,
- reason: 'Completed'
- }
- }
- ],
- startTime: '2021-03-03T15:25:27Z',
- results
- }
- }
+ logs: 'Command completed with a warning exit code.',
+ taskRun: taskRunWithWarning,
+ view: 'logs'
},
- render: args => {
- const [, updateArgs] = useArgs();
- return (
- updateArgs({ view: selectedView })}
- />
- );
- }
+ render: renderStory
};
export const Pod = {
args: {
- pod: {
- events: [
- {
- metadata: {
- name: 'guarded-pr-vkm6w-check-file-pod.1721f00ca1846de4',
- namespace: 'test',
- uid: '0f4218f0-270a-408d-b5bd-56fc35dda853',
- resourceVersion: '2047658',
- creationTimestamp: '2022-10-27T13:27:54Z'
- },
- involvedObject: {
- kind: 'Pod',
- namespace: 'test',
- name: 'guarded-pr-vkm6w-check-file-pod',
- uid: '939a4823-2203-4b5a-8c00-6a2c9f15549d',
- apiVersion: 'v1',
- resourceVersion: '2047624'
- },
- reason: 'Scheduled',
- message:
- 'Successfully assigned test/guarded-pr-vkm6w-check-file-pod to tekton-dashboard-control-plane',
- '…': ''
- },
- {
- metadata: {
- name: 'guarded-pr-vkm6w-check-file-pod.1721f00cb6ef6ea7',
- namespace: 'test',
- uid: 'd1c8e367-66d1-4cd7-a04b-e49bdf9f322e',
- resourceVersion: '2047664',
- creationTimestamp: '2022-10-27T13:27:54Z'
- },
- involvedObject: {
- kind: 'Pod',
- namespace: 'test',
- name: 'guarded-pr-vkm6w-check-file-pod',
- uid: '939a4823-2203-4b5a-8c00-6a2c9f15549d',
- apiVersion: 'v1',
- resourceVersion: '2047657',
- fieldPath: 'spec.initContainers{prepare}'
- },
- reason: 'Pulled',
- message:
- 'Container image "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/entrypoint:v0.40.0@sha256:ee6c81fa567c97b4dba0fb315fa038c671a0250ac3a5d43e6ccf8a91e86e6352" already present on machine',
- '…': ''
- }
- ],
- resource: {
- kind: 'Pod',
- apiVersion: 'v1',
- metadata: {
- name: 'some-pod-name',
- namespace: 'test',
- uid: '939a4823-2203-4b5a-8c00-6a2c9f15549d',
- resourceVersion: '2047732',
- creationTimestamp: '2022-10-27T13:27:49Z'
- },
- spec: {
- '…': ''
- }
- }
- },
+ pod,
taskRun: {
- metadata: { name: 'my-task' },
+ metadata: { name: 'my-task', uid: 'my-task' },
spec: {},
status: {
completionTime: '2021-03-03T15:25:34Z',
podName: 'my-task-h7d6j-pod-pdtb7',
startTime: '2021-03-03T15:25:27Z'
}
- }
+ },
+ view: 'pod'
},
- render: args => {
- const [, updateArgs] = useArgs();
- return (
- updateArgs({ view: selectedView })}
- />
- );
- }
+ render: renderStory
};
export const Skipped = {
args: {
- ...Pod.args,
+ logs: 'This step did not run as the task was skipped. See status for more details.',
skippedTask: {
reason: 'When Expressions evaluated to false',
whenExpressions: [{ cel: `'yes'=='missing'` }]
- }
- }
+ },
+ taskRun,
+ view: 'logs'
+ },
+ render: renderStory
};
diff --git a/packages/components/src/components/TaskRunDetails/_TaskRunDetails.scss b/packages/components/src/components/TaskRunDetails/_TaskRunDetails.scss
index 935c618d5..f0e4e366b 100644
--- a/packages/components/src/components/TaskRunDetails/_TaskRunDetails.scss
+++ b/packages/components/src/components/TaskRunDetails/_TaskRunDetails.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2024 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -20,6 +20,10 @@ limitations under the License.
align-items: stretch;
overflow: hidden;
+ .#{$prefix}--tab-content {
+ padding-inline-start: 0;
+ }
+
.#{$prefix}--content-switcher {
margin-block-end: 1rem;
}
diff --git a/packages/components/src/components/TaskRunLogs/TaskRunLogs.jsx b/packages/components/src/components/TaskRunLogs/TaskRunLogs.jsx
new file mode 100644
index 000000000..57336499f
--- /dev/null
+++ b/packages/components/src/components/TaskRunLogs/TaskRunLogs.jsx
@@ -0,0 +1,102 @@
+/*
+Copyright 2025-2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { useIntl } from 'react-intl';
+import { Accordion } from '@carbon/react';
+import { getStatus } from '@tektoncd/dashboard-utils';
+
+import TaskRunStep from '../TaskRunStep';
+
+function TaskRunLogs({
+ expandedSteps,
+ getLogContainer,
+ ignoredSidecars,
+ onStepSelected,
+ selectedRetry,
+ selectedTaskId,
+ skippedTask,
+ task,
+ taskRun
+}) {
+ const intl = useIntl();
+
+ const taskRunStatus = getStatus(taskRun);
+ const { reason } = taskRunStatus;
+ const { sidecars, steps } = taskRun.status || {};
+ const sidecarsToRender =
+ sidecars?.filter(sidecar => !ignoredSidecars[sidecar.name]) || [];
+
+ if (!steps) {
+ return (
+
+ {intl.formatMessage({
+ id: 'dashboard.taskRun.logs.unavailable',
+ defaultMessage: 'No logs are available. See status for more details.'
+ })}
+
+ );
+ }
+ if (skippedTask) {
+ return (
+
+ {intl.formatMessage({
+ id: 'dashboard.taskRun.logs.skipped',
+ defaultMessage:
+ 'This step did not run as the task was skipped. See status for more details.'
+ })}
+
+ );
+ }
+
+ return (
+
+ {steps.map(step => (
+
+ ))}
+ {sidecarsToRender.map(sidecar => (
+
+ ))}
+
+ );
+}
+
+export default TaskRunLogs;
diff --git a/packages/components/src/components/TaskRunLogs/TaskRunLogs.stories.jsx b/packages/components/src/components/TaskRunLogs/TaskRunLogs.stories.jsx
new file mode 100644
index 000000000..8a38c0635
--- /dev/null
+++ b/packages/components/src/components/TaskRunLogs/TaskRunLogs.stories.jsx
@@ -0,0 +1,343 @@
+/*
+Copyright 2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { action } from 'storybook/actions';
+import { useArgs } from 'storybook/preview-api';
+
+import TaskRunLogs from './TaskRunLogs';
+
+export default {
+ component: TaskRunLogs,
+ title: 'TaskRunLogs'
+};
+
+const baseProps = {
+ expandedSteps: {},
+ getLogContainer: () => (
+
+ {`[2025-01-01 10:00:00] Starting step…
+[2025-01-01 10:00:01] Processing files…
+${Array.from({ length: 50 }, (_, index) => `[2025-01-01 10:00:02] ${index}`).join('\n')}
+[2025-01-01 10:00:05] Step completed successfully`}
+
+ ),
+ ignoredSidecars: {},
+ onStepSelected: action('onStepSelected'),
+ selectedRetry: 0,
+ selectedTaskId: 'task-1',
+ skippedTask: false,
+ task: {
+ spec: {
+ steps: [
+ {
+ args: [
+ 'build',
+ '-f',
+ '${params.pathToDockerFile}',
+ '-t',
+ '${resources.outputs.builtImage.url}',
+ '${params.pathToContext}'
+ ],
+ command: ['docker'],
+ image: 'docker',
+ name: 'build',
+ volumeMounts: [
+ {
+ mountPath: '/var/run/docker.sock',
+ name: 'docker-socket'
+ }
+ ]
+ }
+ ]
+ }
+ }
+};
+
+export const SingleStep = {
+ args: {
+ ...baseProps,
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: [
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:05:30Z'
+ }
+ }
+ ]
+ }
+ }
+ }
+};
+
+export const MultipleSteps = {
+ args: {
+ ...baseProps,
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: [
+ {
+ name: 'clone',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:01:00Z'
+ }
+ },
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:01:00Z',
+ finishedAt: '2025-01-01T10:05:30Z'
+ }
+ },
+ {
+ name: 'test',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:05:30Z',
+ finishedAt: '2025-01-01T10:06:00Z'
+ }
+ }
+ ]
+ }
+ }
+ },
+ render: args => {
+ const [, updateArgs] = useArgs();
+ return (
+
+ updateArgs({
+ expandedSteps: { ...args.expandedSteps, [stepId]: isOpen }
+ })
+ }
+ />
+ );
+ }
+};
+
+export const WithRunningStep = {
+ args: {
+ ...baseProps,
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: [
+ {
+ name: 'clone',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:01:00Z'
+ }
+ },
+ {
+ name: 'build',
+ running: {
+ startedAt: '2025-01-01T10:01:00Z'
+ }
+ },
+ {
+ name: 'test',
+ waiting: {}
+ }
+ ]
+ }
+ }
+ }
+};
+
+export const WithFailedStep = {
+ args: {
+ ...baseProps,
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: [
+ {
+ name: 'clone',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:01:00Z'
+ }
+ },
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 1,
+ reason: 'Error',
+ startedAt: '2025-01-01T10:01:00Z',
+ finishedAt: '2025-01-01T10:01:30Z'
+ }
+ }
+ ]
+ }
+ }
+ }
+};
+
+export const WithSidecars = {
+ args: {
+ ...baseProps,
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: [
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:05:30Z'
+ }
+ }
+ ],
+ sidecars: [
+ {
+ name: 'logging',
+ running: {
+ startedAt: '2025-01-01T10:00:00Z'
+ }
+ },
+ {
+ name: 'monitoring',
+ running: {
+ startedAt: '2025-01-01T10:00:00Z'
+ }
+ }
+ ]
+ }
+ }
+ },
+ render: args => {
+ const [, updateArgs] = useArgs();
+ return (
+
+ updateArgs({
+ expandedSteps: { ...args.expandedSteps, [stepId]: isOpen }
+ })
+ }
+ />
+ );
+ }
+};
+
+export const WithIgnoredSidecars = {
+ args: {
+ ...baseProps,
+ ignoredSidecars: { monitoring: true },
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: [
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:05:30Z'
+ }
+ }
+ ],
+ sidecars: [
+ {
+ name: 'logging',
+ running: {
+ startedAt: '2025-01-01T10:00:00Z'
+ }
+ },
+ {
+ name: 'monitoring',
+ running: {
+ startedAt: '2025-01-01T10:00:00Z'
+ }
+ }
+ ]
+ }
+ }
+ }
+};
+
+export const NoStepsAvailable = {
+ args: {
+ ...baseProps,
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {}
+ }
+ }
+};
+
+export const SkippedTask = {
+ args: {
+ ...baseProps,
+ skippedTask: true,
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: []
+ }
+ }
+ }
+};
+
+export const WithExpandedStep = {
+ args: {
+ ...baseProps,
+ expandedSteps: { build: true },
+ taskRun: {
+ metadata: { name: 'taskrun-1' },
+ status: {
+ steps: [
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:05:30Z'
+ }
+ },
+ {
+ name: 'test',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:05:30Z',
+ finishedAt: '2025-01-01T10:06:00Z'
+ }
+ }
+ ]
+ }
+ }
+ }
+};
diff --git a/packages/components/src/components/TaskRunLogs/TaskRunLogs.test.jsx b/packages/components/src/components/TaskRunLogs/TaskRunLogs.test.jsx
new file mode 100644
index 000000000..4b043e140
--- /dev/null
+++ b/packages/components/src/components/TaskRunLogs/TaskRunLogs.test.jsx
@@ -0,0 +1,175 @@
+/*
+Copyright 2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { render } from '../../utils/test';
+
+import TaskRunLogs from './TaskRunLogs';
+
+describe('TaskRunLogs', () => {
+ const defaultProps = {
+ expandedSteps: {},
+ getLogContainer: vi.fn(() => Logs
),
+ ignoredSidecars: {},
+ onStepSelected: vi.fn(),
+ selectedRetry: 0,
+ selectedTaskId: 'task1',
+ skippedTask: false,
+ task: {},
+ taskRun: {
+ status: {
+ steps: [
+ {
+ name: 'step1',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ]
+ }
+ }
+ };
+
+ it('should render steps in an accordion', () => {
+ const { queryByText } = render( );
+ expect(queryByText('step1')).toBeTruthy();
+ });
+
+ it('should display message when no steps are available', () => {
+ const taskRun = { status: {} };
+ const { queryByText } = render(
+
+ );
+ expect(
+ queryByText('No logs are available. See status for more details.')
+ ).toBeTruthy();
+ });
+
+ it('should display message when task is skipped', () => {
+ const { queryByText } = render(
+
+ );
+ expect(
+ queryByText(
+ 'This step did not run as the task was skipped. See status for more details.'
+ )
+ ).toBeTruthy();
+ });
+
+ it('should render multiple steps', () => {
+ const taskRun = {
+ status: {
+ steps: [
+ {
+ name: 'step1',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ },
+ {
+ name: 'step2',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ]
+ }
+ };
+ const { queryByText } = render(
+
+ );
+ expect(queryByText('step1')).toBeTruthy();
+ expect(queryByText('step2')).toBeTruthy();
+ });
+
+ it('should auto-expand single step', () => {
+ const { queryByText } = render( );
+ // When there's only one step, it should be expanded and show logs
+ expect(queryByText('Logs')).toBeTruthy();
+ });
+
+ it('should render sidecars', () => {
+ const taskRun = {
+ status: {
+ steps: [
+ {
+ name: 'step1',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ],
+ sidecars: [
+ {
+ name: 'sidecar1',
+ running: {}
+ }
+ ]
+ }
+ };
+ const { queryByText } = render(
+
+ );
+ expect(queryByText('step1')).toBeTruthy();
+ expect(queryByText('Sidecar: sidecar1')).toBeTruthy();
+ });
+
+ it('should filter ignored sidecars', () => {
+ const taskRun = {
+ status: {
+ steps: [
+ {
+ name: 'step1',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ],
+ sidecars: [
+ {
+ name: 'sidecar1',
+ running: {}
+ },
+ {
+ name: 'sidecar2',
+ running: {}
+ }
+ ]
+ }
+ };
+ const ignoredSidecars = { sidecar2: true };
+ const { queryByText } = render(
+
+ );
+ expect(queryByText('Sidecar: sidecar1')).toBeTruthy();
+ expect(queryByText('Sidecar: sidecar2')).toBeFalsy();
+ });
+
+ it('should pass correct props to TaskRunStep', () => {
+ const onStepSelected = vi.fn();
+ const { container } = render(
+
+ );
+ const heading = container.querySelector('.cds--accordion__heading');
+ heading.click();
+ expect(onStepSelected).toHaveBeenCalled();
+ });
+});
diff --git a/packages/components/src/components/Step/index.js b/packages/components/src/components/TaskRunLogs/index.js
similarity index 88%
rename from packages/components/src/components/Step/index.js
rename to packages/components/src/components/TaskRunLogs/index.js
index 96c860cdd..bed139a15 100644
--- a/packages/components/src/components/Step/index.js
+++ b/packages/components/src/components/TaskRunLogs/index.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2021 The Tekton Authors
+Copyright 2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -12,4 +12,4 @@ limitations under the License.
*/
/* istanbul ignore file */
-export { default } from './Step';
+export { default } from './TaskRunLogs';
diff --git a/packages/components/src/components/TaskRunStep/TaskRunStep.jsx b/packages/components/src/components/TaskRunStep/TaskRunStep.jsx
new file mode 100644
index 000000000..23c675435
--- /dev/null
+++ b/packages/components/src/components/TaskRunStep/TaskRunStep.jsx
@@ -0,0 +1,239 @@
+/*
+Copyright 2025-2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { useIntl } from 'react-intl';
+import { Pending as DefaultIcon } from '@carbon/react/icons';
+import {
+ getStepDefinition,
+ getStepStatusReason,
+ updateUnexecutedSteps
+} from '@tektoncd/dashboard-utils';
+
+import AccordionItem from '../AccordionItem';
+import StatusIcon from '../StatusIcon';
+import FormattedDuration from '../FormattedDuration';
+import StepDefinition from '../StepDefinition';
+
+export function getStepData({ isSidecar, reason, selectedStepId, steps }) {
+ // sidecars run in parallel to the regular steps, so keep their actual status
+ const stepsToUse = isSidecar ? steps : updateUnexecutedSteps(steps);
+ const step = stepsToUse.find(
+ stepToCheck => stepToCheck.name === selectedStepId
+ );
+ if (!step) {
+ return null;
+ }
+
+ let hasWarning = false;
+ const { name, terminationReason } = step;
+ const { exitCode, status, reason: stepReason } = getStepStatusReason(step);
+
+ if (stepReason === 'Completed') {
+ hasWarning = hasWarning || exitCode !== 0;
+ }
+
+ const selected = selectedStepId === name;
+ const stepStatus =
+ reason === 'TaskRunCancelled' && status !== 'terminated'
+ ? 'cancelled'
+ : status;
+
+ return {
+ exitCode,
+ hasWarning,
+ name,
+ selected,
+ stepReason,
+ stepStatus,
+ terminationReason
+ };
+}
+
+function TaskRunStep({
+ expandedSteps,
+ getLogContainer,
+ isSidecar,
+ onStepSelected,
+ selectedRetry,
+ selectedTaskId,
+ step,
+ steps,
+ task,
+ taskRun,
+ taskRunReason
+}) {
+ const intl = useIntl();
+ const stepNamePrefix = isSidecar ? 'sidecar:' : '';
+
+ let duration;
+ if (step.terminated && step.terminationReason !== 'Skipped') {
+ const { finishedAt, startedAt } = step.terminated;
+
+ if (finishedAt && startedAt && new Date(startedAt).getTime() !== 0) {
+ duration = (
+
+ );
+ }
+ }
+
+ const stepData = getStepData({
+ isSidecar,
+ reason: taskRunReason,
+ selectedStepId: step.name,
+ steps
+ });
+
+ const { exitCode, stepReason, stepStatus, terminationReason } = stepData;
+
+ function getStatusLabel() {
+ if (terminationReason === 'Skipped') {
+ return intl.formatMessage({
+ id: 'dashboard.taskRun.status.skipped',
+ defaultMessage: 'Skipped'
+ });
+ }
+ if (
+ stepStatus === 'cancelled' ||
+ (stepStatus === 'terminated' &&
+ (stepReason === 'TaskRunCancelled' || stepReason === 'TaskRunTimeout'))
+ ) {
+ return intl.formatMessage({
+ id: 'dashboard.taskRun.status.cancelled',
+ defaultMessage: 'Cancelled'
+ });
+ }
+
+ if (stepStatus === 'running') {
+ return intl.formatMessage({
+ id: 'dashboard.taskRun.status.running',
+ defaultMessage: 'Running'
+ });
+ }
+
+ if (stepStatus === 'terminated') {
+ if (stepReason === 'Completed') {
+ return exitCode !== 0
+ ? intl.formatMessage(
+ {
+ id: 'dashboard.taskRun.status.succeeded.warning',
+ defaultMessage: 'Completed with exit code {exitCode}'
+ },
+ { exitCode }
+ )
+ : intl.formatMessage({
+ id: 'dashboard.taskRun.status.succeeded',
+ defaultMessage: 'Completed'
+ });
+ }
+ return intl.formatMessage({
+ id: 'dashboard.taskRun.status.failed',
+ defaultMessage: 'Failed'
+ });
+ }
+
+ if (stepStatus === 'waiting') {
+ return intl.formatMessage({
+ id: 'dashboard.taskRun.status.waiting',
+ defaultMessage: 'Waiting'
+ });
+ }
+
+ return intl.formatMessage({
+ id: 'dashboard.taskRun.status.notRun',
+ defaultMessage: 'Not run'
+ });
+ }
+
+ const statusLabel = getStatusLabel();
+ const stepDefinition = getStepDefinition({
+ isSidecar,
+ selectedStepId: step.name,
+ task,
+ taskRun
+ });
+ const stepDisplayName = stepDefinition?.displayName || step.name;
+ const displayName = isSidecar
+ ? intl.formatMessage(
+ {
+ id: 'dashboard.taskRun.sidecar',
+ defaultMessage: 'Sidecar: {name}'
+ },
+ { name: stepDisplayName }
+ )
+ : stepDisplayName;
+
+ return (
+ {
+ onStepSelected({
+ isOpen,
+ selectedRetry,
+ selectedStepId: `${stepNamePrefix}${step.name}`,
+ selectedTaskId,
+ taskRunName: taskRun.metadata?.name
+ });
+ }}
+ title={
+ <>
+ }
+ hasWarning={exitCode !== 0}
+ reason={stepReason}
+ status={stepStatus}
+ terminationReason={terminationReason}
+ title={statusLabel}
+ type="inverse"
+ />
+
+ {displayName}
+
+ {!isSidecar ? (
+ {duration}
+ ) : null}
+ >
+ }
+ >
+ {expandedSteps[`${stepNamePrefix}${step.name}`] ? (
+ <>
+
+
+ {intl.formatMessage({
+ id: 'dashboard.step.definition',
+ defaultMessage: 'Definition'
+ })}
+
+
+
+
+ {getLogContainer({
+ isSidecar,
+ stepName: step.name,
+ stepStatus: step,
+ taskRun
+ })}
+ >
+ ) : null}
+
+ );
+}
+
+export default TaskRunStep;
diff --git a/packages/components/src/components/TaskRunStep/TaskRunStep.stories.jsx b/packages/components/src/components/TaskRunStep/TaskRunStep.stories.jsx
new file mode 100644
index 000000000..dd6adf127
--- /dev/null
+++ b/packages/components/src/components/TaskRunStep/TaskRunStep.stories.jsx
@@ -0,0 +1,281 @@
+/*
+Copyright 2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { Accordion } from '@carbon/react';
+import { action } from 'storybook/actions';
+
+import TaskRunStep from './TaskRunStep';
+
+export default {
+ component: TaskRunStep,
+ decorators: [
+ Story => (
+
+
+
+ )
+ ],
+ title: 'TaskRunStep'
+};
+
+const baseProps = {
+ expandedSteps: {},
+ getLogContainer: () => Step logs would appear here... ,
+ isSidecar: false,
+ onStepSelected: action('onStepSelected'),
+ selectedRetry: 0,
+ selectedTaskId: 'task-1',
+ steps: [],
+ task: {},
+ taskRun: {
+ metadata: { name: 'taskrun-1' }
+ },
+ taskRunReason: 'Succeeded'
+};
+
+export const CompletedStep = {
+ args: {
+ ...baseProps,
+ step: {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:05:30Z'
+ }
+ },
+ steps: [
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ]
+ }
+};
+
+export const CompletedStepExpanded = {
+ args: {
+ ...baseProps,
+ expandedSteps: { build: true },
+ step: {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:05:30Z'
+ }
+ },
+ steps: [
+ {
+ name: 'build',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ]
+ }
+};
+
+export const CompletedWithWarning = {
+ args: {
+ ...baseProps,
+ step: {
+ name: 'test',
+ terminated: {
+ exitCode: 1,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:05:30Z',
+ finishedAt: '2025-01-01T10:06:00Z'
+ }
+ },
+ steps: [
+ {
+ name: 'test',
+ terminated: {
+ exitCode: 1,
+ reason: 'Completed'
+ }
+ }
+ ]
+ }
+};
+
+export const RunningStep = {
+ args: {
+ ...baseProps,
+ step: {
+ name: 'deploy',
+ running: {
+ startedAt: '2025-01-01T10:06:00Z'
+ }
+ },
+ steps: [
+ {
+ name: 'deploy',
+ running: {
+ startedAt: '2025-01-01T10:06:00Z'
+ }
+ }
+ ],
+ taskRunReason: 'Running'
+ }
+};
+
+export const FailedStep = {
+ args: {
+ ...baseProps,
+ step: {
+ name: 'validate',
+ terminated: {
+ exitCode: 1,
+ reason: 'Error',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:00:30Z'
+ }
+ },
+ steps: [
+ {
+ name: 'validate',
+ terminated: {
+ exitCode: 1,
+ reason: 'Error'
+ }
+ }
+ ],
+ taskRunReason: 'Failed'
+ }
+};
+
+export const SkippedStep = {
+ args: {
+ ...baseProps,
+ step: {
+ name: 'optional-step',
+ terminated: {
+ reason: 'Completed'
+ },
+ terminationReason: 'Skipped'
+ },
+ steps: [
+ {
+ name: 'optional-step',
+ terminated: {
+ reason: 'Completed'
+ },
+ terminationReason: 'Skipped'
+ }
+ ]
+ }
+};
+
+export const CancelledStep = {
+ args: {
+ ...baseProps,
+ step: {
+ name: 'long-running',
+ waiting: {}
+ },
+ steps: [
+ {
+ name: 'long-running',
+ waiting: {}
+ }
+ ],
+ taskRunReason: 'TaskRunCancelled'
+ }
+};
+
+export const WaitingStep = {
+ args: {
+ ...baseProps,
+ step: {
+ name: 'pending',
+ waiting: {}
+ },
+ steps: [
+ {
+ name: 'pending',
+ waiting: {}
+ }
+ ],
+ taskRunReason: 'Pending'
+ }
+};
+
+export const SidecarStep = {
+ args: {
+ ...baseProps,
+ isSidecar: true,
+ step: {
+ name: 'logging-sidecar',
+ running: {
+ startedAt: '2025-01-01T10:00:00Z'
+ }
+ },
+ steps: [
+ {
+ name: 'logging-sidecar',
+ running: {
+ startedAt: '2025-01-01T10:00:00Z'
+ }
+ }
+ ],
+ taskRunReason: 'Running'
+ }
+};
+
+export const MultipleSteps = {
+ render: () => {
+ const steps = [
+ {
+ name: 'clone',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T10:00:00Z',
+ finishedAt: '2025-01-01T10:01:00Z'
+ }
+ },
+ {
+ name: 'build',
+ running: {
+ startedAt: '2025-01-01T10:01:00Z'
+ }
+ },
+ {
+ name: 'test',
+ waiting: {}
+ }
+ ];
+
+ return (
+
+ {steps.map(step => (
+
+ ))}
+
+ );
+ }
+};
diff --git a/packages/components/src/components/TaskRunStep/TaskRunStep.test.jsx b/packages/components/src/components/TaskRunStep/TaskRunStep.test.jsx
new file mode 100644
index 000000000..6ab93fe3b
--- /dev/null
+++ b/packages/components/src/components/TaskRunStep/TaskRunStep.test.jsx
@@ -0,0 +1,221 @@
+/*
+Copyright 2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { render } from '../../utils/test';
+
+import TaskRunStep, { getStepData } from './TaskRunStep';
+
+describe('getStepData', () => {
+ it('should return null when step is not found', () => {
+ const steps = [{ name: 'step1' }];
+ const result = getStepData({
+ isSidecar: false,
+ reason: 'Succeeded',
+ selectedStepId: 'nonexistent',
+ steps
+ });
+ expect(result).toBeNull();
+ });
+
+ it('should return step data for regular step', () => {
+ const steps = [
+ {
+ name: 'step1',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ];
+ const result = getStepData({
+ isSidecar: false,
+ reason: 'Succeeded',
+ selectedStepId: 'step1',
+ steps
+ });
+ expect(result).toEqual({
+ exitCode: 0,
+ hasWarning: false,
+ name: 'step1',
+ selected: true,
+ stepReason: 'Completed',
+ stepStatus: 'terminated',
+ terminationReason: undefined
+ });
+ });
+
+ it('should mark step as cancelled when TaskRunCancelled', () => {
+ const steps = [
+ {
+ name: 'step1',
+ waiting: {}
+ }
+ ];
+ const result = getStepData({
+ isSidecar: false,
+ reason: 'TaskRunCancelled',
+ selectedStepId: 'step1',
+ steps
+ });
+ expect(result.stepStatus).toBe('cancelled');
+ });
+
+ it('should set hasWarning when exit code is non-zero', () => {
+ const steps = [
+ {
+ name: 'step1',
+ terminated: {
+ exitCode: 1,
+ reason: 'Completed'
+ }
+ }
+ ];
+ const result = getStepData({
+ isSidecar: false,
+ reason: 'Succeeded',
+ selectedStepId: 'step1',
+ steps
+ });
+ expect(result.hasWarning).toBe(true);
+ expect(result.exitCode).toBe(1);
+ });
+
+ it('should handle sidecar steps', () => {
+ const steps = [
+ {
+ name: 'sidecar1',
+ running: {}
+ }
+ ];
+ const result = getStepData({
+ isSidecar: true,
+ reason: 'Running',
+ selectedStepId: 'sidecar1',
+ steps
+ });
+ expect(result.stepStatus).toBe('running');
+ });
+});
+
+describe('TaskRunStep', () => {
+ const defaultProps = {
+ expandedSteps: {},
+ getLogContainer: vi.fn(() => Logs
),
+ isSidecar: false,
+ onStepSelected: vi.fn(),
+ selectedRetry: 0,
+ selectedTaskId: 'task1',
+ step: {
+ name: 'step1',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed',
+ startedAt: '2025-01-01T00:00:00Z',
+ finishedAt: '2025-01-01T00:01:00Z'
+ }
+ },
+ steps: [
+ {
+ name: 'step1',
+ terminated: {
+ exitCode: 0,
+ reason: 'Completed'
+ }
+ }
+ ],
+ task: {},
+ taskRun: {
+ metadata: { name: 'taskrun1' }
+ },
+ taskRunReason: 'Succeeded'
+ };
+
+ it('should render step with status icon and name', () => {
+ const { queryByText } = render( );
+ expect(queryByText('step1')).toBeTruthy();
+ });
+
+ it('should render duration for completed step', () => {
+ const { container } = render( );
+ const duration = container.querySelector('.tkn--step-duration');
+ expect(duration).toBeTruthy();
+ });
+
+ it('should not render duration for sidecar', () => {
+ const { container } = render( );
+ const duration = container.querySelector('.tkn--step-duration');
+ expect(duration).toBeFalsy();
+ });
+
+ it('should call onStepSelected when heading is clicked', () => {
+ const onStepSelected = vi.fn();
+ const { container } = render(
+
+ );
+ const heading = container.querySelector('.cds--accordion__heading');
+ heading.click();
+ expect(onStepSelected).toHaveBeenCalledWith({
+ isOpen: true,
+ selectedRetry: 0,
+ selectedStepId: 'step1',
+ selectedTaskId: 'task1',
+ taskRunName: 'taskrun1'
+ });
+ });
+
+ it('should render step definition and logs when expanded', () => {
+ const expandedSteps = { step1: true };
+ const { queryByText } = render(
+
+ );
+ expect(queryByText('Definition')).toBeTruthy();
+ expect(queryByText('Logs')).toBeTruthy();
+ });
+
+ it('should not render step definition and logs when collapsed', () => {
+ const { queryByText } = render( );
+ expect(queryByText('Definition')).toBeFalsy();
+ expect(queryByText('Logs')).toBeFalsy();
+ });
+
+ it('should render sidecar prefix for sidecar steps', () => {
+ const { queryByText } = render( );
+ expect(queryByText(/Sidecar:/)).toBeTruthy();
+ });
+
+ it('should handle skipped step', () => {
+ const step = {
+ name: 'step1',
+ terminated: {
+ reason: 'Completed'
+ },
+ terminationReason: 'Skipped'
+ };
+ const steps = [
+ {
+ name: 'step1',
+ terminated: {
+ reason: 'Completed'
+ },
+ terminationReason: 'Skipped'
+ }
+ ];
+ const { container } = render(
+
+ );
+ const accordionItem = container.querySelector(
+ '[data-termination-reason="Skipped"]'
+ );
+ expect(accordionItem).toBeTruthy();
+ });
+});
diff --git a/packages/components/src/components/TaskRunStep/index.js b/packages/components/src/components/TaskRunStep/index.js
new file mode 100644
index 000000000..0947f23d9
--- /dev/null
+++ b/packages/components/src/components/TaskRunStep/index.js
@@ -0,0 +1,15 @@
+/*
+Copyright 2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+/* istanbul ignore file */
+
+export { default, getStepData } from './TaskRunStep';
diff --git a/packages/components/src/components/TaskRunTabPanels/TaskRunTabPanels.jsx b/packages/components/src/components/TaskRunTabPanels/TaskRunTabPanels.jsx
index 6a139ee5c..08bd6ec8d 100644
--- a/packages/components/src/components/TaskRunTabPanels/TaskRunTabPanels.jsx
+++ b/packages/components/src/components/TaskRunTabPanels/TaskRunTabPanels.jsx
@@ -12,328 +12,13 @@ limitations under the License.
*/
/* istanbul ignore file */
-import { useIntl } from 'react-intl';
import {
- Accordion,
- AccordionItem as CarbonAccordionItem,
TabPanel as CarbonTabPanel,
TabPanels as CarbonTabPanels
} from '@carbon/react';
-import { Pending as DefaultIcon } from '@carbon/react/icons';
-import {
- getStatus,
- getStepDefinition,
- getStepStatusReason,
- updateUnexecutedSteps
-} from '@tektoncd/dashboard-utils';
import TaskRunDetails from '../TaskRunDetails';
-import StatusIcon from '../StatusIcon';
-import FormattedDuration from '../FormattedDuration';
-import StepDefinition from '../StepDefinition';
-
-function getStepData({ isSidecar, reason, selectedStepId, steps }) {
- // sidecars run in parallel to the regular steps, so keep their actual status
- const stepsToUse = isSidecar ? steps : updateUnexecutedSteps(steps);
- const step = stepsToUse.find(
- stepToCheck => stepToCheck.name === selectedStepId
- );
- if (!step) {
- return null;
- }
-
- let hasWarning = false;
- const { name, terminationReason } = step;
- const { exitCode, status, reason: stepReason } = getStepStatusReason(step);
-
- if (stepReason === 'Completed') {
- hasWarning = hasWarning || exitCode !== 0;
- }
-
- const selected = selectedStepId === name;
- const stepStatus =
- reason === 'TaskRunCancelled' && status !== 'terminated'
- ? 'cancelled'
- : status;
-
- return {
- exitCode,
- hasWarning,
- name,
- selected,
- stepReason,
- stepStatus,
- terminationReason
- };
-}
-
-function AccordionItem({ children, open, ...rest }) {
- return (
-
- {open ? children : null}
-
- );
-}
-
-function Step({
- expandedSteps,
- getLogContainer,
- isSidecar,
- onStepSelected,
- selectedRetry,
- selectedTaskId,
- step,
- steps,
- task,
- taskRun,
- taskRunReason
-}) {
- const intl = useIntl();
- const stepNamePrefix = isSidecar ? 'sidecar:' : '';
-
- let duration;
- if (step.terminated && step.terminationReason !== 'Skipped') {
- const { finishedAt, startedAt } = step.terminated;
-
- if (finishedAt && startedAt && new Date(startedAt).getTime() !== 0) {
- duration = (
-
- );
- }
- }
-
- const stepData = getStepData({
- isSidecar,
- reason: taskRunReason,
- selectedStepId: step.name,
- steps
- });
-
- const { exitCode, stepReason, stepStatus, terminationReason } = stepData;
-
- function getStatusLabel() {
- if (terminationReason === 'Skipped') {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.skipped',
- defaultMessage: 'Skipped'
- });
- }
- if (
- stepStatus === 'cancelled' ||
- (stepStatus === 'terminated' &&
- (stepReason === 'TaskRunCancelled' || stepReason === 'TaskRunTimeout'))
- ) {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.cancelled',
- defaultMessage: 'Cancelled'
- });
- }
-
- if (stepStatus === 'running') {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.running',
- defaultMessage: 'Running'
- });
- }
-
- if (stepStatus === 'terminated') {
- if (stepReason === 'Completed') {
- return exitCode !== 0
- ? intl.formatMessage(
- {
- id: 'dashboard.taskRun.status.succeeded.warning',
- defaultMessage: 'Completed with exit code {exitCode}'
- },
- { exitCode }
- )
- : intl.formatMessage({
- id: 'dashboard.taskRun.status.succeeded',
- defaultMessage: 'Completed'
- });
- }
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.failed',
- defaultMessage: 'Failed'
- });
- }
-
- if (stepStatus === 'waiting') {
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.waiting',
- defaultMessage: 'Waiting'
- });
- }
-
- return intl.formatMessage({
- id: 'dashboard.taskRun.status.notRun',
- defaultMessage: 'Not run'
- });
- }
-
- const statusLabel = getStatusLabel();
- const stepDefinition = getStepDefinition({
- isSidecar,
- selectedStepId: step.name,
- task,
- taskRun
- });
- const stepDisplayName = stepDefinition?.displayName || step.name;
- const displayName = isSidecar
- ? intl.formatMessage(
- {
- id: 'dashboard.taskRun.sidecar',
- defaultMessage: 'Sidecar: {name}'
- },
- { name: stepDisplayName }
- )
- : stepDisplayName;
-
- return (
- {
- onStepSelected({
- isOpen,
- selectedRetry,
- selectedStepId: `${stepNamePrefix}${step.name}`,
- selectedTaskId,
- taskRunName: taskRun.metadata?.name
- });
- }}
- title={
- <>
- }
- hasWarning={exitCode !== 0}
- reason={stepReason}
- status={stepStatus}
- terminationReason={terminationReason}
- title={statusLabel}
- type="inverse"
- />
-
- {displayName}
-
- {!isSidecar ? (
- {duration}
- ) : null}
- >
- }
- >
- {expandedSteps[`${stepNamePrefix}${step.name}`] ? (
- <>
-
-
- {intl.formatMessage({
- id: 'dashboard.step.definition',
- defaultMessage: 'Definition'
- })}
-
-
-
-
- {getLogContainer({
- disableLogsToolbar: true,
- isSidecar,
- stepName: step.name,
- stepStatus: step,
- taskRun
- })}
- >
- ) : null}
-
- );
-}
-
-function Logs({
- expandedSteps,
- getLogContainer,
- ignoredSidecars,
- onStepSelected,
- selectedRetry,
- selectedTaskId,
- skippedTask,
- task,
- taskRun
-}) {
- const intl = useIntl();
-
- const taskRunStatus = getStatus(taskRun);
- const { reason } = taskRunStatus;
- const { sidecars, steps } = taskRun.status || {};
- const sidecarsToRender =
- sidecars?.filter(sidecar => !ignoredSidecars[sidecar.name]) || [];
-
- if (!steps) {
- return (
-
- {intl.formatMessage({
- id: 'dashboard.taskRun.logs.unavailable',
- defaultMessage: 'No logs are available. See status for more details.'
- })}
-
- );
- }
- if (skippedTask) {
- return (
-
- {intl.formatMessage({
- id: 'dashboard.taskRun.logs.skipped',
- defaultMessage:
- 'This step did not run as the task was skipped. See status for more details.'
- })}
-
- );
- }
-
- return (
-
- {steps.map(step => (
-
- ))}
- {sidecarsToRender.map(sidecar => (
-
- ))}
-
- );
-}
+import TaskRunLogs from '../TaskRunLogs';
const TaskRunTabPanels = ({
expandedSteps,
@@ -360,12 +45,11 @@ const TaskRunTabPanels = ({
view
}) => {
const logs = (
- {
- if (!taskRuns) {
- return
;
- }
-
- const erroredTask = taskRuns
- .filter(Boolean)
- .find(taskRun => getStatus(taskRun).status === 'False');
-
- let hasExpandedTask = false;
-
- return (
-
- {taskRuns.map((taskRun, index) => {
- if (!taskRun) {
- return null;
- }
- const { uid, labels, name } = taskRun.metadata;
- const {
- [labelConstants.DASHBOARD_DISPLAY_NAME]: displayName,
- [labelConstants.PIPELINE_TASK]: pipelineTaskName
- } = labels;
-
- let taskRunToUse = taskRun;
- if (
- selectedRetry &&
- selectedTaskId === pipelineTaskName &&
- (selectedTaskRunName ? name === selectedTaskRunName : true)
- ) {
- taskRunToUse = {
- ...taskRunToUse,
- status: taskRunToUse.status?.retriesStatus?.[selectedRetry]
- };
- }
-
- 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)
- // we expand the first task matching the rules below
- !hasExpandedTask &&
- // no explicitly selected task and current task failed, expand this one
- ((!selectedTaskId && erroredTask?.metadata.uid === uid) ||
- // or this task is the selected one
- (selectedTaskId === pipelineTaskName &&
- // if it's a matrixed TaskRun only expand if it matches the specified taskRunName
- // if it's not a matrixed TaskRun, or no taskRunName specified, this is the first match so expand it
- (selectedTaskRunName ? name === selectedTaskRunName : true)) ||
- // otherwise there's no error and no explicit selection, expand the first task by default
- (!erroredTask && !selectedTaskId && index === 0));
-
- if (!hasExpandedTask && expanded) {
- hasExpandedTask = true;
- }
-
- if (
- !reason &&
- skippedTasks.find(
- skippedTask => skippedTask.name === pipelineTaskName
- )
- ) {
- reason = dashboardReasonSkipped;
- }
-
- const selectDefaultStep =
- !selectedTaskId || (isSelectedTaskMatrix && !selectedTaskRunName);
- return (
-
- );
- })}
-
- );
-};
-
-export default TaskTree;
diff --git a/packages/components/src/components/TaskTree/TaskTree.stories.jsx b/packages/components/src/components/TaskTree/TaskTree.stories.jsx
deleted file mode 100644
index 5a00a8ad5..000000000
--- a/packages/components/src/components/TaskTree/TaskTree.stories.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
-Copyright 2019-2025 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { useArgs } from 'storybook/preview-api';
-
-import TaskTree from './TaskTree';
-
-export default {
- args: {
- selectedRetry: '',
- selectedStepId: undefined,
- selectedTaskId: undefined,
- skippedTasks: [{ name: 'Task 2' }],
- taskRuns: [
- {
- metadata: {
- labels: { 'tekton.dev/pipelineTask': 'Task 1' },
- uid: 'task'
- },
- status: {
- conditions: [
- { reason: 'Completed', status: 'True', type: 'Succeeded' }
- ],
- steps: [
- { name: 'build', terminated: { exitCode: 0, reason: 'Completed' } },
- { name: 'test', terminated: { exitCode: 1, reason: 'Completed' } }
- ]
- }
- },
- {
- metadata: {
- 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' }
- ],
- retriesStatus: [
- {
- conditions: [
- {
- reason: 'Failed',
- status: 'False',
- type: 'Succeeded'
- }
- ],
- steps: [
- { name: 'step 1', terminated: { reason: 'Error' } },
- // The next step will be displayed as 'Not run' by the Dashboard
- { name: 'step 2', terminated: { reason: 'Error' } }
- ]
- }
- ],
- steps: [
- { name: 'step 1', terminated: { reason: 'Error' } },
- // The next step will be displayed as 'Not run' by the Dashboard
- { name: 'step 2', terminated: { reason: 'Error' } }
- ]
- }
- },
- {
- metadata: {
- labels: { 'tekton.dev/pipelineTask': 'Task 4' },
- uid: 'task4'
- },
- pipelineTaskName: 'Task 4',
- status: {
- conditions: [
- { reason: 'Running', status: 'Unknown', type: 'Succeeded' }
- ],
- steps: [
- { name: 'step 1', terminated: { reason: 'Completed' } },
- { name: 'step 2', running: {} }
- ]
- }
- }
- ]
- },
- component: TaskTree,
- decorators: [
- Story => (
-
-
-
- )
- ],
- title: 'TaskTree'
-};
-
-export const Default = {
- render: args => {
- const [, updateArgs] = useArgs();
-
- return (
-
- updateArgs({ selectedRetry: `${selectedRetry}` })
- }
- onSelect={({ selectedStepId: stepId, selectedTaskId: taskId }) => {
- updateArgs({ selectedStepId: stepId, selectedTaskId: taskId });
- }}
- />
- );
- }
-};
diff --git a/packages/components/src/components/TaskTree/TaskTree.test.jsx b/packages/components/src/components/TaskTree/TaskTree.test.jsx
deleted file mode 100644
index 55e68bf36..000000000
--- a/packages/components/src/components/TaskTree/TaskTree.test.jsx
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
-Copyright 2019-2024 The Tekton Authors
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { fireEvent } from '@testing-library/react';
-import TaskTree from './TaskTree';
-import { render } from '../../utils/test';
-
-const getProps = () => ({
- onSelect: () => {},
- taskRuns: [
- {
- metadata: {
- labels: {
- 'tekton.dev/pipelineTask': 'A Task'
- },
- uid: 'task'
- },
- status: {
- conditions: [
- {
- type: 'Succeeded',
- status: 'True'
- }
- ],
- steps: [{ name: 'build' }, { name: 'test' }]
- }
- },
- {
- metadata: {
- labels: {
- 'tekton.dev/pipelineTask': 'A Second Task'
- },
- uid: 'task2'
- },
- status: {
- conditions: [
- {
- type: 'Succeeded',
- status: 'True'
- }
- ],
- steps: [{ name: 'build' }, { name: 'test' }]
- }
- },
- {
- metadata: {
- labels: {
- 'tekton.dev/pipelineTask': 'A Third Task'
- },
- uid: 'task3'
- },
- status: {
- conditions: [
- {
- type: 'Succeeded',
- status: 'True'
- }
- ],
- steps: [{ name: 'build' }, { name: 'test' }]
- }
- }
- ]
-});
-
-it('TaskTree renders', () => {
- render( );
-});
-
-it('TaskTree renders when taskRuns is falsy', () => {
- render( );
-});
-
-it('TaskTree renders when taskRuns contains a falsy run', () => {
- render( );
-});
-
-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
- 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 renders and expands error Task', () => {
- const props = getProps();
- props.taskRuns[1].status.conditions[0].status = 'False';
-
- 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(
- 1
- );
- expect(
- queryByText('A Second Task').parentNode.parentNode.childNodes
- ).toHaveLength(2);
- expect(
- queryByText('A Third Task').parentNode.parentNode.childNodes
- ).toHaveLength(1);
-});
-
-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';
-
- 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(
- 1
- );
- expect(
- queryByText('A Second Task').parentNode.parentNode.childNodes
- ).toHaveLength(2);
- expect(
- queryByText('A Third Task').parentNode.parentNode.childNodes
- ).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(
-
- );
- expect(onSelect).toHaveBeenCalledTimes(1);
- onSelect.mockClear();
- fireEvent.click(getByText(/a task/i));
- expect(onSelect).toHaveBeenCalledTimes(1);
-});
-
-it('TaskTree handles click event on Step', () => {
- const onSelect = vi.fn();
- const { getByText } = render(
-
- );
- onSelect.mockClear();
- fireEvent.click(getByText(/build/i));
- expect(onSelect).toHaveBeenCalledTimes(1);
-});
diff --git a/packages/components/src/components/index.js b/packages/components/src/components/index.js
index 5cdaf52f2..4509e4ac9 100644
--- a/packages/components/src/components/index.js
+++ b/packages/components/src/components/index.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2025 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -43,17 +43,14 @@ export { default as ScrollButtons } from './ScrollButtons';
export { default as Spinner } from './Spinner';
export { default as StatusFilterDropdown } from './StatusFilterDropdown';
export { default as StatusIcon } from './StatusIcon';
-export { default as Step } from './Step';
export { default as StepDefinition } from './StepDefinition';
-export { default as StepDetails } from './StepDetails';
+export { default as StepLogToolbar } from './StepLogToolbar';
export { default as StopModal } from './StopModal';
export { default as Table } from './Table';
-export { default as Task } from './Task';
export { default as TaskRuns } from './TaskRuns';
export { default as TaskRunDetails } from './TaskRunDetails';
export { default as TaskRunTabPanels } from './TaskRunTabPanels';
export { default as TaskRunTabs } from './TaskRunTabs';
-export { default as TaskTree } from './TaskTree';
export { default as TooltipDropdown } from './TooltipDropdown';
export { default as Trigger } from './Trigger';
export { default as ViewYAML } from './ViewYAML';
diff --git a/packages/components/src/scss/_Run.scss b/packages/components/src/scss/_Run.scss
index d152da58a..aaef02907 100644
--- a/packages/components/src/scss/_Run.scss
+++ b/packages/components/src/scss/_Run.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2025 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -27,10 +27,6 @@ limitations under the License.
align-items: stretch;
}
- .tkn--task-tree {
- flex-shrink: 0;
- }
-
.tkn--step-details {
background-color: $layer;
flex-grow: 1;
@@ -120,3 +116,11 @@ limitations under the License.
.tkn--task-skipped {
@include type-style('body-compact-01');
}
+
+.tkn--toolbar {
+ font-family: 'IBM Plex Sans', sans-serif;
+
+ .#{$prefix}--btn--icon-only {
+ inline-size: var(--cds-layout-size-height-local);
+ }
+}
diff --git a/src/api/utils.js b/src/api/utils.js
index ecb806cc3..4cdcaef02 100644
--- a/src/api/utils.js
+++ b/src/api/utils.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2025 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -105,11 +105,6 @@ export function getTektonPipelinesAPIVersion() {
return isPipelinesV1ResourcesEnabled() ? 'v1' : 'v1beta1';
}
-export function isPipelineRunTabLayoutEnabled() {
- localStorage.removeItem('tkn-pipelinerun-tab-layout');
- return true;
-}
-
export const NamespaceContext = createContext();
NamespaceContext.displayName = 'Namespace';
diff --git a/src/containers/LogsToolbar/LogsToolbar.jsx b/src/containers/LogsToolbar/LogsToolbar.jsx
index ab110b3ca..5874c5eae 100644
--- a/src/containers/LogsToolbar/LogsToolbar.jsx
+++ b/src/containers/LogsToolbar/LogsToolbar.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2020-2025 The Tekton Authors
+Copyright 2020-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -13,12 +13,8 @@ limitations under the License.
import { LogsToolbar } from '@tektoncd/dashboard-components';
-import { getExternalLogURL, getPodLogURL } from '../../api';
-
export default function LogsToolbarContainer({
- externalLogsURL,
isMaximized,
- isUsingExternalLogs,
logLevels,
onToggleLogLevel,
onToggleMaximized,
@@ -28,31 +24,17 @@ export default function LogsToolbarContainer({
taskRun
}) {
const { container } = stepStatus || {};
- const { namespace } = taskRun.metadata;
const { podName } = taskRun.status || {};
- let logURL;
- if (container && podName) {
- logURL = isUsingExternalLogs
- ? getExternalLogURL({ container, externalLogsURL, namespace, podName })
- : getPodLogURL({
- container,
- name: podName,
- namespace
- });
- }
-
return (
);
}
diff --git a/src/containers/LogsToolbar/LogsToolbar.test.jsx b/src/containers/LogsToolbar/LogsToolbar.test.jsx
index a17e5af78..67b1e7983 100644
--- a/src/containers/LogsToolbar/LogsToolbar.test.jsx
+++ b/src/containers/LogsToolbar/LogsToolbar.test.jsx
@@ -1,5 +1,5 @@
/*
-Copyright 2020-2025 The Tekton Authors
+Copyright 2020-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -11,70 +11,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import * as API from '../../api';
import { render } from '../../utils/test';
import LogsToolbarContainer from './LogsToolbar';
describe('getLogsToolbar', () => {
- it('should handle pod logs (default)', () => {
+ it('should render', () => {
const container = 'fake_container';
const namespace = 'fake_namespace';
const podName = 'fake_podname';
const stepStatus = { container };
const taskRun = { metadata: { namespace }, status: { podName } };
- vi.spyOn(API, 'getPodLogURL');
- vi.spyOn(API, 'getExternalLogURL');
- render( );
-
- expect(API.getExternalLogURL).not.toHaveBeenCalled();
- expect(API.getPodLogURL).toHaveBeenCalledWith({
- container,
- name: podName,
- namespace
- });
- });
-
- it('should handle external logs', () => {
- const container = 'fake_container';
- const externalLogsURL = 'fake_externalLogsURL';
- const namespace = 'fake_namespace';
- const podName = 'fake_podname';
- const stepStatus = { container };
- const taskRun = { metadata: { namespace }, status: { podName } };
- vi.spyOn(API, 'getPodLogURL');
- vi.spyOn(API, 'getExternalLogURL');
-
- render(
+ const { queryByText } = render(
{}}
stepStatus={stepStatus}
taskRun={taskRun}
/>
);
-
- expect(API.getPodLogURL).not.toHaveBeenCalled();
- expect(API.getExternalLogURL).toHaveBeenCalledWith({
- container,
- externalLogsURL,
- namespace,
- podName
- });
- });
-
- it('should handle missing TaskRun status', () => {
- const container = 'fake_container';
- const namespace = 'fake_namespace';
- const stepStatus = { container };
- const taskRun = { metadata: { namespace } };
- vi.spyOn(API, 'getPodLogURL');
- vi.spyOn(API, 'getExternalLogURL');
-
- render( );
-
- expect(API.getExternalLogURL).not.toHaveBeenCalled();
- expect(API.getPodLogURL).not.toHaveBeenCalled();
+ expect(queryByText('Maximize')).toBeTruthy();
});
});
diff --git a/src/containers/PipelineRun/PipelineRun.jsx b/src/containers/PipelineRun/PipelineRun.jsx
index 83d1aedf5..e38b4dfc2 100644
--- a/src/containers/PipelineRun/PipelineRun.jsx
+++ b/src/containers/PipelineRun/PipelineRun.jsx
@@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useState } from 'react';
import { InlineNotification } from '@carbon/react';
import {
ActionableNotification,
@@ -35,6 +35,7 @@ import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useIntl } from 'react-intl';
import LogsToolbar from '../LogsToolbar';
+import StepLogToolbar from '../StepLogToolbar';
import {
cancelPipelineRun,
deletePipelineRun,
@@ -55,7 +56,6 @@ import NotFound from '../NotFound';
import {
getLogLevels,
isLogTimestampsEnabled,
- isPipelineRunTabLayoutEnabled,
setLogLevels,
setLogTimestampsEnabled
} from '../../api/utils';
@@ -89,8 +89,6 @@ export /* istanbul ignore next */ function PipelineRunContainer({
setLogTimestampsEnabled(show);
}
- const [enableTabLayout] = useState(isPipelineRunTabLayoutEnabled());
-
const { name, namespace } = params;
const queryParams = new URLSearchParams(location.search);
@@ -107,7 +105,6 @@ export /* istanbul ignore next */ function PipelineRunContainer({
const currentSelectedStepId = queryParams.get(STEP);
const view = queryParams.get(VIEW);
- const maximizedLogsContainer = useRef();
const [showRunActionNotification, setShowRunActionNotification] =
useState(null);
@@ -495,7 +492,6 @@ export /* istanbul ignore next */ function PipelineRunContainer({
return (
<>
-
{showRunActionNotification?.logsURL && (
)}
- maximizedLogsContainer={maximizedLogsContainer.current}
+ getStepLogToolbar={toolbarProps => }
onRetryChange={retry => {
if (Number.isInteger(retry)) {
queryParams.set(RETRY, retry);
diff --git a/src/containers/StepLogToolbar/StepLogToolbar.jsx b/src/containers/StepLogToolbar/StepLogToolbar.jsx
new file mode 100644
index 000000000..aec455d08
--- /dev/null
+++ b/src/containers/StepLogToolbar/StepLogToolbar.jsx
@@ -0,0 +1,42 @@
+/*
+Copyright 2020-2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { StepLogToolbar } from '@tektoncd/dashboard-components';
+
+import { getExternalLogURL, getPodLogURL } from '../../api';
+
+export default function StepLogToolbarContainer({
+ externalLogsURL,
+ isUsingExternalLogs,
+ stepStatus,
+ taskRun
+}) {
+ const { container } = stepStatus || {};
+ const { namespace } = taskRun.metadata;
+ const { podName } = taskRun.status || {};
+
+ let logURL;
+ if (container && podName) {
+ logURL = isUsingExternalLogs
+ ? getExternalLogURL({ container, externalLogsURL, namespace, podName })
+ : getPodLogURL({
+ container,
+ name: podName,
+ namespace
+ });
+ }
+
+ return (
+
+ );
+}
diff --git a/src/containers/StepLogToolbar/StepLogToolbar.test.jsx b/src/containers/StepLogToolbar/StepLogToolbar.test.jsx
new file mode 100644
index 000000000..ff3b90152
--- /dev/null
+++ b/src/containers/StepLogToolbar/StepLogToolbar.test.jsx
@@ -0,0 +1,80 @@
+/*
+Copyright 2020-2026 The Tekton Authors
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import * as API from '../../api';
+import { render } from '../../utils/test';
+
+import LogsToolbarContainer from './StepLogToolbar';
+
+describe('getLogsToolbar', () => {
+ it('should handle pod logs (default)', () => {
+ const container = 'fake_container';
+ const namespace = 'fake_namespace';
+ const podName = 'fake_podname';
+ const stepStatus = { container };
+ const taskRun = { metadata: { namespace }, status: { podName } };
+ vi.spyOn(API, 'getPodLogURL');
+ vi.spyOn(API, 'getExternalLogURL');
+
+ render( );
+
+ expect(API.getExternalLogURL).not.toHaveBeenCalled();
+ expect(API.getPodLogURL).toHaveBeenCalledWith({
+ container,
+ name: podName,
+ namespace
+ });
+ });
+
+ it('should handle external logs', () => {
+ const container = 'fake_container';
+ const externalLogsURL = 'fake_externalLogsURL';
+ const namespace = 'fake_namespace';
+ const podName = 'fake_podname';
+ const stepStatus = { container };
+ const taskRun = { metadata: { namespace }, status: { podName } };
+ vi.spyOn(API, 'getPodLogURL');
+ vi.spyOn(API, 'getExternalLogURL');
+
+ render(
+
+ );
+
+ expect(API.getPodLogURL).not.toHaveBeenCalled();
+ expect(API.getExternalLogURL).toHaveBeenCalledWith({
+ container,
+ externalLogsURL,
+ namespace,
+ podName
+ });
+ });
+
+ it('should handle missing TaskRun status', () => {
+ const container = 'fake_container';
+ const namespace = 'fake_namespace';
+ const stepStatus = { container };
+ const taskRun = { metadata: { namespace } };
+ vi.spyOn(API, 'getPodLogURL');
+ vi.spyOn(API, 'getExternalLogURL');
+
+ render( );
+
+ expect(API.getExternalLogURL).not.toHaveBeenCalled();
+ expect(API.getPodLogURL).not.toHaveBeenCalled();
+ });
+});
diff --git a/packages/components/src/components/StepDetails/index.js b/src/containers/StepLogToolbar/index.js
similarity index 87%
rename from packages/components/src/components/StepDetails/index.js
rename to src/containers/StepLogToolbar/index.js
index 2762b2773..0388f541c 100644
--- a/packages/components/src/components/StepDetails/index.js
+++ b/src/containers/StepLogToolbar/index.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2021 The Tekton Authors
+Copyright 2024-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -12,4 +12,4 @@ limitations under the License.
*/
/* istanbul ignore file */
-export { default } from './StepDetails';
+export { default } from './StepLogToolbar';
diff --git a/src/containers/TaskRun/TaskRun.jsx b/src/containers/TaskRun/TaskRun.jsx
index e2522f47a..56b6e8e67 100644
--- a/src/containers/TaskRun/TaskRun.jsx
+++ b/src/containers/TaskRun/TaskRun.jsx
@@ -12,7 +12,7 @@ limitations under the License.
*/
/* istanbul ignore file */
-import { Fragment, useRef, useState } from 'react';
+import { Fragment, useState } from 'react';
import { useIntl } from 'react-intl';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { InlineNotification, SkeletonText } from '@carbon/react';
@@ -20,20 +20,13 @@ import {
ActionableNotification,
Actions,
Log,
- Portal,
RunHeader,
- StepDetails,
- TaskRunDetails,
- TaskRunTabPanels,
- TaskTree
+ TaskRunTabPanels
} from '@tektoncd/dashboard-components';
import {
getStatus,
- getStepDefinition,
- getStepStatus,
isPending,
isRunning,
- labels as labelConstants,
queryParams as queryParamConstants,
urls,
useTitleSync
@@ -56,10 +49,10 @@ import {
useTaskRun
} from '../../api';
import NotFound from '../NotFound';
+import StepLogToolbar from '../StepLogToolbar';
import {
getLogLevels,
isLogTimestampsEnabled,
- isPipelineRunTabLayoutEnabled,
setLogLevels,
setLogTimestampsEnabled
} from '../../api/utils';
@@ -93,7 +86,6 @@ export function TaskRunContainer({
const view = queryParams.get(VIEW);
const showTaskRunDetails = queryParams.get(TASK_RUN_DETAILS);
- const [enableTabLayout] = useState(isPipelineRunTabLayoutEnabled());
const [isTaskRunMaximized, setIsTaskRunMaximized] = useState(false);
const [expandedSteps, setExpandedSteps] = useState(() =>
selectedStepId ? { [selectedStepId]: true } : {}
@@ -119,8 +111,6 @@ export function TaskRunContainer({
const { selectedNamespace } = useSelectedNamespace();
const namespace = namespaceParam || selectedNamespace;
- const maximizedLogsContainer = useRef();
- const [isLogsMaximized, setIsLogsMaximized] = useState(false);
const [showNotification, setShowNotification] = useState(null);
const [isUsingExternalLogs, setIsUsingExternalLogs] = useState(false);
@@ -171,10 +161,6 @@ export function TaskRunContainer({
{ enabled: !!podName && view === 'pod' }
);
- function onToggleLogsMaximized() {
- setIsLogsMaximized(state => !state);
- }
-
function getLogContainer({ stepName, stepStatus, taskRun: run }) {
if ((!selectedStepId && !stepName) || !stepStatus) {
return null;
@@ -186,47 +172,25 @@ export function TaskRunContainer({
onFallback: setIsUsingExternalLogs
});
- const LogsRoot = isLogsMaximized ? Portal : Fragment;
-
return (
-
-
- logsRetriever({ stepName, stepStatus, taskRun: run })
- }
- isLogsMaximized={isLogsMaximized}
- key={`${stepName}:${currentRetry}`}
- logLevels={logLevels}
- showLevels={showLogLevels}
- showTimestamps={showTimestamps}
- stepStatus={stepStatus}
- toolbar={
- !enableTabLayout && (
-
- )
- }
- />
-
+ logsRetriever({ stepName, stepStatus, taskRun: run })}
+ key={`${stepName}:${currentRetry}`}
+ logLevels={logLevels}
+ showLevels={showLogLevels}
+ showTimestamps={showTimestamps}
+ stepStatus={stepStatus}
+ toolbar={
+
+ }
+ />
);
}
@@ -477,41 +441,17 @@ export function TaskRunContainer({
};
}
- const definition =
- !enableTabLayout &&
- getStepDefinition({
- selectedStepId,
- task,
- taskRun: taskRunToUse
- });
-
- const stepStatus = getStepStatus({
- selectedStepId,
- taskRun: taskRunToUse
- });
-
const {
reason: taskRunStatusReason,
message: taskRunStatusMessage,
status: succeeded
- } = getStatus(enableTabLayout ? taskRun : taskRunToUse);
-
- const logContainer =
- !enableTabLayout &&
- getLogContainer({
- stepName: selectedStepId,
- stepStatus,
- taskRun: taskRunToUse
- });
+ } = getStatus(taskRun);
const onViewChange = getViewChangeHandler({ location, navigate });
const runActions = taskRunActions();
- let podDetails;
- if (!selectedStepId || enableTabLayout) {
- podDetails = (events || pod) && { events, resource: pod };
- }
+ const podDetails = (events || pod) && { events, resource: pod };
let duration;
if (taskRun.status) {
@@ -530,7 +470,6 @@ export function TaskRunContainer({
return (
<>
-
{showNotification?.logsURL && (
- {enableTabLayout ? (
- (
-
- )}
- isMaximized={isTaskRunMaximized}
- onRetryChange={handleRetryChange}
- onStepSelected={onStepSelected}
- onToggleMaximized={onToggleTaskRunMaximized}
- onViewChange={onViewChange}
- pod={podDetails}
- selectedIndex={1}
- selectedRetry={currentRetry}
- selectedStepId={selectedStepId}
- TabPanel={Fragment}
- TabPanels={Fragment}
- task={task}
- taskRun={taskRunToUse}
- taskRuns={[taskRun]}
- view={view}
- />
- ) : (
- <>
- (
+
- {(selectedStepId && (
-
- )) || (
-
- )}
- >
- )}
+ )}
+ isMaximized={isTaskRunMaximized}
+ onRetryChange={handleRetryChange}
+ onStepSelected={onStepSelected}
+ onToggleMaximized={onToggleTaskRunMaximized}
+ onViewChange={onViewChange}
+ pod={podDetails}
+ selectedIndex={1}
+ selectedRetry={currentRetry}
+ selectedStepId={selectedStepId}
+ TabPanel={Fragment}
+ TabPanels={Fragment}
+ task={task}
+ taskRun={taskRunToUse}
+ taskRuns={[taskRun]}
+ view={view}
+ />
>
);
diff --git a/src/nls/messages_de.json b/src/nls/messages_de.json
index e7cc53e0d..cebe987ba 100644
--- a/src/nls/messages_de.json
+++ b/src/nls/messages_de.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "Keine Protokollausgabe",
"dashboard.pipelineRun.logFailed": "Das Protokoll kann nicht abgerufen werden",
"dashboard.pipelineRun.notFound": "PipelineRun nicht gefunden",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "Schritt abgeschlossen",
"dashboard.pipelineRun.stepCompleted.exitCode": "",
"dashboard.pipelineRun.stepFailed": "Schritt fehlgeschlagen",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "Details",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "",
diff --git a/src/nls/messages_en.json b/src/nls/messages_en.json
index 51755b7f0..4f0bfd62e 100644
--- a/src/nls/messages_en.json
+++ b/src/nls/messages_en.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "No log available",
"dashboard.pipelineRun.logFailed": "Unable to fetch log",
"dashboard.pipelineRun.notFound": "PipelineRun not found",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "{pipelineTaskName} (first attempt)",
- "dashboard.pipelineRun.pipelineTaskName.retry": "{pipelineTaskName} (retry {retryNumber, number})",
"dashboard.pipelineRun.retries.view": "View retries",
"dashboard.pipelineRun.retries.viewAttempt": "Attempt #{retryNumber, number}",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "View first attempt",
"dashboard.pipelineRun.retries.viewLatestAttempt": "Latest attempt #{retryNumber, number}",
- "dashboard.pipelineRun.retries.viewLatestRetry": "View latest retry",
- "dashboard.pipelineRun.retries.viewRetry": "View retry {retryNumber, number}",
"dashboard.pipelineRun.stepCompleted": "Step completed successfully",
"dashboard.pipelineRun.stepCompleted.exitCode": "Step completed with exit code {exitCode}",
"dashboard.pipelineRun.stepFailed": "Step failed",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "Triggered rerun",
"dashboard.resource.apiVersion": "API version:",
"dashboard.resource.createdTime": "Created: {created}",
- "dashboard.resource.detailsTab": "Details",
"dashboard.resource.kind": "Kind:",
"dashboard.resource.name": "Name:",
"dashboard.resource.overviewTab": "Overview",
diff --git a/src/nls/messages_es.json b/src/nls/messages_es.json
index 8699601a5..d7221d2a0 100644
--- a/src/nls/messages_es.json
+++ b/src/nls/messages_es.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "No hay salida de registros",
"dashboard.pipelineRun.logFailed": "No se puede recuperar el registro",
"dashboard.pipelineRun.notFound": "No se ha encontrado PipelineRun",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "Paso completado",
"dashboard.pipelineRun.stepCompleted.exitCode": "",
"dashboard.pipelineRun.stepFailed": "Paso fallido",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "Detalles",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "",
diff --git a/src/nls/messages_fr.json b/src/nls/messages_fr.json
index f897ab54f..34aef7da9 100644
--- a/src/nls/messages_fr.json
+++ b/src/nls/messages_fr.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "Aucune sortie de journal",
"dashboard.pipelineRun.logFailed": "Impossible d'extraire le journal",
"dashboard.pipelineRun.notFound": "PipelineRun introuvable",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "Etape terminée",
"dashboard.pipelineRun.stepCompleted.exitCode": "",
"dashboard.pipelineRun.stepFailed": "Echec de l'étape",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "Détails",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "",
diff --git a/src/nls/messages_it.json b/src/nls/messages_it.json
index a96ea3207..572cc6b6d 100644
--- a/src/nls/messages_it.json
+++ b/src/nls/messages_it.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "Nessun output di log",
"dashboard.pipelineRun.logFailed": "Impossibile richiamare il log",
"dashboard.pipelineRun.notFound": "Esecuzione pipeline non trovata",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "Passo completato",
"dashboard.pipelineRun.stepCompleted.exitCode": "",
"dashboard.pipelineRun.stepFailed": "Passo non riuscito",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "Dettagli",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "",
diff --git a/src/nls/messages_ja.json b/src/nls/messages_ja.json
index 3b65bf058..2741b5790 100644
--- a/src/nls/messages_ja.json
+++ b/src/nls/messages_ja.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "ログがありません",
"dashboard.pipelineRun.logFailed": "ログを取得できません",
"dashboard.pipelineRun.notFound": "PipelineRunが見つかりません",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "{pipelineTaskName}(retry{retryNumber,number})",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "ステップが完了しました",
"dashboard.pipelineRun.stepCompleted.exitCode": "",
"dashboard.pipelineRun.stepFailed": "ステップが失敗しました",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "トリガーされた再実行",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "詳細",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "概要",
diff --git a/src/nls/messages_ko.json b/src/nls/messages_ko.json
index 998bd57df..8c6108bb0 100644
--- a/src/nls/messages_ko.json
+++ b/src/nls/messages_ko.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "로그 출력이 없음",
"dashboard.pipelineRun.logFailed": "로그를 페치할 수 없음",
"dashboard.pipelineRun.notFound": "PipelineRun을 찾을 수 없음",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "{pipelineTaskName} (첫번째 시도)",
- "dashboard.pipelineRun.pipelineTaskName.retry": "{pipelineTaskName} (재시도 {retryNumber, number})",
"dashboard.pipelineRun.retries.view": "재시도 보기",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "첫번째 시도 보기",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "최근 재시도 보기",
- "dashboard.pipelineRun.retries.viewRetry": "재시도 보기 {retryNumber, number}",
"dashboard.pipelineRun.stepCompleted": "단계 완료",
"dashboard.pipelineRun.stepCompleted.exitCode": "{exitCode} 종료 코드와 함께 단계가 완료됨",
"dashboard.pipelineRun.stepFailed": "단계 실패",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "트리거된 재실행",
"dashboard.resource.apiVersion": "API 버전",
"dashboard.resource.createdTime": "만들어진: {created}",
- "dashboard.resource.detailsTab": "세부사항",
"dashboard.resource.kind": "종류:",
"dashboard.resource.name": "이름:",
"dashboard.resource.overviewTab": "개요",
diff --git a/src/nls/messages_pt.json b/src/nls/messages_pt.json
index 71791d289..96c29e886 100644
--- a/src/nls/messages_pt.json
+++ b/src/nls/messages_pt.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "Nenhuma saída de log",
"dashboard.pipelineRun.logFailed": "Não é possível buscar o log",
"dashboard.pipelineRun.notFound": "PipelineRun não localizado",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "Etapa concluída",
"dashboard.pipelineRun.stepCompleted.exitCode": "",
"dashboard.pipelineRun.stepFailed": "Etapa com falha",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "Detalhes",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "",
diff --git a/src/nls/messages_zh-Hans.json b/src/nls/messages_zh-Hans.json
index 424c461d2..f5be95fd6 100644
--- a/src/nls/messages_zh-Hans.json
+++ b/src/nls/messages_zh-Hans.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "没有日志输出",
"dashboard.pipelineRun.logFailed": "无法访问日志",
"dashboard.pipelineRun.notFound": "未找到 PipelineRun",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "{pipelineTaskName}(重试 {retryNumber, number})",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "步骤已完成",
"dashboard.pipelineRun.stepCompleted.exitCode": "步骤已完成,退出代码为 {exitCode}",
"dashboard.pipelineRun.stepFailed": "步骤失败",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "已触发的重新运行",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "详情",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "概览",
diff --git a/src/nls/messages_zh-Hant.json b/src/nls/messages_zh-Hant.json
index 2837ffd1f..daf3b271d 100644
--- a/src/nls/messages_zh-Hant.json
+++ b/src/nls/messages_zh-Hant.json
@@ -185,14 +185,9 @@
"dashboard.pipelineRun.logEmpty": "沒有日誌輸出",
"dashboard.pipelineRun.logFailed": "無法提取日誌",
"dashboard.pipelineRun.notFound": "找不到 PipelineRun",
- "dashboard.pipelineRun.pipelineTaskName.firstAttempt": "",
- "dashboard.pipelineRun.pipelineTaskName.retry": "",
"dashboard.pipelineRun.retries.view": "",
"dashboard.pipelineRun.retries.viewAttempt": "",
- "dashboard.pipelineRun.retries.viewFirstAttempt": "",
"dashboard.pipelineRun.retries.viewLatestAttempt": "",
- "dashboard.pipelineRun.retries.viewLatestRetry": "",
- "dashboard.pipelineRun.retries.viewRetry": "",
"dashboard.pipelineRun.stepCompleted": "步驟已完成",
"dashboard.pipelineRun.stepCompleted.exitCode": "",
"dashboard.pipelineRun.stepFailed": "步驟失敗",
@@ -211,7 +206,6 @@
"dashboard.rerun.triggered": "",
"dashboard.resource.apiVersion": "",
"dashboard.resource.createdTime": "",
- "dashboard.resource.detailsTab": "詳細資料",
"dashboard.resource.kind": "",
"dashboard.resource.name": "",
"dashboard.resource.overviewTab": "",
diff --git a/src/scss/App.scss b/src/scss/App.scss
index 1ba15bda3..1bf378f5d 100644
--- a/src/scss/App.scss
+++ b/src/scss/App.scss
@@ -1,5 +1,5 @@
/*
-Copyright 2019-2024 The Tekton Authors
+Copyright 2019-2026 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
@@ -45,11 +45,9 @@ limitations under the License.
@use '@tektoncd/dashboard-components/src/components/Spinner/Spinner';
@use '@tektoncd/dashboard-components/src/components/StatusIcon/StatusIcon';
@use '@tektoncd/dashboard-components/src/components/Step/Step';
-@use '@tektoncd/dashboard-components/src/components/StepDetails/StepDetails';
@use '@tektoncd/dashboard-components/src/components/Table/Table';
@use '@tektoncd/dashboard-components/src/components/Task/Task';
@use '@tektoncd/dashboard-components/src/components/TaskRunDetails/TaskRunDetails';
-@use '@tektoncd/dashboard-components/src/components/TaskTree/TaskTree';
@use '@tektoncd/dashboard-components/src/components/Trigger/Trigger';
@use '@tektoncd/dashboard-components/src/components/ViewYAML/ViewYAML';
@@ -121,24 +119,6 @@ body:has(.tkn--taskrun--maximized) {
margin-block-start: 1rem;
}
-#tkn--maximized-logs-container {
- position: absolute;
- inset-block-start: 0;
- inset-inline-start: 0;
- inline-size: 100%;
- block-size: 100vb;
- z-index: -1;
-
- .tkn--log {
- min-block-size: 100%;
- }
-
- &:not(:empty) {
- background-color: $layer;
- z-index: 9999;
- }
-}
-
.#{$prefix}--overflow-menu-options:after {
display: none;
}