diff --git a/app/scripts/modules/core/src/pipeline/executionBuild/ExecutionBuildLink.tsx b/app/scripts/modules/core/src/pipeline/executionBuild/ExecutionBuildLink.tsx index dff2ce5720b..1add67737b2 100644 --- a/app/scripts/modules/core/src/pipeline/executionBuild/ExecutionBuildLink.tsx +++ b/app/scripts/modules/core/src/pipeline/executionBuild/ExecutionBuildLink.tsx @@ -2,8 +2,6 @@ import React from 'react'; import ReactGA from 'react-ga'; import { IExecution } from 'core/domain'; -import { ReactInjector } from 'core/reactShims'; - import './ExecutionBuildLink.less'; export interface IExecutionBuildLinkProps { @@ -15,33 +13,13 @@ export class ExecutionBuildLink extends React.Component { - const { parentExecution } = this.props.execution.trigger; - const { $state } = ReactInjector; - ReactGA.event({ category: 'Pipeline', action: 'Execution build number clicked - parent pipeline' }); - const toStateParams = { application: parentExecution.application, executionId: parentExecution.id }; - const toStateOptions = { inherit: false, reload: 'home.applications.application.pipelines.executionDetails' }; - const nextState = `${$state.current.name.endsWith('.execution') ? '^' : ''}.^.executionDetails.execution`; - $state.go(nextState, toStateParams, toStateOptions); - }; - private handleBuildInfoClick = (event: React.MouseEvent) => { ReactGA.event({ category: 'Pipeline', action: 'Execution build number clicked - build info' }); event.stopPropagation(); }; public render() { - const { trigger } = this.props.execution; - return ( - - {trigger.parentExecution && trigger.parentExecution.id && ( - - {trigger.parentExecution.name} - - )} - {this.getBuildLink()} - - ); + return {this.getBuildLink()}; } private getBuildText(execution: IExecution) { diff --git a/app/scripts/modules/core/src/pipeline/executions/execution/Execution.tsx b/app/scripts/modules/core/src/pipeline/executions/execution/Execution.tsx index 6f0cc779980..0af97efc656 100644 --- a/app/scripts/modules/core/src/pipeline/executions/execution/Execution.tsx +++ b/app/scripts/modules/core/src/pipeline/executions/execution/Execution.tsx @@ -23,11 +23,12 @@ import { ISortFilter } from 'core/filterModel'; import { ExecutionState } from 'core/state'; // react components +import { CancelModal } from 'core/cancelModal/CancelModal'; +import { ExecutionBreadcrumbs } from './ExecutionBreadcrumbs'; import { ExecutionMarker } from './ExecutionMarker'; +import { ExecutionPermalink } from './ExecutionPermalink'; import { PipelineGraph } from '../../config/graph/PipelineGraph'; import { Tooltip } from 'core/presentation/Tooltip'; -import { CancelModal } from 'core/cancelModal/CancelModal'; -import { ExecutionPermalink } from './ExecutionPermalink'; import './execution.less'; @@ -326,6 +327,8 @@ export class Execution extends React.PureComponent
@@ -336,6 +339,11 @@ export class Execution extends React.PureComponent )} + {hasParentExecution && ( +
+ +
+ )}
diff --git a/app/scripts/modules/core/src/pipeline/executions/execution/ExecutionBreadcrumbs.tsx b/app/scripts/modules/core/src/pipeline/executions/execution/ExecutionBreadcrumbs.tsx new file mode 100644 index 00000000000..3377d75ec4d --- /dev/null +++ b/app/scripts/modules/core/src/pipeline/executions/execution/ExecutionBreadcrumbs.tsx @@ -0,0 +1,61 @@ +import React, { MouseEventHandler } from 'react'; +import { useSref, useCurrentStateAndParams } from '@uirouter/react'; +import ReactGA from 'react-ga'; + +import { ExecutionInformationService } from './executionInformation.service'; +import { IExecution } from 'core/domain'; + +export interface IExecutionBreadcrumbsProps { + execution: IExecution; +} + +export const ExecutionBreadcrumbs = ({ execution }: IExecutionBreadcrumbsProps) => { + const parentExecutions = React.useMemo(() => { + return new ExecutionInformationService() + .getAllParentExecutions(execution) + .filter((x) => x !== execution) + .reverse(); + }, []); + + const label = parentExecutions.length === 1 ? 'Parent Execution' : 'Parent Executions'; + + return ( +
+ {label}: + {parentExecutions.map((execution, index, array) => ( + + + {index !== array.length - 1 && } + + ))} +
+ ); +}; + +function ExecutionPermaLink({ execution }: IExecutionBreadcrumbsProps) { + const { application, id: executionId } = execution; + const { state } = useCurrentStateAndParams(); + + const isProject = state.name.includes('.project.'); + const executionDetails = `home.${isProject ? 'project.' : 'applications.'}application.pipelines.executionDetails`; + const toState = executionDetails + '.execution'; + const srefParams = { application, executionId }; + const srefOptions = { reload: executionDetails }; + const sref = useSref(toState, srefParams, srefOptions); + + const handleClick: MouseEventHandler = (e) => { + ReactGA.event({ category: 'Pipeline', action: 'Execution build number clicked - parent pipeline' }); + sref.onClick(e); + }; + + return ( + + {execution.name} + + ); +} diff --git a/app/scripts/modules/core/src/pipeline/executions/execution/execution.less b/app/scripts/modules/core/src/pipeline/executions/execution/execution.less index d3641c394a6..699313dc854 100644 --- a/app/scripts/modules/core/src/pipeline/executions/execution/execution.less +++ b/app/scripts/modules/core/src/pipeline/executions/execution/execution.less @@ -132,3 +132,11 @@ execution-details-section-nav { .execution-cancellation-reason-button { margin-bottom: 6px; } + +.execution-breadcrumbs { + margin-bottom: 5px; +} + +.execution-breadcrumb-marker { + margin: 0px 3px; +} diff --git a/app/scripts/modules/core/src/pipeline/executions/execution/executionInformation.service.ts b/app/scripts/modules/core/src/pipeline/executions/execution/executionInformation.service.ts index 2df0a535ad9..bcdc0010f91 100644 --- a/app/scripts/modules/core/src/pipeline/executions/execution/executionInformation.service.ts +++ b/app/scripts/modules/core/src/pipeline/executions/execution/executionInformation.service.ts @@ -34,6 +34,19 @@ export class ExecutionInformationService { }); }; + // Returns an array of parent executions starting with the one passed in. + public getAllParentExecutions = (execution: IExecution): IExecution[] => { + const executions: any[] = []; + + executions.push(execution); + + if (execution.trigger.parentExecution) { + executions.push(...this.getAllParentExecutions(execution.trigger.parentExecution)); + } + + return executions; + }; + public getPipelineConfig = async (application: string, pipelineConfigId: string): Promise => { let pipelineConfig; if (this.calledPipelineConfigs.hasOwnProperty(application)) {