From 3d1715eef32d7387bca2401517a60c93ebb78537 Mon Sep 17 00:00:00 2001 From: Anwesha Palit Date: Mon, 8 Sep 2025 16:49:44 +0530 Subject: [PATCH] feat: added step url navigation in pipeline run log tab --- .../log-scroll-test-pipeline.feature | 48 +++++ .../pipelines/log-scroll-test-pipeline.ts | 143 ++++++++++++++ .../log-scroll-test-pipeline-run.yaml | 8 + .../log-scroll-test-pipeline.yaml | 180 ++++++++++++++++++ src/components/logs/Logs.tsx | 55 +++++- src/components/logs/LogsWrapperComponent.tsx | 3 + src/components/logs/MultiStreamLogs.tsx | 3 + .../logs/__tests__/logs-utils.spec.ts | 115 +++++++++++ .../pipelineRuns-details/PipelineRunLogs.tsx | 18 +- .../tasks-details-pages/TaskRunLogs.tsx | 8 +- .../tasks-details-pages/TaskRunLogsTab.tsx | 8 +- 11 files changed, 581 insertions(+), 8 deletions(-) create mode 100644 integration-tests/cypress/features/pipelines/log-scroll-test-pipeline.feature create mode 100644 integration-tests/cypress/support/step-definitions/pipelines/log-scroll-test-pipeline.ts create mode 100644 integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline-run.yaml create mode 100644 integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline.yaml diff --git a/integration-tests/cypress/features/pipelines/log-scroll-test-pipeline.feature b/integration-tests/cypress/features/pipelines/log-scroll-test-pipeline.feature new file mode 100644 index 00000000..10725923 --- /dev/null +++ b/integration-tests/cypress/features/pipelines/log-scroll-test-pipeline.feature @@ -0,0 +1,48 @@ +@pipelines +Feature: log-scroll-test-pipeline + As a user, I want to create a pipeline and pipeline run using oc commands and then navigate to the logs tab to verify all tasks are available and if step name is present in the url then scroll to that step in the logs tab. + + Background: + Given user is at Administrator perspective + And user clicks on Pipelines Tab + And user has created or selected namespace "aut-pipelines" + + @smoke + Scenario:Create and start pipeline using CLI: LS-01-TC01 + When user creates pipeline run using YAML and CLI "testData/multistep-yaml-log-scroll/log-scroll-test-pipeline.yaml" in namespace "aut-pipelines" + Then pipeline "log-scroll-test-pipeline" should be created successfully in namespace "aut-pipelines" + + @smoke + Scenario: Create pipeline run using oc commands: LS-01-TC02 + Given user clicks on Pipelines Tab + And user has created or selected namespace "aut-pipelines" + When user creates pipeline run using YAML and CLI "testData/multistep-yaml-log-scroll/log-scroll-test-pipeline-run.yaml" in namespace "aut-pipelines" + Then pipeline run "log-scroll-test-pipeline-run" should be created successfully in namespace "aut-pipelines" + + @smoke + Scenario: Access logs tab and verify all tasks are available: LS-01-TC03 + Given user is at pipeline run details page for "log-scroll-test-pipeline-run" in namespace "aut-pipelines" + When user navigates to Logs Tab for "log-scroll-test-pipeline-run" in namespace "aut-pipelines" + Then user should see "frontend-build" task in task list + And user should see "backend-build" task in task list + + @smoke + Scenario: Scroll to step in logs tab using URL: LS-01-TC04 + Given user tries to navigate to task "task-4" and step "step-9" in Logs tab using URL for "log-scroll-test-pipeline-run" in namespace "aut-pipelines" + Then user should see "STEP-STEP-9" step is visible in logs tab + + @smoke + Scenario: Scroll to last step of last task using URL: LS-01-TC05 + Given user tries to navigate to task "task-5" and step "step-10" in Logs tab using URL for "log-scroll-test-pipeline-run" in namespace "aut-pipelines" + Then user should see "STEP-STEP-10" step is visible in logs tab + + @regression + Scenario: Invalid step name in existing task using URL: LS-01-TC06 + Given user tries to navigate to task "task-4" and step "step-50" in Logs tab using URL for "log-scroll-test-pipeline-run" in namespace "aut-pipelines" + Then user should expect default behavior of log viewer and scroll to end of log + + @regression + Scenario: Missing step parameter in URL defaults to first step: LS-01-TC10 + Given user tries to navigate to task "backend-build" and step "" in Logs tab using URL for "log-scroll-test-pipeline-run" in namespace "aut-pipelines" + Then user should see "backend-build" task in task list + And user should expect default behavior of log viewer and scroll to end of log \ No newline at end of file diff --git a/integration-tests/cypress/support/step-definitions/pipelines/log-scroll-test-pipeline.ts b/integration-tests/cypress/support/step-definitions/pipelines/log-scroll-test-pipeline.ts new file mode 100644 index 00000000..1f2afb92 --- /dev/null +++ b/integration-tests/cypress/support/step-definitions/pipelines/log-scroll-test-pipeline.ts @@ -0,0 +1,143 @@ +import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor'; +import { switchPerspective } from '../../constants/global'; +import { perspective } from '../../pages/app'; + +Given('user is at Administrator perspective', () => { + perspective.switchTo(switchPerspective.Administrator); +}); + +When('user clicks on Pipelines Tab', () => { + cy.get('[data-test="nav-pipelines"]').then(($el) => { + if ($el.attr('aria-expanded') === 'false') { + cy.wrap($el).click(); + } + }); + cy.get('[data-test="nav"][data-quickstart-id="qs-nav-pipelines"]') + .contains('Pipelines') + .click(); +}); + +When( + 'user creates pipeline using YAML and CLI {string} in namespace {string}', + (yamlFile: string, namespace: string) => { + const fullPath = yamlFile.startsWith('cypress/') + ? yamlFile + : `cypress/${yamlFile}`; + cy.exec(`oc apply -f ${fullPath} -n ${namespace}`, { + failOnNonZeroExit: false, + }).then((result) => { + if (result.stderr) { + throw new Error(result.stderr); + /* cy.log('CLI failed, falling back to UI'); + cy.get('[data-test="item-create"]').click(); + cy.get('[data-test="list-page-create-dropdown-item-pipeline"]').click(); + cy.get('[data-test="yaml-view-input"]').click(); + cy.get('button').contains('Create').click(); + cy.log('Pipeline created via UI'); */ + } + }); + }, +); + +Then( + 'pipeline {string} should be created successfully in namespace {string}', + (pipelineName: string, namespace: string) => { + cy.exec(`oc get pipeline ${pipelineName} -n ${namespace}`, { + failOnNonZeroExit: false, + }).then((result) => { + if (result.code !== 0) { + throw new Error( + `Pipeline ${pipelineName} was not created successfully: ${result.stderr}`, + ); + } + cy.log(`Pipeline ${pipelineName} created successfully`); + }); + }, +); + +When( + 'user creates pipeline run using YAML and CLI {string} in namespace {string}', + (yamlFile: string, namespace: string) => { + const fullPath = yamlFile.startsWith('cypress/') + ? yamlFile + : `cypress/${yamlFile}`; + cy.exec(`oc apply -f ${fullPath} -n ${namespace}`, { + failOnNonZeroExit: false, + }).then((result) => { + if (result.stderr) { + throw new Error(result.stderr); + } + }); + }, +); + +Then( + 'pipeline run {string} should be created successfully in namespace {string}', + (pipelineRunName: string, namespace: string) => { + cy.exec(`oc get pipelinerun ${pipelineRunName} -n ${namespace}`); + cy.exec( + `oc wait --for=condition=Succeeded pipelinerun/${pipelineRunName} -n ${namespace} --timeout=300s`, + ); + }, +); + +Given( + 'user is at pipeline run details page for {string} in namespace {string}', + (pipelineRunName: string, namespace: string) => { + cy.visit( + `/k8s/ns/${namespace}/tekton.dev~v1~PipelineRun/${pipelineRunName}`, + ); + }, +); + +When( + 'user navigates to Logs Tab for {string} in namespace {string}', + (pipelineRunName: string, namespace: string) => { + cy.visit( + `/k8s/ns/${namespace}/tekton.dev~v1~PipelineRun/${pipelineRunName}/logs`, + ); + }, +); + +Then('user should see {string} task in task list', (taskName: string) => { + cy.get('[data-test-id="logs-tasklist"]') + .contains(taskName) + .should('be.visible'); +}); + +Given( + 'user tries to navigate to task {string} and step {string} in Logs tab using URL for {string} in namespace {string}', + ( + taskName: string, + stepName: string, + pipelineRunName: string, + namespace: string, + ) => { + cy.visit( + `/k8s/ns/${namespace}/tekton.dev~v1~PipelineRun/${pipelineRunName}/logs?taskName=${taskName}&step=${stepName}`, + ); + }, +); + +Then( + 'user should see {string} step is visible in logs tab', + (stepName: string) => { + cy.contains(stepName).should('be.visible'); + cy.log(`${stepName} is visible in logs tab`); + cy.visit('/'); + }, +); + +Then( + 'user should expect default behavior of log viewer and scroll to end of log', + () => { + cy.get('.pf-v5-c-log-viewer__scroll-container').then(($el) => { + const el = $el[0]; + //eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + // Verifying if the scroll is at the bottom + expect(el.scrollHeight - el.scrollTop).to.be.closeTo(el.clientHeight, 1); + cy.visit('/'); + }); + }, +); diff --git a/integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline-run.yaml b/integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline-run.yaml new file mode 100644 index 00000000..7c6afd6f --- /dev/null +++ b/integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline-run.yaml @@ -0,0 +1,8 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + name: log-scroll-test-pipeline-run + namespace: aut-pipelines +spec: + pipelineRef: + name: log-scroll-test-pipeline \ No newline at end of file diff --git a/integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline.yaml b/integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline.yaml new file mode 100644 index 00000000..6acd1fa9 --- /dev/null +++ b/integration-tests/cypress/testData/multistep-yaml-log-scroll/log-scroll-test-pipeline.yaml @@ -0,0 +1,180 @@ +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: log-scroll-test-pipeline + namespace: aut-pipelines +spec: + tasks: + - name: frontend-build + taskSpec: + steps: + - name: step-1 + image: alpine:3.18 + script: "echo 'frontend-build / step-1'" + - name: step-2 + image: alpine:3.18 + script: "echo 'frontend-build / step-2'" + - name: step-3 + image: alpine:3.18 + script: "echo 'frontend-build / step-3'" + - name: step-4 + image: alpine:3.18 + script: "echo 'frontend-build / step-4'" + - name: step-5 + image: alpine:3.18 + script: "echo 'frontend-build / step-5'" + - name: step-6 + image: alpine:3.18 + script: "echo 'frontend-build / step-6'" + - name: step-7 + image: alpine:3.18 + script: "echo 'frontend-build / step-7'" + - name: step-8 + image: alpine:3.18 + script: "echo 'frontend-build / step-8'" + - name: step-9 + image: alpine:3.18 + script: "echo 'frontend-build / step-9'" + - name: step-10 + image: alpine:3.18 + script: "echo 'frontend-build / step-10'" + + - name: backend-build + runAfter: [frontend-build] + taskSpec: + steps: + - name: step-1 + image: alpine:3.18 + script: "echo 'backend-build / step-1'" + - name: step-2 + image: alpine:3.18 + script: "echo 'backend-build / step-2'" + - name: step-3 + image: alpine:3.18 + script: "echo 'backend-build / step-3'" + - name: step-4 + image: alpine:3.18 + script: "echo 'backend-build / step-4'" + - name: step-5 + image: alpine:3.18 + script: "echo 'backend-build / step-5'" + - name: step-6 + image: alpine:3.18 + script: "echo 'backend-build / step-6'" + - name: step-7 + image: alpine:3.18 + script: "echo 'backend-build / step-7'" + - name: step-8 + image: alpine:3.18 + script: "echo 'backend-build / step-8'" + - name: step-9 + image: alpine:3.18 + script: "echo 'backend-build / step-9'" + - name: step-10 + image: alpine:3.18 + script: "echo 'backend-build / step-10'" + + - name: api-tests + runAfter: [backend-build] + taskSpec: + steps: + - name: step-1 + image: alpine:3.18 + script: "echo 'api-tests / step-1'" + - name: step-2 + image: alpine:3.18 + script: "echo 'api-tests / step-2'" + - name: step-3 + image: alpine:3.18 + script: "echo 'api-tests / step-3'" + - name: step-4 + image: alpine:3.18 + script: "echo 'api-tests / step-4'" + - name: step-5 + image: alpine:3.18 + script: "echo 'api-tests / step-5'" + - name: step-6 + image: alpine:3.18 + script: "echo 'api-tests / step-6'" + - name: step-7 + image: alpine:3.18 + script: "echo 'api-tests / step-7'" + - name: step-8 + image: alpine:3.18 + script: "echo 'api-tests / step-8'" + - name: step-9 + image: alpine:3.18 + script: "echo 'api-tests / step-9'" + - name: step-10 + image: alpine:3.18 + script: "echo 'api-tests / step-10'" + + - name: task-4 + runAfter: [api-tests] + taskSpec: + steps: + - name: step-1 + image: alpine:3.18 + script: "echo 'task-4 / step-1'" + - name: step-2 + image: alpine:3.18 + script: "echo 'task-4 / step-2'" + - name: step-3 + image: alpine:3.18 + script: "echo 'task-4 / step-3'" + - name: step-4 + image: alpine:3.18 + script: "echo 'task-4 / step-4'" + - name: step-5 + image: alpine:3.18 + script: "echo 'task-4 / step-5'" + - name: step-6 + image: alpine:3.18 + script: "echo 'task-4 / step-6'" + - name: step-7 + image: alpine:3.18 + script: "echo 'task-4 / step-7'" + - name: step-8 + image: alpine:3.18 + script: "echo 'task-4 / step-8'" + - name: step-9 + image: alpine:3.18 + script: "echo 'task-4 / step-9'" + - name: step-10 + image: alpine:3.18 + script: "echo 'task-4 / step-10'" + + - name: task-5 + runAfter: [task-4] + taskSpec: + steps: + - name: step-1 + image: alpine:3.18 + script: "echo 'task-5 / step-1'" + - name: step-2 + image: alpine:3.18 + script: "echo 'task-5 / step-2'" + - name: step-3 + image: alpine:3.18 + script: "echo 'task-5 / step-3'" + - name: step-4 + image: alpine:3.18 + script: "echo 'task-5 / step-4'" + - name: step-5 + image: alpine:3.18 + script: "echo 'task-5 / step-5'" + - name: step-6 + image: alpine:3.18 + script: "echo 'task-5 / step-6'" + - name: step-7 + image: alpine:3.18 + script: "echo 'task-5 / step-7'" + - name: step-8 + image: alpine:3.18 + script: "echo 'task-5 / step-8'" + - name: step-9 + image: alpine:3.18 + script: "echo 'task-5 / step-9'" + - name: step-10 + image: alpine:3.18 + script: "echo 'task-5 / step-10'" diff --git a/src/components/logs/Logs.tsx b/src/components/logs/Logs.tsx index 14f9ed5d..300e3aa7 100644 --- a/src/components/logs/Logs.tsx +++ b/src/components/logs/Logs.tsx @@ -16,6 +16,7 @@ type LogsProps = { resource: PodKind; containers: ContainerSpec[]; setCurrentLogsGetter?: (getter: () => string) => void; + activeStep?: string; }; type LogData = { @@ -53,6 +54,7 @@ const Logs: React.FC = ({ resource, containers, setCurrentLogsGetter, + activeStep, }) => { if (!resource) return null; const { t } = useTranslation('plugin__pipelines-console-plugin'); @@ -66,6 +68,23 @@ const Logs: React.FC = ({ new Set(), ); + const findTargetRowForActiveStep = React.useMemo( + () => (formattedString: string) => { + if (!activeStep) return null; + const lines = formattedString.split('\n'); + let targetLine: number | null = null; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.includes(`STEP-${activeStep}`.toUpperCase())) { + targetLine = i; + break; + } + } + return targetLine; + }, + [activeStep], + ); + React.useEffect(() => { setCurrentLogsGetter(() => { return formattedLogString; @@ -206,10 +225,38 @@ const Logs: React.FC = ({ React.useEffect(() => { const formattedString = processLogData(logData, containers); - setFormattedLogString(formattedString); - const totalLines = formattedString.split('\n').length; - setScrollToRow(totalLines); - }, [logData]); + const targetRow = findTargetRowForActiveStep(formattedString); + if (typeof targetRow === 'number') { + setScrollToRow(targetRow); + const searchTerm = `STEP-${activeStep}`.toUpperCase(); + const formattedStringAfterHighlighting = formattedString.replace( + new RegExp(`(${searchTerm})`, 'gi'), + '\x1b[48;2;253;240;171m\x1b[38;2;21;21;21m$1\x1b[0m', + ); // yellow background with dark text + setFormattedLogString(formattedStringAfterHighlighting); + let isHighlighted = true; + const blinkInterval = setInterval(() => { + if (isHighlighted) { + setFormattedLogString(formattedString); + } else { + setFormattedLogString(formattedStringAfterHighlighting); + } + isHighlighted = !isHighlighted; + }, 300); + const stopBlinkingTimeout = setTimeout(() => { + clearInterval(blinkInterval); + setFormattedLogString(formattedString); + }, 1000); + return () => { + clearInterval(blinkInterval); + clearTimeout(stopBlinkingTimeout); + }; + } else { + const totalLines = formattedString.split('\n').length; + setScrollToRow(totalLines); + setFormattedLogString(formattedString); + } + }, [logData, activeStep, findTargetRowForActiveStep]); return (
diff --git a/src/components/logs/LogsWrapperComponent.tsx b/src/components/logs/LogsWrapperComponent.tsx index 3d73751c..ebe5d9b4 100644 --- a/src/components/logs/LogsWrapperComponent.tsx +++ b/src/components/logs/LogsWrapperComponent.tsx @@ -25,6 +25,7 @@ type LogsWrapperComponentProps = { downloadAllLabel?: string; onDownloadAll?: () => Promise; resource: WatchK8sResource; + activeStep?: string; }; const LogsWrapperComponent: React.FC< @@ -34,6 +35,7 @@ const LogsWrapperComponent: React.FC< taskRun, onDownloadAll, downloadAllLabel = 'Download all', + activeStep, ...props }) => { const { t } = useTranslation('plugin__pipelines-console-plugin'); @@ -139,6 +141,7 @@ const LogsWrapperComponent: React.FC< taskName={taskName} resource={resourceRef.current} setCurrentLogsGetter={setLogGetter} + activeStep={activeStep} /> ) : ( string) => void; + activeStep?: string; }; export const MultiStreamLogs: React.FC = ({ resource, taskName, setCurrentLogsGetter, + activeStep, }) => { const { containers, stillFetching } = getRenderContainers(resource); @@ -39,6 +41,7 @@ export const MultiStreamLogs: React.FC = ({ resource={resource} containers={containers} setCurrentLogsGetter={setCurrentLogsGetter} + activeStep={activeStep} />
diff --git a/src/components/logs/__tests__/logs-utils.spec.ts b/src/components/logs/__tests__/logs-utils.spec.ts index 896ced47..10c91eb4 100644 --- a/src/components/logs/__tests__/logs-utils.spec.ts +++ b/src/components/logs/__tests__/logs-utils.spec.ts @@ -31,3 +31,118 @@ describe('logs utils', () => { expect(stillFetching).toBe(true); }); }); + +describe('tests for findTargetRowForActiveStep logic', () => { + // Testing only the method logic + const findTargetRowForActiveStep = ( + formattedString: string, + activeStep: string, + ) => { + if (!activeStep) return null; + const lines = formattedString.split('\n'); + let targetLine: number | null = null; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.includes(`STEP-${activeStep}`.toUpperCase())) { + targetLine = i; + break; + } + } + return targetLine; + }; + // Mock formatted log data for testing 1 step name corresponds to 3 lines of content + const createMockFormattedString = (stepNames: string[]): string => { + return stepNames + .map( + (stepName) => + `STEP-${stepName.toUpperCase()}\nSome log content for ${stepName}\n`, + ) + .join('\n'); + }; + + it('should return 0 when activeStep is found at the beginning', () => { + const formattedString = createMockFormattedString([ + 'build', + 'test', + 'deploy', + ]); + const result = findTargetRowForActiveStep(formattedString, 'build'); + expect(result).toBe(0); + }); + + it('should return correct line number when activeStep is found in the middle', () => { + const formattedString = createMockFormattedString([ + 'build', + 'test', + 'deploy', + ]); + const result = findTargetRowForActiveStep(formattedString, 'test'); + expect(result).toBe(3); // Line 0: STEP-BUILD, Line 1: content, Line 2: empty, Line 3: STEP-TEST + }); + + it('should return correct line number when activeStep is found at the end', () => { + const formattedString = createMockFormattedString([ + 'build', + 'test', + 'deploy', + ]); + const result = findTargetRowForActiveStep(formattedString, 'deploy'); + expect(result).toBe(6); + }); + + it('should return null when activeStep is not found', () => { + const formattedString = createMockFormattedString(['build', 'test']); + const result = findTargetRowForActiveStep(formattedString, 'nonexistent'); + expect(result).toBe(null); + }); + + it('should handle case insensitive matching', () => { + const formattedString = 'STEP-BUILD\nSome content\nSTEP-TEST\nMore content'; + const result = findTargetRowForActiveStep(formattedString, 'test'); + expect(result).toBe(2); + }); + + it('should return null for empty formattedString', () => { + const result = findTargetRowForActiveStep('', 'build'); + expect(result).toBe(null); + }); + + it('should return null for empty activeStep', () => { + const formattedString = createMockFormattedString(['build']); + const result = findTargetRowForActiveStep(formattedString, ''); + expect(result).toBe(null); + }); + + it('should handle step names with special characters', () => { + const formattedString = `STEP-BUILD-AND-TEST +Some content`; + const result = findTargetRowForActiveStep( + formattedString, + 'build-and-test', + ); + expect(result).toBe(0); + }); + + it('should handle realistic log format', () => { + const formattedString = `STEP-GIT-CLONE +Cloning repository... +Cloned successfully + +STEP-BUILD +Building application... +Build completed + +STEP-TEST +Running tests... +All tests passed + +STEP-DEPLOY +Deploying application... +Deployment successful`; + + expect(findTargetRowForActiveStep(formattedString, 'git-clone')).toBe(0); + expect(findTargetRowForActiveStep(formattedString, 'build')).toBe(4); + expect(findTargetRowForActiveStep(formattedString, 'test')).toBe(8); + expect(findTargetRowForActiveStep(formattedString, 'deploy')).toBe(12); + }); +}); diff --git a/src/components/pipelineRuns-details/PipelineRunLogs.tsx b/src/components/pipelineRuns-details/PipelineRunLogs.tsx index 565128f9..5ab72fa2 100644 --- a/src/components/pipelineRuns-details/PipelineRunLogs.tsx +++ b/src/components/pipelineRuns-details/PipelineRunLogs.tsx @@ -31,6 +31,7 @@ import './PipelineRunLogs.scss'; interface PipelineRunLogsProps { obj: PipelineRunKind; activeTask?: string; + activeStep?: string; t: TFunction; taskRuns: TaskRunKind[]; isDevConsoleProxyAvailable?: boolean; @@ -123,7 +124,13 @@ class PipelineRunLogsWithTranslation extends React.Component< }; render() { - const { obj, t, taskRuns: tRuns, isDevConsoleProxyAvailable } = this.props; + const { + obj, + t, + taskRuns: tRuns, + isDevConsoleProxyAvailable, + activeStep, + } = this.props; const { activeItem } = this.state; const taskRunNames = this.getSortedTaskRun(tRuns, [ ...(obj?.status?.pipelineSpec?.tasks || []), @@ -236,6 +243,7 @@ class PipelineRunLogsWithTranslation extends React.Component< downloadAllLabel={t('Download all task logs')} onDownloadAll={downloadAllCallback} taskRun={activeTaskRun} + activeStep={activeStep} /> ) : (
@@ -290,6 +298,7 @@ export const PipelineRunLogsWithActiveTask: React.FC< const location = useLocation(); const params = new URLSearchParams(location.search); const activeTask = params?.get('taskName'); + const activeStep = params?.get('step'); const [taskRuns, taskRunsLoaded] = useTaskRuns( obj?.metadata?.namespace, obj?.metadata?.name, @@ -297,7 +306,12 @@ export const PipelineRunLogsWithActiveTask: React.FC< return ( taskRunsLoaded && ( - + ) ); }; diff --git a/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogs.tsx b/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogs.tsx index 7df9c5d2..7876d615 100644 --- a/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogs.tsx +++ b/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogs.tsx @@ -7,11 +7,13 @@ import './TaskRunLog.scss'; type Props = { taskRun: TaskRunKind; status: ComputedStatus; + activeStep?: string; }; const TaskRunLogs: React.FC> = ({ taskRun, status, + activeStep, }) => { const podName = taskRun?.status?.podName; @@ -32,7 +34,11 @@ const TaskRunLogs: React.FC> = ({ }; return (
- +
); }; diff --git a/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogsTab.tsx b/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogsTab.tsx index f64e3ae7..848e7bb5 100644 --- a/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogsTab.tsx +++ b/src/components/pipelines-tasks/tasks-details-pages/TaskRunLogsTab.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useLocation } from 'react-router-dom-v5-compat'; import { taskRunStatus } from '../../utils/pipeline-utils'; import TaskRunLogs from './TaskRunLogs'; import { TaskRunKind } from '../../../types'; @@ -9,8 +10,13 @@ export interface TaskRunDetailsProps { const TaskRunLogsTab: React.FC = ({ obj: taskRun }) => { const status = taskRunStatus(taskRun); + const location = useLocation(); + const params = new URLSearchParams(location.search); + const activeStep = params?.get('step'); - return ; + return ( + + ); }; export default TaskRunLogsTab;