Skip to content

Commit

Permalink
feat(pipeline_template): Better support for templated pipelines with …
Browse files Browse the repository at this point in the history
…dynamic sources

* Add "Rendered pipeline" tab to "Edit as JSON" modal for templated pipelines
* Don't display error message when editing templated pipelines with dynamic source - real fix in Orca
* Render execution graph when editing templated pipelines with dynamic source - real fix in Orca
* Display warning in Deck if configuration can't be rendered because no executions have been run
* Display information about which execution used for rendering
* Let the user select which configuration that's used for rendering

Depends on spinnaker/orca#1718 and spinnaker/gate#471
  • Loading branch information
jervi committed Oct 31, 2017
1 parent 4f2a14d commit c294785
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 24 deletions.
2 changes: 1 addition & 1 deletion app/scripts/modules/core/src/delivery/delivery.states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module(DELIVERY_STATES, [

const pipelineConfig: INestedState = {
name: 'pipelineConfig',
url: '/configure/:pipelineId',
url: '/configure/:pipelineId?executionId',
views: {
'pipelines': {
templateUrl: require('../pipeline/config/pipelineConfig.html'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class ExecutionBuildNumber extends React.Component<IExecutionBuildNumberP
public render() {
return (
<span>
{ this.props.execution.trigger.parentPipelineId && (
{ this.props.execution.trigger && this.props.execution.trigger.parentPipelineId && (
<a
className="execution-build-number clickable"
onClick={this.handleParentPipelineClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('Controller: editPipelineJsonModal', () => {

function initializeController(pipeline: IPipeline) {
$uibModalInstance = { close: () => {} };
controller = $ctrl(EditPipelineJsonModalCtrl, { $uibModalInstance, pipeline });
controller = $ctrl(EditPipelineJsonModalCtrl, { $uibModalInstance, pipeline, plan: null });
}

it('controller removes name, application, appConfig, all fields and hash keys', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ export interface IEditPipelineJsonModalCommand {
errorMessage?: string;
invalid?: boolean;
pipelineJSON: string;
pipelinePlanJSON?: string;
locked: boolean;
}

export class EditPipelineJsonModalCtrl implements IController {

public isStrategy: boolean;
public command: IEditPipelineJsonModalCommand;
public mode = 'pipeline'
private immutableFields = ['name', 'application', 'index', 'id', '$$hashKey'];

constructor(private $uibModalInstance: IModalServiceInstance,
private pipeline: IPipeline) {
private pipeline: IPipeline, private plan?: IPipeline) {
'ngInject';
}

Expand Down Expand Up @@ -47,21 +49,31 @@ export class EditPipelineJsonModalCtrl implements IController {
}

public $onInit(): void {
const copy = cloneDeepWith<IPipeline>(this.pipeline, (value: any) => {
if (value && value.$$hashKey) {
delete value.$$hashKey;
}
return undefined; // required for clone operation and typescript happiness
});
this.removeImmutableFields(copy);
const copy = this.clone(this.pipeline);
let copyPlan: IPipeline;
if (this.plan) {
copyPlan = this.clone(this.plan);
}

this.isStrategy = this.pipeline.strategy || false;
this.command = {
pipelineJSON: jsonUtilityService.makeSortedStringFromObject(copy),
pipelinePlanJSON: copyPlan ? jsonUtilityService.makeSortedStringFromObject(copyPlan) : null,
locked: copy.locked
};
}

private clone(pipeline: IPipeline): IPipeline {
const copy = cloneDeepWith<IPipeline>(pipeline, (value: any) => {
if (value && value.$$hashKey) {
delete value.$$hashKey;
}
return undefined; // required for clone operation and typescript happiness
});
this.removeImmutableFields(copy);
return copy;
}

public updatePipeline(): void {
try {
const parsed = JSON.parse(this.command.pipelineJSON);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
<div class="modal-header">
<h3><span ng-if="!command.locked">Edit </span>{{$ctrl.isStrategy === true ? 'Strategy' : 'Pipeline'}} JSON</h3>
</div>
<div class="modal-body flex-fill">
<ul class="tabs-basic" ng-if="$ctrl.command.pipelinePlanJSON">
<li role="presentation" ng-class="{selected: $ctrl.mode == 'pipeline'}"><a ng-click="$ctrl.mode = 'pipeline'">Configuration</a></li>
<li role="presentation" ng-class="{selected: $ctrl.mode == 'renderedPipeline'}"><a ng-click="$ctrl.mode = 'renderedPipeline'">Rendered pipeline</a></li>
</ul>
<div class="modal-body flex-fill" ng-if="$ctrl.mode == 'pipeline'">
<div class="row">
<div class="col-md-12">
<p>
Expand Down Expand Up @@ -34,6 +38,20 @@ <h3><span ng-if="!command.locked">Edit </span>{{$ctrl.isStrategy === true ? 'Str
</div>
</div>
</div>
<div class="modal-body flex-fill" ng-if="$ctrl.mode == 'renderedPipeline'">
<div class="row">
<div class="col-md-12">
<p>
This pipeline is based on a template. The JSON below represents the rendered pipeline.
</p>
</div>
</div>
<form role="form" name="form" class="form-horizontal flex-fill">
<div class="form-group flex-fill">
<textarea class="code form-control flex-fill" ng-model="$ctrl.command.pipelinePlanJSON" disabled></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-default" ng-click="$dismiss()">Cancel</button>
<button class="btn btn-primary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {PIPELINE_TEMPLATE_SERVICE} from './templates/pipelineTemplate.service';

const angular = require('angular');

function containsJinja(source) {
return source && (source.includes('{{') || source.includes('{%'));
}

module.exports = angular.module('spinnaker.core.pipeline.config.controller', [
require('@uirouter/angularjs').default,
PIPELINE_TEMPLATE_SERVICE,
Expand All @@ -21,8 +25,9 @@ module.exports = angular.module('spinnaker.core.pipeline.config.controller', [
this.pipelineConfig = _.find(app.pipelineConfigs.data, { id: $stateParams.pipelineId });
if (this.pipelineConfig && this.pipelineConfig.type === 'templatedPipeline') {
this.isTemplatedPipeline = true;
this.hasDynamicSource = containsJinja(this.pipelineConfig.config.pipeline.template.source);
if (!this.pipelineConfig.isNew) {
return pipelineTemplateService.getPipelinePlan(this.pipelineConfig)
return pipelineTemplateService.getPipelinePlan(this.pipelineConfig, $stateParams.executionId)
.then(plan => this.pipelinePlan = plan)
.catch(() => this.pipelineConfig.isNew = true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<pipeline-configurer pipeline="vm.pipelineConfig"
plan="vm.pipelinePlan"
is-templated-pipeline="vm.isTemplatedPipeline"
has-dynamic-source="vm.hasDynamicSource"
application="vm.application"></pipeline-configurer>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,27 @@ <h3>
<div class="band band-info" ng-if="isTemplatedPipeline && !pipeline.locked">
<span class="glyphicon glyphicon small glyphicon-lock"></span> Manual edits are not allowed on templated pipelines
</div>

<div class="band band-info" ng-if="hasDynamicSource && pipelineExecutions && pipelineExecutions.length">
<span class="glyphicon glyphicon small glyphicon-wrench"></span> This template has a dynamic source. The
configuration is currently rendered using
<span ng-if="pipelineExecutions.length > 1" class="dropdown" uib-dropdown>
&nbsp;&nbsp;<button type="button" class="btn btn-xs btn-default dropdown-toggle" uib-dropdown-toggle>
{{ renderablePipeline.trigger.parentPipelineName || "Build #" + renderablePipeline.trigger.buildNumber }} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" uib-dropdown-menu>
<li ng-repeat="p in pipelineExecutions" ng-class="{disabled: p.id == renderablePipeline.executionId}">
<a title="{{p.startTime | date:'medium'}}" ng-click="(p.id == renderablePipeline.executionId) ? false : changePipelineExecution(p.id); true;">{{ p.trigger.parentPipelineName || "Build #" + p.trigger.buildNumber }}
<span class="text-muted small">({{p.startTime | relativeTime}})</span>
<span class="glyphicon small glyphicon-ok" ng-if="p.id == renderablePipeline.executionId"></span></a>
</li>
</ul>
</span>
<span ng-if="pipelineExecutions.length === 1">{{ renderablePipeline.trigger.parentPipelineName || "Build #" + renderablePipeline.trigger.buildNumber }}</span>
</div>
<div class="band band-warning" ng-if="pipelineExecutions && !pipelineExecutions.length">
<span class="glyphicon glyphicon small glyphicon-alert"></span> This template has a dynamic source. The
configuration cannot be rendered before the pipeline has executed at least once.
</div>
<div class="config-heading-body">
<div class="pipeline-graph-container pipeline-config-graph">
<pipeline-graph view-state="viewState"
Expand Down
26 changes: 23 additions & 3 deletions app/scripts/modules/core/src/pipeline/config/pipelineConfigurer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ module.exports = angular.module('spinnaker.core.pipeline.config.pipelineConfigur
application: '=',
plan: '<',
isTemplatedPipeline: '<',
hasDynamicSource: '<',
},
controller: 'PipelineConfigurerCtrl as pipelineConfigurerCtrl',
templateUrl: require('./pipelineConfigurer.html'),
};
})
.controller('PipelineConfigurerCtrl', function($scope, $uibModal, $timeout, $window, $q,
pipelineConfigValidator, pipelineTemplateService,
pipelineConfigValidator, pipelineTemplateService, executionService,
pipelineConfigService, viewStateCache, overrideRegistry, $location) {
// For standard pipelines, a 'renderablePipeline' is just the pipeline config.
// For templated pipelines, a 'renderablePipeline' is the pipeline template plan, and '$scope.pipeline' is the template config.
Expand Down Expand Up @@ -173,7 +174,8 @@ module.exports = angular.module('spinnaker.core.pipeline.config.pipelineConfigur
controllerAs: '$ctrl',
size: 'lg modal-fullscreen',
resolve: {
pipeline: () => $scope.renderablePipeline,
pipeline: () => $scope.pipeline,
plan: () => $scope.plan,
}
}).result.then(() => {
$scope.$broadcast('pipeline-json-edited');
Expand Down Expand Up @@ -331,6 +333,7 @@ module.exports = angular.module('spinnaker.core.pipeline.config.pipelineConfigur
pipelineTemplateConfig: () => _.cloneDeep($scope.pipeline),
isNew: () => $scope.pipeline.isNew,
pipelineId: () => $scope.pipeline.id,
executionId: () => $scope.renderablePipeline.executionId,
}
}).result.then(({plan, config}) => {
$scope.pipeline = config;
Expand Down Expand Up @@ -365,6 +368,19 @@ module.exports = angular.module('spinnaker.core.pipeline.config.pipelineConfigur
return msg;
};

this.getPipelineExecutions = () => {
executionService.getExecutionsForConfigIds($scope.pipeline.application, $scope.pipeline.id, 5)
.then(executions => {
$scope.pipelineExecutions = executions;
})
.catch(() => $scope.pipelineExecutions = []);
return $scope.pipelineExecutions;
};

$scope.changePipelineExecution = executionId => {
$location.search('executionId', executionId);
};

this.revertPipelineChanges = () => {
let original = getOriginal();
Object.keys($scope.pipeline).forEach(key => {
Expand Down Expand Up @@ -445,7 +461,11 @@ module.exports = angular.module('spinnaker.core.pipeline.config.pipelineConfigur
$window.onbeforeunload = undefined;
});

if ($scope.isTemplatedPipeline && $scope.pipeline.isNew) {
if ($scope.isTemplatedPipeline && $scope.pipeline.isNew && !$scope.hasDynamicSource) {
this.configureTemplate();
}

if ($scope.hasDynamicSource) {
this.getPipelineExecutions();
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe('Controller: ConfigurePipelineTemplateModalCtrl', () => {
}
},
pipelineId: '1234',
executionId: null,
isNew: true,
}) as ConfigurePipelineTemplateModalController;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class ConfigurePipelineTemplateModalController implements IController {

constructor(private $scope: IScope, private $uibModalInstance: IModalInstanceService,
private application: Application, public pipelineTemplateConfig: IPipelineTemplateConfig,
public isNew: boolean, private pipelineId: string) {
public isNew: boolean, private pipelineId: string, private executionId: string) {
'ngInject';
}

Expand Down Expand Up @@ -109,7 +109,7 @@ export class ConfigurePipelineTemplateModalController implements IController {
}

private loadTemplate(): IPromise<void> {
return ReactInjector.pipelineTemplateService.getPipelineTemplateFromSourceUrl(this.source)
return ReactInjector.pipelineTemplateService.getPipelineTemplateFromSourceUrl(this.source, this.executionId, this.pipelineId)
.then(template => { this.template = template });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,16 @@ export class PipelineTemplateService {
'ngInject';
}

public getPipelineTemplateFromSourceUrl(source: string): IPromise<IPipelineTemplate> {
return this.API.one('pipelineTemplates').one('resolve').withParams({ source }).get()
public getPipelineTemplateFromSourceUrl(source: string, executionId?: String, pipelineConfigId?: string): IPromise<IPipelineTemplate> {
return this.API.one('pipelineTemplates').one('resolve').withParams({ source: source, executionId: executionId, pipelineConfigId: pipelineConfigId }).get()
.then((template: IPipelineTemplate) => {
template.selfLink = source;
return template;
});
}

public getPipelinePlan(config: IPipelineTemplateConfig): IPromise<IPipeline> {
return this.API.one('pipelines').one('start').post(Object.assign({}, config, { plan: true }));
public getPipelinePlan(config: IPipelineTemplateConfig, executionId?: String): IPromise<IPipeline> {
return this.API.one('pipelines').one('start').post(Object.assign({}, config, { plan: true, executionId: executionId }));
}

public getPipelineTemplatesByScope(scope: string): IPromise<IPipelineTemplate[]> {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ ul.tabs-basic {
background-color: inherit;
width: 100%;
border-bottom: 1px solid var(--color-accent-g3);
list-style: none;
}

ul.tabs-basic > li, ul.tabs-basic > li * {
Expand Down

0 comments on commit c294785

Please sign in to comment.