Skip to content

Commit

Permalink
feat(core/execution-parameters): condense parameters/artifacts and ma…
Browse files Browse the repository at this point in the history
…ke it collapsable (#6756)
  • Loading branch information
kevinawoo authored and christopherthielen committed Apr 9, 2019
1 parent ca36727 commit 2020bf6
Show file tree
Hide file tree
Showing 21 changed files with 569 additions and 186 deletions.
2 changes: 2 additions & 0 deletions app/scripts/modules/core/src/domain/IPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export interface IPipeline {
type: string;
};
type?: string;
pinAllParameters?: boolean;
}

export interface IParameter {
name: string;
description: string;
default: string;
hasOptions: boolean;
pinned: boolean;
options: IParameterOption[];
condition?: IParameterCondition;
}
Expand Down
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/help/help.contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ const helpContents: { [key: string]: string } = {
'pipeline.config.parameter.label': '(Optional): a label to display when users are triggering the pipeline manually',
'pipeline.config.parameter.description': `(Optional): if supplied, will be displayed to users as a tooltip
when triggering the pipeline manually. You can include HTML in this field.`,
'pipeline.config.parameter.pinned': `(Optional): if checked, this parameter will be always shown in a pipeline execution view, otherwise it'll be collapsed by default.`,
'pipeline.config.failOnFailedExpressions': `When this option is enabled, the stage will be marked as failed if it contains any failed expressions`,
'pipeline.config.roles.help': `
<p> When the pipeline is triggered using an automated trigger, these roles will be used to decide if the pipeline has permissions to access a protected application or account.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
</label>
</div>
</stage-config-field>
<stage-config-field label="Pin Parameter" help-key="pipeline.config.parameter.pinned">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="parameter.pinned" />
</label>
</div>
</stage-config-field>

<stage-config-field label="Description" help-key="pipeline.config.parameter.description">
<input type="text" ng-model="parameter.description" class="form-control input-sm" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<page-navigator scrollable-container="[ui-view]">
<page-section key="concurrent" label="Concurrent Executions" visible="!pipeline.strategy">
<page-section key="concurrent" label="Execution Options" visible="!pipeline.strategy">
<div class="row">
<div class="col-md-11 col-md-offset-1">
<div class="checkbox">
Expand All @@ -15,6 +15,12 @@
<help-field key="pipeline.config.parallel.cancel.queue"></help-field>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="pipeline.pinAllParameters" />
<strong>Pin all parameters when viewing an execution</strong>
</label>
</div>
</div>
</div>
</page-section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export class SingleExecutionDetails extends React.Component<
<Execution
execution={execution}
application={app}
pipelineConfig={null}
standalone={true}
showDurations={sortFilter.showDurations}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import * as React from 'react';
import * as ReactGA from 'react-ga';
import { clone, isEqual } from 'lodash';
import { clone, isEqual, keyBy, truncate } from 'lodash';
import { $location } from 'ngimport';
import { Subscription } from 'rxjs';
import * as classNames from 'classnames';
import memoizeOne from 'memoize-one';

import { Application } from 'core/application/application.model';
import { CopyToClipboard } from 'core/utils';
import { StageExecutionDetails } from 'core/pipeline/details/StageExecutionDetails';
import { ExecutionStatus } from 'core/pipeline/status/ExecutionStatus';
import { ExecutionParameters } from 'core/pipeline/status/ExecutionParameters';
import { IExecution, IRestartDetails, IPipeline } from 'core/domain';
import { IExecutionViewState, IPipelineGraphNode } from 'core/pipeline/config/graph/pipelineGraph.service';
import { ResolvedArtifactList } from 'core/pipeline/status/ResolvedArtifactList';
import { OrchestratedItemRunningTime } from './OrchestratedItemRunningTime';
import { SETTINGS } from 'core/config/settings';
import { AccountTag } from 'core/account';
Expand All @@ -30,6 +33,7 @@ import './execution.less';
export interface IExecutionProps {
application: Application;
execution: IExecution;
pipelineConfig: IPipeline;
showDurations?: boolean;
standalone?: boolean;
title?: string | JSX.Element;
Expand All @@ -42,13 +46,21 @@ export interface IExecutionProps {

export interface IExecutionState {
showingDetails: boolean;
showingParams: boolean;
pipelinesUrl: string;
viewState: IExecutionViewState;
sortFilter: ISortFilter;
restartDetails: IRestartDetails;
runningTimeInMs: number;
}

export interface IDisplayableParameter {
key: string;
value: string;
showTruncatedValue?: boolean;
valueTruncated?: string;
}

export class Execution extends React.Component<IExecutionProps, IExecutionState> {
public static defaultProps: Partial<IExecutionProps> = {
dataSourceKey: 'executions',
Expand All @@ -60,7 +72,7 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>

constructor(props: IExecutionProps) {
super(props);
const { execution } = this.props;
const { execution, standalone } = this.props;
const { $stateParams } = ReactInjector;

const initialViewState = {
Expand All @@ -75,6 +87,7 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>

this.state = {
showingDetails: this.invalidateShowingDetails(props),
showingParams: standalone,
pipelinesUrl: [SETTINGS.gateUrl, 'pipelines/'].join('/'),
viewState: initialViewState,
sortFilter: ExecutionState.filterModel.asFilterModel.sortFilter,
Expand Down Expand Up @@ -124,6 +137,11 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>
});
};

public toggleParams = (): void => {
const { showingParams } = this.state;
this.setState({ showingParams: !showingParams });
};

public getUrl(): string {
let url = $location.absUrl();
if (!this.props.standalone) {
Expand Down Expand Up @@ -183,6 +201,55 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>
});
}

private getDisplayableParameters = memoizeOne(
(
execution: IExecution,
pipelineConfig: IPipeline,
): { displayableParameters: IDisplayableParameter[]; pinnedDisplayableParameters: IDisplayableParameter[] } => {
// these are internal parameters that are not useful to end users
const strategyExclusions = [
'parentPipelineId',
'strategy',
'parentStageId',
'deploymentDetails',
'cloudProvider',
];

const truncateLength = 200;

const isParamDisplayable = (paramKey: string) =>
execution.isStrategy ? !strategyExclusions.includes(paramKey) : true;

const displayableParameters: IDisplayableParameter[] = Object.keys(
(execution.trigger && execution.trigger.parameters) || {},
)
.filter(isParamDisplayable)
.sort()
.map((key: string) => {
const value = JSON.stringify(execution.trigger.parameters[key]);
const showTruncatedValue = value.length > truncateLength;
let valueTruncated;
if (showTruncatedValue) {
valueTruncated = truncate(value, { length: truncateLength });
}
return { key, value, valueTruncated, showTruncatedValue };
});

let pinnedDisplayableParameters: IDisplayableParameter[] = [];

if (pipelineConfig) {
const paramConfigIndexByName = keyBy(pipelineConfig.parameterConfig, 'name');
const isParamPinned = (param: IDisplayableParameter): boolean =>
pipelineConfig.pinAllParameters ||
(paramConfigIndexByName[param.key] && paramConfigIndexByName[param.key].pinned); // an older execution's parameter might be missing from a newer pipelineConfig.parameterConfig

pinnedDisplayableParameters = displayableParameters.filter(isParamPinned);
}

return { displayableParameters, pinnedDisplayableParameters };
},
);

public componentDidMount(): void {
const { execution } = this.props;
this.runningTime = new OrchestratedItemRunningTime(execution, (time: number) =>
Expand Down Expand Up @@ -250,9 +317,25 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>
ReactGA.event({ category: 'Pipeline', action: 'Permalink clicked' });
};

private handleToggleDetails = (): void => {
ReactGA.event({ category: 'Pipeline', action: 'Execution details toggled (Details link)' });
this.toggleDetails();
};

public render() {
const { application, execution, showAccountLabels, showDurations, standalone, title, cancelHelpText } = this.props;
const { pipelinesUrl, restartDetails, showingDetails, sortFilter, viewState } = this.state;
const {
application,
execution,
showAccountLabels,
showDurations,
standalone,
title,
cancelHelpText,
pipelineConfig,
} = this.props;
const { pipelinesUrl, restartDetails, showingDetails, showingParams, sortFilter, viewState } = this.state;
const { trigger } = execution;
const { artifacts, resolvedExpectedArtifacts } = trigger;

const accountLabels = this.props.execution.deploymentTargets.map(account => (
<AccountTag key={account} account={account} />
Expand Down Expand Up @@ -280,6 +363,15 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>
'show-durations': showDurations,
});

const { displayableParameters, pinnedDisplayableParameters } = this.getDisplayableParameters(
execution,
pipelineConfig,
);

const parametersAndArtifactsExpanded =
showingParams ||
(displayableParameters.length === pinnedDisplayableParameters.length && !resolvedExpectedArtifacts.length);

return (
<div className={className} id={`execution-${execution.id}`}>
<div className={`execution-overview group-by-${sortFilter.groupBy}`}>
Expand All @@ -299,8 +391,8 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>
)}
<ExecutionStatus
execution={execution}
toggleDetails={this.toggleDetails}
showingDetails={showingDetails}
showingParams={showingParams}
standalone={standalone}
/>
<div className="execution-bar">
Expand Down Expand Up @@ -379,7 +471,45 @@ export class Execution extends React.Component<IExecutionProps, IExecutionState>
</Tooltip>
)}
</div>

<div className="execution-parameters-button">
<a className="clickable" onClick={this.toggleParams}>
<span
className={`small glyphicon ${
parametersAndArtifactsExpanded ? 'glyphicon-chevron-down' : 'glyphicon-chevron-right'
}`}
/>
{parametersAndArtifactsExpanded ? '' : 'View All '}
Parameters/Artifacts ({displayableParameters.length}/{`${resolvedExpectedArtifacts.length}`})
</a>
</div>
<ExecutionParameters
shouldShowAllParams={showingParams}
displayableParameters={displayableParameters}
pinnedDisplayableParameters={pinnedDisplayableParameters}
pipelineConfig={pipelineConfig}
/>

{SETTINGS.feature.artifacts && (
<ResolvedArtifactList
artifacts={artifacts}
resolvedExpectedArtifacts={resolvedExpectedArtifacts}
showingExpandedArtifacts={showingParams}
/>
)}

{!standalone && (
<div className="execution-details-button">
<a className="clickable" onClick={this.handleToggleDetails}>
<span
className={`small glyphicon ${showingDetails ? 'glyphicon-chevron-down' : 'glyphicon-chevron-right'}`}
/>
Execution Details
</a>
</div>
)}
</div>

{showingDetails && (
<div className="execution-graph">
<PipelineGraph execution={execution} onNodeClick={this.handleNodeClick} viewState={viewState} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@import (reference) '~core/presentation/less/imports/commonImports.less';

@execution-parameters-icon-width: 1.4em;

.execution {
.label {
text-transform: uppercase;
Expand Down Expand Up @@ -114,3 +116,17 @@ execution-details-section-nav {
min-width: 50px;
}
}

.execution-parameters-button,
.execution-details-button {
font-size: 0.8em;
line-height: 12px;

span.glyphicon {
width: @execution-parameters-icon-width;
}
}

.execution-details-button {
margin-top: 6px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export class ExecutionGroup extends React.Component<IExecutionGroupProps, IExecu
<Execution
key={execution.id}
execution={execution}
pipelineConfig={pipelineConfig}
application={this.props.application}
onRerun={pipelineConfig && !hasMPTv2PipelineConfig ? this.rerunExecutionClicked : undefined}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,6 @@
margin-top: 0;
font-weight: 600;
}
&.group-by-name {
.execution-bar {
padding-top: 20px;
}
}
}
}
}
Expand Down
25 changes: 11 additions & 14 deletions app/scripts/modules/core/src/pipeline/status/Artifact.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ describe('<Artifact/>', () => {
type: ARTIFACT_TYPE,
name: ARTIFACT_NAME,
};

component = shallow(<Artifact artifact={artifact} />);
const dl = component.find('dl');
const dt = dl.find('dt');
const dd = dl.find('dd');
expect(dl.length).toEqual(1);
expect(dt.length).toEqual(1);
expect(dd.length).toEqual(1);
expect(dd.at(0).text()).toEqual(ARTIFACT_NAME);

const artifactName = component.find('.artifact-name');

expect(artifactName.length).toEqual(1);
expect(artifactName.text()).toEqual(ARTIFACT_NAME);
});

it('renders an artifact version if present', function() {
Expand All @@ -42,13 +41,11 @@ describe('<Artifact/>', () => {
version,
};
component = shallow(<Artifact artifact={artifact} />);
const dl = component.find('dl');
const dt = dl.find('dt');
const dd = dl.find('dd');
expect(dl.length).toEqual(1);
expect(dt.length).toEqual(2);
expect(dd.length).toEqual(2);
expect(dd.at(1).text()).toEqual(version);

const artifactVersion = component.find('.artifact-version');

expect(artifactVersion.length).toEqual(1);
expect(artifactVersion.text()).toEqual(` - ${version}`);
});

it('includes the artifact reference in the tootip', function() {
Expand Down
Loading

0 comments on commit 2020bf6

Please sign in to comment.