From 0c4db0354e24a6710946ae78250c53608aa08172 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Thu, 3 Nov 2016 14:40:47 -0400 Subject: [PATCH 01/45] Add and edit workflow job templates from the UI --- awx/ui/client/legacy-styles/forms.less | 10 +- awx/ui/client/legacy-styles/lists.less | 27 + awx/ui/client/src/app.js | 3 + .../job-templates-list.directive.js | 6 +- .../job-templates-list.partial.html | 8 +- awx/ui/client/src/forms.js | 6 +- awx/ui/client/src/forms/JobTemplates.js | 4 +- awx/ui/client/src/forms/WorkflowMaker.js | 154 ++++ awx/ui/client/src/forms/Workflows.js | 203 +++++ .../edit/inventory-edit.controller.js | 40 +- .../src/job-detail/job-detail.controller.js | 2 +- .../initiateplaybookrun.factory.js | 4 +- .../job-submission.controller.js | 9 +- .../job-submission.directive.js | 2 +- .../job-template-add.controller.js} | 5 +- .../{add => add-job-template}/main.js | 6 +- .../{edit => add-workflow}/main.js | 6 +- .../add-workflow/workflow-add.controller.js | 187 ++++ .../add-workflow/workflow-add.partial.html | 4 + .../copy/job-templates-copy.controller.js | 7 +- .../copy/job-templates-copy.route.js | 2 +- .../delete-job-template.service.js | 18 - .../job-template-edit.controller.js} | 0 .../job-templates/edit-job-template/main.js | 11 + .../src/job-templates/edit-workflow/main.js | 11 + .../edit-workflow/workflow-edit.controller.js | 762 ++++++++++++++++ .../edit-workflow/workflow-edit.partial.html | 5 + .../src/job-templates/job-template.service.js | 190 ++++ .../list/job-templates-list.controller.js | 137 ++- .../list/job-templates-list.route.js | 42 + awx/ui/client/src/job-templates/list/main.js | 2 +- awx/ui/client/src/job-templates/main.js | 111 ++- .../src/job-templates/workflow-chart/main.js | 11 + .../workflow-chart/workflow-chart.block.less | 71 ++ .../workflow-chart.directive.js | 455 ++++++++++ .../src/job-templates/workflow-maker/main.js | 11 + .../workflow-maker/workflow-help.service.js | 166 ++++ .../workflow-maker/workflow-maker.block.less | 194 +++++ .../workflow-maker.controller.js | 814 ++++++++++++++++++ .../workflow-maker.directive.js | 20 + .../workflow-maker.partial.html | 106 +++ awx/ui/client/src/lists.js | 2 + awx/ui/client/src/lists/InventorySources.js | 29 + awx/ui/client/src/lists/JobTemplates.js | 68 +- awx/ui/client/src/lists/PortalJobTemplates.js | 2 +- .../src/main-menu/main-menu.partial.html | 12 +- awx/ui/client/src/scheduler/main.js | 42 +- awx/ui/client/src/shared/Modal.js | 4 +- awx/ui/client/src/shared/form-generator.js | 76 +- .../list-generator/list-actions.partial.html | 82 +- .../src/shared/stateDefinitions.factory.js | 4 +- .../standard-out/standard-out.controller.js | 2 +- .../job-templates-list.controller-test.js | 265 ++++++ .../workflows/workflow-add.controller-test.js | 169 ++++ .../workflow-maker.controller-test.js | 85 ++ 55 files changed, 4472 insertions(+), 202 deletions(-) create mode 100644 awx/ui/client/src/forms/WorkflowMaker.js create mode 100644 awx/ui/client/src/forms/Workflows.js rename awx/ui/client/src/job-templates/{add/job-templates-add.controller.js => add-job-template/job-template-add.controller.js} (99%) rename awx/ui/client/src/job-templates/{add => add-job-template}/main.js (54%) rename awx/ui/client/src/job-templates/{edit => add-workflow}/main.js (54%) create mode 100644 awx/ui/client/src/job-templates/add-workflow/workflow-add.controller.js create mode 100644 awx/ui/client/src/job-templates/add-workflow/workflow-add.partial.html delete mode 100644 awx/ui/client/src/job-templates/delete-job-template.service.js rename awx/ui/client/src/job-templates/{edit/job-templates-edit.controller.js => edit-job-template/job-template-edit.controller.js} (100%) create mode 100644 awx/ui/client/src/job-templates/edit-job-template/main.js create mode 100644 awx/ui/client/src/job-templates/edit-workflow/main.js create mode 100644 awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js create mode 100644 awx/ui/client/src/job-templates/edit-workflow/workflow-edit.partial.html create mode 100644 awx/ui/client/src/job-templates/job-template.service.js create mode 100644 awx/ui/client/src/job-templates/list/job-templates-list.route.js create mode 100644 awx/ui/client/src/job-templates/workflow-chart/main.js create mode 100644 awx/ui/client/src/job-templates/workflow-chart/workflow-chart.block.less create mode 100644 awx/ui/client/src/job-templates/workflow-chart/workflow-chart.directive.js create mode 100644 awx/ui/client/src/job-templates/workflow-maker/main.js create mode 100644 awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js create mode 100644 awx/ui/client/src/job-templates/workflow-maker/workflow-maker.block.less create mode 100644 awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js create mode 100644 awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js create mode 100644 awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html create mode 100644 awx/ui/client/src/lists/InventorySources.js create mode 100644 awx/ui/tests/spec/job-templates/job-templates-list.controller-test.js create mode 100644 awx/ui/tests/spec/workflows/workflow-add.controller-test.js create mode 100644 awx/ui/tests/spec/workflows/workflow-maker.controller-test.js diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index b36f1bdd5b..89ff33dd03 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -184,6 +184,7 @@ .Form-formGroup--fullWidth { max-width: none !important; width: 100% !important; + padding-right: 0px !important; } .Form-formGroup--checkbox{ @@ -553,19 +554,24 @@ input[type='radio']:checked:before { color: @btn-txt; } -.Form-surveyButton { +.Form-primaryButton { background-color: @default-link; color: @default-bg; text-transform: uppercase; padding-left:15px; padding-right: 15px; + margin-right: 20px; } -.Form-surveyButton:hover{ +.Form-primaryButton:hover { background-color: @default-link-hov; color: @default-bg; } +.Form-primaryButton.Form-tab--disabled:hover { + background-color: @default-link; +} + .Form-formGroup--singleColumn { width: 100% !important; padding-right: 0px; diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index 763694f6d2..3081725e69 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -357,6 +357,33 @@ table, tbody { cursor: not-allowed; } +.List-dropdownButton { + border: none; +} + +.List-dropdownSuccess { + background-color: @submit-button-bg; + color: @submit-button-text; + border-color: @submit-button-bg-hov; +} + +.List-dropdownSuccess:hover, +.List-dropdownSuccess:focus { + color: @submit-button-text; + background-color: @submit-button-bg-hov; +} + +.List-dropdownCarat { + display: inline-block; + width: 0; + height: 0; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid\9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} + @media (max-width: 991px) { .List-searchWidget + .List-searchWidget { margin-top: 20px; diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index cdc39200f3..7d3f3d302e 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -195,6 +195,9 @@ var tower = angular.module('Tower', [ 'ActivityStreamHelper', 'gettext', 'I18N', + 'WorkflowFormDefinition', + 'InventorySourcesListDefinition', + 'WorkflowMakerFormDefinition' ]) .constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/') diff --git a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js b/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js index 5052e073a4..714b3c088a 100644 --- a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js +++ b/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.directive.js @@ -2,8 +2,8 @@ export default [ 'InitiatePlaybookRun', 'templateUrl', - '$location', - function JobTemplatesList(InitiatePlaybookRun, templateUrl, $location) { + '$state', + function JobTemplatesList(InitiatePlaybookRun, templateUrl, $state) { return { restrict: 'E', link: link, @@ -47,7 +47,7 @@ export default }; scope.editJobTemplate = function (jobTemplateId) { - $location.path( '/job_templates/' + jobTemplateId); + $state.go('templates.editJobTemplate', {id: jobTemplateId}); }; } }]; diff --git a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.partial.html b/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.partial.html index 8114f037d4..77ba2f41ac 100644 --- a/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.partial.html +++ b/awx/ui/client/src/dashboard/lists/job-templates/job-templates-list.partial.html @@ -3,7 +3,7 @@

RECENTLY USED JOB TEMPLATES

- + VIEW ALL @@ -25,7 +25,7 @@ ng-class-even="'List-tableRow--evenRow'" ng-repeat = "job_template in job_templates"> - + {{ job_template.name }} @@ -53,7 +53,7 @@
-

No job templates were recently used.
- You can create a job template here.

+

No job templates were recently used.
+ You can create a job template here.

diff --git a/awx/ui/client/src/forms.js b/awx/ui/client/src/forms.js index 51cbc4a6e7..dca21972f2 100644 --- a/awx/ui/client/src/forms.js +++ b/awx/ui/client/src/forms.js @@ -24,6 +24,8 @@ import ProjectStatus from "./forms/ProjectStatus"; import Projects from "./forms/Projects"; import Teams from "./forms/Teams"; import Users from "./forms/Users"; +import WorkflowMaker from "./forms/WorkflowMaker"; +import Workflows from "./forms/Workflows"; export @@ -46,5 +48,7 @@ export ProjectStatus, Projects, Teams, - Users + Users, + WorkflowMaker, + Workflows }; diff --git a/awx/ui/client/src/forms/JobTemplates.js b/awx/ui/client/src/forms/JobTemplates.js index 0c276777dc..d7e0b502ee 100644 --- a/awx/ui/client/src/forms/JobTemplates.js +++ b/awx/ui/client/src/forms/JobTemplates.js @@ -20,10 +20,12 @@ export default addTitle: i18n._('New Job Template'), editTitle: '{{ name }}', name: 'job_template', + breadcrumbName: 'JOB TEMPLATE', basePath: 'job_templates', // the top-most node of generated state tree - stateTree: 'jobTemplates', + stateTree: 'templates', tabs: true, + activeEditState: 'templates.editJobTemplate', // (optional) array of supporting templates to ng-include inside generated html include: ['/static/partials/survey-maker-modal.html'], diff --git a/awx/ui/client/src/forms/WorkflowMaker.js b/awx/ui/client/src/forms/WorkflowMaker.js new file mode 100644 index 0000000000..6d3d602ed8 --- /dev/null +++ b/awx/ui/client/src/forms/WorkflowMaker.js @@ -0,0 +1,154 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name forms.function:JobTemplate + * @description This form is for adding/editing a Job Template +*/ + +export default + angular.module('WorkflowMakerFormDefinition', []) + + .value ('WorkflowMakerFormObject', { + + addTitle: '', + editTitle: '', + name: 'workflow_maker', + base: 'job_templates', + tabs: false, + cancelButton: false, + showHeader: false, + + fields: { + credential: { + label: 'Credential', + type: 'lookup', + sourceModel: 'credential', + sourceField: 'name', + ngClick: 'lookUpCredential()', + requiredErrorMsg: "Please select a Credential.", + column: 1, + class: 'Form-formGroup--fullWidth', + awPopOver: "

Select the credential you want the job to use when accessing the remote hosts. Choose the credential containing " + + " the username and SSH key or password that Ansible will need to log into the remote hosts.

", + dataTitle: 'Credential', + dataPlacement: 'right', + dataContainer: "body", + ngShow: "selectedTemplate.ask_credential_on_launch", + awRequiredWhen: { + reqExpression: 'selectedTemplate.ask_credential_on_launch' + } + }, + inventory: { + label: 'Inventory', + type: 'lookup', + sourceModel: 'inventory', + sourceField: 'name', + ngClick: 'lookUpInventory()', + requiredErrorMsg: "Please select an Inventory.", + column: 1, + class: 'Form-formGroup--fullWidth', + awPopOver: "

Select the inventory containing the hosts you want this job to manage.

", + dataTitle: 'Inventory', + dataPlacement: 'right', + dataContainer: "body", + ngShow: "selectedTemplate.ask_inventory_on_launch", + awRequiredWhen: { + reqExpression: 'selectedTemplate.ask_inventory_on_launch' + } + }, + job_type: { + label: 'Job Type', + type: 'select', + ngOptions: 'type.label for type in job_type_options track by type.value', + "default": 0, + column: 1, + class: 'Form-formGroup--fullWidth', + awPopOver: "

When this template is submitted as a job, setting the type to run will execute the playbook, running tasks " + + " on the selected hosts.

Setting the type to check will not execute the playbook. Instead, ansible will check playbook " + + " syntax, test environment setup and report problems.

Setting the type to scan will execute the playbook and store any " + + " scanned facts for use with Tower's System Tracking feature.

", + dataTitle: 'Job Type', + dataPlacement: 'right', + dataContainer: "body", + ngShow: "selectedTemplate.ask_job_type_on_launch", + awRequiredWhen: { + reqExpression: 'selectedTemplate.ask_job_type_on_launch' + } + }, + limit: { + label: 'Limit', + type: 'text', + addRequired: false, + editRequired: false, + column: 1, + class: 'Form-formGroup--fullWidth', + awPopOver: "

Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. " + + "Multiple patterns can be separated by ; : or ,

For more information and examples see " + + "the Patterns topic at docs.ansible.com.

", + dataTitle: 'Limit', + dataPlacement: 'right', + dataContainer: "body", + ngShow: "selectedTemplate.ask_limit_on_launch" + }, + job_tags: { + label: 'Job Tags', + type: 'textarea', + rows: 5, + addRequired: false, + editRequired: false, + 'elementClass': 'Form-textInput', + column: 2, + class: 'Form-formGroup--fullWidth', + awPopOver: "

Provide a comma separated list of tags.

\n" + + "

Tags are useful when you have a large playbook, and you want to run a specific part of a play or task.

" + + "

Consult the Ansible documentation for further details on the usage of tags.

", + dataTitle: "Job Tags", + dataPlacement: "right", + dataContainer: "body", + ngShow: "selectedTemplate.ask_tags_on_launch" + }, + skip_tags: { + label: 'Skip Tags', + type: 'textarea', + rows: 5, + addRequired: false, + editRequired: false, + 'elementClass': 'Form-textInput', + column: 2, + class: 'Form-formGroup--fullWidth', + awPopOver: "

Provide a comma separated list of tags.

\n" + + "

Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task.

" + + "

Consult the Ansible documentation for further details on the usage of tags.

", + dataTitle: "Skip Tags", + dataPlacement: "right", + dataContainer: "body", + ngShow: "selectedTemplate.ask_skip_tags_on_launch" + } + }, + buttons: { + cancel: { + ngClick: 'cancelNodeForm()' + }, + save: { + ngClick: 'confirmNodeForm()', + ngDisabled: "workflow_maker_form.$invalid || !selectedTemplate" + } + } + }) + .factory('WorkflowMakerForm', ['WorkflowMakerFormObject', 'NotificationsList', function(WorkflowMakerFormObject, NotificationsList) { + return function() { + var itm; + for (itm in WorkflowMakerFormObject.related) { + if (WorkflowMakerFormObject.related[itm].include === "NotificationsList") { + WorkflowMakerFormObject.related[itm] = NotificationsList; + WorkflowMakerFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list + } + } + return WorkflowMakerFormObject; + }; + }]); diff --git a/awx/ui/client/src/forms/Workflows.js b/awx/ui/client/src/forms/Workflows.js new file mode 100644 index 0000000000..442c651d7a --- /dev/null +++ b/awx/ui/client/src/forms/Workflows.js @@ -0,0 +1,203 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name forms.function:Workflow + * @description This form is for adding/editing a Workflow +*/ + +export default + angular.module('WorkflowFormDefinition', []) + + .value ('WorkflowFormObject', { + + addTitle: 'New Workflow', + editTitle: '{{ name }}', + name: 'workflow_job_template', + base: 'workflow', + basePath: 'workflow_job_templates', + // the top-most node of generated state tree + stateTree: 'templates', + activeEditState: 'templates.editWorkflowJobTemplate', + tabs: true, + + fields: { + name: { + label: 'Name', + type: 'text', + addRequired: true, + editRequired: true, + column: 1 + }, + description: { + label: 'Description', + type: 'text', + addRequired: false, + editRequired: false, + column: 1 + }, + organization: { + label: 'Organization', + type: 'lookup', + sourceModel: 'organization', + basePath: 'organizations', + list: 'OrganizationList', + sourceField: 'name', + dataTitle: 'Organization', + dataContainer: 'body', + dataPlacement: 'right', + column: 1 + }, + labels: { + label: 'Labels', + type: 'select', + class: 'Form-formGroup--fullWidth', + ngOptions: 'label.label for label in labelOptions track by label.value', + multiSelect: true, + addRequired: false, + editRequired: false, + dataTitle: 'Labels', + dataPlacement: 'right', + awPopOver: "

Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs in the Tower display.

", + dataContainer: 'body' + }, + variables: { + label: 'Extra Variables', + type: 'textarea', + class: 'Form-textAreaLabel Form-formGroup--fullWidth', + rows: 6, + addRequired: false, + editRequired: false, + "default": "---", + column: 2, + awPopOver: "

Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter " + + "for ansible-playbook. Provide key/value pairs using either YAML or JSON.

" + + "JSON:
\n" + + "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + + "YAML:
\n" + + "
---
somevar: somevalue
password: magic
\n", + dataTitle: 'Extra Variables', + dataPlacement: 'right', + dataContainer: "body" + } + }, + + buttons: { //for now always generates + + + + + + +
+
+
EDIT WORKFLOW
+
+
+ +
+
+
+
+
+
+
KEY:
+
+
+
On Success
+
+
+
+
On Fail
+
+
+
+
Always
+
+
+
P
+
Project Sync
+
+
+
I
+
Inventory Sync
+
+
+
+ TOTAL JOBS + +
+
+ +
+
+
{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited && nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : "ADD A TEMPLATE"}}
+
Please hover over a template and click the Add button.
+
+
+
JOBS
+
PROJECT SYNC
+
INVENTORY SYNC
+
+
+
+
+
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ + +
+ diff --git a/awx/ui/client/src/lists.js b/awx/ui/client/src/lists.js index 545c3189fb..f7b0288822 100644 --- a/awx/ui/client/src/lists.js +++ b/awx/ui/client/src/lists.js @@ -13,6 +13,7 @@ import Hosts from "./lists/Hosts"; import Inventories from "./lists/Inventories"; import InventoryGroups from "./lists/InventoryGroups"; import InventoryHosts from "./lists/InventoryHosts"; +import InventorySources from "./lists/InventorySources"; import JobEvents from "./lists/JobEvents"; import JobHosts from "./lists/JobHosts"; import JobTemplates from "./lists/JobTemplates"; @@ -38,6 +39,7 @@ export Inventories, InventoryGroups, InventoryHosts, + InventorySources, JobEvents, JobHosts, JobTemplates, diff --git a/awx/ui/client/src/lists/InventorySources.js b/awx/ui/client/src/lists/InventorySources.js new file mode 100644 index 0000000000..e946e9d4d0 --- /dev/null +++ b/awx/ui/client/src/lists/InventorySources.js @@ -0,0 +1,29 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + +export default + angular.module('InventorySourcesListDefinition', []) + .value('InventorySourcesList', { + + name: 'workflow_inventory_sources', + iterator: 'inventory_source', + listTitle: 'Inventory Sources', + index: false, + hover: true, + + fields: { + name: { + key: true, + label: 'Name', + columnClass: 'col-md-11' + } + }, + + actions: {}, + + fieldActions: {} + }); diff --git a/awx/ui/client/src/lists/JobTemplates.js b/awx/ui/client/src/lists/JobTemplates.js index e809683460..fd5cbac79a 100644 --- a/awx/ui/client/src/lists/JobTemplates.js +++ b/awx/ui/client/src/lists/JobTemplates.js @@ -10,11 +10,12 @@ export default .factory('JobTemplateList', ['i18n', function(i18n) { return { - name: 'job_templates', - iterator: 'job_template', - selectTitle: i18n._('Add Job Template'), - editTitle: i18n._('Job Templates'), - listTitle: i18n._('Job Templates'), + name: 'templates', + iterator: 'unified_job_templates', + basePath: 'unified_job_templates', + selectTitle: i18n._('Template'), + editTitle: i18n._('Templates'), + listTitle: i18n._('Templates'), selectInstructions: "Click on a row to select it, and click Finished when done. Use the " + "button to create a new job template.", index: false, @@ -24,7 +25,14 @@ export default name: { key: true, label: i18n._('Name'), - columnClass: 'col-lg-2 col-md-2 col-sm-4 col-xs-9' + columnClass: 'col-lg-2 col-md-2 col-sm-4 col-xs-9', + ngHref: "{{job_template.editLink}}" + }, + type: { + label: i18n._('Type'), + searchType: 'select', + searchOptions: [], // will be set by Options call to job templates resource + columnClass: 'col-lg-2 col-md-2 col-sm-4 hidden-xs' }, description: { label: i18n._('Description'), @@ -34,80 +42,90 @@ export default label: i18n._('Activity'), columnClass: 'List-tableCell col-lg-2 col-md-2 hidden-sm hidden-xs', nosort: true, - ngInclude: "'/static/partials/job-template-smart-status.html'", + //ngInclude: "'/static/partials/job-template-smart-status.html'", type: 'template' }, labels: { label: i18n._('Labels'), type: 'labels', nosort: true, - columnClass: 'List-tableCell col-lg-4 col-md-4 hidden-sm hidden-xs' + columnClass: 'List-tableCell col-lg-2 col-md-4 hidden-sm hidden-xs' } }, actions: { add: { mode: 'all', // One of: edit, select, all - ngClick: 'addJobTemplate()', - basePaths: ['job_templates'], + type: 'buttonDropdown', + basePaths: ['templates'], awToolTip: i18n._('Create a new template'), - actionClass: 'btn List-buttonSubmit', - buttonContent: i18n._('+ ADD'), + actionClass: 'btn List-dropdownSuccess', + buttonContent: i18n._('ADD'), + options: [ + { + optionContent: 'Job Template', + optionSref: 'templates.addJobTemplate' + }, + { + optionContent: 'Workflow Job Template', + optionSref: 'templates.addWorkflowJobTemplate' + } + ], ngShow: 'canAdd' } }, fieldActions: { - columnClass: 'col-lg-2 col-md-3 col-sm-3 col-xs-3', + columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-3', submit: { label: i18n._('Launch'), mode: 'all', - ngClick: 'submitJob(job_template.id)', + ngClick: 'submitJob(unified_job_templates)', awToolTip: i18n._('Start a job using this template'), dataPlacement: 'top', - ngShow: 'job_template.summary_fields.user_capabilities.start' + ngShow: 'unified_job_templates.summary_fields.user_capabilities.start' }, schedule: { label: i18n._('Schedule'), mode: 'all', - ngClick: 'scheduleJob(job_template.id)', + ngClick: 'scheduleJob(unified_job_templates)', awToolTip: i18n._('Schedule future job template runs'), dataPlacement: 'top', - ngShow: 'job_template.summary_fields.user_capabilities.schedule' + ngShow: 'unified_job_templates.summary_fields.user_capabilities.schedule' }, copy: { label: i18n._('Copy'), - 'ui-sref': 'jobTemplates.copy({id: job_template.id})', + 'ui-sref': 'templates.copy({id: unified_job_templates.id})', "class": 'btn-danger btn-xs', awToolTip: i18n._('Copy template'), dataPlacement: 'top', - ngShow: 'job_template.summary_fields.user_capabilities.copy' + ngShow: 'unified_job_templates.summary_fields.user_capabilities.copy' }, edit: { label: i18n._('Edit'), - ngClick: "editJobTemplate(job_template.id)", + ngClick: "editJobTemplate(unified_job_templates)", awToolTip: i18n._('Edit template'), "class": 'btn-default btn-xs', dataPlacement: 'top', - ngShow: 'job_template.summary_fields.user_capabilities.edit' + ngShow: 'unified_job_templates.summary_fields.user_capabilities.edit' }, view: { label: i18n._('View'), - ngClick: "editJobTemplate(job_template.id)", + ngClick: "editJobTemplate(unified_job_templates.id)", awToolTip: i18n._('View template'), "class": 'btn-default btn-xs', dataPlacement: 'top', - ngShow: '!job_template.summary_fields.user_capabilities.edit' + ngShow: '!unified_job_templates.summary_fields.user_capabilities.edit' }, "delete": { label: i18n._('Delete'), - ngClick: "deleteJobTemplate(job_template.id, job_template.name)", + ngClick: "deleteJobTemplate(unified_job_templates)", "class": 'btn-danger btn-xs', awToolTip: i18n._('Delete template'), dataPlacement: 'top', - ngShow: 'job_template.summary_fields.user_capabilities.delete' + ngShow: 'unified_job_templates.summary_fields.user_capabilities.delete' } } };}]); diff --git a/awx/ui/client/src/lists/PortalJobTemplates.js b/awx/ui/client/src/lists/PortalJobTemplates.js index 9a9f8c0ca7..b542ac1181 100644 --- a/awx/ui/client/src/lists/PortalJobTemplates.js +++ b/awx/ui/client/src/lists/PortalJobTemplates.js @@ -23,7 +23,7 @@ export default key: true, label: i18n._('Name'), columnClass: 'col-lg-5 col-md-5 col-sm-9 col-xs-8', - linkTo: '/#/job_templates/{{job_template.id}}', + linkTo: '/#/templates/{{job_template.id}}' }, description: { label: i18n._('Description'), diff --git a/awx/ui/client/src/main-menu/main-menu.partial.html b/awx/ui/client/src/main-menu/main-menu.partial.html index 558d733197..398488add7 100644 --- a/awx/ui/client/src/main-menu/main-menu.partial.html +++ b/awx/ui/client/src/main-menu/main-menu.partial.html @@ -29,10 +29,10 @@ + href="/#/templates" + ng-class="{'is-currentRoute' : isCurrentState('templates')}"> - JOB TEMPLATES + TEMPLATES + ng-class="{'is-currentRoute' : isCurrentState('templates'), 'is-loggedOut' : !current_user || !current_user.username}"> - JOB TEMPLATES + TEMPLATES ww) ? ww - 10 : width; y = (height > wh) ? wh - 10 : height; @@ -108,6 +109,7 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper']) resizable: resizable, draggable: draggable, dialogClass: dialogClass, + position: position, create: function () { // Fix the close button $('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).html(''); diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index f3ea41ea07..548a36caee 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -142,10 +142,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat .factory('GenerateForm', ['$rootScope', '$location', '$compile', 'generateList', 'Attr', 'Icon', 'Column', 'NavigationLink', 'HelpCollapse', 'DropDown', 'Empty', 'SelectIcon', - 'Store', 'ActionButton', '$log', 'i18n', + 'Store', 'ActionButton', '$log', 'i18n', '$timeout', function ($rootScope, $location, $compile, GenerateList, Attr, Icon, Column, NavigationLink, HelpCollapse, - DropDown, Empty, SelectIcon, Store, ActionButton, $log, i18n) { + DropDown, Empty, SelectIcon, Store, ActionButton, $log, i18n, $timeout) { return { setForm: function (form) { this.form = form; }, @@ -523,7 +523,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat if ((!field.readonly) || (field.readonly && options.mode === 'edit')) { - if((field.excludeMode === undefined || field.excludeMode !== options.mode) && field.type !== 'alertblock') { + if((field.excludeMode === undefined || field.excludeMode !== options.mode) && field.type !== 'alertblock' && field.type !== 'workflow-chart') { html += "
`; + html += "
"; if(this.mode === "edit"){ html += `
${(collection.title || collection.editTitle)}
`; } + + for (itm in this.form.relatedButtons) { + button = this.form.relatedButtons[itm]; + + // Build button HTML + html += "
";//tabHolder } @@ -1430,6 +1492,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat button.label = i18n._('View Survey'); button['class'] = 'Form-surveyButton'; } + if (btn === 'workflow_editor') { + button.label = i18n._('Workflow Editor'); + button['class'] = 'Form-primaryButton'; + } // Build button HTML html += " +
+ + +
- +
+ + +
+ + +
diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js index 0c4112aa7b..79170ac8bb 100644 --- a/awx/ui/client/src/shared/stateDefinitions.factory.js +++ b/awx/ui/client/src/shared/stateDefinitions.factory.js @@ -140,12 +140,14 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat let formNode, states = []; switch (mode) { case 'add': + // breadcrumbName necessary for resources that are more than one word like + // job templates. form.name can't have spaces in it or it busts form gen formNode = $stateExtender.buildDefinition({ name: params.name || `${params.parent}.add`, url: params.url || '/add', ncyBreadcrumb: { [params.parent ? 'parent' : null]: `${params.parent}`, - label: `CREATE ${form.name}` + label: `CREATE ${form.breadcrumbName || form.name}` }, views: { 'form': { diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index 663d165cf3..ffed821d7d 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -52,7 +52,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, $scope.created_by = data.summary_fields.created_by; $scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : ''; $scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : ''; - $scope.job_template_url = '/#/job_templates/' + data.unified_job_template; + $scope.job_template_url = '/#/templates/' + data.unified_job_template; $scope.inventory_url = ($scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : ''; $scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : ''; $scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : ''; diff --git a/awx/ui/tests/spec/job-templates/job-templates-list.controller-test.js b/awx/ui/tests/spec/job-templates/job-templates-list.controller-test.js new file mode 100644 index 0000000000..1088d5fc48 --- /dev/null +++ b/awx/ui/tests/spec/job-templates/job-templates-list.controller-test.js @@ -0,0 +1,265 @@ +'use strict'; + +describe('Controller: JobTemplatesList', () => { + // Setup + let scope, + rootScope, + state, + JobTemplatesList, + ClearScope, + GetChoices, + Alert, + Prompt, + InitiatePlaybookRun, + rbacUiControlService, + canAddDeferred, + q, + JobTemplateService, + deleteWorkflowJobTemplateDeferred, + deleteJobTemplateDeferred; + + beforeEach(angular.mock.module('Tower')); + beforeEach(angular.mock.module('jobTemplates', ($provide) => { + + state = jasmine.createSpyObj('state', [ + '$get', + 'transitionTo', + 'go' + ]); + + state.params = { + id: 1 + }; + + rbacUiControlService = { + canAdd: function(){ + return angular.noop; + } + }; + + JobTemplateService = { + deleteWorkflowJobTemplate: function(){ + return angular.noop; + }, + deleteJobTemplate: function(){ + return angular.noop; + } + }; + + ClearScope = jasmine.createSpy('ClearScope'); + GetChoices = jasmine.createSpy('GetChoices'); + Alert = jasmine.createSpy('Alert'); + Prompt = jasmine.createSpy('Prompt').and.callFake(function(args) { + args.action(); + }); + InitiatePlaybookRun = jasmine.createSpy('InitiatePlaybookRun'); + + $provide.value('ClearScope', ClearScope); + $provide.value('GetChoices', GetChoices); + $provide.value('Alert', Alert); + $provide.value('Prompt', Prompt); + $provide.value('state', state); + $provide.value('InitiatePlaybookRun', InitiatePlaybookRun); + })); + + beforeEach(angular.mock.inject( ($rootScope, $controller, $q, _state_, _ConfigService_, _ClearScope_, _GetChoices_, _Alert_, _Prompt_, _InitiatePlaybookRun_) => { + scope = $rootScope.$new(); + rootScope = $rootScope; + q = $q; + state = _state_; + ClearScope = _ClearScope_; + GetChoices = _GetChoices_; + Alert = _Alert_; + Prompt = _Prompt_; + InitiatePlaybookRun = _InitiatePlaybookRun_; + canAddDeferred = q.defer(); + deleteWorkflowJobTemplateDeferred = q.defer(); + deleteJobTemplateDeferred = q.defer(); + + rbacUiControlService.canAdd = jasmine.createSpy('canAdd').and.returnValue(canAddDeferred.promise); + + JobTemplateService.deleteWorkflowJobTemplate = jasmine.createSpy('deleteWorkflowJobTemplate').and.returnValue(deleteWorkflowJobTemplateDeferred.promise); + JobTemplateService.deleteJobTemplate = jasmine.createSpy('deleteJobTemplate').and.returnValue(deleteJobTemplateDeferred.promise); + + JobTemplatesList = $controller('JobTemplatesList', { + $scope: scope, + $rootScope: rootScope, + $state: state, + ClearScope: ClearScope, + GetChoices: GetChoices, + Alert: Alert, + Prompt: Prompt, + InitiatePlaybookRun: InitiatePlaybookRun, + rbacUiControlService: rbacUiControlService, + JobTemplateService: JobTemplateService + }); + })); + + it('should call GetChoices', ()=> { + expect(GetChoices).toHaveBeenCalled(); + }); + + describe('scope.editJobTemplate()', () => { + + it('should call Alert when template param is not present', ()=>{ + scope.editJobTemplate(); + expect(Alert).toHaveBeenCalledWith('Error: Unable to edit template', 'Template parameter is missing'); + }); + + it('should transition to templates.editJobTemplate when type is "Job Template"', ()=>{ + + var testTemplate = { + type: "Job Template", + id: 1 + }; + + scope.editJobTemplate(testTemplate); + expect(state.transitionTo).toHaveBeenCalledWith('templates.editJobTemplate', {id: 1}); + }); + + it('should transition to templates.templates.editWorkflowJobTemplate when type is "Workflow Job Template"', ()=>{ + + var testTemplate = { + type: "Workflow Job Template", + id: 1 + }; + + scope.editJobTemplate(testTemplate); + expect(state.transitionTo).toHaveBeenCalledWith('templates.editWorkflowJobTemplate', {id: 1}); + }); + + it('should call Alert when type is not "Job Template" or "Workflow Job Template"', ()=>{ + + var testTemplate = { + type: "Some Other Type", + id: 1 + }; + + scope.editJobTemplate(testTemplate); + expect(Alert).toHaveBeenCalledWith('Error: Unable to determine template type', 'We were unable to determine this template\'s type while routing to edit.'); + }); + + }); + + describe('scope.deleteJobTemplate()', () => { + + it('should call Alert when template param is not present', ()=>{ + scope.deleteJobTemplate(); + expect(Alert).toHaveBeenCalledWith('Error: Unable to delete template', 'Template parameter is missing'); + }); + + it('should call Prompt if template param is present', ()=>{ + + var testTemplate = { + id: 1, + name: "Test Template" + }; + + scope.deleteJobTemplate(testTemplate); + expect(Prompt).toHaveBeenCalled(); + }); + + it('should call JobTemplateService.deleteWorkflowJobTemplate when the user takes affirmative action on the delete modal and type = "Workflow Job Template"', ()=>{ + // Note that Prompt has been mocked up above to immediately call the callback function that gets passed in + // which is how we access the private function in the controller + + var testTemplate = { + id: 1, + name: "Test Template", + type: "Workflow Job Template" + }; + + scope.deleteJobTemplate(testTemplate); + expect(JobTemplateService.deleteWorkflowJobTemplate).toHaveBeenCalled(); + }); + + it('should call JobTemplateService.deleteJobTemplate when the user takes affirmative action on the delete modal and type = "Workflow Job Template"', ()=>{ + // Note that Prompt has been mocked up above to immediately call the callback function that gets passed in + // which is how we access the private function in the controller + + var testTemplate = { + id: 1, + name: "Test Template", + type: "Job Template" + }; + + scope.deleteJobTemplate(testTemplate); + expect(JobTemplateService.deleteJobTemplate).toHaveBeenCalled(); + }); + + }); + + describe('scope.submitJob()', () => { + + it('should call Alert when template param is not present', ()=>{ + scope.submitJob(); + expect(Alert).toHaveBeenCalledWith('Error: Unable to launch template', 'Template parameter is missing'); + }); + + it('should call InitiatePlaybookRun when type is "Job Template"', ()=>{ + + var testTemplate = { + type: "Job Template", + id: 1 + }; + + scope.submitJob(testTemplate); + expect(InitiatePlaybookRun).toHaveBeenCalled(); + }); + + xit('should call [something] when type is "Workflow Job Template"', ()=>{ + + var testTemplate = { + type: "Workflow Job Template", + id: 1 + }; + + scope.submitJob(testTemplate); + expect([something]).toHaveBeenCalled(); + }); + + it('should call Alert when type is not "Job Template" or "Workflow Job Template"', ()=>{ + + var testTemplate = { + type: "Some Other Type", + id: 1 + }; + + scope.submitJob(testTemplate); + expect(Alert).toHaveBeenCalledWith('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.'); + }); + + }); + + describe('scope.scheduleJob()', () => { + + it('should transition to jobTemplateSchedules when type is "Job Template"', ()=>{ + + var testTemplate = { + type: "Job Template", + id: 1 + }; + + scope.scheduleJob(testTemplate); + expect(state.go).toHaveBeenCalledWith('jobTemplateSchedules', {id: 1}); + }); + + it('should transition to workflowJobTemplateSchedules when type is "Workflow Job Template"', ()=>{ + + var testTemplate = { + type: "Workflow Job Template", + id: 1 + }; + + scope.scheduleJob(testTemplate); + expect(state.go).toHaveBeenCalledWith('workflowJobTemplateSchedules', {id: 1}); + }); + + it('should call Alert when template param is not present', ()=>{ + scope.scheduleJob(); + expect(Alert).toHaveBeenCalledWith('Error: Unable to schedule job', 'Template parameter is missing'); + }); + + }); + +}); diff --git a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js new file mode 100644 index 0000000000..c97a0580b4 --- /dev/null +++ b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js @@ -0,0 +1,169 @@ +'use strict'; + +describe('Controller: WorkflowAdd', () => { + // Setup + let scope, + state, + WorkflowAdd, + ClearScope, + Alert, + GenerateForm, + initSurvey, + JobTemplateService, + q, + getLabelsDeferred, + createWorkflowJobTemplateDeferred, + httpBackend, + ProcessErrors, + CreateSelect2, + Wait, + ParseTypeChange, + ToJSON; + + beforeEach(angular.mock.module('Tower')); + beforeEach(angular.mock.module('jobTemplates', ($provide) => { + + state = jasmine.createSpyObj('state', [ + '$get', + 'transitionTo', + 'go' + ]); + + GenerateForm = jasmine.createSpyObj('GenerateForm', [ + 'inject', + 'reset', + 'clearApiErrors' + ]); + + JobTemplateService = { + getLabelOptions: function(){ + return angular.noop; + }, + createWorkflowJobTemplate: function(){ + return angular.noop; + } + }; + + ClearScope = jasmine.createSpy('ClearScope'); + Alert = jasmine.createSpy('Alert'); + ProcessErrors = jasmine.createSpy('ProcessErrors'); + CreateSelect2 = jasmine.createSpy('CreateSelect2'); + Wait = jasmine.createSpy('Wait'); + ParseTypeChange = jasmine.createSpy('ParseTypeChange'); + ToJSON = jasmine.createSpy('ToJSON'); + + $provide.value('ClearScope', ClearScope); + $provide.value('Alert', Alert); + $provide.value('GenerateForm', GenerateForm); + $provide.value('state', state); + $provide.value('ProcessErrors', ProcessErrors); + $provide.value('CreateSelect2', CreateSelect2); + $provide.value('Wait', Wait); + $provide.value('ParseTypeChange', ParseTypeChange); + $provide.value('ToJSON', ToJSON); + })); + + beforeEach(angular.mock.inject( ($rootScope, $controller, $q, $httpBackend, _state_, _ConfigService_, _ClearScope_, _GetChoices_, _Alert_, _GenerateForm_, _ProcessErrors_, _CreateSelect2_, _Wait_, _ParseTypeChange_, _ToJSON_) => { + scope = $rootScope.$new(); + state = _state_; + q = $q; + ClearScope = _ClearScope_; + Alert = _Alert_; + GenerateForm = _GenerateForm_; + httpBackend = $httpBackend; + ProcessErrors = _ProcessErrors_; + CreateSelect2 = _CreateSelect2_; + Wait = _Wait_; + getLabelsDeferred = q.defer(); + createWorkflowJobTemplateDeferred = q.defer(); + ParseTypeChange = _ParseTypeChange_; + ToJSON = _ToJSON_; + + JobTemplateService.getLabelOptions = jasmine.createSpy('getLabelOptions').and.returnValue(getLabelsDeferred.promise); + JobTemplateService.createWorkflowJobTemplate = jasmine.createSpy('createWorkflowJobTemplate').and.returnValue(createWorkflowJobTemplateDeferred.promise); + + WorkflowAdd = $controller('WorkflowAdd', { + $scope: scope, + $state: state, + ClearScope: ClearScope, + Alert: Alert, + GenerateForm: GenerateForm, + JobTemplateService: JobTemplateService, + ProcessErrors: ProcessErrors, + CreateSelect2: CreateSelect2, + Wait: Wait, + ParseTypeChange: ParseTypeChange, + ToJSON + }); + })); + + it('should call ClearScope', ()=>{ + expect(ClearScope).toHaveBeenCalled(); + }); + + it('should call GenerateForm.inject', ()=>{ + expect(GenerateForm.inject).toHaveBeenCalled(); + }); + + it('should call GenerateForm.reset', ()=>{ + expect(GenerateForm.reset).toHaveBeenCalled(); + }); + + it('should get/set the label options and select2-ify the input', ()=>{ + // Resolve JobTemplateService.getLabelsForJobTemplate + getLabelsDeferred.resolve({ + foo: "bar" + }); + // We expect the digest cycle to fire off this call to /static/config.js so we go ahead and handle it + httpBackend.expectGET('/static/config.js').respond(200); + scope.$digest(); + expect(scope.labelOptions).toEqual({ + foo: "bar" + }); + expect(CreateSelect2).toHaveBeenCalledWith({ + element:'#workflow_labels', + multiple: true, + addNew: true + }); + }); + + it('should call ProcessErrors when getLabelsForJobTemplate returns a rejected promise', ()=>{ + // Reject JobTemplateService.getLabelsForJobTemplate + getLabelsDeferred.reject({ + data: "mockedData", + status: 400 + }); + // We expect the digest cycle to fire off this call to /static/config.js so we go ahead and handle it + httpBackend.expectGET('/static/config.js').respond(200); + scope.$digest(); + expect(ProcessErrors).toHaveBeenCalled(); + }); + + describe('scope.formSave()', () => { + + it('should call JobTemplateService.createWorkflowJobTemplate', ()=>{ + scope.name = "Test Workflow"; + scope.description = "This is a test description"; + scope.formSave(); + expect(JobTemplateService.createWorkflowJobTemplate).toHaveBeenCalledWith({ + name: "Test Workflow", + description: "This is a test description", + labels: undefined, + organization: undefined, + variables: undefined, + extra_vars: undefined + }); + }); + + }); + + describe('scope.formCancel()', () => { + + it('should transition to templates', ()=>{ + scope.formCancel(); + expect(state.transitionTo).toHaveBeenCalledWith('templates'); + }); + + }); + +}); diff --git a/awx/ui/tests/spec/workflows/workflow-maker.controller-test.js b/awx/ui/tests/spec/workflows/workflow-maker.controller-test.js new file mode 100644 index 0000000000..bc2547ef42 --- /dev/null +++ b/awx/ui/tests/spec/workflows/workflow-maker.controller-test.js @@ -0,0 +1,85 @@ +'use strict'; + +describe('Controller: WorkflowMaker', () => { + // Setup + let scope, + WorkflowMakerController, + WorkflowHelpService; + + beforeEach(angular.mock.module('Tower')); + beforeEach(angular.mock.module('jobTemplates', ($provide) => { + + WorkflowHelpService = jasmine.createSpyObj('WorkflowHelpService', [ + 'closeDialog', + 'addPlaceholderNode', + 'getSiblingConnectionTypes' + ]); + + $provide.value('WorkflowHelpService', WorkflowHelpService); + + })); + + beforeEach(angular.mock.inject( ($rootScope, $controller, _WorkflowHelpService_) => { + scope = $rootScope.$new(); + WorkflowHelpService = _WorkflowHelpService_; + + WorkflowMakerController = $controller('WorkflowMakerController', { + $scope: scope, + WorkflowHelpService: WorkflowHelpService + }); + + })); + + describe('scope.saveWorkflowMaker()', () => { + + it('should close the dialog', ()=>{ + scope.saveWorkflowMaker(); + expect(WorkflowHelpService.closeDialog).toHaveBeenCalled(); + }); + + }); + + describe('scope.startAddNode()', () => { + + }); + + describe('scope.confirmNodeForm()', () => { + + }); + + describe('scope.cancelNodeForm()', () => { + + }); + + describe('scope.startEditNode()', () => { + + }); + + describe('scope.startDeleteNode()', () => { + + }); + + describe('scope.cancelDeleteNode()', () => { + + }); + + describe('scope.confirmDeleteNode()', () => { + + }); + + describe('scope.toggleFormTab()', () => { + + }); + + describe('scope.toggle_job_template()', () => { + + }); + + describe('scope.toggle_project()', () => { + + }); + + describe('scope.toggle_inventory_source()', () => { + + }); +}); From 50f0465094cd32dee95b432015d61861b72d150a Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 3 Nov 2016 17:17:27 -0400 Subject: [PATCH 02/45] WIP integration with smart-search --- awx/ui/client/src/forms/Jobs.js | 2 + awx/ui/client/src/forms/WorkflowMaker.js | 14 +- .../edit-workflow/workflow-edit.controller.js | 2 +- .../list/job-templates-list.route.js | 7 + awx/ui/client/src/job-templates/main.js | 107 +- .../workflow-maker/workflow-help.service.js | 1 + .../workflow-maker.controller.js | 1255 ++++++++--------- .../workflow-maker.directive.js | 2 +- .../workflow-maker.partial.html | 12 +- awx/ui/client/src/shared/form-generator.js | 2 +- 10 files changed, 685 insertions(+), 719 deletions(-) diff --git a/awx/ui/client/src/forms/Jobs.js b/awx/ui/client/src/forms/Jobs.js index 0584c10159..dda37e158b 100644 --- a/awx/ui/client/src/forms/Jobs.js +++ b/awx/ui/client/src/forms/Jobs.js @@ -10,6 +10,8 @@ * @description This form is for adding/editing a Job */ +// @issue PLEASE KILL ME I NEED TO DIE + export default angular.module('JobFormDefinition', []) .value('JobForm', { diff --git a/awx/ui/client/src/forms/WorkflowMaker.js b/awx/ui/client/src/forms/WorkflowMaker.js index 6d3d602ed8..f5dda52c0a 100644 --- a/awx/ui/client/src/forms/WorkflowMaker.js +++ b/awx/ui/client/src/forms/WorkflowMaker.js @@ -18,7 +18,7 @@ export default addTitle: '', editTitle: '', name: 'workflow_maker', - base: 'job_templates', + basePath: 'job_templates', tabs: false, cancelButton: false, showHeader: false, @@ -48,7 +48,9 @@ export default type: 'lookup', sourceModel: 'inventory', sourceField: 'name', - ngClick: 'lookUpInventory()', + list: 'OrganizationList', + basePath: 'organization', + //ngClick: 'lookUpInventory()', requiredErrorMsg: "Please select an Inventory.", column: 1, class: 'Form-formGroup--fullWidth', @@ -56,10 +58,10 @@ export default dataTitle: 'Inventory', dataPlacement: 'right', dataContainer: "body", - ngShow: "selectedTemplate.ask_inventory_on_launch", - awRequiredWhen: { - reqExpression: 'selectedTemplate.ask_inventory_on_launch' - } + //ngShow: "selectedTemplate.ask_inventory_on_launch", + // awRequiredWhen: { + // reqExpression: 'selectedTemplate.ask_inventory_on_launch' + // } }, job_type: { label: 'Job Type', diff --git a/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js b/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js index 08eafd07b7..18a07e5fe5 100644 --- a/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js +++ b/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js @@ -480,7 +480,7 @@ } $scope.openWorkflowMaker = function() { - $scope.$broadcast("showWorkflowMaker"); + $state.go('.workflowMaker'); }; $scope.formSave = function () { diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.route.js b/awx/ui/client/src/job-templates/list/job-templates-list.route.js index b853179103..c39d6af766 100644 --- a/awx/ui/client/src/job-templates/list/job-templates-list.route.js +++ b/awx/ui/client/src/job-templates/list/job-templates-list.route.js @@ -10,6 +10,13 @@ export default { ncyBreadcrumb: { label: "TEMPLATES" }, + data: { + socket: { + "groups": { + "jobs": ["status_changed"] + } + } + }, params: { unified_job_templates_search: { value: { diff --git a/awx/ui/client/src/job-templates/main.js b/awx/ui/client/src/job-templates/main.js index 0e2159a1f1..7170a504a6 100644 --- a/awx/ui/client/src/job-templates/main.js +++ b/awx/ui/client/src/job-templates/main.js @@ -4,6 +4,8 @@ * All Rights Reserved *************************************************/ +import { templateUrl } from '../shared/template-url/template-url.factory'; + import jobTemplateService from './job-template.service'; import surveyMaker from './survey-maker/main'; @@ -25,8 +27,9 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp ]) .service('JobTemplateService', jobTemplateService) .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider ) { + function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow, + workflowMaker, inventoryLookup, credentialLookup, stateDefinitions = stateDefinitionsProvider.$get(), stateExtender = $stateExtenderProvider.$get(); @@ -72,6 +75,103 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } }); + workflowMaker = stateExtender.buildDefinition({ + name: 'templates.editWorkflowJobTemplate.workflowMaker', + url: '/workflow-maker', + views: { + 'modal': { + template: ` ` + }, + // 'jobsTemplateList@templates.editWorkflowJobTemplate.workflowMaker': { + // templateProvider: function(JobTemplateList, generateList) { + // let html = generateList.build({ + // list: JobTemplateList, + // mode: 'edit' + // }); + // return html; + // } + // }, + // 'inventorySyncList@templates.editWorkflowJobTemplate.workflowMaker': { + // templateProvider: function(InventoryList, generateList) { + // let html = generateList.build({ + // list: InventoryList, + // mode: 'edit' + // }); + // return html; + // } + // }, + // 'projectList@templates.editWorkflowJobTemplate.workflowMaker': { + // templateProvider: function(ProjectList, generateList) { + // let html = generateList.build({ + // list: ProjectList, + // mode: 'edit' + // }); + // return html; + // } + // }, + 'workflowForm@templates.editWorkflowJobTemplate.workflowMaker': { + templateProvider: function(WorkflowMakerForm, GenerateForm) { + let form = WorkflowMakerForm(); + let html = GenerateForm.buildHTML(form, { + mode: 'add', + related: false, + }); + return html; + } + } + } + }); + + inventoryLookup = stateExtender.buildDefinition({ + searchPrefix: 'inventory', + //squashSearchUrl: true, @issue enable + name: 'templates.editWorkflowJobTemplate.workflowMaker.inventory', + url: '/inventory', + data: { + lookup: true + }, + params: { + inventory_search: { + value: { page_size: '5'} + } + }, + views: { + 'modal@templates.editWorkflowJobTemplate.workflowMaker': { + templateProvider: function(ListDefinition, generateList) { + let list_html = generateList.build({ + mode: 'lookup', + list: ListDefinition, + input_type: 'radio' + }); + return `${list_html}`; + + } + } + }, + resolve: { + // ListDefinition: [InventoryList, function(list) { + // list.iterator = field.sourceModel; + // return list; + // }], + Dataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath', + (list, qs, $stateParams, GetBasePath) => { + let path = GetBasePath(list.name) || GetBasePath(list.basePath); + return qs.search(path, $stateParams[`${list.iterator}_search`]); + } + ] + }, + onExit: function($state) { + if ($state.transition) { + $('#form-modal').modal('hide'); + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + } + }, + }); + + + + return Promise.all([ addJobTemplate, editJobTemplate, @@ -82,8 +182,9 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp states: _.reduce(generated, (result, definition) => { return result.concat(definition.states); }, [ - stateExtender.buildDefinition(jobTemplatesListRoute) - + stateExtender.buildDefinition(jobTemplatesListRoute), + workflowMaker, + //inventoryLookup ]) }; }); diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js index f014ae938e..48cca0ac58 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js @@ -14,6 +14,7 @@ export default ['CreateDialog', 'Wait', '$q', function(CreateDialog, Wait, $q){ deferred.resolve(); }); Wait('start'); + debugger; CreateDialog({ id: 'workflow-modal-dialog', scope: params.scope, diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js index b0c66e6ce9..cd0fec200b 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js @@ -4,525 +4,439 @@ * All Rights Reserved *************************************************/ -export default - [ '$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateList', 'ProjectList', - 'GetBasePath', 'SearchInit', 'PaginateInit', 'Wait', 'JobTemplateService', +export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateList', 'ProjectList', + 'GetBasePath', 'Wait', 'JobTemplateService', 'ProcessErrors', 'InventorySourcesList', 'CreateSelect2', 'WorkflowMakerForm', - 'GenerateForm', 'LookUpInit', 'InventoryList', 'CredentialList', '$q', '$timeout', - function($scope, WorkflowHelpService, GenerateList, JobTemplateList, ProjectList, - GetBasePath, SearchInit, PaginateInit, Wait, JobTemplateService, - ProcessErrors, InventorySourcesList, CreateSelect2, WorkflowMakerForm, - GenerateForm, LookUpInit, InventoryList, CredentialList, $q, $timeout) { + 'GenerateForm', 'InventoryList', 'CredentialList', '$q', '$timeout', + function($scope, WorkflowHelpService, GenerateList, JobTemplateList, ProjectList, + GetBasePath, Wait, JobTemplateService, + ProcessErrors, InventorySourcesList, CreateSelect2, WorkflowMakerForm, + GenerateForm, InventoryList, CredentialList, $q, $timeout) { - let form = WorkflowMakerForm(), - generator = GenerateForm; + let form = WorkflowMakerForm(), + generator = GenerateForm; - $scope.workflowMakerFormConfig = { - nodeMode: "idle", - activeTab: "jobs", - formIsValid: false - }; + $scope.workflowMakerFormConfig = { + nodeMode: "idle", + activeTab: "jobs", + formIsValid: false + }; - // Set the intial edge type to success - $scope.edgeType = "success"; + // Set the intial edge type to success + $scope.edgeType = "success"; - $scope.job_type_options = [ - { - label: "Run", - value: "run" - }, - { - label: "Check", - value: "check" + $scope.job_type_options = [{ + label: "Run", + value: "run" + }, { + label: "Check", + value: "check" + }]; + + let job_template_url = GetBasePath('job_templates'); + // TODO: we won't be able to rely on this in the future for security purposes. Will need to come up + // with another way to get the list of job templates that have credentials that don't require passwords + // on launch + job_template_url += "?not__credential__vault_password=ASK¬__credential__password=ASK"; + //http://localhost:3000/api/v1/job_templates/?not__credential__vault_password=ASK¬__credential__password=ASK + + // Set up the lists for the add/edit node form + // let jobTemplatesList = _.cloneDeep(JobTemplateList); + // delete jobTemplatesList.fields.type; + // delete jobTemplatesList.fields.description; + // delete jobTemplatesList.fields.smart_status; + // delete jobTemplatesList.fields.labels; + // jobTemplatesList.fields.name.columnClass = "col-md-11"; + // jobTemplatesList.name = "workflow_job_templates"; + + // let project_url = GetBasePath('projects'); + + // let projectList = _.cloneDeep(ProjectList); + // delete projectList.fields.status; + // delete projectList.fields.scm_type; + // delete projectList.fields.last_updated; + // projectList.fields.name.columnClass = "col-md-11"; + // projectList.name = "workflow_projects"; + + //let inventory_sources_url = GetBasePath('inventory_sources'); + + //let inventorySourcesList = _.cloneDeep(InventorySourcesList); + + function init() { + $scope.treeDataMaster = angular.copy($scope.treeData.data); + WorkflowHelpService.openDialog({ + scope: $scope + }) + .then(function() { + + //$scope.$broadcast("refreshWorkflowChart"); + + }); + $scope.$watchCollection('workflow_job_templates', function() { + if ($scope.selectedTemplate) { + // Loop across the inventories and see if one of them should be "checked" + $scope.workflow_job_templates.forEach(function(row, i) { + if (row.id === $scope.selectedTemplate.id) { + $scope.workflow_job_templates[i].checked = 1; + } else { + $scope.workflow_job_templates[i].checked = 0; + } + }); } - ]; + }); - let job_template_url = GetBasePath('job_templates'); - // TODO: we won't be able to rely on this in the future for security purposes. Will need to come up - // with another way to get the list of job templates that have credentials that don't require passwords - // on launch - job_template_url += "?not__credential__vault_password=ASK¬__credential__password=ASK"; - //http://localhost:3000/api/v1/job_templates/?not__credential__vault_password=ASK¬__credential__password=ASK + $scope.$watchCollection('workflow_projects', function() { + if ($scope.selectedTemplate) { + // Loop across the inventories and see if one of them should be "checked" + $scope.workflow_projects.forEach(function(row, i) { + if (row.id === $scope.selectedTemplate.id) { + $scope.workflow_projects[i].checked = 1; + } else { + $scope.workflow_projects[i].checked = 0; + } + }); + } + }); - // Set up the lists for the add/edit node form - let jobTemplatesList = _.cloneDeep(JobTemplateList); - delete jobTemplatesList.fields.type; - delete jobTemplatesList.fields.description; - delete jobTemplatesList.fields.smart_status; - delete jobTemplatesList.fields.labels; - jobTemplatesList.fields.name.columnClass = "col-md-11"; - jobTemplatesList.name = "workflow_job_templates"; + $scope.$watchCollection('workflow_inventory_sources', function() { + if ($scope.selectedTemplate) { + // Loop across the inventories and see if one of them should be "checked" + $scope.workflow_inventory_sources.forEach(function(row, i) { + if (row.id === $scope.selectedTemplate.id) { + $scope.workflow_inventory_sources[i].checked = 1; + } else { + $scope.workflow_inventory_sources[i].checked = 0; + } + }); + } + }); - let project_url = GetBasePath('projects'); + $scope.$watchGroup(['selectedTemplate', 'edgeType'], function() { + if ($scope.selectedTemplate && $scope.edgeType) { + $scope.workflowMakerFormConfig.formIsValid = true; + } else { + $scope.workflowMakerFormConfig.formIsValid = false; + } + }); + } - let projectList = _.cloneDeep(ProjectList); - delete projectList.fields.status; - delete projectList.fields.scm_type; - delete projectList.fields.last_updated; - projectList.fields.name.columnClass = "col-md-11"; - projectList.name = "workflow_projects"; + function resetPromptFields() { + $scope.credential = null; + $scope.credential_name = null; + $scope.inventory = null; + $scope.inventory_name = null; + $scope.job_type = null; + $scope.limit = null; + $scope.job_tags = null; + $scope.skip_tags = null; + } - let inventory_sources_url = GetBasePath('inventory_sources'); + function resetNodeForm() { + $scope.workflowMakerFormConfig.nodeMode = "idle"; + $scope.showTypeOptions = false; + delete $scope.selectedTemplate; + delete $scope.workflow_job_templates; + delete $scope.workflow_projects; + delete $scope.workflow_inventory_sources; + delete $scope.placeholderNode; + delete $scope.betweenTwoNodes; + $scope.nodeBeingEdited = null; + $scope.edgeType = "success"; + $scope.edgeTypeRestriction = null; + $scope.workflowMakerFormConfig.activeTab = "jobs"; - let inventorySourcesList = _.cloneDeep(InventorySourcesList); + resetPromptFields(); - function init() { - $scope.$watchCollection('workflow_job_templates', function () { - if($scope.selectedTemplate) { - // Loop across the inventories and see if one of them should be "checked" - $scope.workflow_job_templates.forEach(function(row, i) { - if (row.id === $scope.selectedTemplate.id) { - $scope.workflow_job_templates[i].checked = 1; - } - else { - $scope.workflow_job_templates[i].checked = 0; - } - }); - } - }); + } - $scope.$watchCollection('workflow_projects', function () { - if($scope.selectedTemplate) { - // Loop across the inventories and see if one of them should be "checked" - $scope.workflow_projects.forEach(function(row, i) { - if (row.id === $scope.selectedTemplate.id) { - $scope.workflow_projects[i].checked = 1; - } - else { - $scope.workflow_projects[i].checked = 0; - } - }); - } - }); + $scope.closeWorkflowMaker = function() { + // Revert the data to the master which was created when the dialog was opened + $scope.treeData.data = angular.copy($scope.treeDataMaster); + WorkflowHelpService.closeDialog(); + }; - $scope.$watchCollection('workflow_inventory_sources', function () { - if($scope.selectedTemplate) { - // Loop across the inventories and see if one of them should be "checked" - $scope.workflow_inventory_sources.forEach(function(row, i) { - if (row.id === $scope.selectedTemplate.id) { - $scope.workflow_inventory_sources[i].checked = 1; - } - else { - $scope.workflow_inventory_sources[i].checked = 0; - } - }); - } - }); + $scope.saveWorkflowMaker = function() { + WorkflowHelpService.closeDialog(); + }; - $scope.$watchGroup(['selectedTemplate', 'edgeType'], function() { - if($scope.selectedTemplate && $scope.edgeType) { - $scope.workflowMakerFormConfig.formIsValid = true; - } - else { - $scope.workflowMakerFormConfig.formIsValid = false; - } - }); + /* ADD NODE FUNCTIONS */ + + $scope.startAddNode = function(parent, betweenTwoNodes) { + + if ($scope.placeholderNode || $scope.nodeBeingEdited) { + $scope.cancelNodeForm(); } - function resetPromptFields() { - $scope.credential = null; - $scope.credential_name = null; - $scope.inventory = null; - $scope.inventory_name = null; - $scope.job_type = null; - $scope.limit = null; - $scope.job_tags = null; - $scope.skip_tags = null; - } + $scope.workflowMakerFormConfig.nodeMode = "add"; + $scope.addParent = parent; + $scope.betweenTwoNodes = betweenTwoNodes; - function resetNodeForm() { - $scope.workflowMakerFormConfig.nodeMode = "idle"; + $scope.placeholderNode = WorkflowHelpService.addPlaceholderNode({ + parent: parent, + betweenTwoNodes: betweenTwoNodes, + tree: $scope.treeData.data, + id: $scope.treeData.nextIndex + }); + + $scope.treeData.nextIndex++; + + let siblingConnectionTypes = WorkflowHelpService.getSiblingConnectionTypes({ + tree: $scope.treeData.data, + parentId: betweenTwoNodes ? parent.source.id : parent.id + }); + + if (parent && ((betweenTwoNodes && parent.source.isStartNode) || (!betweenTwoNodes && parent.isStartNode))) { + // We don't want to give the user the option to select + // a type as this node will always be executed + $scope.edgeType = "always"; $scope.showTypeOptions = false; - delete $scope.selectedTemplate; - delete $scope.workflow_job_templates; - delete $scope.workflow_projects; - delete $scope.workflow_inventory_sources; - delete $scope.placeholderNode; - delete $scope.betweenTwoNodes; - $scope.nodeBeingEdited = null; - $scope.edgeType = "success"; - $scope.edgeTypeRestriction = null; - $scope.workflowMakerFormConfig.activeTab = "jobs"; - - resetPromptFields(); + } else { + if ((_.includes(siblingConnectionTypes, "success") || _.includes(siblingConnectionTypes, "failure")) && _.includes(siblingConnectionTypes, "always")) { + // This is a problem... + } else if (_.includes(siblingConnectionTypes, "success") || _.includes(siblingConnectionTypes, "failure")) { + $scope.edgeTypeRestriction = "successFailure"; + $scope.edgeType = "success"; + } else if (_.includes(siblingConnectionTypes, "always")) { + $scope.edgeTypeRestriction = "always"; + $scope.edgeType = "always"; + } + $scope.showTypeOptions = true; } - function loadJobTemplates() { - SearchInit({ - scope: $scope, - set: jobTemplatesList.name, - list: jobTemplatesList, - url: job_template_url - }); + $scope.$broadcast("refreshWorkflowChart"); - PaginateInit({ - scope: $scope, - list: jobTemplatesList, - url: job_template_url, - mode: 'lookup', - pageSize: 5 - }); + }; - $scope.search(JobTemplateList.iterator); + $scope.confirmNodeForm = function() { + if ($scope.workflowMakerFormConfig.nodeMode === "add") { + if ($scope.selectedTemplate && $scope.edgeType) { + + $scope.placeholderNode.unifiedJobTemplate = $scope.selectedTemplate; + $scope.placeholderNode.edgeType = $scope.edgeType; + if ($scope.placeholderNode.unifiedJobTemplate.type === 'job_template') { + $scope.placeholderNode.promptValues = { + credential: { + id: $scope.credential, + name: $scope.credential_name + }, + inventory: { + id: $scope.inventory, + name: $scope.inventory_name + }, + limit: $scope.limit, + job_type: $scope.job_type && $scope.job_type.value ? $scope.job_type.value : null, + job_tags: $scope.job_tags, + skip_tags: $scope.skip_tags + }; + } + $scope.placeholderNode.canEdit = true; + + delete $scope.placeholderNode.placeholder; + + resetNodeForm(); + + // Increment the total node counter + $scope.treeData.data.totalNodes++; + + } + } else if ($scope.workflowMakerFormConfig.nodeMode === "edit") { + if ($scope.selectedTemplate && $scope.edgeType) { + $scope.nodeBeingEdited.unifiedJobTemplate = $scope.selectedTemplate; + $scope.nodeBeingEdited.edgeType = $scope.edgeType; + + if ($scope.nodeBeingEdited.unifiedJobTemplate.type === 'job_template') { + $scope.nodeBeingEdited.promptValues = { + credential: { + id: $scope.credential, + name: $scope.credential_name + }, + inventory: { + id: $scope.inventory, + name: $scope.inventory_name + }, + limit: $scope.limit, + job_type: $scope.job_type && $scope.job_type.value ? $scope.job_type.value : null, + job_tags: $scope.job_tags, + skip_tags: $scope.skip_tags + }; + } + + $scope.nodeBeingEdited.isActiveEdit = false; + + $scope.nodeBeingEdited.edited = true; + + resetNodeForm(); + } } - function loadProjects() { - SearchInit({ - scope: $scope, - set: projectList.name, - list: projectList, - url: project_url - }); + $scope.$broadcast("refreshWorkflowChart"); + }; - PaginateInit({ - scope: $scope, - list: projectList, - url: project_url, - mode: 'lookup', - pageSize: 5 + $scope.cancelNodeForm = function() { + if ($scope.workflowMakerFormConfig.nodeMode === "add") { + // Remove the placeholder node from the tree + WorkflowHelpService.removeNodeFromTree({ + tree: $scope.treeData.data, + nodeToBeDeleted: $scope.placeholderNode }); - - $scope.search(ProjectList.iterator); + } else if ($scope.workflowMakerFormConfig.nodeMode === "edit") { + $scope.nodeBeingEdited.isActiveEdit = false; } + // Reset the form + resetNodeForm(); - function loadInventorySources() { - SearchInit({ - scope: $scope, - set: inventorySourcesList.name, - list: inventorySourcesList, - url: inventory_sources_url - }); + $scope.$broadcast("refreshWorkflowChart"); + }; - PaginateInit({ - scope: $scope, - list: inventorySourcesList, - url: inventory_sources_url, - mode: 'lookup', - pageSize: 5 - }); + /* EDIT NODE FUNCTIONS */ - $scope.search(InventorySourcesList.iterator); - } + $scope.startEditNode = function(nodeToEdit) { - $scope.closeWorkflowMaker = function() { - // Revert the data to the master which was created when the dialog was opened - $scope.treeData.data = angular.copy($scope.treeDataMaster); - WorkflowHelpService.closeDialog(); - }; - - $scope.saveWorkflowMaker = function() { - WorkflowHelpService.closeDialog(); - }; - - /* ADD NODE FUNCTIONS */ - - $scope.startAddNode = function(parent, betweenTwoNodes) { - - if($scope.placeholderNode || $scope.nodeBeingEdited) { + if (!$scope.nodeBeingEdited || ($scope.nodeBeingEdited && $scope.nodeBeingEdited.id !== nodeToEdit.id)) { + if ($scope.placeholderNode || $scope.nodeBeingEdited) { $scope.cancelNodeForm(); } - $scope.workflowMakerFormConfig.nodeMode = "add"; - $scope.addParent = parent; - $scope.betweenTwoNodes = betweenTwoNodes; + $scope.workflowMakerFormConfig.nodeMode = "edit"; - $scope.placeholderNode = WorkflowHelpService.addPlaceholderNode({ - parent: parent, - betweenTwoNodes: betweenTwoNodes, - tree: $scope.treeData.data, - id: $scope.treeData.nextIndex + let parent = WorkflowHelpService.searchTree({ + element: $scope.treeData.data, + matchingId: nodeToEdit.parent.id }); - $scope.treeData.nextIndex++; - - loadJobTemplates(); - loadProjects(); - loadInventorySources(); - - let siblingConnectionTypes = WorkflowHelpService.getSiblingConnectionTypes({ - tree: $scope.treeData.data, - parentId: betweenTwoNodes ? parent.source.id : parent.id + $scope.nodeBeingEdited = WorkflowHelpService.searchTree({ + element: parent, + matchingId: nodeToEdit.id }); - if(parent && ((betweenTwoNodes && parent.source.isStartNode) || (!betweenTwoNodes && parent.isStartNode))) { - // We don't want to give the user the option to select - // a type as this node will always be executed - $scope.edgeType = "always"; - $scope.showTypeOptions = false; - } - else { - if((_.includes(siblingConnectionTypes, "success") || _.includes(siblingConnectionTypes, "failure")) && _.includes(siblingConnectionTypes, "always")) { - // This is a problem... - } - else if(_.includes(siblingConnectionTypes, "success") || _.includes(siblingConnectionTypes, "failure")) { - $scope.edgeTypeRestriction = "successFailure"; - $scope.edgeType = "success"; - } - else if(_.includes(siblingConnectionTypes, "always")) { - $scope.edgeTypeRestriction = "always"; - $scope.edgeType = "always"; - } + $scope.nodeBeingEdited.isActiveEdit = true; - $scope.showTypeOptions = true; - } + let finishConfiguringEdit = function() { - $scope.$broadcast("refreshWorkflowChart"); - - }; - - $scope.confirmNodeForm = function() { - if($scope.workflowMakerFormConfig.nodeMode === "add") { - if($scope.selectedTemplate && $scope.edgeType) { - - $scope.placeholderNode.unifiedJobTemplate = $scope.selectedTemplate; - $scope.placeholderNode.edgeType = $scope.edgeType; - if($scope.placeholderNode.unifiedJobTemplate.type === 'job_template') { - $scope.placeholderNode.promptValues = { - credential: { - id: $scope.credential, - name: $scope.credential_name - }, - inventory: { - id: $scope.inventory, - name: $scope.inventory_name - }, - limit: $scope.limit, - job_type: $scope.job_type && $scope.job_type.value ? $scope.job_type.value : null, - job_tags: $scope.job_tags, - skip_tags: $scope.skip_tags - }; + // build any prompt values + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_credential_on_launch) { + if ($scope.nodeBeingEdited.promptValues && $scope.nodeBeingEdited.promptValues.credential) { + $scope.credential_name = $scope.nodeBeingEdited.promptValues.credential.name; + $scope.credentiial = $scope.nodeBeingEdited.promptValues.credential.id; + } else if ($scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential) { + $scope.credential_name = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.name ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.name : null; + $scope.credential = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.id ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.id : null; + } else { + $scope.credential_name = null; + $scope.credential = null; } - $scope.placeholderNode.canEdit = true; - - delete $scope.placeholderNode.placeholder; - - resetNodeForm(); - - // Increment the total node counter - $scope.treeData.data.totalNodes++; - } - } - else if($scope.workflowMakerFormConfig.nodeMode === "edit") { - if($scope.selectedTemplate && $scope.edgeType) { - $scope.nodeBeingEdited.unifiedJobTemplate = $scope.selectedTemplate; - $scope.nodeBeingEdited.edgeType = $scope.edgeType; - if($scope.nodeBeingEdited.unifiedJobTemplate.type === 'job_template') { - $scope.nodeBeingEdited.promptValues = { - credential: { - id: $scope.credential, - name: $scope.credential_name - }, - inventory: { - id: $scope.inventory, - name: $scope.inventory_name - }, - limit: $scope.limit, - job_type: $scope.job_type && $scope.job_type.value ? $scope.job_type.value : null, - job_tags: $scope.job_tags, - skip_tags: $scope.skip_tags + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_inventory_on_launch) { + if ($scope.nodeBeingEdited.promptValues && $scope.nodeBeingEdited.promptValues.inventory) { + $scope.inventory_name = $scope.nodeBeingEdited.promptValues.inventory.name; + $scope.inventory = $scope.nodeBeingEdited.promptValues.inventory.id; + } else if ($scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory) { + $scope.inventory_name = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.name ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.name : null; + $scope.inventory = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.id ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.id : null; + } else { + $scope.inventory_name = null; + $scope.inventory = null; + } + } + + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_job_type_on_launch) { + if ($scope.nodeBeingEdited.promptValues && $scope.nodeBeingEdited.promptValues.job_type) { + $scope.job_type = { + value: $scope.nodeBeingEdited.promptValues.job_type + }; + } else if ($scope.nodeBeingEdited.originalNodeObj.job_type) { + $scope.job_type = { + value: $scope.nodeBeingEdited.originalNodeObj.job_type + }; + } else if ($scope.nodeBeingEdited.unifiedJobTemplate.job_type) { + $scope.job_type = { + value: $scope.nodeBeingEdited.unifiedJobTemplate.job_type + }; + } else { + $scope.job_type = { + value: null }; } - $scope.nodeBeingEdited.isActiveEdit = false; - - $scope.nodeBeingEdited.edited = true; - - resetNodeForm(); - } - } - - $scope.$broadcast("refreshWorkflowChart"); - }; - - $scope.cancelNodeForm = function() { - if($scope.workflowMakerFormConfig.nodeMode === "add") { - // Remove the placeholder node from the tree - WorkflowHelpService.removeNodeFromTree({ - tree: $scope.treeData.data, - nodeToBeDeleted: $scope.placeholderNode - }); - } - else if($scope.workflowMakerFormConfig.nodeMode === "edit") { - $scope.nodeBeingEdited.isActiveEdit = false; - } - - // Reset the form - resetNodeForm(); - - $scope.$broadcast("refreshWorkflowChart"); - }; - - /* EDIT NODE FUNCTIONS */ - - $scope.startEditNode = function(nodeToEdit) { - - if(!$scope.nodeBeingEdited || ($scope.nodeBeingEdited && $scope.nodeBeingEdited.id !== nodeToEdit.id)) { - if($scope.placeholderNode || $scope.nodeBeingEdited) { - $scope.cancelNodeForm(); - } - - $scope.workflowMakerFormConfig.nodeMode = "edit"; - - let parent = WorkflowHelpService.searchTree({ - element: $scope.treeData.data, - matchingId: nodeToEdit.parent.id - }); - - $scope.nodeBeingEdited = WorkflowHelpService.searchTree({ - element: parent, - matchingId: nodeToEdit.id - }); - - $scope.nodeBeingEdited.isActiveEdit = true; - - let finishConfiguringEdit = function() { - - // build any prompt values - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_credential_on_launch) { - if($scope.nodeBeingEdited.promptValues && $scope.nodeBeingEdited.promptValues.credential) { - $scope.credential_name = $scope.nodeBeingEdited.promptValues.credential.name; - $scope.credentiial = $scope.nodeBeingEdited.promptValues.credential.id; - } - else if($scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential) { - $scope.credential_name = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.name ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.name : null; - $scope.credential = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.id ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.credential.id : null; - } - else { - $scope.credential_name = null; - $scope.credential = null; - } - } - - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_inventory_on_launch) { - if($scope.nodeBeingEdited.promptValues && $scope.nodeBeingEdited.promptValues.inventory) { - $scope.inventory_name = $scope.nodeBeingEdited.promptValues.inventory.name; - $scope.inventory = $scope.nodeBeingEdited.promptValues.inventory.id; - } - else if($scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory) { - $scope.inventory_name = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.name ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.name : null; - $scope.inventory = $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.id ? $scope.nodeBeingEdited.unifiedJobTemplate.summary_fields.inventory.id : null; - } - else { - $scope.inventory_name = null; - $scope.inventory = null; - } - } - - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_job_type_on_launch) { - if($scope.nodeBeingEdited.promptValues && $scope.nodeBeingEdited.promptValues.job_type) { - $scope.job_type = { - value: $scope.nodeBeingEdited.promptValues.job_type - }; - } - else if($scope.nodeBeingEdited.originalNodeObj.job_type) { - $scope.job_type = { - value: $scope.nodeBeingEdited.originalNodeObj.job_type - }; - } - else if($scope.nodeBeingEdited.unifiedJobTemplate.job_type) { - $scope.job_type = { - value: $scope.nodeBeingEdited.unifiedJobTemplate.job_type - }; - } - else { - $scope.job_type = { - value: null - }; - } - - // The default needs to be in place before we can select2-ify the dropdown - $timeout(function() { - CreateSelect2({ - element: '#workflow_maker_job_type', - multiple: false - }); + // The default needs to be in place before we can select2-ify the dropdown + $timeout(function() { + CreateSelect2({ + element: '#workflow_maker_job_type', + multiple: false }); - } + }); + } - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_limit_on_launch) { - if($scope.nodeBeingEdited.promptValues && typeof $scope.nodeBeingEdited.promptValues.limit === 'string') { - $scope.limit = $scope.nodeBeingEdited.promptValues.limit; - } - else if(typeof $scope.nodeBeingEdited.originalNodeObj.limit === 'string') { - $scope.limit = $scope.nodeBeingEdited.originalNodeObj.limit; - } - else if(typeof $scope.nodeBeingEdited.unifiedJobTemplate.limit === 'string') { - $scope.limit = $scope.nodeBeingEdited.unifiedJobTemplate.limit; - } - else { - $scope.limit = null; - } + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_limit_on_launch) { + if ($scope.nodeBeingEdited.promptValues && typeof $scope.nodeBeingEdited.promptValues.limit === 'string') { + $scope.limit = $scope.nodeBeingEdited.promptValues.limit; + } else if (typeof $scope.nodeBeingEdited.originalNodeObj.limit === 'string') { + $scope.limit = $scope.nodeBeingEdited.originalNodeObj.limit; + } else if (typeof $scope.nodeBeingEdited.unifiedJobTemplate.limit === 'string') { + $scope.limit = $scope.nodeBeingEdited.unifiedJobTemplate.limit; + } else { + $scope.limit = null; } - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_skip_tags_on_launch) { - if($scope.nodeBeingEdited.promptValues && typeof $scope.nodeBeingEdited.promptValues.skip_tags === 'string') { - $scope.skip_tags = $scope.nodeBeingEdited.promptValues.skip_tags; - } - else if(typeof $scope.nodeBeingEdited.originalNodeObj.skip_tags === 'string') { - $scope.skip_tags = $scope.nodeBeingEdited.originalNodeObj.skip_tags; - } - else if(typeof $scope.nodeBeingEdited.unifiedJobTemplate.skip_tags === 'string') { - $scope.skip_tags = $scope.nodeBeingEdited.unifiedJobTemplate.skip_tags; - } - else { - $scope.skip_tags = null; - } + } + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_skip_tags_on_launch) { + if ($scope.nodeBeingEdited.promptValues && typeof $scope.nodeBeingEdited.promptValues.skip_tags === 'string') { + $scope.skip_tags = $scope.nodeBeingEdited.promptValues.skip_tags; + } else if (typeof $scope.nodeBeingEdited.originalNodeObj.skip_tags === 'string') { + $scope.skip_tags = $scope.nodeBeingEdited.originalNodeObj.skip_tags; + } else if (typeof $scope.nodeBeingEdited.unifiedJobTemplate.skip_tags === 'string') { + $scope.skip_tags = $scope.nodeBeingEdited.unifiedJobTemplate.skip_tags; + } else { + $scope.skip_tags = null; } - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_tags_on_launch) { - if($scope.nodeBeingEdited.promptValues && typeof $scope.nodeBeingEdited.promptValues.job_tags === 'string') { - $scope.job_tags = $scope.nodeBeingEdited.promptValues.job_tags; - } - else if(typeof $scope.nodeBeingEdited.originalNodeObj.job_tags === 'string') { - $scope.job_tags = $scope.nodeBeingEdited.originalNodeObj.job_tags; - } - else if(typeof $scope.nodeBeingEdited.unifiedJobTemplate.job_tags === 'string') { - $scope.job_tags = $scope.nodeBeingEdited.unifiedJobTemplate.job_tags; - } - else { - $scope.job_tags = null; - } + } + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_tags_on_launch) { + if ($scope.nodeBeingEdited.promptValues && typeof $scope.nodeBeingEdited.promptValues.job_tags === 'string') { + $scope.job_tags = $scope.nodeBeingEdited.promptValues.job_tags; + } else if (typeof $scope.nodeBeingEdited.originalNodeObj.job_tags === 'string') { + $scope.job_tags = $scope.nodeBeingEdited.originalNodeObj.job_tags; + } else if (typeof $scope.nodeBeingEdited.unifiedJobTemplate.job_tags === 'string') { + $scope.job_tags = $scope.nodeBeingEdited.unifiedJobTemplate.job_tags; + } else { + $scope.job_tags = null; } + } - if($scope.nodeBeingEdited.unifiedJobTemplate.type === "job_template") { + if ($scope.nodeBeingEdited.unifiedJobTemplate.type === "job_template") { + $scope.workflowMakerFormConfig.activeTab = "jobs"; + } + + $scope.selectedTemplate = $scope.nodeBeingEdited.unifiedJobTemplate; + + switch ($scope.nodeBeingEdited.unifiedJobTemplate.type) { + case "job_template": $scope.workflowMakerFormConfig.activeTab = "jobs"; - } + break; + case "project": + $scope.workflowMakerFormConfig.activeTab = "project_sync"; + break; + case "inventory_source": + $scope.workflowMakerFormConfig.activeTab = "inventory_sync"; + break; + } - $scope.selectedTemplate = $scope.nodeBeingEdited.unifiedJobTemplate; + $scope.edgeType = $scope.nodeBeingEdited.edgeType; + $scope.showTypeOptions = (parent && parent.isStartNode) ? false : true; - switch($scope.nodeBeingEdited.unifiedJobTemplate.type) { - case "job_template": - $scope.workflowMakerFormConfig.activeTab = "jobs"; - break; - case "project": - $scope.workflowMakerFormConfig.activeTab = "project_sync"; - break; - case "inventory_source": - $scope.workflowMakerFormConfig.activeTab = "inventory_sync"; - break; - } + $scope.$broadcast("refreshWorkflowChart"); + }; - loadJobTemplates(); - loadProjects(); - loadInventorySources(); + // Determine whether or not we need to go out and GET this nodes unified job template + // in order to determine whether or not prompt fields are needed - $scope.edgeType = $scope.nodeBeingEdited.edgeType; - $scope.showTypeOptions = (parent && parent.isStartNode) ? false : true; + if (!$scope.nodeBeingEdited.isNew && !$scope.nodeBeingEdited.edited && $scope.nodeBeingEdited.unifiedJobTemplate.unified_job_type && $scope.nodeBeingEdited.unifiedJobTemplate.unified_job_type === 'job') { + // This is a node that we got back from the api with an incomplete + // unified job template so we're going to pull down the whole object - $scope.$broadcast("refreshWorkflowChart"); - }; - - // Determine whether or not we need to go out and GET this nodes unified job template - // in order to determine whether or not prompt fields are needed - - if(!$scope.nodeBeingEdited.isNew && !$scope.nodeBeingEdited.edited && $scope.nodeBeingEdited.unifiedJobTemplate.unified_job_type && $scope.nodeBeingEdited.unifiedJobTemplate.unified_job_type === 'job') { - // This is a node that we got back from the api with an incomplete - // unified job template so we're going to pull down the whole object - - JobTemplateService.getUnifiedJobTemplate($scope.nodeBeingEdited.unifiedJobTemplate.id) - .then(function(data){ + JobTemplateService.getUnifiedJobTemplate($scope.nodeBeingEdited.unifiedJobTemplate.id) + .then(function(data) { $scope.nodeBeingEdited.unifiedJobTemplate = _.clone(data.data.results[0]); @@ -530,285 +444,222 @@ export default let retrievingCredential = false; let retrievingInventory = false; - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_credential_on_launch && $scope.nodeBeingEdited.originalNodeObj.credential) { + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_credential_on_launch && $scope.nodeBeingEdited.originalNodeObj.credential) { defers.push(JobTemplateService.getCredential($scope.nodeBeingEdited.originalNodeObj.credential)); retrievingCredential = true; } - if($scope.nodeBeingEdited.unifiedJobTemplate.ask_inventory_on_launch && $scope.nodeBeingEdited.originalNodeObj.inventory) { + if ($scope.nodeBeingEdited.unifiedJobTemplate.ask_inventory_on_launch && $scope.nodeBeingEdited.originalNodeObj.inventory) { defers.push(JobTemplateService.getInventory($scope.nodeBeingEdited.originalNodeObj.inventory)); retrievingInventory = true; } $q.all(defers) - .then(function(responses) { - if(retrievingCredential) { - $scope.credential = responses[0].data.id; - $scope.credential_name = responses[0].data.name; - $scope.nodeBeingEdited.promptValues.credential = { - name: responses[0].data.name, - id: responses[0].data.id - }; + .then(function(responses) { + if (retrievingCredential) { + $scope.credential = responses[0].data.id; + $scope.credential_name = responses[0].data.name; + $scope.nodeBeingEdited.promptValues.credential = { + name: responses[0].data.name, + id: responses[0].data.id + }; - if(retrievingInventory) { - $scope.inventory = responses[1].data.id; - $scope.inventory_name = responses[1].data.name; + if (retrievingInventory) { + $scope.inventory = responses[1].data.id; + $scope.inventory_name = responses[1].data.name; + $scope.nodeBeingEdited.promptValues.inventory = { + name: responses[1].data.name, + id: responses[1].data.id + }; + } + } else if (retrievingInventory) { + $scope.inventory = responses[0].data.id; + $scope.inventory_name = responses[0].data.name; $scope.nodeBeingEdited.promptValues.inventory = { - name: responses[1].data.name, - id: responses[1].data.id + name: responses[0].data.name, + id: responses[0].data.id }; } - } - else if(retrievingInventory) { - $scope.inventory = responses[0].data.id; - $scope.inventory_name = responses[0].data.name; - $scope.nodeBeingEdited.promptValues.inventory = { - name: responses[0].data.name, - id: responses[0].data.id - }; - } - finishConfiguringEdit(); - }); + finishConfiguringEdit(); + }); }, function(error) { ProcessErrors($scope, error.data, error.status, form, { hdr: 'Error!', msg: 'Failed to get unified job template. GET returned ' + - 'status: ' + error.status + 'status: ' + error.status }); }); - } - else { - finishConfiguringEdit(); - } - + } else { + finishConfiguringEdit(); } - }; - - /* DELETE NODE FUNCTIONS */ - - function resetDeleteNode() { - $scope.nodeToBeDeleted = null; - $scope.deleteOverlayVisible = false; } - $scope.startDeleteNode = function(nodeToDelete) { - $scope.nodeToBeDeleted = nodeToDelete; - $scope.deleteOverlayVisible = true; - }; + }; + + /* DELETE NODE FUNCTIONS */ + + function resetDeleteNode() { + $scope.nodeToBeDeleted = null; + $scope.deleteOverlayVisible = false; + } + + $scope.startDeleteNode = function(nodeToDelete) { + $scope.nodeToBeDeleted = nodeToDelete; + $scope.deleteOverlayVisible = true; + }; + + $scope.cancelDeleteNode = function() { + resetDeleteNode(); + }; + + $scope.confirmDeleteNode = function() { + if ($scope.nodeToBeDeleted) { + + // TODO: turn this into a promise so that we can handle errors + + WorkflowHelpService.removeNodeFromTree({ + tree: $scope.treeData.data, + nodeToBeDeleted: $scope.nodeToBeDeleted + }); + + if ($scope.nodeToBeDeleted.isNew !== true) { + $scope.treeData.data.deletedNodes.push($scope.nodeToBeDeleted.nodeId); + } + + if ($scope.nodeToBeDeleted.isActiveEdit) { + resetNodeForm(); + } - $scope.cancelDeleteNode = function() { resetDeleteNode(); - }; - $scope.confirmDeleteNode = function() { - if($scope.nodeToBeDeleted) { + $scope.$broadcast("refreshWorkflowChart"); - // TODO: turn this into a promise so that we can handle errors + $scope.treeData.data.totalNodes--; + } - WorkflowHelpService.removeNodeFromTree({ - tree: $scope.treeData.data, - nodeToBeDeleted: $scope.nodeToBeDeleted - }); + }; - if($scope.nodeToBeDeleted.isNew !== true) { - $scope.treeData.data.deletedNodes.push($scope.nodeToBeDeleted.nodeId); - } + $scope.toggleFormTab = function(tab) { + if ($scope.workflowMakerFormConfig.activeTab !== tab) { + $scope.workflowMakerFormConfig.activeTab = tab; + } + }; - if($scope.nodeToBeDeleted.isActiveEdit) { - resetNodeForm(); - } + $scope.toggle_job_template = function(id) { - resetDeleteNode(); - - $scope.$broadcast("refreshWorkflowChart"); - - $scope.treeData.data.totalNodes--; - } - - }; - - $scope.toggleFormTab = function(tab) { - if($scope.workflowMakerFormConfig.activeTab !== tab) { - $scope.workflowMakerFormConfig.activeTab = tab; - } - }; - - $scope.toggle_job_template = function(id) { - - $scope.workflow_projects.forEach(function(row, i) { - $scope.workflow_projects[i].checked = 0; - }); - - $scope.workflow_inventory_sources.forEach(function(row, i) { - $scope.workflow_inventory_sources[i].checked = 0; - }); - - $scope.workflow_job_templates.forEach(function(row, i) { - if (row.id === id) { - $scope.selectedTemplate = angular.copy(row); - if($scope.selectedTemplate.ask_credential_on_launch) { - if($scope.selectedTemplate.summary_fields.credential) { - $scope.credential_name = $scope.selectedTemplate.summary_fields.credential.name ? $scope.selectedTemplate.summary_fields.credential.name : null; - $scope.credential = $scope.selectedTemplate.summary_fields.credential.id ? $scope.selectedTemplate.summary_fields.credential.id : null; - } - else { - $scope.credential_name = null; - $scope.credential = null; - } - } - - if($scope.selectedTemplate.ask_inventory_on_launch) { - if($scope.selectedTemplate.summary_fields.inventory) { - $scope.inventory_name = $scope.selectedTemplate.summary_fields.inventory.name ? $scope.selectedTemplate.summary_fields.inventory.name : null; - $scope.inventory = $scope.selectedTemplate.summary_fields.inventory.id ? $scope.selectedTemplate.summary_fields.inventory.id : null; - } - else { - $scope.inventory_name = null; - $scope.inventory = null; - } - } - - if($scope.selectedTemplate.ask_job_type_on_launch) { - $scope.job_type = { - value: $scope.selectedTemplate.job_type ? $scope.selectedTemplate.job_type : null - }; - - // The default needs to be in place before we can select2-ify the dropdown - CreateSelect2({ - element: '#workflow_maker_job_type', - multiple: false - }); - } - - if($scope.selectedTemplate.ask_limit_on_launch) { - $scope.limit = $scope.selectedTemplate.limit ? $scope.selectedTemplate.limit : null; - } - - if($scope.selectedTemplate.ask_skip_tags_on_launch) { - $scope.skip_tags = $scope.selectedTemplate.skip_tags ? $scope.selectedTemplate.skip_tags : null; - } - - if($scope.selectedTemplate.ask_tags_on_launch) { - $scope.job_tags = $scope.selectedTemplate.job_tags ? $scope.selectedTemplate.job_tags : null; - } - - $scope.workflow_job_templates[i].checked = 1; - } else { - $scope.workflow_job_templates[i].checked = 0; - } - }); - - }; - - $scope.toggle_project = function(id) { - - resetPromptFields(); - - $scope.workflow_job_templates.forEach(function(row, i) { - $scope.workflow_job_templates[i].checked = 0; - }); - - $scope.workflow_inventory_sources.forEach(function(row, i) { - $scope.workflow_inventory_sources[i].checked = 0; - }); - - $scope.workflow_projects.forEach(function(row, i) { - if (row.id === id) { - $scope.selectedTemplate = angular.copy(row); - $scope.workflow_projects[i].checked = 1; - } else { - $scope.workflow_projects[i].checked = 0; - } - }); - - }; - - $scope.toggle_inventory_source = function(id) { - - resetPromptFields(); - - $scope.workflow_job_templates.forEach(function(row, i) { - $scope.workflow_job_templates[i].checked = 0; - }); - - $scope.workflow_projects.forEach(function(row, i) { - $scope.workflow_projects[i].checked = 0; - }); - - $scope.workflow_inventory_sources.forEach(function(row, i) { - if (row.id === id) { - $scope.selectedTemplate = angular.copy(row); - $scope.workflow_inventory_sources[i].checked = 1; - } else { - $scope.workflow_inventory_sources[i].checked = 0; - } - }); - - }; - - $scope.$on('showWorkflowMaker', function(){ - $scope.treeDataMaster = angular.copy($scope.treeData.data); - WorkflowHelpService.openDialog({ - scope: $scope - }) - .then(function(){ - - $scope.$broadcast("refreshWorkflowChart"); - - generator.inject(form, { - mode: 'add', - related: false, - scope: $scope, - id: 'workflow-maker-form' - }); - - LookUpInit({ - scope: $scope, - form: form, - list: InventoryList, - field: 'inventory', - input_type: "radio" - }); - - LookUpInit({ - url: GetBasePath('credentials') + '?kind=ssh', - scope: $scope, - form: form, - current_item: null, - list: CredentialList, - field: 'credential', - hdr: 'Select Machine Credential', - input_type: "radio" - }); - - GenerateList.inject(jobTemplatesList, { - mode: 'lookup', - id: 'workflow-jobs-list', - scope: $scope, - input_type: 'radio' - }); - - GenerateList.inject(projectList, { - mode: 'lookup', - id: 'workflow-project-sync-list', - scope: $scope, - input_type: 'radio' - }); - - GenerateList.inject(inventorySourcesList, { - mode: 'lookup', - id: 'workflow-inventory-sync-list', - scope: $scope, - input_type: 'radio' - }); - - }); + $scope.workflow_projects.forEach(function(row, i) { + $scope.workflow_projects[i].checked = 0; }); - init(); + $scope.workflow_inventory_sources.forEach(function(row, i) { + $scope.workflow_inventory_sources[i].checked = 0; + }); - } - ]; + $scope.workflow_job_templates.forEach(function(row, i) { + if (row.id === id) { + $scope.selectedTemplate = angular.copy(row); + if ($scope.selectedTemplate.ask_credential_on_launch) { + if ($scope.selectedTemplate.summary_fields.credential) { + $scope.credential_name = $scope.selectedTemplate.summary_fields.credential.name ? $scope.selectedTemplate.summary_fields.credential.name : null; + $scope.credential = $scope.selectedTemplate.summary_fields.credential.id ? $scope.selectedTemplate.summary_fields.credential.id : null; + } else { + $scope.credential_name = null; + $scope.credential = null; + } + } + + if ($scope.selectedTemplate.ask_inventory_on_launch) { + if ($scope.selectedTemplate.summary_fields.inventory) { + $scope.inventory_name = $scope.selectedTemplate.summary_fields.inventory.name ? $scope.selectedTemplate.summary_fields.inventory.name : null; + $scope.inventory = $scope.selectedTemplate.summary_fields.inventory.id ? $scope.selectedTemplate.summary_fields.inventory.id : null; + } else { + $scope.inventory_name = null; + $scope.inventory = null; + } + } + + if ($scope.selectedTemplate.ask_job_type_on_launch) { + $scope.job_type = { + value: $scope.selectedTemplate.job_type ? $scope.selectedTemplate.job_type : null + }; + + // The default needs to be in place before we can select2-ify the dropdown + CreateSelect2({ + element: '#workflow_maker_job_type', + multiple: false + }); + } + + if ($scope.selectedTemplate.ask_limit_on_launch) { + $scope.limit = $scope.selectedTemplate.limit ? $scope.selectedTemplate.limit : null; + } + + if ($scope.selectedTemplate.ask_skip_tags_on_launch) { + $scope.skip_tags = $scope.selectedTemplate.skip_tags ? $scope.selectedTemplate.skip_tags : null; + } + + if ($scope.selectedTemplate.ask_tags_on_launch) { + $scope.job_tags = $scope.selectedTemplate.job_tags ? $scope.selectedTemplate.job_tags : null; + } + + $scope.workflow_job_templates[i].checked = 1; + } else { + $scope.workflow_job_templates[i].checked = 0; + } + }); + + }; + + $scope.toggle_project = function(id) { + + resetPromptFields(); + + $scope.workflow_job_templates.forEach(function(row, i) { + $scope.workflow_job_templates[i].checked = 0; + }); + + $scope.workflow_inventory_sources.forEach(function(row, i) { + $scope.workflow_inventory_sources[i].checked = 0; + }); + + $scope.workflow_projects.forEach(function(row, i) { + if (row.id === id) { + $scope.selectedTemplate = angular.copy(row); + $scope.workflow_projects[i].checked = 1; + } else { + $scope.workflow_projects[i].checked = 0; + } + }); + + }; + + $scope.toggle_inventory_source = function(id) { + + resetPromptFields(); + + $scope.workflow_job_templates.forEach(function(row, i) { + $scope.workflow_job_templates[i].checked = 0; + }); + + $scope.workflow_projects.forEach(function(row, i) { + $scope.workflow_projects[i].checked = 0; + }); + + $scope.workflow_inventory_sources.forEach(function(row, i) { + if (row.id === id) { + $scope.selectedTemplate = angular.copy(row); + $scope.workflow_inventory_sources[i].checked = 1; + } else { + $scope.workflow_inventory_sources[i].checked = 0; + } + }); + + }; + + init(); + + } +]; diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js index c421a6c414..899d7b851e 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js @@ -14,7 +14,7 @@ export default [ 'templateUrl', }, restrict: 'E', templateUrl: templateUrl('job-templates/workflow-maker/workflow-maker'), - controller: workflowMakerController + controller: workflowMakerController, }; } ]; diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html index 1b8d0a8e95..35234f9b93 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html @@ -60,21 +60,23 @@ +
{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited && nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : "ADD A TEMPLATE"}}
Please hover over a template and click the Add button.
-
+
JOBS
PROJECT SYNC
INVENTORY SYNC
-
-
-
+
+
+
-
+
diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 548a36caee..9386ef6641 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -178,7 +178,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat // html = GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); this.mode = options.mode; - this.modal = (options.modal) ? true : false; + //this.modal = (options.modal) ? true : false; this.setForm(form); return this.build(options); }, From ed5fb05c177b4a7454f8e11b6efb01602f9e2468 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 3 Nov 2016 18:07:41 -0400 Subject: [PATCH 03/45] WIP working base case: inventory lookup state --- awx/ui/client/src/forms/WorkflowMaker.js | 2 +- awx/ui/client/src/job-templates/main.js | 15 ++--- .../workflow-maker/workflow-help.service.js | 66 +++++++++---------- .../workflow-maker.controller.js | 18 ++--- .../workflow-maker.directive.js | 36 +++++++++- 5 files changed, 84 insertions(+), 53 deletions(-) diff --git a/awx/ui/client/src/forms/WorkflowMaker.js b/awx/ui/client/src/forms/WorkflowMaker.js index f5dda52c0a..a9992342c6 100644 --- a/awx/ui/client/src/forms/WorkflowMaker.js +++ b/awx/ui/client/src/forms/WorkflowMaker.js @@ -50,7 +50,7 @@ export default sourceField: 'name', list: 'OrganizationList', basePath: 'organization', - //ngClick: 'lookUpInventory()', + ngClick: 'lookUpInventory()', requiredErrorMsg: "Please select an Inventory.", column: 1, class: 'Form-formGroup--fullWidth', diff --git a/awx/ui/client/src/job-templates/main.js b/awx/ui/client/src/job-templates/main.js index 7170a504a6..3ff23a97c9 100644 --- a/awx/ui/client/src/job-templates/main.js +++ b/awx/ui/client/src/job-templates/main.js @@ -136,11 +136,11 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } }, views: { - 'modal@templates.editWorkflowJobTemplate.workflowMaker': { - templateProvider: function(ListDefinition, generateList) { + 'related': { + templateProvider: function(InventoryList, generateList) { let list_html = generateList.build({ mode: 'lookup', - list: ListDefinition, + list: InventoryList, input_type: 'radio' }); return `${list_html}`; @@ -149,10 +149,9 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } }, resolve: { - // ListDefinition: [InventoryList, function(list) { - // list.iterator = field.sourceModel; - // return list; - // }], + ListDefinition: ['InventoryList', function(list) { + return list; + }], Dataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => { let path = GetBasePath(list.name) || GetBasePath(list.basePath); @@ -184,7 +183,7 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp }, [ stateExtender.buildDefinition(jobTemplatesListRoute), workflowMaker, - //inventoryLookup + inventoryLookup ]) }; }); diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js index 48cca0ac58..24c7fd9679 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js @@ -3,44 +3,44 @@ export default ['CreateDialog', 'Wait', '$q', function(CreateDialog, Wait, $q){ openDialog: function(params){ // params.scope - let deferred = $q.defer(); + // let deferred = $q.defer(); - if (params.scope.removeWorkflowDialogReady) { - params.scope.removeWorkflowDialogReady(); - } - params.scope.removeWorkflowDialogReady = params.scope.$on('WorkflowDialogReady', function() { - $('#workflow-modal-dialog').dialog('open'); + // if (params.scope.removeWorkflowDialogReady) { + // params.scope.removeWorkflowDialogReady(); + // } + // params.scope.removeWorkflowDialogReady = params.scope.$on('WorkflowDialogReady', function() { + // $('#workflow-modal-dialog').dialog('open'); - deferred.resolve(); - }); - Wait('start'); - debugger; - CreateDialog({ - id: 'workflow-modal-dialog', - scope: params.scope, - width: 1400, - height: 720, - draggable: false, - dialogClass: 'SurveyMaker-dialog', - position: ['center',20], - onClose: function() { - $('#workflow-modal-dialog').empty(); - }, - onOpen: function() { - Wait('stop'); + // deferred.resolve(); + // }); + // Wait('start'); + // debugger; + // CreateDialog({ + // id: 'workflow-modal-dialog', + // scope: params.scope, + // width: 1400, + // height: 720, + // draggable: false, + // dialogClass: 'SurveyMaker-dialog', + // position: ['center',20], + // onClose: function() { + // $('#workflow-modal-dialog').empty(); + // }, + // onOpen: function() { + // Wait('stop'); - // Let the modal height be variable based on the content - // and set a uniform padding - $('#workflow-modal-dialog').css({'padding': '20px'}); + // // Let the modal height be variable based on the content + // // and set a uniform padding + // $('#workflow-modal-dialog').css({'padding': '20px'}); - }, - _allowInteraction: function(e) { - return !!$(e.target).is('.select2-input') || this._super(e); - }, - callback: 'WorkflowDialogReady' - }); + // }, + // _allowInteraction: function(e) { + // return !!$(e.target).is('.select2-input') || this._super(e); + // }, + // callback: 'WorkflowDialogReady' + // }); - return deferred.promise; + // return deferred.promise; }, closeDialog: function() { $('#workflow-modal-dialog').dialog('destroy'); diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js index cd0fec200b..64fe177df1 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js @@ -5,11 +5,11 @@ *************************************************/ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateList', 'ProjectList', - 'GetBasePath', 'Wait', 'JobTemplateService', + 'GetBasePath', 'Wait', 'JobTemplateService', '$state', 'ProcessErrors', 'InventorySourcesList', 'CreateSelect2', 'WorkflowMakerForm', 'GenerateForm', 'InventoryList', 'CredentialList', '$q', '$timeout', function($scope, WorkflowHelpService, GenerateList, JobTemplateList, ProjectList, - GetBasePath, Wait, JobTemplateService, + GetBasePath, Wait, JobTemplateService, $state, ProcessErrors, InventorySourcesList, CreateSelect2, WorkflowMakerForm, GenerateForm, InventoryList, CredentialList, $q, $timeout) { @@ -64,14 +64,8 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis function init() { $scope.treeDataMaster = angular.copy($scope.treeData.data); - WorkflowHelpService.openDialog({ - scope: $scope - }) - .then(function() { + $scope.$broadcast("refreshWorkflowChart"); - //$scope.$broadcast("refreshWorkflowChart"); - - }); $scope.$watchCollection('workflow_job_templates', function() { if ($scope.selectedTemplate) { // Loop across the inventories and see if one of them should be "checked" @@ -149,6 +143,12 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis } + + $scope.lookUpInventory = function(){ + console.log($state) + $state.go('.inventory') + }; + $scope.closeWorkflowMaker = function() { // Revert the data to the master which was created when the dialog was opened $scope.treeData.data = angular.copy($scope.treeDataMaster); diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js index 899d7b851e..aacb606fdd 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js @@ -6,8 +6,8 @@ import workflowMakerController from './workflow-maker.controller'; -export default [ 'templateUrl', - function(templateUrl) { +export default ['templateUrl', 'CreateDialog', 'Wait', + function(templateUrl, CreateDialog, Wait) { return { scope: { treeData: '=' @@ -15,6 +15,38 @@ export default [ 'templateUrl', restrict: 'E', templateUrl: templateUrl('job-templates/workflow-maker/workflow-maker'), controller: workflowMakerController, + link: function(scope) { + CreateDialog({ + id: 'workflow-modal-dialog', + scope: scope, + width: 1400, + height: 720, + draggable: false, + dialogClass: 'SurveyMaker-dialog', + position: ['center', 20], + onClose: function() { + $('#workflow-modal-dialog').empty(); + }, + onOpen: function() { + Wait('stop'); + + // Let the modal height be variable based on the content + // and set a uniform padding + $('#workflow-modal-dialog').css({ 'padding': '20px' }); + + }, + _allowInteraction: function(e) { + return !!$(e.target).is('.select2-input') || this._super(e); + }, + callback: 'WorkflowDialogReady' + }); + if (scope.removeWorkflowDialogReady) { + scope.removeWorkflowDialogReady(); + } + scope.removeWorkflowDialogReady = scope.$on('WorkflowDialogReady', function() { + $('#workflow-modal-dialog').dialog('open'); + }); + } }; } ]; From c091590d670f0abf45e60af066b42006b5ab58dc Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Fri, 4 Nov 2016 13:35:32 -0400 Subject: [PATCH 04/45] Integrate smart-search into workflow maker --- awx/ui/client/src/app.js | 4 +- .../list/job-templates-list.route.js | 4 +- awx/ui/client/src/job-templates/main.js | 213 ++++++++++++++---- .../workflow-maker.partial.html | 2 +- awx/ui/client/src/lists/InventorySources.js | 1 + awx/ui/client/src/lists/JobTemplates.js | 14 +- awx/ui/client/src/lists/Projects.js | 1 + 7 files changed, 187 insertions(+), 52 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 7d3f3d302e..012928f590 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -19,8 +19,8 @@ import uiRouter from 'angular-ui-router'; // backwards compatibility for $stateChange* events import 'angular-ui-router/release/stateEvents'; // ui-router debugging -//import { trace } from 'angular-ui-router'; -//trace.enable(); +import { trace } from 'angular-ui-router'; +trace.enable(); // Configuration dependencies global.$AnsibleConfig = null; diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.route.js b/awx/ui/client/src/job-templates/list/job-templates-list.route.js index c39d6af766..3e64e8cd1a 100644 --- a/awx/ui/client/src/job-templates/list/job-templates-list.route.js +++ b/awx/ui/client/src/job-templates/list/job-templates-list.route.js @@ -18,13 +18,13 @@ export default { } }, params: { - unified_job_templates_search: { + template_search: { value: { type: 'workflow_job_template,job_template' } } }, - searchPrefix: 'unified_job_templates', + searchPrefix: 'template', views: { '@': { controller: 'JobTemplatesListController', diff --git a/awx/ui/client/src/job-templates/main.js b/awx/ui/client/src/job-templates/main.js index 3ff23a97c9..f22f573236 100644 --- a/awx/ui/client/src/job-templates/main.js +++ b/awx/ui/client/src/job-templates/main.js @@ -4,8 +4,6 @@ * All Rights Reserved *************************************************/ -import { templateUrl } from '../shared/template-url/template-url.factory'; - import jobTemplateService from './job-template.service'; import surveyMaker from './survey-maker/main'; @@ -75,40 +73,102 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } }); - workflowMaker = stateExtender.buildDefinition({ + workflowMaker = { name: 'templates.editWorkflowJobTemplate.workflowMaker', url: '/workflow-maker', + params: { + template_search: { + value: { + page_size: '5' + }, + squash: true, + dynamic: true + }, + project_search: { + value: { + page_size: '5' + }, + squash: true, + dynamic: true + }, + inventory_source_search: { + value: { + page_size: '5' + }, + squash: true, + dynamic: true + } + }, views: { 'modal': { template: ` ` }, - // 'jobsTemplateList@templates.editWorkflowJobTemplate.workflowMaker': { - // templateProvider: function(JobTemplateList, generateList) { - // let html = generateList.build({ - // list: JobTemplateList, - // mode: 'edit' - // }); - // return html; - // } - // }, - // 'inventorySyncList@templates.editWorkflowJobTemplate.workflowMaker': { - // templateProvider: function(InventoryList, generateList) { - // let html = generateList.build({ - // list: InventoryList, - // mode: 'edit' - // }); - // return html; - // } - // }, - // 'projectList@templates.editWorkflowJobTemplate.workflowMaker': { - // templateProvider: function(ProjectList, generateList) { - // let html = generateList.build({ - // list: ProjectList, - // mode: 'edit' - // }); - // return html; - // } - // }, + 'jobTemplateList@templates.editWorkflowJobTemplate.workflowMaker': { + templateProvider: function(JobTemplateList, generateList) { + let list = _.cloneDeep(JobTemplateList); + delete list.fields.type; + delete list.fields.description; + delete list.fields.smart_status; + delete list.fields.labels; + list.fields.name.columnClass = "col-md-11"; + let html = generateList.build({ + list: list, + mode: 'edit' + }); + return html; + }, + // $scope encapsulated in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy + controller: ['$scope', 'JobTemplateList', 'JobTemplateDataset', + function($scope, list, Dataset) { + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + + } + ] + }, + 'inventorySyncList@templates.editWorkflowJobTemplate.workflowMaker': { + templateProvider: function(InventorySourcesList, generateList) { + let list = _.cloneDeep(InventorySourcesList); + // mutate list definition here! + let html = generateList.build({ + list: list, + mode: 'edit' + }); + return html; + }, + // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy + controller: ['$scope', 'InventorySourcesList', 'InventorySourcesDataset', + function($scope, list, Dataset) { + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + + } + ] + }, + 'projectList@templates.editWorkflowJobTemplate.workflowMaker': { + templateProvider: function(ProjectList, generateList) { + let list = _.cloneDeep(ProjectList); + delete list.fields.status; + delete list.fields.scm_type; + delete list.fields.last_updated; + list.fields.name.columnClass = "col-md-11"; + let html = generateList.build({ + list: list, + mode: 'edit' + }); + return html; + }, + // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy + controller: ['$scope', 'ProjectList', 'ProjectDataset', + function($scope, list, Dataset) { + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + } + ] + }, 'workflowForm@templates.editWorkflowJobTemplate.workflowMaker': { templateProvider: function(WorkflowMakerForm, GenerateForm) { let form = WorkflowMakerForm(); @@ -119,12 +179,31 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp return html; } } + }, + resolve: { + JobTemplateDataset: ['JobTemplateList', 'QuerySet', '$stateParams', 'GetBasePath', + (list, qs, $stateParams, GetBasePath) => { + let path = GetBasePath(list.basePath); + return qs.search(path, $stateParams[`${list.iterator}_search`]); + } + ], + ProjectDataset: ['ProjectList', 'QuerySet', '$stateParams', 'GetBasePath', + (list, qs, $stateParams, GetBasePath) => { + let path = GetBasePath(list.basePath); + return qs.search(path, $stateParams[`${list.iterator}_search`]); + } + ], + InventorySourcesDataset: ['InventorySourcesList', 'QuerySet', '$stateParams', 'GetBasePath', + (list, qs, $stateParams, GetBasePath) => { + let path = GetBasePath(list.basePath); + return qs.search(path, $stateParams[`${list.iterator}_search`]); + } + ] } - }); + }; - inventoryLookup = stateExtender.buildDefinition({ + inventoryLookup = { searchPrefix: 'inventory', - //squashSearchUrl: true, @issue enable name: 'templates.editWorkflowJobTemplate.workflowMaker.inventory', url: '/inventory', data: { @@ -132,15 +211,19 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp }, params: { inventory_search: { - value: { page_size: '5'} + value: { + page_size: '5' + }, + squash: true, + dynamic: true } }, views: { 'related': { - templateProvider: function(InventoryList, generateList) { + templateProvider: function(ListDefinition, generateList) { let list_html = generateList.build({ mode: 'lookup', - list: InventoryList, + list: ListDefinition, input_type: 'radio' }); return `${list_html}`; @@ -150,9 +233,10 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp }, resolve: { ListDefinition: ['InventoryList', function(list) { + // mutate the provided list definition here return list; }], - Dataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath', + Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => { let path = GetBasePath(list.name) || GetBasePath(list.basePath); return qs.search(path, $stateParams[`${list.iterator}_search`]); @@ -166,9 +250,57 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp $('body').removeClass('modal-open'); } }, - }); + }; + credentialLookup = { + searchPrefix: 'credential', + name: 'templates.editWorkflowJobTemplate.workflowMaker.credential', + url: '/credential', + data: { + lookup: true + }, + params: { + credential_search: { + value: { + page_size: '5' + }, + squash: true, + dynamic: true + } + }, + views: { + 'related': { + templateProvider: function(ListDefinition, generateList) { + let list_html = generateList.build({ + mode: 'lookup', + list: ListDefinition, + input_type: 'radio' + }); + return `${list_html}`; + } + } + }, + resolve: { + ListDefinition: ['ListDefinition', function(list) { + // mutate the provided list definition here + return list; + }], + Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', + (list, qs, $stateParams, GetBasePath) => { + let path = GetBasePath(list.name) || GetBasePath(list.basePath); + return qs.search(path, $stateParams[`${list.iterator}_search`]); + } + ] + }, + onExit: function($state) { + if ($state.transition) { + $('#form-modal').modal('hide'); + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + } + }, + }; return Promise.all([ @@ -182,8 +314,9 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp return result.concat(definition.states); }, [ stateExtender.buildDefinition(jobTemplatesListRoute), - workflowMaker, - inventoryLookup + stateExtender.buildDefinition(workflowMaker), + stateExtender.buildDefinition(inventoryLookup), + stateExtender.buildDefinition(credentialLookup) ]) }; }); diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html index 35234f9b93..2b36654125 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html @@ -74,7 +74,7 @@
INVENTORY SYNC
-
+
diff --git a/awx/ui/client/src/lists/InventorySources.js b/awx/ui/client/src/lists/InventorySources.js index e946e9d4d0..127352c72b 100644 --- a/awx/ui/client/src/lists/InventorySources.js +++ b/awx/ui/client/src/lists/InventorySources.js @@ -11,6 +11,7 @@ export default name: 'workflow_inventory_sources', iterator: 'inventory_source', + basePath: 'inventory_sources', listTitle: 'Inventory Sources', index: false, hover: true, diff --git a/awx/ui/client/src/lists/JobTemplates.js b/awx/ui/client/src/lists/JobTemplates.js index fd5cbac79a..d776f2e680 100644 --- a/awx/ui/client/src/lists/JobTemplates.js +++ b/awx/ui/client/src/lists/JobTemplates.js @@ -11,7 +11,7 @@ export default return { name: 'templates', - iterator: 'unified_job_templates', + iterator: 'template', basePath: 'unified_job_templates', selectTitle: i18n._('Template'), editTitle: i18n._('Templates'), @@ -85,7 +85,7 @@ export default ngClick: 'submitJob(unified_job_templates)', awToolTip: i18n._('Start a job using this template'), dataPlacement: 'top', - ngShow: 'unified_job_templates.summary_fields.user_capabilities.start' + ngShow: 'template.summary_fields.user_capabilities.start' }, schedule: { label: i18n._('Schedule'), @@ -93,7 +93,7 @@ export default ngClick: 'scheduleJob(unified_job_templates)', awToolTip: i18n._('Schedule future job template runs'), dataPlacement: 'top', - ngShow: 'unified_job_templates.summary_fields.user_capabilities.schedule' + ngShow: 'template.summary_fields.user_capabilities.schedule' }, copy: { label: i18n._('Copy'), @@ -101,7 +101,7 @@ export default "class": 'btn-danger btn-xs', awToolTip: i18n._('Copy template'), dataPlacement: 'top', - ngShow: 'unified_job_templates.summary_fields.user_capabilities.copy' + ngShow: 'template.summary_fields.user_capabilities.copy' }, edit: { label: i18n._('Edit'), @@ -109,7 +109,7 @@ export default awToolTip: i18n._('Edit template'), "class": 'btn-default btn-xs', dataPlacement: 'top', - ngShow: 'unified_job_templates.summary_fields.user_capabilities.edit' + ngShow: 'template.summary_fields.user_capabilities.edit' }, view: { label: i18n._('View'), @@ -117,7 +117,7 @@ export default awToolTip: i18n._('View template'), "class": 'btn-default btn-xs', dataPlacement: 'top', - ngShow: '!unified_job_templates.summary_fields.user_capabilities.edit' + ngShow: '!template.summary_fields.user_capabilities.edit' }, "delete": { label: i18n._('Delete'), @@ -125,7 +125,7 @@ export default "class": 'btn-danger btn-xs', awToolTip: i18n._('Delete template'), dataPlacement: 'top', - ngShow: 'unified_job_templates.summary_fields.user_capabilities.delete' + ngShow: 'template.summary_fields.user_capabilities.delete' } } };}]); diff --git a/awx/ui/client/src/lists/Projects.js b/awx/ui/client/src/lists/Projects.js index ce478129ac..31d176bd98 100644 --- a/awx/ui/client/src/lists/Projects.js +++ b/awx/ui/client/src/lists/Projects.js @@ -11,6 +11,7 @@ export default name: 'projects', iterator: 'project', + basePath: 'projects', selectTitle: i18n._('Add Project'), editTitle: i18n._('Projects'), listTitle: i18n._('Projects'), From 1656ab45f1882c44bec803ea574e80b3f5f96623 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Fri, 4 Nov 2016 15:16:56 -0400 Subject: [PATCH 05/45] Post-smart search cleanup/implementation based on new patterns. This is just the beginning of the fixes. --- awx/ui/client/src/app.js | 4 ++-- awx/ui/client/src/forms/WorkflowMaker.js | 8 +++---- awx/ui/client/src/job-templates/main.js | 13 +++++++---- .../workflow-maker.controller.js | 5 +--- .../workflow-maker.directive.js | 2 ++ .../workflow-maker.partial.html | 6 ++--- awx/ui/client/src/lists/JobTemplates.js | 12 +++++----- awx/ui/client/src/shared/form-generator.js | 23 +++++++++++++------ 8 files changed, 42 insertions(+), 31 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 012928f590..7d3f3d302e 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -19,8 +19,8 @@ import uiRouter from 'angular-ui-router'; // backwards compatibility for $stateChange* events import 'angular-ui-router/release/stateEvents'; // ui-router debugging -import { trace } from 'angular-ui-router'; -trace.enable(); +//import { trace } from 'angular-ui-router'; +//trace.enable(); // Configuration dependencies global.$AnsibleConfig = null; diff --git a/awx/ui/client/src/forms/WorkflowMaker.js b/awx/ui/client/src/forms/WorkflowMaker.js index a9992342c6..e243818639 100644 --- a/awx/ui/client/src/forms/WorkflowMaker.js +++ b/awx/ui/client/src/forms/WorkflowMaker.js @@ -58,10 +58,10 @@ export default dataTitle: 'Inventory', dataPlacement: 'right', dataContainer: "body", - //ngShow: "selectedTemplate.ask_inventory_on_launch", - // awRequiredWhen: { - // reqExpression: 'selectedTemplate.ask_inventory_on_launch' - // } + ngShow: "selectedTemplate.ask_inventory_on_launch", + awRequiredWhen: { + reqExpression: 'selectedTemplate.ask_inventory_on_launch' + } }, job_type: { label: 'Job Type', diff --git a/awx/ui/client/src/job-templates/main.js b/awx/ui/client/src/job-templates/main.js index f22f573236..f9c16d757b 100644 --- a/awx/ui/client/src/job-templates/main.js +++ b/awx/ui/client/src/job-templates/main.js @@ -110,10 +110,12 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp delete list.fields.description; delete list.fields.smart_status; delete list.fields.labels; + delete list.fieldActions; list.fields.name.columnClass = "col-md-11"; let html = generateList.build({ list: list, - mode: 'edit' + input_type: 'radio', + mode: 'lookup' }); return html; }, @@ -133,7 +135,8 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp // mutate list definition here! let html = generateList.build({ list: list, - mode: 'edit' + input_type: 'radio', + mode: 'lookup' }); return html; }, @@ -147,7 +150,7 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } ] }, - 'projectList@templates.editWorkflowJobTemplate.workflowMaker': { + 'projectSyncList@templates.editWorkflowJobTemplate.workflowMaker': { templateProvider: function(ProjectList, generateList) { let list = _.cloneDeep(ProjectList); delete list.fields.status; @@ -156,7 +159,8 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp list.fields.name.columnClass = "col-md-11"; let html = generateList.build({ list: list, - mode: 'edit' + input_type: 'radio', + mode: 'lookup' }); return html; }, @@ -175,6 +179,7 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp let html = GenerateForm.buildHTML(form, { mode: 'add', related: false, + noPanel: true }); return html; } diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js index 64fe177df1..bcc721b738 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js @@ -13,8 +13,7 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis ProcessErrors, InventorySourcesList, CreateSelect2, WorkflowMakerForm, GenerateForm, InventoryList, CredentialList, $q, $timeout) { - let form = WorkflowMakerForm(), - generator = GenerateForm; + let form = WorkflowMakerForm(); $scope.workflowMakerFormConfig = { nodeMode: "idle", @@ -143,9 +142,7 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis } - $scope.lookUpInventory = function(){ - console.log($state) $state.go('.inventory') }; diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js index aacb606fdd..e2d81c01ca 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.directive.js @@ -45,6 +45,8 @@ export default ['templateUrl', 'CreateDialog', 'Wait', } scope.removeWorkflowDialogReady = scope.$on('WorkflowDialogReady', function() { $('#workflow-modal-dialog').dialog('open'); + + scope.$broadcast("refreshWorkflowChart"); }); } }; diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html index 2b36654125..c611477ed0 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.partial.html @@ -60,21 +60,19 @@ -
{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited && nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : "ADD A TEMPLATE"}}
Please hover over a template and click the Add button.
-
+
JOBS
PROJECT SYNC
INVENTORY SYNC
-
+
diff --git a/awx/ui/client/src/lists/JobTemplates.js b/awx/ui/client/src/lists/JobTemplates.js index d776f2e680..a85c49613b 100644 --- a/awx/ui/client/src/lists/JobTemplates.js +++ b/awx/ui/client/src/lists/JobTemplates.js @@ -82,7 +82,7 @@ export default submit: { label: i18n._('Launch'), mode: 'all', - ngClick: 'submitJob(unified_job_templates)', + ngClick: 'submitJob(template)', awToolTip: i18n._('Start a job using this template'), dataPlacement: 'top', ngShow: 'template.summary_fields.user_capabilities.start' @@ -90,14 +90,14 @@ export default schedule: { label: i18n._('Schedule'), mode: 'all', - ngClick: 'scheduleJob(unified_job_templates)', + ngClick: 'scheduleJob(template)', awToolTip: i18n._('Schedule future job template runs'), dataPlacement: 'top', ngShow: 'template.summary_fields.user_capabilities.schedule' }, copy: { label: i18n._('Copy'), - 'ui-sref': 'templates.copy({id: unified_job_templates.id})', + 'ui-sref': 'templates.copy({id: template.id})', "class": 'btn-danger btn-xs', awToolTip: i18n._('Copy template'), dataPlacement: 'top', @@ -105,7 +105,7 @@ export default }, edit: { label: i18n._('Edit'), - ngClick: "editJobTemplate(unified_job_templates)", + ngClick: "editJobTemplate(template)", awToolTip: i18n._('Edit template'), "class": 'btn-default btn-xs', dataPlacement: 'top', @@ -113,7 +113,7 @@ export default }, view: { label: i18n._('View'), - ngClick: "editJobTemplate(unified_job_templates.id)", + ngClick: "editJobTemplate(template.id)", awToolTip: i18n._('View template'), "class": 'btn-default btn-xs', dataPlacement: 'top', @@ -121,7 +121,7 @@ export default }, "delete": { label: i18n._('Delete'), - ngClick: "deleteJobTemplate(unified_job_templates)", + ngClick: "deleteJobTemplate(template)", "class": 'btn-danger btn-xs', awToolTip: i18n._('Delete template'), dataPlacement: 'top', diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 9386ef6641..955a5d1e7e 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -164,12 +164,21 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat // Not a very good way to do this // Form sub-states expect to target ui-views related@stateName & modal@stateName // Also wraps mess of generated HTML in a .Panel - wrapPanel(html){ - return `
- ${html} -
-
-
`; + wrapPanel(html, ignorePanel){ + if(ignorePanel) { + return `
+ ${html} +
+
+
`; + } + else { + return `
+ ${html} +
+
+
`; + } }, buildHTML: function(form, options) { @@ -1548,7 +1557,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat }); } // console.log(html) - return this.wrapPanel(html); + return this.wrapPanel(html, options.noPanel); }, buildCollection: function (params) { From ce5f95c206ee1dff3b0aa99f475c530d99e5d27f Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Fri, 4 Nov 2016 16:32:35 -0400 Subject: [PATCH 06/45] Reset list radio selection when switching to a new tab --- awx/ui/client/src/job-templates/main.js | 69 ++++++++++++++++--- .../workflow-maker.controller.js | 38 +++++----- 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/awx/ui/client/src/job-templates/main.js b/awx/ui/client/src/job-templates/main.js index f9c16d757b..6b2ea34945 100644 --- a/awx/ui/client/src/job-templates/main.js +++ b/awx/ui/client/src/job-templates/main.js @@ -120,12 +120,29 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp return html; }, // $scope encapsulated in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy - controller: ['$scope', 'JobTemplateList', 'JobTemplateDataset', - function($scope, list, Dataset) { - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + controller: ['$scope', 'JobTemplateList', 'JobTemplateDataset', '$log', + function($scope, list, Dataset, $log) { + // name of this tab + let tab = 'jobs'; + init(); + + function init() { + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + } + + // resets any selected list items, if this tab is not active + $scope.$on('resetWorkflowList', function(e, active) { + // e.targetScope is a reference to the outer scope if you need to manipulate it! + + // a reference to the currently-selected radio is stored in $scope.selection[list.iterator] + // clear it out! + if (active !== tab) { + $scope.selection[list.iterator] = null; + } + }); } ] }, @@ -143,10 +160,25 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy controller: ['$scope', 'InventorySourcesList', 'InventorySourcesDataset', function($scope, list, Dataset) { - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + let tab = 'inventory_sync'; + init(); + + function init() { + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + + } + + // resets any selected list items, if this tab is not active + $scope.$on('resetWorkflowList', function(e, active) { + // e.targetScope is a reference to the outer scope if you need to manipulate it! + + if (active !== tab) { + $scope.selection[list.iterator] = null; + } + }); } ] }, @@ -167,9 +199,24 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy controller: ['$scope', 'ProjectList', 'ProjectDataset', function($scope, list, Dataset) { - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + let tab = 'project_sync'; + + init(); + + function init() { + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; + + } + // resets any selected list items, if this tab is not active + $scope.$on('resetWorkflowList', function(e, active) { + // e.targetScope is a reference to the outer scope if you need to manipulate it! + + if (active !== tab) { + $scope.selection[list.iterator] = null; + } + }); } ] }, diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js index bcc721b738..498dd73965 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js @@ -540,6 +540,8 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis }; $scope.toggleFormTab = function(tab) { + // a dictionary of settings required by each encapsulated tab + $scope.$broadcast('resetWorkflowList', tab); if ($scope.workflowMakerFormConfig.activeTab !== tab) { $scope.workflowMakerFormConfig.activeTab = tab; } @@ -547,13 +549,13 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis $scope.toggle_job_template = function(id) { - $scope.workflow_projects.forEach(function(row, i) { - $scope.workflow_projects[i].checked = 0; - }); + // $scope.workflow_projects.forEach(function(row, i) { + // $scope.workflow_projects[i].checked = 0; + // }); - $scope.workflow_inventory_sources.forEach(function(row, i) { - $scope.workflow_inventory_sources[i].checked = 0; - }); + // $scope.workflow_inventory_sources.forEach(function(row, i) { + // $scope.workflow_inventory_sources[i].checked = 0; + // }); $scope.workflow_job_templates.forEach(function(row, i) { if (row.id === id) { @@ -614,13 +616,13 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis resetPromptFields(); - $scope.workflow_job_templates.forEach(function(row, i) { - $scope.workflow_job_templates[i].checked = 0; - }); + // $scope.workflow_job_templates.forEach(function(row, i) { + // $scope.workflow_job_templates[i].checked = 0; + // }); - $scope.workflow_inventory_sources.forEach(function(row, i) { - $scope.workflow_inventory_sources[i].checked = 0; - }); + // $scope.workflow_inventory_sources.forEach(function(row, i) { + // $scope.workflow_inventory_sources[i].checked = 0; + // }); $scope.workflow_projects.forEach(function(row, i) { if (row.id === id) { @@ -637,13 +639,13 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis resetPromptFields(); - $scope.workflow_job_templates.forEach(function(row, i) { - $scope.workflow_job_templates[i].checked = 0; - }); + // $scope.workflow_job_templates.forEach(function(row, i) { + // $scope.workflow_job_templates[i].checked = 0; + // }); - $scope.workflow_projects.forEach(function(row, i) { - $scope.workflow_projects[i].checked = 0; - }); + // $scope.workflow_projects.forEach(function(row, i) { + // $scope.workflow_projects[i].checked = 0; + // }); $scope.workflow_inventory_sources.forEach(function(row, i) { if (row.id === id) { From 161c320f051f6c356e21edd81a46451c4ba111e5 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Mon, 7 Nov 2016 17:25:04 -0500 Subject: [PATCH 07/45] More integration work - getting to the point where you can add nodes to the workflow after integrating with smart search. --- .../edit-workflow/workflow-edit.controller.js | 2 +- awx/ui/client/src/job-templates/main.js | 148 ++++++++---- .../workflow-maker/workflow-help.service.js | 46 +--- .../workflow-maker.controller.js | 218 ++++-------------- awx/ui/client/src/shared/form-generator.js | 6 +- .../shared/lookup/lookup-modal.directive.js | 3 + .../shared/lookup/lookup-modal.partial.html | 4 +- .../src/shared/stateDefinitions.factory.js | 2 +- awx/ui/grunt-tasks/browserSync.js | 8 +- 9 files changed, 169 insertions(+), 268 deletions(-) diff --git a/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js b/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js index 18a07e5fe5..b79b10ce83 100644 --- a/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js +++ b/awx/ui/client/src/job-templates/edit-workflow/workflow-edit.controller.js @@ -14,7 +14,7 @@ ClearScope, GetBasePath, $q, ParseTypeChange, Wait, Empty, ToJSON, SurveyControllerInit, $state, CreateSelect2, ParseVariableString, JobTemplateService, OrganizationList, Rest - ) { + ) {window.state = $state; ClearScope(); diff --git a/awx/ui/client/src/job-templates/main.js b/awx/ui/client/src/job-templates/main.js index 6b2ea34945..ba0fae8dd5 100644 --- a/awx/ui/client/src/job-templates/main.js +++ b/awx/ui/client/src/job-templates/main.js @@ -76,10 +76,17 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp workflowMaker = { name: 'templates.editWorkflowJobTemplate.workflowMaker', url: '/workflow-maker', + // ncyBreadcrumb: { + // label: 'WORKFLOW MAKER' + // }, + data: { + formChildState: true + }, params: { - template_search: { + job_template_search: { value: { - page_size: '5' + page_size: '5', + type: 'job_template' }, squash: true, dynamic: true @@ -104,26 +111,18 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp template: ` ` }, 'jobTemplateList@templates.editWorkflowJobTemplate.workflowMaker': { - templateProvider: function(JobTemplateList, generateList) { - let list = _.cloneDeep(JobTemplateList); - delete list.fields.type; - delete list.fields.description; - delete list.fields.smart_status; - delete list.fields.labels; - delete list.fieldActions; - list.fields.name.columnClass = "col-md-11"; + templateProvider: function(WorkflowMakerJobTemplateList, generateList) { + //debugger; let html = generateList.build({ - list: list, + list: WorkflowMakerJobTemplateList, input_type: 'radio', mode: 'lookup' }); return html; }, // $scope encapsulated in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy - controller: ['$scope', 'JobTemplateList', 'JobTemplateDataset', '$log', - function($scope, list, Dataset, $log) { - // name of this tab - let tab = 'jobs'; + controller: ['$scope', 'WorkflowMakerJobTemplateList', 'JobTemplateDataset', + function($scope, list, Dataset) { init(); @@ -133,14 +132,25 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp $scope[list.name] = $scope[`${list.iterator}_dataset`].results; } - // resets any selected list items, if this tab is not active - $scope.$on('resetWorkflowList', function(e, active) { - // e.targetScope is a reference to the outer scope if you need to manipulate it! + $scope.toggle_job_template = function(id) { - // a reference to the currently-selected radio is stored in $scope.selection[list.iterator] - // clear it out! - if (active !== tab) { - $scope.selection[list.iterator] = null; + $scope.job_templates.forEach(function(row, i) { + if (row.id === id) { + $scope.job_templates[i].checked = 1; + $scope.selection[list.iterator] = { + id: row.id, + name: row.name + }; + + $scope.$emit('templateSelected', row); + } + }); + + }; + + $scope.$on('clearOtherTemplateLists', function(e, tab) { + if(tab !== 'jobs') { + // Clear out any selected job } }); } @@ -160,7 +170,6 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy controller: ['$scope', 'InventorySourcesList', 'InventorySourcesDataset', function($scope, list, Dataset) { - let tab = 'inventory_sync'; init(); @@ -171,35 +180,42 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } - // resets any selected list items, if this tab is not active - $scope.$on('resetWorkflowList', function(e, active) { - // e.targetScope is a reference to the outer scope if you need to manipulate it! + $scope.toggle_inventory_source = function(id) { + + $scope.inventory_sources.forEach(function(row, i) { + if (row.id === id) { + $scope.inventory_sources[i].checked = 1; + $scope.selection[list.iterator] = { + id: row.id, + name: row.name + }; + + $scope.$emit('templateSelected', row); + } + }); + + }; + + $scope.$on('clearOtherTemplateLists', function(e, tab) { + if(tab !== 'project_sync') { - if (active !== tab) { - $scope.selection[list.iterator] = null; } }); } ] }, 'projectSyncList@templates.editWorkflowJobTemplate.workflowMaker': { - templateProvider: function(ProjectList, generateList) { - let list = _.cloneDeep(ProjectList); - delete list.fields.status; - delete list.fields.scm_type; - delete list.fields.last_updated; - list.fields.name.columnClass = "col-md-11"; + templateProvider: function(WorkflowProjectList, generateList) { let html = generateList.build({ - list: list, + list: WorkflowProjectList, input_type: 'radio', mode: 'lookup' }); return html; }, // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy - controller: ['$scope', 'ProjectList', 'ProjectDataset', + controller: ['$scope', 'WorkflowProjectList', 'ProjectDataset', function($scope, list, Dataset) { - let tab = 'project_sync'; init(); @@ -209,12 +225,26 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp $scope[list.name] = $scope[`${list.iterator}_dataset`].results; } - // resets any selected list items, if this tab is not active - $scope.$on('resetWorkflowList', function(e, active) { - // e.targetScope is a reference to the outer scope if you need to manipulate it! - if (active !== tab) { - $scope.selection[list.iterator] = null; + $scope.toggle_project = function(id) { + + $scope.projects.forEach(function(row, i) { + if (row.id === id) { + $scope.projects[i].checked = 1; + $scope.selection[list.iterator] = { + id: row.id, + name: row.name + }; + + $scope.$emit('templateSelected', row); + } + }); + + }; + + $scope.$on('clearOtherTemplateLists', function(e, tab) { + if(tab !== 'inventory_sync') { + } }); } @@ -233,7 +263,7 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } }, resolve: { - JobTemplateDataset: ['JobTemplateList', 'QuerySet', '$stateParams', 'GetBasePath', + JobTemplateDataset: ['WorkflowMakerJobTemplateList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => { let path = GetBasePath(list.basePath); return qs.search(path, $stateParams[`${list.iterator}_search`]); @@ -250,6 +280,32 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp let path = GetBasePath(list.basePath); return qs.search(path, $stateParams[`${list.iterator}_search`]); } + ], + WorkflowMakerJobTemplateList: ['JobTemplateList', + (JobTemplateList) => { + let list = _.cloneDeep(JobTemplateList); + delete list.fields.type; + delete list.fields.description; + delete list.fields.smart_status; + delete list.fields.labels; + delete list.fieldActions; + list.fields.name.columnClass = "col-md-11"; + list.iterator = 'job_template'; + list.name = 'job_templates'; + + return list; + } + ], + WorkflowProjectList: ['ProjectList', + (ProjectList) => { + let list = _.cloneDeep(ProjectList); + delete list.fields.status; + delete list.fields.scm_type; + delete list.fields.last_updated; + list.fields.name.columnClass = "col-md-11"; + + return list; + } ] } }; @@ -259,7 +315,7 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp name: 'templates.editWorkflowJobTemplate.workflowMaker.inventory', url: '/inventory', data: { - lookup: true + formChildState: true }, params: { inventory_search: { @@ -309,7 +365,7 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp name: 'templates.editWorkflowJobTemplate.workflowMaker.credential', url: '/credential', data: { - lookup: true + formChildState: true }, params: { credential_search: { @@ -334,7 +390,7 @@ angular.module('jobTemplates', [surveyMaker.name, jobTemplatesList.name, jobTemp } }, resolve: { - ListDefinition: ['ListDefinition', function(list) { + ListDefinition: ['CredentialList', function(list) { // mutate the provided list definition here return list; }], diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js index 24c7fd9679..25b889e4fd 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-help.service.js @@ -1,49 +1,9 @@ -export default ['CreateDialog', 'Wait', '$q', function(CreateDialog, Wait, $q){ +export default ['CreateDialog', 'Wait', '$q', '$state', function(CreateDialog, Wait, $q, $state){ return { - openDialog: function(params){ - // params.scope - - // let deferred = $q.defer(); - - // if (params.scope.removeWorkflowDialogReady) { - // params.scope.removeWorkflowDialogReady(); - // } - // params.scope.removeWorkflowDialogReady = params.scope.$on('WorkflowDialogReady', function() { - // $('#workflow-modal-dialog').dialog('open'); - - // deferred.resolve(); - // }); - // Wait('start'); - // debugger; - // CreateDialog({ - // id: 'workflow-modal-dialog', - // scope: params.scope, - // width: 1400, - // height: 720, - // draggable: false, - // dialogClass: 'SurveyMaker-dialog', - // position: ['center',20], - // onClose: function() { - // $('#workflow-modal-dialog').empty(); - // }, - // onOpen: function() { - // Wait('stop'); - - // // Let the modal height be variable based on the content - // // and set a uniform padding - // $('#workflow-modal-dialog').css({'padding': '20px'}); - - // }, - // _allowInteraction: function(e) { - // return !!$(e.target).is('.select2-input') || this._super(e); - // }, - // callback: 'WorkflowDialogReady' - // }); - - // return deferred.promise; - }, closeDialog: function() { $('#workflow-modal-dialog').dialog('destroy'); + + $state.go('^'); }, searchTree: function(params) { // params.element diff --git a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js index 498dd73965..c68a4fbf63 100644 --- a/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/job-templates/workflow-maker/workflow-maker.controller.js @@ -32,78 +32,10 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis value: "check" }]; - let job_template_url = GetBasePath('job_templates'); - // TODO: we won't be able to rely on this in the future for security purposes. Will need to come up - // with another way to get the list of job templates that have credentials that don't require passwords - // on launch - job_template_url += "?not__credential__vault_password=ASK¬__credential__password=ASK"; - //http://localhost:3000/api/v1/job_templates/?not__credential__vault_password=ASK¬__credential__password=ASK - - // Set up the lists for the add/edit node form - // let jobTemplatesList = _.cloneDeep(JobTemplateList); - // delete jobTemplatesList.fields.type; - // delete jobTemplatesList.fields.description; - // delete jobTemplatesList.fields.smart_status; - // delete jobTemplatesList.fields.labels; - // jobTemplatesList.fields.name.columnClass = "col-md-11"; - // jobTemplatesList.name = "workflow_job_templates"; - - // let project_url = GetBasePath('projects'); - - // let projectList = _.cloneDeep(ProjectList); - // delete projectList.fields.status; - // delete projectList.fields.scm_type; - // delete projectList.fields.last_updated; - // projectList.fields.name.columnClass = "col-md-11"; - // projectList.name = "workflow_projects"; - - //let inventory_sources_url = GetBasePath('inventory_sources'); - - //let inventorySourcesList = _.cloneDeep(InventorySourcesList); - function init() { $scope.treeDataMaster = angular.copy($scope.treeData.data); $scope.$broadcast("refreshWorkflowChart"); - $scope.$watchCollection('workflow_job_templates', function() { - if ($scope.selectedTemplate) { - // Loop across the inventories and see if one of them should be "checked" - $scope.workflow_job_templates.forEach(function(row, i) { - if (row.id === $scope.selectedTemplate.id) { - $scope.workflow_job_templates[i].checked = 1; - } else { - $scope.workflow_job_templates[i].checked = 0; - } - }); - } - }); - - $scope.$watchCollection('workflow_projects', function() { - if ($scope.selectedTemplate) { - // Loop across the inventories and see if one of them should be "checked" - $scope.workflow_projects.forEach(function(row, i) { - if (row.id === $scope.selectedTemplate.id) { - $scope.workflow_projects[i].checked = 1; - } else { - $scope.workflow_projects[i].checked = 0; - } - }); - } - }); - - $scope.$watchCollection('workflow_inventory_sources', function() { - if ($scope.selectedTemplate) { - // Loop across the inventories and see if one of them should be "checked" - $scope.workflow_inventory_sources.forEach(function(row, i) { - if (row.id === $scope.selectedTemplate.id) { - $scope.workflow_inventory_sources[i].checked = 1; - } else { - $scope.workflow_inventory_sources[i].checked = 0; - } - }); - } - }); - $scope.$watchGroup(['selectedTemplate', 'edgeType'], function() { if ($scope.selectedTemplate && $scope.edgeType) { $scope.workflowMakerFormConfig.formIsValid = true; @@ -143,7 +75,11 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis } $scope.lookUpInventory = function(){ - $state.go('.inventory') + $state.go('.inventory'); + }; + + $scope.lookUpCredential = function(){ + $state.go('.credential'); }; $scope.closeWorkflowMaker = function() { @@ -540,123 +476,63 @@ export default ['$scope', 'WorkflowHelpService', 'generateList', 'JobTemplateLis }; $scope.toggleFormTab = function(tab) { - // a dictionary of settings required by each encapsulated tab - $scope.$broadcast('resetWorkflowList', tab); if ($scope.workflowMakerFormConfig.activeTab !== tab) { $scope.workflowMakerFormConfig.activeTab = tab; } }; - $scope.toggle_job_template = function(id) { - - // $scope.workflow_projects.forEach(function(row, i) { - // $scope.workflow_projects[i].checked = 0; - // }); - - // $scope.workflow_inventory_sources.forEach(function(row, i) { - // $scope.workflow_inventory_sources[i].checked = 0; - // }); - - $scope.workflow_job_templates.forEach(function(row, i) { - if (row.id === id) { - $scope.selectedTemplate = angular.copy(row); - if ($scope.selectedTemplate.ask_credential_on_launch) { - if ($scope.selectedTemplate.summary_fields.credential) { - $scope.credential_name = $scope.selectedTemplate.summary_fields.credential.name ? $scope.selectedTemplate.summary_fields.credential.name : null; - $scope.credential = $scope.selectedTemplate.summary_fields.credential.id ? $scope.selectedTemplate.summary_fields.credential.id : null; - } else { - $scope.credential_name = null; - $scope.credential = null; - } - } - - if ($scope.selectedTemplate.ask_inventory_on_launch) { - if ($scope.selectedTemplate.summary_fields.inventory) { - $scope.inventory_name = $scope.selectedTemplate.summary_fields.inventory.name ? $scope.selectedTemplate.summary_fields.inventory.name : null; - $scope.inventory = $scope.selectedTemplate.summary_fields.inventory.id ? $scope.selectedTemplate.summary_fields.inventory.id : null; - } else { - $scope.inventory_name = null; - $scope.inventory = null; - } - } - - if ($scope.selectedTemplate.ask_job_type_on_launch) { - $scope.job_type = { - value: $scope.selectedTemplate.job_type ? $scope.selectedTemplate.job_type : null - }; - - // The default needs to be in place before we can select2-ify the dropdown - CreateSelect2({ - element: '#workflow_maker_job_type', - multiple: false - }); - } - - if ($scope.selectedTemplate.ask_limit_on_launch) { - $scope.limit = $scope.selectedTemplate.limit ? $scope.selectedTemplate.limit : null; - } - - if ($scope.selectedTemplate.ask_skip_tags_on_launch) { - $scope.skip_tags = $scope.selectedTemplate.skip_tags ? $scope.selectedTemplate.skip_tags : null; - } - - if ($scope.selectedTemplate.ask_tags_on_launch) { - $scope.job_tags = $scope.selectedTemplate.job_tags ? $scope.selectedTemplate.job_tags : null; - } - - $scope.workflow_job_templates[i].checked = 1; - } else { - $scope.workflow_job_templates[i].checked = 0; - } - }); - - }; - - $scope.toggle_project = function(id) { + $scope.$on('templateSelected', function(e, selectedTemplate) { resetPromptFields(); - // $scope.workflow_job_templates.forEach(function(row, i) { - // $scope.workflow_job_templates[i].checked = 0; - // }); + $scope.selectedTemplate = angular.copy(selectedTemplate); - // $scope.workflow_inventory_sources.forEach(function(row, i) { - // $scope.workflow_inventory_sources[i].checked = 0; - // }); - - $scope.workflow_projects.forEach(function(row, i) { - if (row.id === id) { - $scope.selectedTemplate = angular.copy(row); - $scope.workflow_projects[i].checked = 1; + if ($scope.selectedTemplate.ask_credential_on_launch) { + if ($scope.selectedTemplate.summary_fields.credential) { + $scope.credential_name = $scope.selectedTemplate.summary_fields.credential.name ? $scope.selectedTemplate.summary_fields.credential.name : null; + $scope.credential = $scope.selectedTemplate.summary_fields.credential.id ? $scope.selectedTemplate.summary_fields.credential.id : null; } else { - $scope.workflow_projects[i].checked = 0; + $scope.credential_name = null; + $scope.credential = null; } - }); + } - }; - - $scope.toggle_inventory_source = function(id) { - - resetPromptFields(); - - // $scope.workflow_job_templates.forEach(function(row, i) { - // $scope.workflow_job_templates[i].checked = 0; - // }); - - // $scope.workflow_projects.forEach(function(row, i) { - // $scope.workflow_projects[i].checked = 0; - // }); - - $scope.workflow_inventory_sources.forEach(function(row, i) { - if (row.id === id) { - $scope.selectedTemplate = angular.copy(row); - $scope.workflow_inventory_sources[i].checked = 1; + if ($scope.selectedTemplate.ask_inventory_on_launch) { + if ($scope.selectedTemplate.summary_fields.inventory) { + $scope.inventory_name = $scope.selectedTemplate.summary_fields.inventory.name ? $scope.selectedTemplate.summary_fields.inventory.name : null; + $scope.inventory = $scope.selectedTemplate.summary_fields.inventory.id ? $scope.selectedTemplate.summary_fields.inventory.id : null; } else { - $scope.workflow_inventory_sources[i].checked = 0; + $scope.inventory_name = null; + $scope.inventory = null; } - }); + } - }; + if ($scope.selectedTemplate.ask_job_type_on_launch) { + $scope.job_type = { + value: $scope.selectedTemplate.job_type ? $scope.selectedTemplate.job_type : null + }; + + // The default needs to be in place before we can select2-ify the dropdown + CreateSelect2({ + element: '#workflow_maker_job_type', + multiple: false + }); + } + + if ($scope.selectedTemplate.ask_limit_on_launch) { + $scope.limit = $scope.selectedTemplate.limit ? $scope.selectedTemplate.limit : null; + } + + if ($scope.selectedTemplate.ask_skip_tags_on_launch) { + $scope.skip_tags = $scope.selectedTemplate.skip_tags ? $scope.selectedTemplate.skip_tags : null; + } + + if ($scope.selectedTemplate.ask_tags_on_launch) { + $scope.job_tags = $scope.selectedTemplate.job_tags ? $scope.selectedTemplate.job_tags : null; + } + + $scope.$broadcast('clearOtherTemplateLists', $scope.workflowMakerFormConfig.activeTab); + }); init(); diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 955a5d1e7e..d0cfb46a92 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1274,7 +1274,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat if(this.mode === "edit"){ html += `
` + + `ng-class="{'is-selected': $state.is('${this.form.activeEditState}') || $state.is('${this.form.stateTree}.edit') || $state.$current.data.formChildState }">` + `${details}
`; for (itm in this.form.related) { @@ -1372,8 +1372,8 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "
";//tabHolder } - if(!_.isEmpty(this.form.related) && this.mode === "edit"){ - html += `
`; + if(!_.isEmpty(this.form.related) && this.mode === "edit"){// TODO: either include $state.is('templates.editWorkflowJobTemplate') or figure out something else to do here + html += `
`; } html += "
-
@@ -16,7 +16,7 @@
diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js index 79170ac8bb..d3c5806698 100644 --- a/awx/ui/client/src/shared/stateDefinitions.factory.js +++ b/awx/ui/client/src/shared/stateDefinitions.factory.js @@ -372,7 +372,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat // a lookup field's basePath takes precedence over generic list definition's basePath, if supplied data: { basePath: field.basePath || null, - lookup: true + formChildState: true }, params: { [field.sourceModel + '_search']: { diff --git a/awx/ui/grunt-tasks/browserSync.js b/awx/ui/grunt-tasks/browserSync.js index 107444ae0f..b4c9e179c6 100644 --- a/awx/ui/grunt-tasks/browserSync.js +++ b/awx/ui/grunt-tasks/browserSync.js @@ -17,7 +17,13 @@ module.exports = { ws: true }, keepalive: false, - watchTask: true + watchTask: true, + // The browser-sync-client lib will write your current scroll position to window.name + // https://github.com/BrowserSync/browser-sync-client/blob/a2718faa91e11553feca7a3962313bf1ec6ba3e5/dist/index.js#L500 + // This strategy is enabled in the core browser-sync lib, and not externally documented as an option. Yay! + // https://github.com/BrowserSync/browser-sync/blob/a522aaf12b6167d5591ed285eb3086f43a4d9ac2/lib/default-config.js#L312 + scrollRestoreTechnique: null, + injectChanges: true } } }; From abdd9f8372f539311570f2e94d8bd787dd8d57f2 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 8 Nov 2016 08:40:14 -0500 Subject: [PATCH 08/45] adding files for workflow-results --- awx/ui/client/src/controllers/Jobs.js | 3 + awx/ui/client/src/workflow-results/main.js | 14 + .../workflow-results.block.less | 0 .../workflow-results.controller.js | 0 .../workflow-results.partial.html | 522 ++++++++++++++++++ .../workflow-results.route.js | 153 +++++ 6 files changed, 692 insertions(+) create mode 100644 awx/ui/client/src/workflow-results/main.js create mode 100644 awx/ui/client/src/workflow-results/workflow-results.block.less create mode 100644 awx/ui/client/src/workflow-results/workflow-results.controller.js create mode 100644 awx/ui/client/src/workflow-results/workflow-results.partial.html create mode 100644 awx/ui/client/src/workflow-results/workflow-results.route.js diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index 54187896df..dfa9cf1eec 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -84,6 +84,9 @@ export function JobsListController($state, $rootScope, $log, $scope, $compile, $ case 'inventory_update': goToJobDetails('inventorySyncStdout'); break; + case 'workflow_job': + goToJobDetails('workflowResults'); + break; } }; diff --git a/awx/ui/client/src/workflow-results/main.js b/awx/ui/client/src/workflow-results/main.js new file mode 100644 index 0000000000..eefa57c2dc --- /dev/null +++ b/awx/ui/client/src/workflow-results/main.js @@ -0,0 +1,14 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + +import route from './job-results.route.js'; + +export default + angular.module('workflowResults', []) + .run(['$stateExtender', function($stateExtender) { + $stateExtender.addState(route); + }]); diff --git a/awx/ui/client/src/workflow-results/workflow-results.block.less b/awx/ui/client/src/workflow-results/workflow-results.block.less new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/client/src/workflow-results/workflow-results.controller.js b/awx/ui/client/src/workflow-results/workflow-results.controller.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/client/src/workflow-results/workflow-results.partial.html b/awx/ui/client/src/workflow-results/workflow-results.partial.html new file mode 100644 index 0000000000..343c815c08 --- /dev/null +++ b/awx/ui/client/src/workflow-results/workflow-results.partial.html @@ -0,0 +1,522 @@ +
+
+
+ + +
+
+ + +
+
+ RESULTS +
+ + +
+ + + + + + + + + +
+
+ + +
+ + +
+ +
+ {{ job.started | longDate }} +
+
+ + +
+ +
+ {{ (job.finished | + longDate) || "Not Finished" }} +
+
+ + + + + +
+ +
+ {{ type_label }} +
+
+ + + + + + + + + + + +
+ +
+ {{ job.playbook }} +
+
+ + +
+ + +
+ + + + + + + + +
+ +
+ {{ job.forks }} +
+
+ + +
+ +
+ {{ job.limit }} +
+
+ + +
+ +
+ {{ verbosity_label }} +
+
+ + +
+ +
+ {{ job.job_tags }} +
+
+ + +
+ +
+ {{ job.skip_tags }} +
+
+ + +
+ + +
+ + +
+ +
+
+
+
+ {{ label }} +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + +
+
+ + +
+
+ + + {{ job.name }} +
+ + +
+ +
+ Plays +
+ + {{ playCount || 0}} + + + +
+ Tasks +
+ + {{ taskCount || 0}} + + + +
+ Hosts +
+ + {{ hostCount || 0}} + + + +
+ Elapsed +
+ + {{ job.elapsed * 1000 | duration: "hh:mm:ss" }} + +
+ + +
+ + + + + + + + + +
+
+ + +
+ +
+
+
diff --git a/awx/ui/client/src/workflow-results/workflow-results.route.js b/awx/ui/client/src/workflow-results/workflow-results.route.js new file mode 100644 index 0000000000..dc10054c9e --- /dev/null +++ b/awx/ui/client/src/workflow-results/workflow-results.route.js @@ -0,0 +1,153 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import {templateUrl} from '../shared/template-url/template-url.factory'; + +import workflowResultsController from './workflow-results.controller'; + +export default { + name: 'workflowResults', + url: '/jobs/:id', + ncyBreadcrumb: { + parent: 'jobs', + label: '{{ job.id }} - {{ job.name }}' + }, + data: { + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + } + }, + templateUrl: templateUrl('workflow-results/workflow-results'), + controller: workflowResultsController + // resolve: { + // // the GET for the particular job + // jobData: ['Rest', 'GetBasePath', '$stateParams', '$q', '$state', 'Alert', function(Rest, GetBasePath, $stateParams, $q, $state, Alert) { + // Rest.setUrl(GetBasePath('jobs') + $stateParams.id); + // var val = $q.defer(); + // Rest.get() + // .then(function(data) { + // val.resolve(data.data); + // }, function(data) { + // val.reject(data); + // + // if (data.status === 404) { + // Alert('Job Not Found', 'Cannot find job.', 'alert-info'); + // } else if (data.status === 403) { + // Alert('Insufficient Permissions', 'You do not have permission to view this job.', 'alert-info'); + // } + // + // $state.go('jobs'); + // }); + // return val.promise; + // }], + // // after the GET for the job, this helps us keep the status bar from + // // flashing as rest data comes in. If the job is finished and + // // there's a playbook_on_stats event, go ahead and resolve the count + // // so you don't get that flashing! + // count: ['jobData', 'jobResultsService', 'Rest', '$q', function(jobData, jobResultsService, Rest, $q) { + // var defer = $q.defer(); + // if (jobData.finished) { + // // if the job is finished, grab the playbook_on_stats + // // role to get the final count + // Rest.setUrl(jobData.related.job_events + + // "?event=playbook_on_stats"); + // Rest.get() + // .success(function(data) { + // if(!data.results[0]){ + // defer.resolve({val: { + // ok: 0, + // skipped: 0, + // unreachable: 0, + // failures: 0, + // changed: 0 + // }, countFinished: false}); + // } + // else { + // defer.resolve({ + // val: jobResultsService + // .getCountsFromStatsEvent(data + // .results[0].event_data), + // countFinished: true}); + // } + // }) + // .error(function() { + // defer.resolve({val: { + // ok: 0, + // skipped: 0, + // unreachable: 0, + // failures: 0, + // changed: 0 + // }, countFinished: false}); + // }); + // } else { + // // job isn't finished so just send an empty count and read + // // from events + // defer.resolve({val: { + // ok: 0, + // skipped: 0, + // unreachable: 0, + // failures: 0, + // changed: 0 + // }, countFinished: false}); + // } + // return defer.promise; + // }], + // // GET for the particular jobs labels to be displayed in the + // // left-hand pane + // jobLabels: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { + // var getNext = function(data, arr, resolve) { + // Rest.setUrl(data.next); + // Rest.get() + // .success(function (data) { + // if (data.next) { + // getNext(data, arr.concat(data.results), resolve); + // } else { + // resolve.resolve(arr.concat(data.results) + // .map(val => val.name)); + // } + // }); + // }; + // + // var seeMoreResolve = $q.defer(); + // + // Rest.setUrl(GetBasePath('jobs') + $stateParams.id + '/labels/'); + // Rest.get() + // .success(function(data) { + // if (data.next) { + // getNext(data, data.results, seeMoreResolve); + // } else { + // seeMoreResolve.resolve(data.results + // .map(val => val.name)); + // } + // }); + // + // return seeMoreResolve.promise; + // }], + // // OPTIONS request for the job. Used to make things like the + // // verbosity data in the left-hand pane prettier than just an + // // integer + // jobDataOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { + // Rest.setUrl(GetBasePath('jobs') + $stateParams.id); + // var val = $q.defer(); + // Rest.options() + // .then(function(data) { + // val.resolve(data.data); + // }, function(data) { + // val.reject(data); + // }); + // return val.promise; + // }], + // // This clears out the event queue, otherwise it'd be full of events + // // for previous job results the user had navigated to + // eventQueueInit: ['eventQueue', function(eventQueue) { + // eventQueue.initialize(); + // }] + // }, + // +}; From 9cb002b4cb812c3ac034543ddb5986d48f6a884e Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Tue, 8 Nov 2016 12:21:11 -0500 Subject: [PATCH 09/45] adding more workflow results pages and functionality --- awx/ui/client/src/app.js | 2 + awx/ui/client/src/workflow-results/main.js | 7 +- .../workflow-results.block.less | 114 +++++++ .../workflow-results.controller.js | 188 +++++++++++ .../workflow-results.partial.html | 306 +++++------------- .../workflow-results.route.js | 216 +++++-------- .../workflow-results.service.js | 196 +++++++++++ 7 files changed, 668 insertions(+), 361 deletions(-) create mode 100644 awx/ui/client/src/workflow-results/workflow-results.service.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 7d3f3d302e..5db5cb5638 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -48,6 +48,7 @@ import inventoryScripts from './inventory-scripts/main'; import organizations from './organizations/main'; import managementJobs from './management-jobs/main'; import jobDetail from './job-detail/main'; +import workflowResults from './workflow-results/main'; import jobSubmission from './job-submission/main'; import notifications from './notifications/main'; import about from './about/main'; @@ -115,6 +116,7 @@ var tower = angular.module('Tower', [ activityStream.name, footer.name, jobDetail.name, + workflowResults.name, jobSubmission.name, notifications.name, standardOut.name, diff --git a/awx/ui/client/src/workflow-results/main.js b/awx/ui/client/src/workflow-results/main.js index eefa57c2dc..6e6d775dea 100644 --- a/awx/ui/client/src/workflow-results/main.js +++ b/awx/ui/client/src/workflow-results/main.js @@ -5,10 +5,13 @@ *************************************************/ -import route from './job-results.route.js'; +import route from './workflow-results.route.js'; + +import workflowResultsService from './workflow-results.service'; export default angular.module('workflowResults', []) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState(route); - }]); + }]) + .service('workflowResultsService', workflowResultsService); diff --git a/awx/ui/client/src/workflow-results/workflow-results.block.less b/awx/ui/client/src/workflow-results/workflow-results.block.less index e69de29bb2..a053db98c1 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.block.less +++ b/awx/ui/client/src/workflow-results/workflow-results.block.less @@ -0,0 +1,114 @@ +@import '../shared/branding/colors.less'; +@import '../shared/branding/colors.default.less'; +@import '../shared/layouts/one-plus-two.less'; + +@breakpoint-md: 1200px; +@breakpoint-sm: 623px; + +.WorkflowResults { + .OnePlusTwo-container(100%, @breakpoint-md); + + &.fullscreen { + .WorkflowResults-rightSide { + max-width: 100%; + } + } +} + +.WorkflowResults-leftSide { + .OnePlusTwo-left--panel(100%, @breakpoint-md); + // TODO: needs to be set based on height of browser window + height: 870px !important; +} + +.WorkflowResults-rightSide { + .OnePlusTwo-right--panel(100%, @breakpoint-md); + // TODO: needs to be set based on height of browser window + height: 870px !important; + + @media (max-width: @breakpoint-md - 1px) { + padding-right: 15px; + } +} + +.WorkflowResults-stdoutActionButton--active { + display: none; + visibility: hidden; + flex:none; + width:0px; + padding-right: 0px; +} + +.WorkflowResults-panelHeader { + display: flex; + height: 30px; +} + +.WorkflowResults-panelHeaderText { + color: @default-interface-txt; + flex: 1 0 auto; + font-size: 14px; + font-weight: bold; + margin-right: 10px; + text-transform: uppercase; +} + +.WorkflowResults-resultRow { + width: 100%; + display: flex; + padding-bottom: 10px; + flex-wrap: wrap; +} + +.WorkflowResults-resultRow--variables { + flex-direction: column; +} + +.WorkflowResults-resultRowLabel { + text-transform: uppercase; + color: @default-interface-txt; + font-size: 14px; + font-weight: normal!important; + width: 30%; + margin-right: 20px; + + @media screen and (max-width: @breakpoint-md) { + flex: 2.5 0 auto; + } +} + +.WorkflowResults-resultRowLabel--fullWidth { + width: 100%; + margin-right: 0px; +} + +.WorkflowResults-resultRowText { + width: ~"calc(70% - 20px)"; + flex: 1 0 auto; + text-transform: none; + word-wrap: break-word; +} + +.WorkflowResults-resultRowText--fullWidth { + width: 100%; +} + +.WorkflowResults-statusResultIcon { + padding-left: 0px; + padding-right: 10px; +} + +.WorkflowResults-badgeRow { + display: flex; + align-items: center; + margin-right: 5px; +} + +.WorkflowResults-badgeTitle{ + color: @default-interface-txt; + font-size: 14px; + margin-right: 10px; + font-weight: normal; + text-transform: uppercase; + margin-left: 20px; +} diff --git a/awx/ui/client/src/workflow-results/workflow-results.controller.js b/awx/ui/client/src/workflow-results/workflow-results.controller.js index e69de29bb2..3b96f6e0d2 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.controller.js +++ b/awx/ui/client/src/workflow-results/workflow-results.controller.js @@ -0,0 +1,188 @@ +export default ['workflowData', + 'workflowResultsService', + 'workflowDataOptions', + 'jobLabels', + 'workflowNodes', + '$scope', + 'ParseTypeChange', + 'ParseVariableString', + function(workflowData, + workflowResultsService, + workflowDataOptions, + jobLabels, + workflowNodes, + $scope, + ParseTypeChange, + ParseVariableString, + ) { + var getTowerLinks = function() { + var getTowerLink = function(key) { + if ($scope.workflow.related[key]) { + return '/#/' + $scope.workflow.related[key] + .split('api/v1/')[1]; + } + else { + return null; + } + }; + + $scope.workflow_template_link = '/#/templates/workflow_job_template/'+$scope.workflow.workflow_job_template; + $scope.created_by_link = getTowerLink('created_by'); + $scope.cloud_credential_link = getTowerLink('cloud_credential'); + $scope.network_credential_link = getTowerLink('network_credential'); + }; + + var getTowerLabels = function() { + var getTowerLabel = function(key) { + if ($scope.workflowOptions && $scope.workflowOptions[key]) { + return $scope.workflowOptions[key].choices + .filter(val => val[0] === $scope.workflow[key]) + .map(val => val[1])[0]; + } else { + return null; + } + }; + + $scope.status_label = getTowerLabel('status'); + $scope.type_label = getTowerLabel('job_type'); + $scope.verbosity_label = getTowerLabel('verbosity'); + }; + + var getTotalHostCount = function(count) { + return Object + .keys(count).reduce((acc, i) => acc += count[i], 0); + }; + + // put initially resolved request data on scope + $scope.workflow = workflowData; + $scope.workflow_nodes = workflowNodes; + $scope.workflowOptions = workflowDataOptions.actions.GET; + $scope.labels = jobLabels; + + // turn related api browser routes into tower routes + getTowerLinks(); + + // use options labels to manipulate display of details + getTowerLabels(); + + // set up a read only code mirror for extra vars + $scope.variables = ParseVariableString($scope.workflow.extra_vars); + $scope.parseType = 'yaml'; + ParseTypeChange({ scope: $scope, + field_id: 'pre-formatted-variables', + readOnly: true }); + + // Click binding for the expand/collapse button on the standard out log + $scope.stdoutFullScreen = false; + $scope.toggleStdoutFullscreen = function() { + $scope.stdoutFullScreen = !$scope.stdoutFullScreen; + }; + + $scope.deleteJob = function() { + workflowResultsService.deleteJob($scope.workflow); + }; + + $scope.cancelJob = function() { + workflowResultsService.cancelJob($scope.workflow); + }; + + $scope.relaunchJob = function() { + workflowResultsService.relaunchJob($scope); + }; + + $scope.stdoutArr = []; + + // EVENT STUFF BELOW + + // just putting the event queue on scope so it can be inspected in the + // console + // $scope.event_queue = eventQueue.queue; + // $scope.defersArr = eventQueue.populateDefers; + + // This is where the async updates to the UI actually happen. + // Flow is event queue munging in the service -> $scope setting in here + var processEvent = function(event) { + // put the event in the queue + eventQueue.populate(event).then(mungedEvent => { + // make changes to ui based on the event returned from the queue + if (mungedEvent.changes) { + mungedEvent.changes.forEach(change => { + // we've got a change we need to make to the UI! + // update the necessary scope and make the change + if (change === 'startTime' && !$scope.workflow.start) { + $scope.workflow.start = mungedEvent.startTime; + } + + if (change === 'count' && !$scope.countFinished) { + // for all events that affect the host count, + // update the status bar as well as the host + // count badge + $scope.count = mungedEvent.count; + $scope.hostCount = getTotalHostCount(mungedEvent + .count); + } + + if (change === 'playCount') { + $scope.playCount = mungedEvent.playCount; + } + + if (change === 'taskCount') { + $scope.taskCount = mungedEvent.taskCount; + } + + if (change === 'finishedTime' && !$scope.workflow.finished) { + $scope.workflow.finished = mungedEvent.finishedTime; + } + + if (change === 'countFinished') { + // the playbook_on_stats event actually lets + // us know that we don't need to iteratively + // look at event to update the host counts + // any more. + $scope.countFinished = true; + } + + if(change === 'stdout'){ + angular + .element(".JobResultsStdOut-stdoutContainer") + .append($compile(mungedEvent + .stdout)($scope)); + } + }); + } + + // the changes have been processed in the ui, mark it in the queue + eventQueue.markProcessed(event); + }); + }; + + // PULL! grab completed event data and process each event + // TODO: implement retry logic in case one of these requests fails + // var getEvents = function(url) { + // workflowResultsService.getEvents(url) + // .then(events => { + // events.results.forEach(event => { + // // get the name in the same format as the data + // // coming over the websocket + // event.event_name = event.event; + // processEvent(event); + // }); + // if (events.next) { + // getEvents(events.next); + // } + // }); + // }; + // getEvents($scope.job.related.job_events); + + // Processing of job_events messages from the websocket + $scope.$on(`ws-job_events-${$scope.workflow.id}`, function(e, data) { + processEvent(data); + }); + + // Processing of job-status messages from the websocket + $scope.$on(`ws-jobs`, function(e, data) { + if (parseInt(data.unified_job_id, 10) === parseInt($scope.workflow.id,10)) { + $scope.workflow.status = data.status; + } + }); +}]; diff --git a/awx/ui/client/src/workflow-results/workflow-results.partial.html b/awx/ui/client/src/workflow-results/workflow-results.partial.html index 343c815c08..a418451de7 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.partial.html +++ b/awx/ui/client/src/workflow-results/workflow-results.partial.html @@ -1,20 +1,20 @@ -
+
-
+
-
+
+ class="WorkflowResults-panelHeaderText"> RESULTS
@@ -37,7 +37,7 @@ List-actionButton--delete" data-placement="top" ng-click="deleteJob()" - ng-show="job_status.status == 'running' || + ng-show="workflow_status.status == 'running' || job_status.status=='pending' " aw-tool-tip="Cancel" data-original-title="" title=""> @@ -63,237 +63,97 @@
-
-