diff --git a/app/scripts/modules/core/src/delivery/delivery.states.ts b/app/scripts/modules/core/src/delivery/delivery.states.ts index ebd962b02c9..a30889a74b6 100644 --- a/app/scripts/modules/core/src/delivery/delivery.states.ts +++ b/app/scripts/modules/core/src/delivery/delivery.states.ts @@ -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'), diff --git a/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildNumber.less b/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildLink.less similarity index 100% rename from app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildNumber.less rename to app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildLink.less diff --git a/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildNumber.tsx b/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildLink.tsx similarity index 89% rename from app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildNumber.tsx rename to app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildLink.tsx index 2bdcdf886cf..e8d7241d97c 100644 --- a/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildNumber.tsx +++ b/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildLink.tsx @@ -5,15 +5,15 @@ import { BindAll } from 'lodash-decorators'; import { IExecution } from 'core/domain'; import { ReactInjector } from 'core/reactShims'; -import './ExecutionBuildNumber.less'; +import './ExecutionBuildLink.less'; -export interface IExecutionBuildNumberProps { +export interface IExecutionBuildLinkProps { execution: IExecution; } @BindAll() -export class ExecutionBuildNumber extends React.Component { - constructor(props: IExecutionBuildNumberProps) { +export class ExecutionBuildLink extends React.Component { + constructor(props: IExecutionBuildLinkProps) { super(props); } diff --git a/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildTitle.tsx b/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildTitle.tsx new file mode 100644 index 00000000000..d43c8e88f0a --- /dev/null +++ b/app/scripts/modules/core/src/delivery/executionBuild/ExecutionBuildTitle.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { BindAll } from 'lodash-decorators'; + +import { module } from 'angular'; +import { react2angular } from 'react2angular'; + +import { IExecutionBuildLinkProps } from './ExecutionBuildLink'; +import { timestamp } from 'core/utils' + +export interface IExecutionBuildTitleProps extends IExecutionBuildLinkProps { + defaultToTimestamp?: boolean; +} + +@BindAll() +export class ExecutionBuildTitle extends React.Component { + + public static defaultProps: Partial = { + defaultToTimestamp: false + }; + + private hasParentPipeline: boolean; + private hasBuildNumber: boolean; + + constructor(props: IExecutionBuildTitleProps) { + super(props); + this.hasParentPipeline = !!props.execution.trigger.parentPipelineName; + this.hasBuildNumber = !!(props.execution.buildInfo && props.execution.buildInfo.number); + } + + public render() { + return ( + + { this.hasParentPipeline && ( + {this.props.execution.trigger.parentPipelineName} + )} + { this.hasBuildNumber && !this.hasParentPipeline && ( + Build #{this.props.execution.buildInfo.number} + )} + { this.props.defaultToTimestamp && !this.hasParentPipeline && !this.hasBuildNumber && ( + {timestamp(this.props.execution.startTime)} + )} + + ); + } +} + +export const EXECUTION_BUILD_TITLE = 'spinnaker.core.delivery.executionbuild.executionbuildtitle'; +const ngmodule = module(EXECUTION_BUILD_TITLE, []); + +ngmodule.component('executionBuildTitle', react2angular(ExecutionBuildTitle, ['execution', 'defaultToTimestamp'])); diff --git a/app/scripts/modules/core/src/delivery/service/executions.transformer.service.ts b/app/scripts/modules/core/src/delivery/service/executions.transformer.service.ts index a5805b2fdbc..f418b5d5a48 100644 --- a/app/scripts/modules/core/src/delivery/service/executions.transformer.service.ts +++ b/app/scripts/modules/core/src/delivery/service/executions.transformer.service.ts @@ -193,7 +193,7 @@ export class ExecutionsTransformerService { return null; } - private addBuildInfo(execution: IExecution): void { + public addBuildInfo(execution: IExecution): void { execution.buildInfo = this.findNearestBuildInfo(execution); if (has(execution, 'trigger.buildInfo.lastBuild.number')) { diff --git a/app/scripts/modules/core/src/delivery/status/ExecutionStatus.tsx b/app/scripts/modules/core/src/delivery/status/ExecutionStatus.tsx index 69c5fd34113..57c51f8fbfb 100644 --- a/app/scripts/modules/core/src/delivery/status/ExecutionStatus.tsx +++ b/app/scripts/modules/core/src/delivery/status/ExecutionStatus.tsx @@ -9,7 +9,7 @@ import { ReactInjector } from 'core/reactShims'; import { relativeTime, timestamp } from 'core/utils'; import { buildDisplayName } from '../executionBuild/buildDisplayName.filter'; -import { ExecutionBuildNumber } from '../executionBuild/ExecutionBuildNumber'; +import { ExecutionBuildLink } from '../executionBuild/ExecutionBuildLink'; import './executionStatus.less'; @@ -109,7 +109,7 @@ export class ExecutionStatus extends React.Component -
+
{this.getExecutionTypeDisplay()}
    diff --git a/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.spec.ts b/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.spec.ts index ce13dc3feb4..c989234c4b3 100644 --- a/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.spec.ts +++ b/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.spec.ts @@ -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', () => { diff --git a/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.ts b/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.ts index b9bb520b1eb..173a2497fb1 100644 --- a/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.ts +++ b/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.controller.ts @@ -9,6 +9,7 @@ export interface IEditPipelineJsonModalCommand { errorMessage?: string; invalid?: boolean; pipelineJSON: string; + pipelinePlanJSON?: string; locked: boolean; } @@ -16,10 +17,11 @@ 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'; } @@ -47,21 +49,31 @@ export class EditPipelineJsonModalCtrl implements IController { } public $onInit(): void { - const copy = cloneDeepWith(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(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); diff --git a/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.html b/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.html index fcc0f9899cb..222e1c99ecb 100644 --- a/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.html +++ b/app/scripts/modules/core/src/pipeline/config/actions/json/editPipelineJsonModal.html @@ -3,7 +3,12 @@ - diff --git a/app/scripts/modules/core/src/pipeline/config/pipelineConfigurer.html b/app/scripts/modules/core/src/pipeline/config/pipelineConfigurer.html index 349866b4fbc..be5ffbba68a 100644 --- a/app/scripts/modules/core/src/pipeline/config/pipelineConfigurer.html +++ b/app/scripts/modules/core/src/pipeline/config/pipelineConfigurer.html @@ -35,7 +35,32 @@

    Manual edits are not allowed on templated pipelines
    - +
    + This template has a dynamic source. The + configuration is currently rendered using + +    + + + +
    +
    + This template has a dynamic source. The + configuration cannot be rendered before the pipeline has executed at least once. +
    +
    + Could not render the pipeline configuration because + of an error: {{ templateError.data.message }} {{ templateError.data.error }} +
    $scope.renderablePipeline, + pipeline: () => $scope.pipeline, + plan: () => $scope.plan, } }).result.then(() => { $scope.$broadcast('pipeline-json-edited'); @@ -332,6 +337,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; @@ -366,6 +372,22 @@ module.exports = angular.module('spinnaker.core.pipeline.config.pipelineConfigur return msg; }; + this.getPipelineExecutions = () => { + executionService.getExecutionsForConfigIds($scope.pipeline.application, $scope.pipeline.id, 5) + .then(executions => { + executions.forEach(execution => executionsTransformer.addBuildInfo(execution)); + $scope.pipelineExecutions = executions; + if ($scope.plan && $scope.plan.executionId) { + $scope.currentExecution = _.find($scope.pipelineExecutions, { id: $scope.plan.executionId }); + } else if ($location.search().executionId) { + $scope.currentExecution = _.find($scope.pipelineExecutions, { id: $location.search().executionId }); + } else { + $scope.currentExecution = $scope.pipelineExecutions[0]; + } + }) + .catch(() => $scope.pipelineExecutions = []); + }; + this.revertPipelineChanges = () => { let original = getOriginal(); Object.keys($scope.pipeline).forEach(key => { @@ -446,7 +468,11 @@ module.exports = angular.module('spinnaker.core.pipeline.config.pipelineConfigur $window.onbeforeunload = undefined; }); - if ($scope.isTemplatedPipeline && $scope.pipeline.isNew) { + if ($scope.hasDynamicSource) { + this.getPipelineExecutions(); + } + + if ($scope.isTemplatedPipeline && $scope.pipeline.isNew && !$scope.hasDynamicSource) { this.configureTemplate(); } }); diff --git a/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.spec.ts b/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.spec.ts index 2fbc8b623db..bfc95cfd826 100644 --- a/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.spec.ts +++ b/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.spec.ts @@ -91,6 +91,7 @@ describe('Controller: ConfigurePipelineTemplateModalCtrl', () => { } }, pipelineId: '1234', + executionId: null, isNew: true, }) as ConfigurePipelineTemplateModalController; }); diff --git a/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.ts b/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.ts index 9291488230e..09fc9a79961 100644 --- a/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.ts +++ b/app/scripts/modules/core/src/pipeline/config/templates/configurePipelineTemplateModal.controller.ts @@ -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'; } @@ -109,7 +109,7 @@ export class ConfigurePipelineTemplateModalController implements IController { } private loadTemplate(): IPromise { - return ReactInjector.pipelineTemplateService.getPipelineTemplateFromSourceUrl(this.source) + return ReactInjector.pipelineTemplateService.getPipelineTemplateFromSourceUrl(this.source, this.executionId, this.pipelineId) .then(template => { this.template = template }); } diff --git a/app/scripts/modules/core/src/pipeline/config/templates/pipelineTemplate.service.ts b/app/scripts/modules/core/src/pipeline/config/templates/pipelineTemplate.service.ts index bfa2786e2ea..98e3b3f220a 100644 --- a/app/scripts/modules/core/src/pipeline/config/templates/pipelineTemplate.service.ts +++ b/app/scripts/modules/core/src/pipeline/config/templates/pipelineTemplate.service.ts @@ -79,16 +79,16 @@ export class PipelineTemplateService { 'ngInject'; } - public getPipelineTemplateFromSourceUrl(source: string): IPromise { - return this.API.one('pipelineTemplates').one('resolve').withParams({ source }).get() + public getPipelineTemplateFromSourceUrl(source: string, executionId?: String, pipelineConfigId?: string): IPromise { + return this.API.one('pipelineTemplates').one('resolve').withParams({ source, executionId, pipelineConfigId }).get() .then((template: IPipelineTemplate) => { template.selfLink = source; return template; }); } - public getPipelinePlan(config: IPipelineTemplateConfig): IPromise { - return this.API.one('pipelines').one('start').post(Object.assign({}, config, { plan: true })); + public getPipelinePlan(config: IPipelineTemplateConfig, executionId?: String): IPromise { + return this.API.one('pipelines').one('start').post(Object.assign({}, config, { plan: true, executionId })); } public getPipelineTemplatesByScope(scope: string): IPromise { diff --git a/app/scripts/modules/core/src/projects/dashboard/pipeline/ProjectPipeline.tsx b/app/scripts/modules/core/src/projects/dashboard/pipeline/ProjectPipeline.tsx index e933ce39172..b0442b545f0 100644 --- a/app/scripts/modules/core/src/projects/dashboard/pipeline/ProjectPipeline.tsx +++ b/app/scripts/modules/core/src/projects/dashboard/pipeline/ProjectPipeline.tsx @@ -3,7 +3,7 @@ import { has } from 'lodash'; import { BindAll } from 'lodash-decorators'; import { Application } from 'core/application/application.model'; -import { ExecutionBuildNumber } from 'core/delivery/executionBuild/ExecutionBuildNumber'; +import { ExecutionBuildLink } from 'core/delivery/executionBuild/ExecutionBuildLink'; import { ExecutionMarker } from 'core/delivery/executionGroup/execution/ExecutionMarker'; import { IExecution } from 'core/domain'; import { ReactInjector } from 'core/reactShims'; @@ -53,7 +53,7 @@ export class ProjectPipeline extends React.Component

    -  ({this.state.hasBuildInfo && (, )}started {timestamp(this.props.execution.startTime)}) +  ({this.state.hasBuildInfo && (, )}started {timestamp(this.props.execution.startTime)})
    {stages}
    diff --git a/app/scripts/modules/core/src/reactShims/react.injector.ts b/app/scripts/modules/core/src/reactShims/react.injector.ts index dde7196fd89..b2c5001d77b 100644 --- a/app/scripts/modules/core/src/reactShims/react.injector.ts +++ b/app/scripts/modules/core/src/reactShims/react.injector.ts @@ -20,6 +20,7 @@ import { ExecutionDetailsSectionService } from 'core/delivery/details/executionD import { ExecutionFilterModel } from '../delivery/filter/executionFilter.model'; import { ExecutionFilterService } from '../delivery/filter/executionFilter.service'; import { ExecutionService } from '../delivery/service/execution.service'; +import { ExecutionsTransformerService } from '../delivery/service/executions.transformer.service'; import { HelpContentsRegistry } from 'core/help'; import { IHelpContents } from 'core/help'; import { InfrastructureSearchService } from '../search/infrastructure/infrastructureSearch.service'; @@ -86,6 +87,7 @@ export class CoreReactInject extends ReactInject { public get executionFilterModel() { return this.$injector.get('executionFilterModel') as ExecutionFilterModel; } public get executionFilterService() { return this.$injector.get('executionFilterService') as ExecutionFilterService; } public get executionService() { return this.$injector.get('executionService') as ExecutionService; } + public get executionsTransformer() { return this.$injector.get('executionsTransformer') as ExecutionsTransformerService; } public get helpContents() { return this.$injector.get('helpContents') as IHelpContents } public get helpContentsRegistry() { return this.$injector.get('helpContentsRegistry') as HelpContentsRegistry; } public get infrastructureSearchService() { return this.$injector.get('infrastructureSearchService') as InfrastructureSearchService; }