Skip to content

Commit

Permalink
feat(templates): add Save button to export pipeline json modal (#6761)
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott authored Mar 29, 2019
1 parent 20d0d7a commit 36b86da
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
.show-pipeline-template-json-modal__copy {
margin-bottom: 5px;
}

.show-pipeline-template-json-modal__save-error {
color: var(--color-danger);
}

.show-pipeline-template-json-modal__saving-spinner {
justify-content: center;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as React from 'react';
import { IPromise } from 'angular';
import * as classnames from 'classnames';

import { Spinner } from 'core/widgets/spinners/Spinner';
import { IModalComponentProps, JsonEditor } from 'core/presentation';
import { IPipelineTemplateV2 } from 'core/domain';
import { CopyToClipboard, noop, JsonUtils } from 'core/utils';
Expand All @@ -11,10 +14,13 @@ export interface IShowPipelineTemplateJsonModalProps extends IModalComponentProp
editable?: boolean;
modalHeading?: string;
descriptionText?: string;
saveTemplate?: (template: IPipelineTemplateV2) => IPromise<boolean>;
}

export interface IShowPipelineTemplateJsonModalState {
template: IPipelineTemplateV2;
saveError: Error;
saving: boolean;
}

export class ShowPipelineTemplateJsonModal extends React.Component<
Expand All @@ -31,7 +37,7 @@ export class ShowPipelineTemplateJsonModal extends React.Component<

constructor(props: IShowPipelineTemplateJsonModalProps) {
super(props);
this.state = { template: props.template };
this.state = { template: props.template, saveError: null, saving: false };
}

private onChange = (e: React.ChangeEvent<HTMLInputElement>, property: string) => {
Expand All @@ -49,13 +55,44 @@ export class ShowPipelineTemplateJsonModal extends React.Component<
});
};

private onTemplateSaved = (error?: Error) => {
this.setState({ saveError: error, saving: false });
};

private saveTemplate = () => {
this.setState({ saveError: null, saving: true });
this.props.saveTemplate(this.state.template).then(
(shouldClose: boolean) => {
if (shouldClose) {
this.props.dismissModal();
} else {
this.onTemplateSaved();
}
},
(err: Error) => this.onTemplateSaved(err),
);
};

public render() {
const { dismissModal, editable, modalHeading, descriptionText } = this.props;
const { template } = this.state;
const { dismissModal, editable, modalHeading, descriptionText, saveTemplate } = this.props;
const { template, saveError, saving } = this.state;
const sortedTemplate = JsonUtils.sortObject(template);
const templateStr = JsonUtils.makeStringFromObject(sortedTemplate, 0);
const templateStrWithSpacing = JsonUtils.makeStringFromObject(sortedTemplate);
const disabled = !editable || !!saving;

if (saving) {
return (
<div className="flex-fill">
<div className="modal-header">
<h3>{modalHeading}</h3>
</div>
<div className="modal-body flex-fill show-pipeline-template-json-modal__saving-spinner">
<Spinner size="medium" message="Saving ..." />
</div>
</div>
);
}
return (
<div className="flex-fill">
<div className="modal-header">
Expand All @@ -75,7 +112,7 @@ export class ShowPipelineTemplateJsonModal extends React.Component<
type="text"
value={template.metadata.name}
onChange={e => this.onChange(e, 'name')}
disabled={!editable}
disabled={disabled}
/>
</div>
</div>
Expand All @@ -91,7 +128,7 @@ export class ShowPipelineTemplateJsonModal extends React.Component<
value={template.metadata.description}
onChange={e => this.onChange(e, 'description')}
placeholder="Template Description"
disabled={!editable}
disabled={disabled}
/>
</div>
</div>
Expand All @@ -106,11 +143,11 @@ export class ShowPipelineTemplateJsonModal extends React.Component<
type="text"
value={template.metadata.owner}
onChange={e => this.onChange(e, 'owner')}
disabled={!editable}
disabled={disabled}
/>
</div>
</div>
{editable && (
{!disabled && (
<div className="show-pipeline-template-json-modal__copy text-right">
<CopyToClipboard
buttonInnerNode={<a>Copy the spin command for saving this template</a>}
Expand All @@ -122,11 +159,33 @@ export class ShowPipelineTemplateJsonModal extends React.Component<
<JsonEditor value={templateStrWithSpacing} readOnly={true} />
</div>
<div className="modal-footer">
<button className="btn btn-primary" onClick={dismissModal}>
Close
</button>
{saveError && <span className="show-pipeline-template-json-modal__save-error">{String(saveError)}</span>}
<ShowPipelineTemplateJsonModalButtons onSave={saveTemplate && this.saveTemplate} onClose={dismissModal} />
</div>
</div>
);
}
}

interface IShowPipelineTemplateJsonModalButtons {
onSave: (e: React.SyntheticEvent<HTMLButtonElement>) => void;
onClose: (e: React.SyntheticEvent<HTMLButtonElement>) => void;
}

const ShowPipelineTemplateJsonModalButtons = (props: IShowPipelineTemplateJsonModalButtons) => {
const { onClose, onSave } = props;
const closeClasses = classnames({ btn: true, 'btn-primary': !onSave });
const saveClasses = classnames({ btn: true, 'btn-primary': true });
return (
<>
<button className={closeClasses} onClick={onClose}>
Close
</button>
{onSave && (
<button className={saveClasses} onClick={onSave}>
Save
</button>
)}
</>
);
};
25 changes: 23 additions & 2 deletions app/scripts/modules/core/src/pipeline/config/pipelineConfigurer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ExecutionsTransformer } from 'core/pipeline/service/ExecutionsTransform
import { EditPipelineJsonModal } from 'core/pipeline/config/actions/pipelineJson/EditPipelineJsonModal';
import { ShowPipelineTemplateJsonModal } from 'core/pipeline/config/actions/templateJson/ShowPipelineTemplateJsonModal';
import { PipelineTemplateV2Service } from 'core/pipeline';
import { PipelineTemplateWriter } from 'core/pipeline/config/templates/PipelineTemplateWriter';

module.exports = angular
.module('spinnaker.core.pipeline.config.pipelineConfigurer', [OVERRIDE_REGISTRY, EXECUTION_BUILD_TITLE])
Expand All @@ -40,10 +41,11 @@ module.exports = angular
'$timeout',
'$window',
'$q',
'$state',
'executionService',
'overrideRegistry',
'$location',
function($scope, $uibModal, $timeout, $window, $q, executionService, overrideRegistry, $location) {
function($scope, $uibModal, $timeout, $window, $q, $state, executionService, 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.
$scope.renderablePipeline = $scope.plan || $scope.pipeline;
Expand Down Expand Up @@ -218,7 +220,11 @@ module.exports = angular
const pipeline = $scope.pipeline;
const ownerEmail = _.get($scope, 'application.attributes.email', '');
const template = PipelineTemplateV2Service.createPipelineTemplate(pipeline, ownerEmail);
ReactModal.show(ShowPipelineTemplateJsonModal, { template }, modalProps);
const templateProps = {
template,
saveTemplate: this.saveTemplate,
};
ReactModal.show(ShowPipelineTemplateJsonModal, templateProps, modalProps);
};

// Disabling a pipeline also just toggles the disabled flag - it does not save any pending changes
Expand Down Expand Up @@ -537,5 +543,20 @@ module.exports = angular
if ($scope.isTemplatedPipeline && $scope.pipeline.isNew && !$scope.hasDynamicSource) {
this.configureTemplate();
}

this.saveTemplate = template => {
return PipelineTemplateWriter.savePipelineTemplateV2(template).then(
response => {
const id = response.variables.find(v => v.key === 'pipelineTemplate.id').value;
$state.go('home.pipeline-templates.pipeline-templates-detail', {
templateId: PipelineTemplateV2Service.idForTemplate({ id }),
});
return true;
},
err => {
throw err;
},
);
};
},
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { IPromise } from 'angular';
import { $q } from 'ngimport';
import { API } from 'core/api/ApiService';
import { IPipelineTemplateV2 } from 'core/domain/IPipelineTemplateV2';

export class PipelineTemplateWriter {
public static savePipelineTemplateV2(template: IPipelineTemplateV2): IPromise<any> {
return $q((resolve, reject) => {
API.one('v2')
.one('pipelineTemplates')
.one('create')
.post(template)
.then(resolve, reject);
});
}
}
Loading

0 comments on commit 36b86da

Please sign in to comment.