diff --git a/package-lock.json b/package-lock.json index d5cd30ab3..e527b5407 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 97c4863b9..08666cca5 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..8bf990847 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..42a5b35e0 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,7 @@ pre.tkn--log { } line-height: 1rem; // Update the react-window List itemSize if changing this - overflow: hidden; + // overflow: hidden; // TODO: logs - verify it's ok to remove this as it's interfering with the popover background-color: $background; color: $text-primary; @@ -95,7 +95,6 @@ pre.tkn--log { block-size: 2rem; min-block-size: 2rem; background-color: $background; - &:hover { background-color: $layer-hover; } @@ -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..a9d5e4175 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,64 @@ 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 keys = { + Escape: { + key: ['Escape'], + which: 27, + keyCode: 27, + code: 'Esc' + } +}; + +/** + * Check to see if the given key matches the corresponding keyboard event. Also + * supports passing in the value directly if you can't used the given event. + * + * @example + * import * as keys from '../keys'; + * import { matches } from '../match'; + * + * function handleOnKeyDown(event) { + * if (match(event, keys.Enter) { + * // ... + * } + * } + * + * @param {Event|number|string} eventOrCode + * @param {Key} key + * @returns {boolean} + */ +function match(eventOrCode, { key, which, keyCode, code } = {}) { + if (typeof eventOrCode === 'string') { + return eventOrCode === key; + } + + if (typeof eventOrCode === 'number') { + return eventOrCode === which || eventOrCode === keyCode; + } + + if (eventOrCode.key && Array.isArray(key)) { + return key.indexOf(eventOrCode.key) !== -1; + } + + return ( + eventOrCode.key === key || + eventOrCode.which === which || + eventOrCode.keyCode === keyCode || + eventOrCode.code === code + ); +} const LogsToolbar = ({ + id, isMaximized, name, logLevels, @@ -40,6 +89,7 @@ const LogsToolbar = ({ }) => { const carbonPrefix = usePrefix(); const intl = useIntl(); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); return (
@@ -103,74 +153,118 @@ const LogsToolbar = ({ ) : null} - - { + if (match(event, keys.Escape)) { + setIsSettingsOpen(false); + event.stopPropagation(); + } + }} + 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={