diff --git a/docs/logs.md b/docs/logs.md index 81cf4b03a..3dff712cb 100644 --- a/docs/logs.md +++ b/docs/logs.md @@ -15,7 +15,7 @@ The Tekton Dashboard log viewer supports ANSI colour codes and text styles, and ## Toolbar -The toolbar diplayed in the log viewer includes a number of additional features, including: +The toolbar displayed in the log viewer includes a number of additional features, including: - maximize: increase the area available to the log viewer by hiding the task list and run header. This allows the user to eliminate distractions from other parts of the app and focus on the log content. - open in new window: open the raw logs in a separate browser window. This provides an unmodified and unprocessed view of the logs, without any of the added features provided by the log viewer. @@ -26,7 +26,7 @@ The toolbar diplayed in the log viewer includes a number of additional features, The Dashboard will always request logs with timestamps from the Kubernetes pod logs API, and show / hide the timestamps in the log viewer based on the user's preference. This can be toggled from the settings menu in the toolbar at the top of the log viewer. -The timestamps are localised based on users' browser settings, with the raw timestamp value received from the API provided as a tooltip on hover. +The timestamps are localised based on the user's browser settings, with the raw timestamp value received from the API provided as a tooltip on hover. In releases prior to Tekton Dashboard v0.54, the timestamp preference was found on the Settings page, and governed whether or not timestamps were requested from the Kubernetes API server. diff --git a/package-lock.json b/package-lock.json index c11d7c313..b904a8f6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "packages/graph" ], "dependencies": { - "@carbon/react": "^1.72.0", + "@carbon/react": "^1.73.0", "@codemirror/legacy-modes": "^6.4.2", "@tanstack/react-query": "^4.36.1", "@tektoncd/dashboard-components": "*", @@ -465,9 +465,9 @@ } }, "node_modules/@carbon/grid": { - "version": "11.29.0", - "resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.29.0.tgz", - "integrity": "sha512-SAJhTexN6TjbItcUczOqhzgHBGXLhvUhlTdyqj+wzUH0tqEN8g6gLp+1sn9+rL+kV4obSb/7bdSESZtwQr/tQg==", + "version": "11.30.0", + "resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.30.0.tgz", + "integrity": "sha512-HgeAJqh8Ln7d/HGe8Aw/bxHDQJbJSLxnVol3CwaJ1lRoZscUlFCZgrH60OVvUGXgH6yoUojKkyFovryZ8zxIdw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -520,9 +520,9 @@ } }, "node_modules/@carbon/react": { - "version": "1.72.0", - "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.72.0.tgz", - "integrity": "sha512-cQdf7EDeu7E4fTjP/vqfni4buc8V7XHw2YIlGVeRlLXVSc3WdoJgimLYaKUV4o0vvoqQvmiDEKDu0XdT7USJiw==", + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.73.0.tgz", + "integrity": "sha512-C7yPkl07qkhqxSIxGasFGKZCCVEgx9k/4iMZ0HkeKi9Pxp/fOZ+xDEG7HwNUSOWZB/n06yBMTqDJvnl8Op/Pww==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -530,17 +530,15 @@ "@carbon/feature-flags": "^0.24.0", "@carbon/icons-react": "^11.53.0", "@carbon/layout": "^11.28.0", - "@carbon/styles": "^1.71.0", + "@carbon/styles": "^1.72.0", "@floating-ui/react": "^0.26.0", "@ibm/telemetry-js": "^1.5.0", "classnames": "2.5.1", "copy-to-clipboard": "^3.3.1", "downshift": "9.0.8", + "es-toolkit": "^1.27.0", "flatpickr": "4.6.13", "invariant": "^2.2.3", - "lodash.debounce": "^4.0.8", - "lodash.omit": "^4.5.0", - "lodash.throttle": "^4.1.1", "prop-types": "^15.7.2", "react-fast-compare": "^3.2.2", "react-is": "^18.2.0", @@ -565,19 +563,19 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/@carbon/styles": { - "version": "1.71.0", - "resolved": "https://registry.npmjs.org/@carbon/styles/-/styles-1.71.0.tgz", - "integrity": "sha512-tkQ/Ub7QYHCyFqXJMCe7+Dbpypx7pCefJCeEEluEqpeVSfLu1qtRMZUftfndvzChIZUXtm+ImpHtRknRnyS3+g==", + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@carbon/styles/-/styles-1.72.0.tgz", + "integrity": "sha512-IRVa+Ou8Va42/Cn76hI1/Y/g0EFJkTWG4/6+BtJZFjHrf++gD7QwE1Ul9t8oNzbp13Gz5M+knIimQACdzINW9A==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@carbon/colors": "^11.28.0", "@carbon/feature-flags": "^0.24.0", - "@carbon/grid": "^11.29.0", + "@carbon/grid": "^11.30.0", "@carbon/layout": "^11.28.0", "@carbon/motion": "^11.24.0", - "@carbon/themes": "^11.43.0", - "@carbon/type": "^11.33.0", + "@carbon/themes": "^11.44.0", + "@carbon/type": "^11.34.0", "@ibm/plex": "6.0.0-next.6", "@ibm/plex-mono": "0.0.3-alpha.0", "@ibm/plex-sans": "0.0.3-alpha.0", @@ -607,27 +605,27 @@ } }, "node_modules/@carbon/themes": { - "version": "11.43.0", - "resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.43.0.tgz", - "integrity": "sha512-iBDxHVn1y7QYKVCeBqMjLzryDl5mUG2C67KQbJqGqCfYMKI8L+dkw6KmeeWUYv8rhRhqZq27mm+AODchXO0zcw==", + "version": "11.44.0", + "resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.44.0.tgz", + "integrity": "sha512-5UDn3U6xgwwgVx87HoOmsgOXb3JywKZ9cxKj0x8gyGNywTJFA22EtJEoVUa5/i/MSivp6q4mUcjNd2ImATuFHA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@carbon/colors": "^11.28.0", "@carbon/layout": "^11.28.0", - "@carbon/type": "^11.33.0", + "@carbon/type": "^11.34.0", "@ibm/telemetry-js": "^1.5.0", "color": "^4.0.0" } }, "node_modules/@carbon/type": { - "version": "11.33.0", - "resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.33.0.tgz", - "integrity": "sha512-v3lfot0vcHNw6WDe32ap3ewpMGwUqhZ6z56sN11jzngRrWVPFgA9U7NciuoylFw301l2htJuZu0dBS2F4ViCXQ==", + "version": "11.34.0", + "resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.34.0.tgz", + "integrity": "sha512-rcKNsW6KzZAnaX0VJ2lbzWvEAJg8AJwG5zvJ+JqFpSs26mCZc9/0xw+J8eE5bZ34eSrgHPGEIHHNV0PEGKcBTQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@carbon/grid": "^11.29.0", + "@carbon/grid": "^11.30.0", "@carbon/layout": "^11.28.0", "@ibm/telemetry-js": "^1.5.0" } @@ -5910,7 +5908,6 @@ "version": "1.29.0", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.29.0.tgz", "integrity": "sha512-GjTll+E6APcfAQA09D89HdT8Qn2Yb+TeDSDBTMcxAo+V+w1amAtCI15LJu4YPH/UCPoSo/F47Gr1LIM0TE0lZA==", - "dev": true, "license": "MIT", "workspaces": [ "docs", @@ -8398,12 +8395,8 @@ "node_modules/lodash.omit": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=" - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=", + "dev": true }, "node_modules/loose-envify": { "version": "1.4.0", diff --git a/package.json b/package.json index 5104a6aef..d3e8e1bca 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "test": "vitest" }, "dependencies": { - "@carbon/react": "^1.72.0", + "@carbon/react": "^1.73.0", "@codemirror/legacy-modes": "^6.4.2", "@tanstack/react-query": "^4.36.1", "@tektoncd/dashboard-components": "*", diff --git a/packages/components/src/components/Log/Log.jsx b/packages/components/src/components/Log/Log.jsx index 9c0b27d4a..5bf522fdd 100644 --- a/packages/components/src/components/Log/Log.jsx +++ b/packages/components/src/components/Log/Log.jsx @@ -1,5 +1,5 @@ /* -Copyright 2019-2024 The Tekton Authors +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 @@ -14,9 +14,9 @@ limitations under the License. import { Component, createRef } from 'react'; import { Button, PrefixContext, SkeletonText } from '@carbon/react'; import { FixedSizeList as List } from 'react-window'; -import { injectIntl } from 'react-intl'; +import { injectIntl, useIntl } from 'react-intl'; import { getStepStatusReason, isRunning } from '@tektoncd/dashboard-utils'; -import { DownToBottom, UpToTop } from '@carbon/react/icons'; +import { DownToBottom, Information, UpToTop } from '@carbon/react/icons'; import { hasElementPositiveVerticalScrollBottom, @@ -33,6 +33,52 @@ const defaultHeight = itemSize * 100 + itemSize / 2; const logFormatRegex = /^((?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3,9}Z)\s?)?(::(?error|warning|info|notice|debug)::)?(?.*)?$/s; +function LogsFilteredNotification({ displayedLogLines, totalLogLines }) { + const intl = useIntl(); + + if (displayedLogLines === totalLogLines) { + return null; + } + + if (displayedLogLines === 0) { + return ( + + + {intl.formatMessage({ + id: 'dashboard.logs.hidden.all', + defaultMessage: 'All lines hidden due to selected log levels' + })} + + ); + } + + const hiddenLines = totalLogLines - displayedLogLines; + const message = + hiddenLines === 1 + ? intl.formatMessage( + { + id: 'dashboard.logs.hidden.one', + defaultMessage: '1 line hidden due to selected log levels' + }, + { numHiddenLines: totalLogLines - displayedLogLines } + ) + : intl.formatMessage( + { + id: 'dashboard.logs.hidden', + defaultMessage: + '{numHiddenLines, plural, other {# lines}} hidden due to selected log levels' + }, + { numHiddenLines: totalLogLines - displayedLogLines } + ); + + return ( + + + {message} + + ); +} + export class LogContainer extends Component { constructor(props) { super(props); @@ -294,12 +340,19 @@ export class LogContainer extends Component { } return acc; }, []); + if (parsedLogs.length < 20_000) { return ( - + <> + + + ); } @@ -308,22 +361,28 @@ export class LogContainer extends Component { : defaultHeight; return ( - - {({ data, index, style }) => ( -
- -
- )} -
+ <> + + + {({ data, index, style }) => ( +
+ +
+ )} +
+ ); }; diff --git a/packages/components/src/components/Log/Log.stories.jsx b/packages/components/src/components/Log/Log.stories.jsx index 61cfd3d4b..e0bff0f4c 100644 --- a/packages/components/src/components/Log/Log.stories.jsx +++ b/packages/components/src/components/Log/Log.stories.jsx @@ -1,5 +1,5 @@ /* -Copyright 2019-2024 The Tekton Authors +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 @@ -135,6 +135,7 @@ export const Toolbar = { {...args} toolbar={ diff --git a/packages/components/src/components/Log/_Log.scss b/packages/components/src/components/Log/_Log.scss index 3bbe1c777..47d881622 100644 --- a/packages/components/src/components/Log/_Log.scss +++ b/packages/components/src/components/Log/_Log.scss @@ -1,5 +1,5 @@ /* -Copyright 2019-2024 The Tekton Authors +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 @@ -29,7 +29,6 @@ pre.tkn--log { } line-height: 1rem; // Update the react-window List itemSize if changing this - overflow: hidden; background-color: $background; color: $text-primary; @@ -124,9 +123,33 @@ pre.tkn--log { .tkn--log-container { overflow-x: auto; + + .tkn--log-filtered { + display: flex; + margin-block-end: 1rem; + + svg { + margin-inline-end: 0.5rem; + } + } } - .tkn--log-container:not(:empty) + .tkn--log-trailer { + .tkn--log-container:has(code:not(:empty)) + .tkn--log-trailer { margin-block-start: 1rem; } + + .tkn--log-settings-menu-content { + padding-block-end: .5rem; + padding-block-start: 1rem; + padding-inline: 1rem; + font-family: 'IBM Plex Sans', sans-serif; + color: $text-secondary; + + hr { + border: none; + margin-block: 1rem; + background: $border-subtle; + block-size: 1px; + } + } } diff --git a/packages/components/src/components/LogsToolbar/LogsToolbar.jsx b/packages/components/src/components/LogsToolbar/LogsToolbar.jsx index a30843578..81a1a3c05 100644 --- a/packages/components/src/components/LogsToolbar/LogsToolbar.jsx +++ b/packages/components/src/components/LogsToolbar/LogsToolbar.jsx @@ -1,5 +1,5 @@ /* -Copyright 2020-2024 The Tekton Authors +Copyright 2020-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 @@ -20,15 +20,16 @@ import { Settings } from '@carbon/react/icons'; import { - unstable_FeatureFlags as FeatureFlags, - MenuItemDivider, - MenuItemGroup, - MenuItemSelectable, - OverflowMenu, + Checkbox, + CheckboxGroup, + Popover, + PopoverContent, usePrefix } from '@carbon/react'; +import { useState } from 'react'; const LogsToolbar = ({ + id, isMaximized, name, logLevels, @@ -40,6 +41,7 @@ const LogsToolbar = ({ }) => { const carbonPrefix = usePrefix(); const intl = useIntl(); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); return (
@@ -103,74 +105,118 @@ const LogsToolbar = ({ ) : null} - - { + if (event.key === 'Escape') { + event.stopPropagation(); + setIsSettingsOpen(false); + } + }} + onRequestClose={_event => { + setIsSettingsOpen(open => !open); + }} + open={isSettingsOpen} + > + + + { + onToggleShowTimestamps(checked); + }} + checked={showTimestamps} /> {logLevels && onToggleLogLevel ? ( <> - - + - onToggleLogLevel({ error })} - selected={logLevels.error} + onChange={(_event, { checked }) => { + onToggleLogLevel({ error: checked }); + }} + checked={logLevels.error} /> - onToggleLogLevel({ warning })} - selected={logLevels.warning} + onChange={(_event, { checked }) => { + onToggleLogLevel({ warning: checked }); + }} + checked={logLevels.warning} /> - onToggleLogLevel({ notice })} - selected={logLevels.notice} + onChange={(_event, { checked }) => { + onToggleLogLevel({ notice: checked }); + }} + checked={logLevels.notice} /> - onToggleLogLevel({ info })} - selected={logLevels.info} + onChange={(_event, { checked }) => { + onToggleLogLevel({ info: checked }); + }} + checked={logLevels.info} /> - onToggleLogLevel({ debug })} - selected={logLevels.debug} + onChange={(_event, { checked }) => { + onToggleLogLevel({ debug: checked }); + }} + checked={logLevels.debug} /> - + ) : null} - - + +
); }; diff --git a/packages/components/src/components/LogsToolbar/LogsToolbar.stories.jsx b/packages/components/src/components/LogsToolbar/LogsToolbar.stories.jsx index 2825226ba..5d204f6a6 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-2024 The Tekton Authors +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 @@ -29,6 +29,7 @@ export default { export const Default = { args: { + id: 'logs-toolbar', showTimestamps: false }, render: args => { diff --git a/packages/components/src/components/PipelineRun/PipelineRun.jsx b/packages/components/src/components/PipelineRun/PipelineRun.jsx index ea335202a..5da3f3314 100644 --- a/packages/components/src/components/PipelineRun/PipelineRun.jsx +++ b/packages/components/src/components/PipelineRun/PipelineRun.jsx @@ -1,5 +1,5 @@ /* -Copyright 2019-2024 The Tekton Authors +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 @@ -112,6 +112,7 @@ export default /* istanbul ignore next */ function PipelineRun({ getLogsToolbar && stepStatus && getLogsToolbar({ + id: `${selectedTaskId}-${selectedStepId}-${selectedRetry}-logs-toolbar`, isMaximized: isLogsMaximized, onToggleMaximized: !!maximizedLogsContainer && onToggleLogsMaximized, diff --git a/packages/e2e/cypress/e2e/common/pipelinerun.cy.js b/packages/e2e/cypress/e2e/common/pipelinerun.cy.js index f93ecc1e2..35c96138a 100644 --- a/packages/e2e/cypress/e2e/common/pipelinerun.cy.js +++ b/packages/e2e/cypress/e2e/common/pipelinerun.cy.js @@ -1,5 +1,5 @@ /* -Copyright 2023-2024 The Tekton Authors +Copyright 2023-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 @@ -58,6 +58,7 @@ spec: cy.contains('.tkn--log', '2024').should('not.exist'); cy.get('.tkn--log-settings-menu button').click(); cy.contains('Show timestamps').click(); + cy.get('.tkn--log-settings-menu button').type('{esc}'); cy.contains( // title starts with date formatted as 'yyyy-MM-dd' `.tkn--log [title^="${new Date().toISOString().substring(0, 10)}"]`, @@ -67,6 +68,7 @@ spec: cy.contains('.tkn--log', 'hidden by default').should('not.exist'); cy.get('.tkn--log-settings-menu button').click(); cy.contains('Debug').click(); + cy.get('.tkn--log-settings-menu button').type('{esc}'); cy.contains('.tkn--log', 'hidden by default'); }); }); diff --git a/src/containers/LogsToolbar/LogsToolbar.jsx b/src/containers/LogsToolbar/LogsToolbar.jsx index 7cf7bafa9..574d12198 100644 --- a/src/containers/LogsToolbar/LogsToolbar.jsx +++ b/src/containers/LogsToolbar/LogsToolbar.jsx @@ -1,5 +1,5 @@ /* -Copyright 2020-2024 The Tekton Authors +Copyright 2020-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 @@ -41,6 +41,7 @@ export default function LogsToolbarContainer({ return ( { const newLevels = { ...levels, ...logLevel }; - // if (!Object.values(newLevels).filter(Boolean).length) { - // // TODO: logs - notification or allow? - // alert('must have at least 1 log level enabled'); - // return levels; - // } setLogLevels(newLevels); return newLevels; }); diff --git a/src/containers/TaskRun/TaskRun.jsx b/src/containers/TaskRun/TaskRun.jsx index 57cf547b4..0a52d5e52 100644 --- a/src/containers/TaskRun/TaskRun.jsx +++ b/src/containers/TaskRun/TaskRun.jsx @@ -1,5 +1,5 @@ /* -Copyright 2019-2024 The Tekton Authors +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 @@ -79,11 +79,6 @@ export function TaskRunContainer({ function onToggleLogLevel(logLevel) { setLogLevelsState(levels => { const newLevels = { ...levels, ...logLevel }; - // if (!Object.values(newLevels).filter(Boolean).length) { - // // TODO: logs - notification or allow? - // alert('must have at least 1 log level enabled'); - // return levels; - // } setLogLevels(newLevels); return newLevels; }); @@ -199,6 +194,7 @@ export function TaskRunContainer({ toolbar={