Skip to content

Commit

Permalink
feat(core/pipelines): Add execution breadcrumbs to the pipeline execu…
Browse files Browse the repository at this point in the history
…tion component (#8462)

* feat(core/pipelines) Adds breadcrumbs to executions that were kicked off by a different pipeline

* Add a label, change breadcrumb icon, & get rid of unnecessary async/awaits.

* Show single pipeline permalink even when there is no parent

* Only show parent executions, not current one, and rename label.

* Switch to using css instead of spaces for spacing.
Add a comment.
Don't exclude ExecutionBreadcrumbs component from re-renders.

* fix(core/pipeline): restore handleParentPipelineClick linking behavior and restore BuildStatus indicator

* chore(core/pipeline): restore imports order

* fix(core/pipeline): Fix links to execution details

* refactor(core/pipeline): Migrate to useCurrentStateAndParams() hook

Co-authored-by: rodrigo-espinosa <rodrigo.espinosa@salesforce.com>
Co-authored-by: Chris Thielen <github@sandgnat.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Nov 12, 2020
1 parent 91cceb3 commit 7271167
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -15,33 +13,13 @@ export class ExecutionBuildLink extends React.Component<IExecutionBuildLinkProps
super(props);
}

private handleParentPipelineClick = () => {
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<HTMLElement>) => {
ReactGA.event({ category: 'Pipeline', action: 'Execution build number clicked - build info' });
event.stopPropagation();
};

public render() {
const { trigger } = this.props.execution;
return (
<span>
{trigger.parentExecution && trigger.parentExecution.id && (
<a className="execution-build-number clickable" onClick={this.handleParentPipelineClick}>
{trigger.parentExecution.name}
</a>
)}
{this.getBuildLink()}
</span>
);
return <span>{this.getBuildLink()}</span>;
}

private getBuildText(execution: IExecution) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -326,6 +327,8 @@ export class Execution extends React.PureComponent<IExecutionProps, IExecutionSt
'show-durations': showDurations,
});

const hasParentExecution = !!execution.trigger?.parentExecution;

return (
<div className={className} id={`execution-${execution.id}`} ref={this.wrapperRef}>
<div className={`execution-overview group-by-${sortFilter.groupBy}`}>
Expand All @@ -336,6 +339,11 @@ export class Execution extends React.PureComponent<IExecutionProps, IExecutionSt
{title || execution.name}
</h4>
)}
{hasParentExecution && (
<div className="execution-breadcrumbs">
<ExecutionBreadcrumbs execution={execution} />
</div>
)}
<ExecutionStatus execution={execution} showingDetails={showingDetails} standalone={standalone} />
<div className="execution-bar">
<div className="stages">
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<span>{label}: </span>
{parentExecutions.map((execution, index, array) => (
<React.Fragment key={execution.id}>
<ExecutionPermaLink execution={execution} />
{index !== array.length - 1 && <i className="fas fa-angle-right execution-breadcrumb-marker"></i>}
</React.Fragment>
))}
</div>
);
};

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<any> = (e) => {
ReactGA.event({ category: 'Pipeline', action: 'Execution build number clicked - parent pipeline' });
sref.onClick(e);
};

return (
<a
href={sref.href}
onClick={handleClick}
style={{ display: 'inline' }}
className="execution-build-number clickable"
>
{execution.name}
</a>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<IPipeline> => {
let pipelineConfig;
if (this.calledPipelineConfigs.hasOwnProperty(application)) {
Expand Down

0 comments on commit 7271167

Please sign in to comment.