Merge pull request #3260 from jlmitch5/launchJTWFForm

add launch button to jt and wf forms

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
This commit is contained in:
softwarefactory-project-zuul[bot] 2019-02-25 17:52:01 +00:00 committed by GitHub
commit bb276a8fcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 289 additions and 207 deletions

View File

@ -169,11 +169,14 @@
border-color: @default-icon;
}
.Form-tab--disabled {
.Form-tab--disabled,
.Form-button--disabled {
opacity: 0.65;
color: @btn-txt;
}
.Form-tab--disabled:hover {
.Form-tab--disabled:hover,
.Form-button--disabled:hover {
color: @btn-txt;
background-color: @btn-bg;
cursor:not-allowed!important;
@ -548,7 +551,6 @@ input[type='radio']:checked:before {
}
}
.FormToggle {}
.FormToggle-container {
margin: 0 0 0 10px;
display: initial;
@ -607,7 +609,7 @@ input[type='radio']:checked:before {
display: flex;
justify-content: flex-end;
button:last-of-type {
button {
margin-left: 20px;
}
}
@ -658,7 +660,6 @@ input[type='radio']:checked:before {
transition: background-color 0.2s;
padding-left:15px;
padding-right: 15px;
margin-left: 20px;
}
.Form-cancelButton:hover {
@ -677,12 +678,17 @@ input[type='radio']:checked:before {
margin-bottom: 20px;
}
.Form-buttons .Form-primaryButton {
margin-right: 0;
}
.Form-primaryButton:hover {
background-color: @default-link-hov;
color: @default-bg;
}
.Form-primaryButton.Form-tab--disabled:hover {
.Form-primaryButton.Form-tab--disabled:hover,
.Form-primaryButton.Form-button--disabled:hover {
background-color: @default-link;
}

View File

@ -105,7 +105,9 @@ function ComponentsStrings (BaseString) {
};
ns.launchTemplate = {
DEFAULT: t.s('Start a job using this template')
DEFAULT: t.s('Start a job using this template'),
DISABLED: t.s('Please save before launching this template.'),
BUTTON_LABEL: t.s('LAUNCH')
};
ns.list = {

View File

@ -3,7 +3,9 @@ import templateUrl from './launchTemplateButton.partial.html';
const atLaunchTemplate = {
templateUrl,
bindings: {
template: '<'
template: '<',
showTextButton: '<',
disabled: '='
},
controller: ['JobTemplateModel', 'WorkflowJobTemplateModel', 'PromptService', '$state',
'ComponentsStrings', 'ProcessErrors', '$scope', 'TemplatesStrings', 'Alert',
@ -19,6 +21,11 @@ function atLaunchTemplateCtrl (
const jobTemplate = new JobTemplate();
const workflowTemplate = new WorkflowTemplate();
vm.strings = componentsStrings;
vm.launchTooltip = vm.strings.get('launchTemplate.DEFAULT');
$scope.$watch('vm.disabled', (val) => {
vm.launchTooltip = (val) ? vm.strings.get('launchTemplate.DISABLED') : vm.strings.get('launchTemplate.DEFAULT');
});
const createErrorHandler = (path, action) =>
({ data, status }) => {
@ -28,101 +35,103 @@ function atLaunchTemplateCtrl (
};
vm.startLaunchTemplate = () => {
if (vm.template.type === 'job_template') {
const selectedJobTemplate = jobTemplate.create();
const preLaunchPromises = [
selectedJobTemplate.getLaunch(vm.template.id),
selectedJobTemplate.optionsLaunch(vm.template.id),
];
if (!vm.disabled) {
if (vm.template.type === 'job_template') {
const selectedJobTemplate = jobTemplate.create();
const preLaunchPromises = [
selectedJobTemplate.getLaunch(vm.template.id),
selectedJobTemplate.optionsLaunch(vm.template.id),
];
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
if (selectedJobTemplate.canLaunchWithoutPrompt()) {
selectedJobTemplate
.postLaunch({ id: vm.template.id })
.then(({ data }) => {
/* Slice Jobs: Redirect to WF Details page if returned
job type is a WF job */
if (data.type === 'workflow_job' && data.workflow_job !== null) {
$state.go('workflowResults', { id: data.workflow_job }, { reload: true });
} else {
$state.go('output', { id: data.job, type: 'playbook' }, { reload: true });
}
});
} else {
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data,
template: vm.template.id,
templateType: vm.template.type,
prompts: PromptService.processPromptValues({
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
if (selectedJobTemplate.canLaunchWithoutPrompt()) {
selectedJobTemplate
.postLaunch({ id: vm.template.id })
.then(({ data }) => {
/* Slice Jobs: Redirect to WF Details page if returned
job type is a WF job */
if (data.type === 'workflow_job' && data.workflow_job !== null) {
$state.go('workflowResults', { id: data.workflow_job }, { reload: true });
} else {
$state.go('output', { id: data.job, type: 'playbook' }, { reload: true });
}
});
} else {
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true
};
launchOptions: launchOptions.data,
template: vm.template.id,
templateType: vm.template.type,
prompts: PromptService.processPromptValues({
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true
};
if (launchData.data.survey_enabled) {
selectedJobTemplate.getSurveyQuestions(vm.template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({
surveyQuestions: data.spec
if (launchData.data.survey_enabled) {
selectedJobTemplate.getSurveyQuestions(vm.template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({
surveyQuestions: data.spec
});
promptData.surveyQuestions = processed.surveyQuestions;
vm.promptData = promptData;
});
promptData.surveyQuestions = processed.surveyQuestions;
vm.promptData = promptData;
} else {
vm.promptData = promptData;
}
}
});
} else if (vm.template.type === 'workflow_job_template') {
const selectedWorkflowJobTemplate = workflowTemplate.create();
const preLaunchPromises = [
selectedWorkflowJobTemplate.request('get', vm.template.id),
selectedWorkflowJobTemplate.getLaunch(vm.template.id),
selectedWorkflowJobTemplate.optionsLaunch(vm.template.id),
];
Promise.all(preLaunchPromises)
.then(([wfjtData, launchData, launchOptions]) => {
if (selectedWorkflowJobTemplate.canLaunchWithoutPrompt()) {
selectedWorkflowJobTemplate
.postLaunch({ id: vm.template.id })
.then(({ data }) => {
$state.go('workflowResults', { id: data.workflow_job }, { reload: true });
});
} else {
vm.promptData = promptData;
}
}
});
} else if (vm.template.type === 'workflow_job_template') {
const selectedWorkflowJobTemplate = workflowTemplate.create();
const preLaunchPromises = [
selectedWorkflowJobTemplate.request('get', vm.template.id),
selectedWorkflowJobTemplate.getLaunch(vm.template.id),
selectedWorkflowJobTemplate.optionsLaunch(vm.template.id),
];
launchData.data.defaults.extra_vars = wfjtData.data.extra_vars;
Promise.all(preLaunchPromises)
.then(([wfjtData, launchData, launchOptions]) => {
if (selectedWorkflowJobTemplate.canLaunchWithoutPrompt()) {
selectedWorkflowJobTemplate
.postLaunch({ id: vm.template.id })
.then(({ data }) => {
$state.go('workflowResults', { id: data.workflow_job }, { reload: true });
});
} else {
launchData.data.defaults.extra_vars = wfjtData.data.extra_vars;
const promptData = {
launchConf: selectedWorkflowJobTemplate.getLaunchConf(),
launchOptions: launchOptions.data,
template: vm.template.id,
templateType: vm.template.type,
prompts: PromptService.processPromptValues({
const promptData = {
launchConf: selectedWorkflowJobTemplate.getLaunchConf(),
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
launchOptions: launchOptions.data,
template: vm.template.id,
templateType: vm.template.type,
prompts: PromptService.processPromptValues({
launchConf: selectedWorkflowJobTemplate.getLaunchConf(),
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
if (launchData.data.survey_enabled) {
selectedWorkflowJobTemplate.getSurveyQuestions(vm.template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({
surveyQuestions: data.spec
if (launchData.data.survey_enabled) {
selectedWorkflowJobTemplate.getSurveyQuestions(vm.template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({
surveyQuestions: data.spec
});
promptData.surveyQuestions = processed.surveyQuestions;
vm.promptData = promptData;
});
promptData.surveyQuestions = processed.surveyQuestions;
vm.promptData = promptData;
});
} else {
vm.promptData = promptData;
} else {
vm.promptData = promptData;
}
}
}
});
} else {
Alert(templatesStrings.get('error.UNKNOWN'), templatesStrings.get('alert.UNKNOWN_LAUNCH'));
});
} else {
Alert(templatesStrings.get('error.UNKNOWN'), templatesStrings.get('alert.UNKNOWN_LAUNCH'));
}
}
};

View File

@ -1,9 +1,12 @@
<div class="at-LaunchTemplate">
<button class="at-LaunchTemplate--button"
<button type="button"
ng-class="{'at-LaunchTemplate--button': !vm.showTextButton, 'btn btn-sm Form-primaryButton': vm.showTextButton, 'Form-button--disabled': vm.disabled}"
ng-click="vm.startLaunchTemplate()"
aw-tool-tip="{{:: vm.strings.get('launchTemplate.DEFAULT') }}"
aw-tool-tip="{{ vm.launchTooltip }}"
data-tip-watch="vm.launchTooltip"
data-placement="top">
<i class="icon-launch"></i>
<i class="icon-launch" ng-show="!vm.showTextButton"></i>
<span ng-show="vm.showTextButton">{{:: vm.strings.get('launchTemplate.BUTTON_LABEL') }}</span>
</button>
<prompt prompt-data="vm.promptData" on-finish="vm.launchTemplateWithPrompts()"></prompt>
</div>

View File

@ -579,8 +579,8 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
* ]
* ```
*/
.factory('CreateSelect2', ['$filter',
function($filter) {
.factory('CreateSelect2', ['$filter', '$q',
function($filter, $q) {
return function(params) {
var element = params.element,
@ -593,7 +593,8 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
selectOptions = params.options,
model = params.model,
original_options,
minimumResultsForSearch = params.minimumResultsForSearch ? params.minimumResultsForSearch : Infinity;
minimumResultsForSearch = params.minimumResultsForSearch ? params.minimumResultsForSearch : Infinity,
defer = $q.defer();
if (scope && selectOptions) {
original_options = _.get(scope, selectOptions);
@ -703,8 +704,10 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
$(element).trigger('change');
}
defer.resolve("select2 loaded");
});
return defer.promise;
};
}
])

View File

@ -1656,90 +1656,94 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if (typeof this.form.buttons[btn] === 'object') {
button = this.form.buttons[btn];
// Set default color and label for Save and Reset
if (btn === 'save') {
button.label = i18n._('Save');
button['class'] = 'Form-saveButton';
}
if (btn === 'select') {
button.label = i18n._('Select');
button['class'] = 'Form-saveButton';
}
if (btn === 'cancel') {
button.label = i18n._('Cancel');
button['class'] = 'Form-cancelButton';
}
if (btn === 'close') {
button.label = i18n._('Close');
button['class'] = 'Form-cancelButton';
}
if (btn === 'launch') {
button.label = i18n._('Launch');
button['class'] = 'Form-launchButton';
}
if (btn === 'add_survey') {
button.label = i18n._('Add Survey');
button['class'] = 'Form-surveyButton';
}
if (btn === 'edit_survey') {
button.label = i18n._('Edit Survey');
button['class'] = 'Form-surveyButton';
}
if (btn === 'view_survey') {
button.label = i18n._('View Survey');
button['class'] = 'Form-surveyButton';
}
if (btn === 'workflow_visualizer') {
button.label = i18n._('Workflow Visualizer');
button['class'] = 'Form-primaryButton';
}
// Build button HTML
html += "<button type=\"button\" ";
html += "class=\"btn btn-sm";
html += (button['class']) ? " " + button['class'] : "";
html += "\" ";
html += "id=\"" + this.form.name + "_" + btn + "_btn\" ";
if(button.ngShow){
html += this.attr(button, 'ngShow');
}
if (button.ngClick) {
html += this.attr(button, 'ngClick');
}
if (button.awFeature) {
html += this.attr(button, 'awFeature');
}
if (button.ngDisabled) {
ngDisabled = (button.ngDisabled===true) ? `${this.form.name}_form.$invalid || ${this.form.name}_form.$pending`: button.ngDisabled;
if (btn !== 'reset') {
//html += "ng-disabled=\"" + this.form.name + "_form.$pristine || " + this.form.name + "_form.$invalid";
if (button.disabled && button.disable !== true) {
// Allow disabled to overrule ng-disabled. Used for permissions.
// Example: system auditor can view but not update. Form validity
// is no longer a concern but ng-disabled will update disabled
// status on render so we stop applying it here.
} else {
html += "ng-disabled=\"" + ngDisabled;
//html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
html += "\" ";
}
} else {
//html += "ng-disabled=\"" + this.form.name + "_form.$pristine";
//html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
//html += "\" ";
if (button.component === 'at-launch-template') {
html += `<at-launch-template template="${button.templateObj}" ng-show="${button.ngShow}" disabled="${button.ngDisabled}" show-text-button="${button.showTextButton}"></at-launch-template>`;
} else {
// Set default color and label for Save and Reset
if (btn === 'save') {
button.label = i18n._('Save');
button['class'] = 'Form-saveButton';
}
if (btn === 'select') {
button.label = i18n._('Select');
button['class'] = 'Form-saveButton';
}
if (btn === 'cancel') {
button.label = i18n._('Cancel');
button['class'] = 'Form-cancelButton';
}
if (btn === 'close') {
button.label = i18n._('Close');
button['class'] = 'Form-cancelButton';
}
if (btn === 'launch') {
button.label = i18n._('Launch');
button['class'] = 'Form-launchButton';
}
if (btn === 'add_survey') {
button.label = i18n._('Add Survey');
button['class'] = 'Form-surveyButton';
}
if (btn === 'edit_survey') {
button.label = i18n._('Edit Survey');
button['class'] = 'Form-surveyButton';
}
if (btn === 'view_survey') {
button.label = i18n._('View Survey');
button['class'] = 'Form-surveyButton';
}
if (btn === 'workflow_visualizer') {
button.label = i18n._('Workflow Visualizer');
button['class'] = 'Form-primaryButton';
}
// Build button HTML
html += "<button type=\"button\" ";
html += "class=\"btn btn-sm";
html += (button['class']) ? " " + button['class'] : "";
html += "\" ";
html += "id=\"" + this.form.name + "_" + btn + "_btn\" ";
if(button.ngShow){
html += this.attr(button, 'ngShow');
}
if (button.ngClick) {
html += this.attr(button, 'ngClick');
}
if (button.awFeature) {
html += this.attr(button, 'awFeature');
}
if (button.ngDisabled) {
ngDisabled = (button.ngDisabled===true) ? `${this.form.name}_form.$invalid || ${this.form.name}_form.$pending`: button.ngDisabled;
if (btn !== 'reset') {
//html += "ng-disabled=\"" + this.form.name + "_form.$pristine || " + this.form.name + "_form.$invalid";
if (button.disabled && button.disable !== true) {
// Allow disabled to overrule ng-disabled. Used for permissions.
// Example: system auditor can view but not update. Form validity
// is no longer a concern but ng-disabled will update disabled
// status on render so we stop applying it here.
} else {
html += "ng-disabled=\"" + ngDisabled;
//html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
html += "\" ";
}
} else {
//html += "ng-disabled=\"" + this.form.name + "_form.$pristine";
//html += (this.form.allowReadonly) ? " || " + this.form.name + "ReadOnly == true" : "";
//html += "\" ";
}
}
if (button.disabled && button.disable !== true) {
html += ` disabled="disabled" `;
}
if(button.awToolTip) {
html += " aw-tool-tip='" + button.awToolTip + "' data-placement='" + button.dataPlacement + "' data-tip-watch='" + button.dataTipWatch + "'";
}
html += ">";
html += " " + button.label + "</button>\n";
}
if (button.disabled && button.disable !== true) {
html += ` disabled="disabled" `;
}
if(button.awToolTip) {
html += " aw-tool-tip='" + button.awToolTip + "' data-placement='" + button.dataPlacement + "' data-tip-watch='" + button.dataTipWatch + "'";
}
html += ">";
html += " " + button.label + "</button>\n";
}
}
html += "</div><!-- buttons -->\n";

View File

@ -31,6 +31,7 @@
const jobTemplate = resolvedModels[0];
$scope.canAddJobTemplate = jobTemplate.options('actions.POST');
$scope.disableLaunch = true;
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);

View File

@ -45,7 +45,9 @@ export default
id = $stateParams.job_template_id,
callback,
choicesCount = 0,
instance_group_url = defaultUrl + id + '/instance_groups';
instance_group_url = defaultUrl + id + '/instance_groups',
select2LoadDefer = [],
launchHasBeenEnabled = false;
init();
function init() {
@ -168,47 +170,74 @@ export default
};
function sync_playbook_select2() {
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element:'#playbook-select',
multiple: false
});
}));
}
function sync_verbosity_select2() {
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element:'#job_template_verbosity',
multiple: false
});
}));
}
function jobTemplateLoadFinished(){
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element:'#job_template_job_type',
multiple: false
});
}));
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element:'#playbook-select',
multiple: false
});
}));
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element:'#job_template_job_tags',
multiple: true,
addNew: true
});
}));
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element:'#job_template_skip_tags',
multiple: true,
addNew: true
});
}));
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element: '#job_template_custom_virtualenv',
multiple: false,
opts: $scope.custom_virtualenvs_options
});
}));
if (!launchHasBeenEnabled) {
$q.all(select2LoadDefer).then(() => {
// updates based on lookups will initially set the form as dirty.
// we need to set it as pristine when it contains the values given by the api
// so that we can enable launching when the two are the same
$scope.job_template_form.$setPristine();
// this is used to set the overall form as dirty for the values
// that don't actually set this internally (lookups, toggles and code mirrors).
$scope.$watchCollection('multiCredential.selectedCredentials', (val, prevVal) => {
if (!_.isEqual(val, prevVal)) {
$scope.job_template_form.$setDirty();
}
});
$scope.$watchGroup([
'inventory',
'project',
'extra_vars',
'diff_mode',
'instance_groups'
], (val, prevVal) => {
if (!_.isEqual(val, prevVal)) {
$scope.job_template_form.$setDirty();
}
});
});
}
}
$scope.toggleForm = function(key) {
@ -259,8 +288,8 @@ export default
variable: 'extra_vars',
onChange: callback
});
jobTemplateLoadFinished();
launchHasBeenEnabled = true;
});
Wait('start');
@ -458,12 +487,12 @@ export default
.map(i => ({id: i.id + "",
test: i.name}));
CreateSelect2({
select2LoadDefer.push(CreateSelect2({
element:'#job_template_labels',
multiple: true,
addNew: true,
opts: opts
});
}));
$scope.$emit("choicesReady");

View File

@ -405,6 +405,13 @@ function(NotificationsList, i18n) {
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: "job_template_form.$invalid",//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
},
launch: {
component: 'at-launch-template',
templateObj: 'job_template_obj',
ngShow: '(job_template_obj.summary_fields.user_capabilities.start || canAddJobTemplate)',
ngDisabled: 'disableLaunch || job_template_form.$dirty',
showTextButton: 'true'
}
},

View File

@ -151,6 +151,13 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: "workflow_job_template_form.$invalid || can_edit!==true", //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
},
launch: {
component: 'at-launch-template',
templateObj: 'workflow_job_template_obj',
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.start || canAddWorkflowJobTemplate)',
ngDisabled: 'disableLaunch || workflow_job_template_form.$dirty',
showTextButton: 'true'
}
},

View File

@ -26,6 +26,7 @@ export default [
$scope.canEditInventory = true;
$scope.parseType = 'yaml';
$scope.can_edit = true;
$scope.disableLaunch = true;
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);

View File

@ -242,13 +242,6 @@ export default [
});
};
// Select2-ify the lables input
CreateSelect2({
element:'#workflow_job_template_labels',
multiple: true,
addNew: true
});
SurveyControllerInit({
scope: $scope,
parent_scope: $scope,
@ -263,11 +256,28 @@ export default [
.map(i => ({id: i.id + "",
test: i.name}));
// Select2-ify the lables input
CreateSelect2({
element:'#workflow_job_template_labels',
multiple: true,
addNew: true,
opts: opts
opts
}).then(() => {
// updates based on lookups will initially set the form as dirty.
// we need to set it as pristine when it contains the values given by the api
// so that we can enable launching when the two are the same
$scope.workflow_job_template_form.$setPristine();
// this is used to set the overall form as dirty for the values
// that don't actually set this internally (lookups, toggles and code mirrors).
$scope.$watchGroup([
'organization',
'inventory',
'variables'
], (val, prevVal) => {
if (!_.isEqual(val, prevVal)) {
$scope.workflow_job_template_form.$setDirty();
}
});
});
$scope.workflowVisualizerTooltip = i18n._("Click here to open the workflow visualizer.");