From 957e6bdcb6717956239c5d74edd98decf61663a4 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 21 Sep 2017 12:48:02 +0100 Subject: [PATCH] Service params editing (#1253) * Allow parameters to be supplied when creating a service instance and binding to an application * Bug fix * Address PR feedback * Fix for failing unit test * e2e test fix * Address PR feedback * Fix bug when re-editing a parameter --- .../app-framework/src/filters/json.filter.js | 32 ++++++++ .../utils/auto-focus/focus-when.directive.js | 28 +++++++ .../focusable-input.directive.js | 2 + .../async-task-dialog/async-task-dialog.html | 38 ++++----- .../json-text-input.directive.js | 34 ++++++++ .../json-text-input/json-text-input.scss | 16 ++++ .../app-framework/src/widgets/widgets.scss | 1 + .../src/widgets/wizard/wizard.html | 59 +++++++------- .../src/widgets/wizard/wizard.scss | 24 +++++- .../style/components/_async_task_dialog.scss | 26 ++++++ .../frontend/i18n/en_US/app.json | 11 ++- .../add-service-workflow.directive.js | 4 + .../add-service-workflow.scss | 4 +- .../add-service-workflow/instance.html | 26 ++++++ .../create-service-instance.html | 26 +++++- .../create-service-instance.scss | 80 +++++++++++++++++-- ....js => create-service-instance.service.js} | 7 +- 17 files changed, 360 insertions(+), 58 deletions(-) create mode 100644 components/app-framework/src/filters/json.filter.js create mode 100644 components/app-framework/src/utils/auto-focus/focus-when.directive.js create mode 100644 components/app-framework/src/widgets/json-text-input/json-text-input.directive.js create mode 100644 components/app-framework/src/widgets/json-text-input/json-text-input.scss rename components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/{create-servince-instance.service.js => create-service-instance.service.js} (95%) diff --git a/components/app-framework/src/filters/json.filter.js b/components/app-framework/src/filters/json.filter.js new file mode 100644 index 0000000000..e22344c886 --- /dev/null +++ b/components/app-framework/src/filters/json.filter.js @@ -0,0 +1,32 @@ +(function () { + 'use strict'; + + // The filters + angular.module('app.framework.filters') + .filter('jsonString', jsonStringFilter); + + /** + * @namespace app.framework.filters.jsonStringFilter + * @memberof app.framework.filters + * @name jsonStringFilter + * @description An angular filter which will format the JSON object as a string OR show a supplied invalid message + * @param {object} $filter - Angular $filter service + * @returns {Function} The filter itself + */ + function jsonStringFilter($filter) { + var jsonStringFilter = function (obj, invalidMsg) { + + try { + return angular.toJson(obj); + } catch (e) { + return $filter('translate')(invalidMsg) || ''; + } + }; + + // Ensure the filter is reapplied on change of language + jsonStringFilter.$stateful = true; + + return jsonStringFilter; + } + +})(); diff --git a/components/app-framework/src/utils/auto-focus/focus-when.directive.js b/components/app-framework/src/utils/auto-focus/focus-when.directive.js new file mode 100644 index 0000000000..27a42ce898 --- /dev/null +++ b/components/app-framework/src/utils/auto-focus/focus-when.directive.js @@ -0,0 +1,28 @@ +(function () { + 'use strict'; + + angular + .module('app.framework.utils') + .directive('focusWhen', focusWhen); + + /** + * A simple attribute directive to set focus on an element when a value is set + * @param {Object} $timeout - the Angular $timeout service + * @returns {Object} the focus-when directive + * */ + function focusWhen($timeout) { + return { + restrict: 'A', + link: function (scope, element, attrs) { + scope.$watch(attrs.focusWhen, function (nv) { + if (nv) { + $timeout(function () { + element[0].focus(); + }, 0); + } + }); + } + }; + } + +})(); diff --git a/components/app-framework/src/utils/focusable-input/focusable-input.directive.js b/components/app-framework/src/utils/focusable-input/focusable-input.directive.js index dca2a9a24a..c85ed94068 100644 --- a/components/app-framework/src/utils/focusable-input/focusable-input.directive.js +++ b/components/app-framework/src/utils/focusable-input/focusable-input.directive.js @@ -28,9 +28,11 @@ element.find('input').on('focus', handleOnFocus); element.find('select-input').on('focus', handleOnFocus); element.find('tags-input').on('focus', handleOnFocus); + element.find('textarea').on('focus', handleOnFocus); element.find('input').on('blur', handleOnBlur); element.find('select-input').on('blur', handleOnBlur); element.find('tags-input').on('blur', handleOnBlur); + element.find('textarea').on('blur', handleOnBlur); function handleOnFocus() { element.addClass(focusedClass); diff --git a/components/app-framework/src/widgets/async-task-dialog/async-task-dialog.html b/components/app-framework/src/widgets/async-task-dialog/async-task-dialog.html index 836863f280..580cfc423f 100644 --- a/components/app-framework/src/widgets/async-task-dialog/async-task-dialog.html +++ b/components/app-framework/src/widgets/async-task-dialog/async-task-dialog.html @@ -23,24 +23,26 @@

- - + diff --git a/components/app-framework/src/widgets/json-text-input/json-text-input.directive.js b/components/app-framework/src/widgets/json-text-input/json-text-input.directive.js new file mode 100644 index 0000000000..f1edfbbbd8 --- /dev/null +++ b/components/app-framework/src/widgets/json-text-input/json-text-input.directive.js @@ -0,0 +1,34 @@ +(function () { + 'use strict'; + + angular + .module('app.framework.widgets') + .directive('jsonTextInput', jsonTextInput); + + /** + * @name jsonTextInput + * @description A directive that displays JSON. + * @returns {object} The directive definition object + */ + function jsonTextInput() { + return { + restrict: 'A', + require: 'ngModel', + scope: { + ngModel: '=' + }, + link: function ($scope, elem, attr, ngModelCtrl) { + ngModelCtrl.$parsers.push(function (viewValue) { + try { + return angular.fromJson(viewValue); + } catch (e) { + return undefined; + } + }); + ngModelCtrl.$formatters.push(function (value) { + return angular.toJson(value, 2); + }); + } + }; + } +})(); diff --git a/components/app-framework/src/widgets/json-text-input/json-text-input.scss b/components/app-framework/src/widgets/json-text-input/json-text-input.scss new file mode 100644 index 0000000000..2a4cb917bb --- /dev/null +++ b/components/app-framework/src/widgets/json-text-input/json-text-input.scss @@ -0,0 +1,16 @@ +.json-input-field { + flex: 1 1 0; + display: flex; + flex-direction: column; + + &.form-group { + width: 100%; + } + + textarea { + outline: 0; + border: 0; + font-family: monospace; + font-size: $font-size-monospace; + } +} \ No newline at end of file diff --git a/components/app-framework/src/widgets/widgets.scss b/components/app-framework/src/widgets/widgets.scss index ace17485dd..b5a8dfad26 100644 --- a/components/app-framework/src/widgets/widgets.scss +++ b/components/app-framework/src/widgets/widgets.scss @@ -11,6 +11,7 @@ @import "flyout/flyout"; @import "gallery-card/gallery-card"; @import "global-spinner/global-spinner"; +@import "json-text-input/json-text-input"; @import "json-tree-view/json-tree-view"; @import "log-viewer/logs-viewer"; @import "paginator/paginator"; diff --git a/components/app-framework/src/widgets/wizard/wizard.html b/components/app-framework/src/widgets/wizard/wizard.html index df013b04eb..80814ed167 100644 --- a/components/app-framework/src/widgets/wizard/wizard.html +++ b/components/app-framework/src/widgets/wizard/wizard.html @@ -69,36 +69,37 @@

{{ wizardCtrl.workflow.title }}

+
+ - - - + - + +
diff --git a/components/app-framework/src/widgets/wizard/wizard.scss b/components/app-framework/src/widgets/wizard/wizard.scss index 4ff52ae3bb..5b8a6f5ef0 100644 --- a/components/app-framework/src/widgets/wizard/wizard.scss +++ b/components/app-framework/src/widgets/wizard/wizard.scss @@ -14,4 +14,26 @@ .wizard-nav-step-number, .wizard-nav-step-number-complete, .wizard-nav-item-sep { display: none; -} \ No newline at end of file +} + + +.wizard-foot { + overflow: hidden; + .wizard-foot-buttons { + + button { + transition: margin-left 0.5s, margin-right 0.5s; + } + + &.wizard-footer-hidden { + + button:first-child { + margin-left: -200px + } + + button:last-child { + margin-right: -200px + } + } + } +} diff --git a/components/app-theme/src/scss/style/components/_async_task_dialog.scss b/components/app-theme/src/scss/style/components/_async_task_dialog.scss index bd3fd3f0ff..cf5dced94d 100644 --- a/components/app-theme/src/scss/style/components/_async_task_dialog.scss +++ b/components/app-theme/src/scss/style/components/_async_task_dialog.scss @@ -1,3 +1,6 @@ +$async-dialog-header-height: $console-unit-space * 3; +$async-dialog-footer-height: $button-height + $console-unit-space * 2 + 1; + .async-dialog { margin-right: $detail-view-margin; margin-left: $detail-view-margin; @@ -20,10 +23,33 @@ .modal-footer { margin-top: $console-unit-space * 2; padding-top: $console-unit-space; + height: $async-dialog-footer-height; + overflow: hidden; + flex: 0 0 $async-dialog-footer-height; } .disable-margin { margin-top: 0; } + + .modal-footer { + .async-footer-buttons { + + button { + transition: margin-left 0.5s, margin-right 0.5s; + } + + &.async-footer-hidden { + + button:first-child { + margin-left: -200px + } + + button:last-child { + margin-right: -200px + } + } + } + } } diff --git a/components/cloud-foundry/frontend/i18n/en_US/app.json b/components/cloud-foundry/frontend/i18n/en_US/app.json index 2c4116bf1b..8ea453b148 100644 --- a/components/cloud-foundry/frontend/i18n/en_US/app.json +++ b/components/cloud-foundry/frontend/i18n/en_US/app.json @@ -244,10 +244,19 @@ "name-error-pattern": "Name contains invalid pattern", "plan-label": "Plan", "plan-placeholder": "Select a Plan", + "params-label": "Instance Parameters", + "binding-params-label": "Binding Parameters", + "service-params": "Service Instance Parameters", + "binding-params": "Service Binding Parameters", + "params-placeholder": "Enter Service Parameter JSON", + "tags-label": "Tags", + "edit-params": "Edit", + "done": "Done", "button": { "yes": "Create", "no": "@:buttons.cancel" - } + }, + "json-error-invalid": "Must be valid JSON" }, "view-envs": { "title": "{{instanceName}}: Variables" diff --git a/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.directive.js b/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.directive.js index 7ec9027471..c605eb62c2 100644 --- a/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.directive.js +++ b/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.directive.js @@ -257,6 +257,10 @@ app_guid: vm.data.app.summary.guid }; + if (vm.options.userInput && vm.options.userInput.params) { + bindingSpec.parameters = vm.options.userInput.params; + } + return bindingModel.createServiceBinding(vm.data.cnsiGuid, bindingSpec) .then(function (newBinding) { if (angular.isDefined(newBinding.metadata)) { diff --git a/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.scss b/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.scss index 163f4dd7a9..fd61524848 100644 --- a/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.scss +++ b/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/add-service-workflow.scss @@ -36,7 +36,7 @@ .select-instance-tabs { - min-height: 264px; + min-height: 300px; .nav.nav-tabs { .uib-tab > a { @@ -62,7 +62,7 @@ .no-bindable-instances > td { text-align: center; p { - margin: $console-unit-space / 2; + margin: $console-half-space; } } diff --git a/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/instance.html b/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/instance.html index 5aa85b5ee2..532ed539a9 100644 --- a/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/instance.html +++ b/components/cloud-foundry/frontend/src/view/applications/workflows/add-service-workflow/instance.html @@ -54,6 +54,32 @@ + +
+ + app.app-info.app-tabs.services.create.edit-params +
{{ wizardCtrl.options.userInput.params | jsonString }}
+
+ + + +
+
+
+ + + app.app-info.app-tabs.services.create.json-error-invalid + + +
+ +
+
\ No newline at end of file diff --git a/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.html b/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.html index 3d954c73ed..17ce1b0bb3 100644 --- a/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.html +++ b/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.html @@ -26,11 +26,35 @@
- +
+
+ + app.app-info.app-tabs.services.create.edit-params +
{{ asyncTaskDialogCtrl.context.options.userInput.params | jsonString }}
+
+ + +
+
+
+ + + app.app-info.app-tabs.services.create.json-error-invalid + + +
+ +
+
\ No newline at end of file diff --git a/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.scss b/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.scss index c9d794cc24..1fbb39a0a1 100644 --- a/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.scss +++ b/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.scss @@ -9,14 +9,84 @@ flex: 1; width: 100%; - >ng-include { + > ng-include { flex: 1 1 0; } } + } + } +} - .form-actions.modal-footer { - flex: 0; - } +.async-dialog-param-editor { + display: none; + top: $async-dialog-header-height; + bottom: $async-dialog-footer-height; + left: $console-unit-space; + right: $console-unit-space; + + &.param-editor-open { + position: absolute; + display: block; + } + + .param-editor-container { + display: flex; + width: 100%; + height: 100%; + } +} + +.async-dialog-param-editor { + .param-editor-container { + flex-direction: column; + background-color: $white; + + p { + flex: 0 0 $console-unit-space; + margin: 0px; } - } + + textarea { + flex: 1 1 0; + resize: none; + } + + .param-editor-buttons { + flex: 0 0 auto; + align-self: center; + margin: $console-half-space 0; + } + } +} + +form .form-group { + + &.tags-input-field { + width: 100%; + } + + &.form-json-editor-input { + padding-right: $console-unit-space * 3; + width: 100%; + + a.input-box-edit { + position: absolute; + right: 0; + border: 1px solid $input-border; + padding: 2px 6px; + margin-right: $console-half-space; + bottom: $padding-base-vertical; + } + + input.json-edit-box { + pointer-events: none; + } + + div.json-edit-box { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + } } \ No newline at end of file diff --git a/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-servince-instance.service.js b/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.service.js similarity index 95% rename from components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-servince-instance.service.js rename to components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.service.js index ee934f66a2..9ad7772094 100644 --- a/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-servince-instance.service.js +++ b/components/cloud-foundry/frontend/src/view/applications/workflows/create-service-instance/create-service-instance.service.js @@ -31,9 +31,14 @@ name: userInput.name, service_plan_guid: userInput.plan.metadata.guid, space_guid: spaceGuid, - tags: _.map(userInput.tags, function (tag) { return tag.text; }) + tags: _.map(userInput.tags, function (tag) { return tag.text; }), + parameters: userInput.params || {} }; + if (userInput.params) { + newInstance.parameters = userInput.params; + } + return instanceModel.createServiceInstance(cnsiGuid, newInstance) .then(function (newServiceInstance) { if (angular.isDefined(newServiceInstance.metadata)) {