You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2022/08/22 20:37:18 UTC

[brooklyn-ui] 04/05: use yaml instead of json for complex fields in spec editor

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-ui.git

commit 201229d897f6746d37071d3271a2ef434b8f37c6
Author: Alex Heneveld <al...@cloudsoft.io>
AuthorDate: Mon Aug 22 20:50:01 2022 +0100

    use yaml instead of json for complex fields in spec editor
    
    easily controllable with CODE_MODE="YAML" or "JSON"
---
 .../spec-editor/spec-editor.directive.js           | 51 +++++++++++++++++-----
 .../app/components/spec-editor/spec-editor.less    |  3 ++
 2 files changed, 44 insertions(+), 10 deletions(-)

diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
index 1b74dafd..ab6df27e 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
@@ -30,6 +30,7 @@ import template from './spec-editor.template.html';
 import {isSensitiveFieldName} from 'brooklyn-ui-utils/sensitive-field/sensitive-field';
 import {computeQuickFixesForIssue} from '../quick-fix/quick-fix';
 import scriptTagDecorator from 'brooklyn-ui-utils/script-tag-non-overwrite/script-tag-non-overwrite';
+import jsYaml from "js-yaml";
 
 const MODULE_NAME = 'brooklyn.components.spec-editor';
 const REPLACED_DSL_ENTITYSPEC = '___brooklyn:entitySpec';
@@ -38,6 +39,8 @@ const SUBSECTION = {
     PARAMETERS: 'parameters'
 }
 
+const CODE_MODE = "YAML";
+
 export const SUBSECTION_TEMPLATE_OTHERS_URL = 'blueprint-composer/component/spec-editor/section-others.html';
 
 angular.module(MODULE_NAME, [onEnter, autoGrow, blurOnEnter, brooklynDslEditor, brooklynDslViewer, scriptTagDecorator])
@@ -640,7 +643,8 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
             if (!specEditor.defined(val)) return false;
             if (typeof val === 'string') {
                 try {
-                    // a YAML parse would be nicer, but JSON is simpler, and sufficient
+                    // a YAML parse would be nicer, but JSON is easier to detect its presence, and sufficient;
+                    // for convenience, we allow yaml to be _entered_
                     // (esp as we export a JSON stringify; preferable would be to export the exact YAML from model,
                     // including comments, but again lower priority)
                     let parsed = JSON.parse(val);
@@ -680,7 +684,7 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                     try {
                         // if it's a parseable string, then parse it
                         if (typeof value === 'string' && value.length) {
-                            value = JSON.parse(value);
+                            value = parseYamlOrJson(value);
                             if (value instanceof Array || value instanceof Object) {
                                 // if result is not a primitive then don't allow user to leave code mode
                                 // (if type is known as a map/list then likely we should allow leaving code mode,
@@ -716,7 +720,7 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                         }
                     } catch (notJson) {
                         // if not parseable or not a string, then convert to json
-                        value = JSON.stringify(value);
+                        value = toCode(value);
                     }
                 }
                 if (value !== null) {
@@ -767,7 +771,7 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                     } catch (notJson) {
                         // if not parseable or not a string, then convert to json
                         // (as with other stringify, this loses any comments etc)
-                        value = JSON.stringify(value, null, "  ");
+                        value = toCode(value, true);
                     }
                     if (value !== null) {
                         scope.state.parameters.edit.json = value;
@@ -975,7 +979,7 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                 // also supporting yaml and comments, but that is a bigger task!)
                 if (scope.config && typeof scope.config[key] === 'string') {
                     try {
-                        if (JSON.stringify(JSON.parse(scope.config[key])) === JSON.stringify(value)) {
+                        if (JSON.stringify(parseYamlOrJson(scope.config[key])) === JSON.stringify(value)) {
                             return scope.config[key];
                         }
                     } catch (ignoredError) {
@@ -985,7 +989,7 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                 // otherwise pretty print it, so they get decent multiline on first load and
                 // if they click off the entity then back on to the entity and this field
                 // (we'd like to respect what they actually typed but that needs the parse tree, as above)
-                return JSON.stringify(value, null, "  ");
+                return toCode(value, true);
             }
 
             // else treat as value, with array/map special
@@ -1026,6 +1030,8 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                 // any map/array with complex json inside, or other value of complex json,
                 // will force the code editor
                 // (previously we did stringify on entries then tried to parse, but that was inconsistent)
+                console.log("Forcing code mode on ", key, "because", hasComplexJson);
+
                 scope.state.config.codeModeActive[key] = true;
                 // and the widget mode updated to be 'manual'
                 scope.getConfigWidgetMode(definition, value);
@@ -1090,13 +1096,15 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                 // if JSON mode then parse
                 scope.state.config.codeModeError[keyRef] = null;
                 if (scope.state.config.codeModeActive && scope.state.config.codeModeActive[keyRef]) {
+                    // first try a yaml parse
                     try {
-                        result[keyRef] = JSON.parse(v);
+                        result[keyRef] = parseYamlOrJson(v);
+                        continue;
                     } catch (ex) {
                         scope.state.config.codeModeError[keyRef] = "Invalid JSON";
                         result[keyRef] = localConfig[keyRef];
+                        continue;
                     }
-                    continue;
                 }
 
                 // else return as is, or introspect for array/map
@@ -1147,7 +1155,7 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                     model.addIssue(Issue.builder().group('config').ref(key).message('<code>' + $sanitize(value) + '</code> is not a number').build());
                 }
                 if (scope.state.config.codeModeError[key]) {
-                    model.addIssue(Issue.builder().group('config').ref(key).message('<code>' + $sanitize(value) + '</code> is not valid JSON').build());
+                    model.addIssue(Issue.builder().group('config').ref(key).message('Content is not valid '+(CODE_MODE)).build());
                 }
             });
         }
@@ -1207,7 +1215,7 @@ export function specEditorDirective($rootScope, $templateCache, $injector, $sani
                 scope.state.parameters.edit.errors = [];
                 if (scope.state.parameters.codeModeActive[item.name]) {
                     try {
-                        let parsed = JSON.parse(scope.state.parameters.edit.json);
+                        let parsed = parseYamlOrJson(scope.state.parameters.edit.json);
                         checkNameChange(item.name, parsed.name);
                         if (JSON.stringify(item)==JSON.stringify(parsed)) {
                             // no change; don't change else we get a digest cycle
@@ -1349,3 +1357,26 @@ export function specEditorTypeFilter() {
 function templateCache($templateCache) {
     $templateCache.put(TEMPLATE_URL, template);
 }
+
+function parseYamlOrJson(v) {
+    // YAML mode experimental; if not working switch back to JSON
+    let error = null;
+    if (CODE_MODE=="YAML") {
+        try {
+            return jsYaml.safeLoad(v);
+        } catch (ex) {
+            error = ex;
+        }
+    }
+    try {
+        return JSON.parse(v);
+    } catch (ex) {
+        if (CODE_MODE=="YAML" && error) throw error;
+        throw ex;
+    }
+}
+
+function toCode(obj, pretty) {
+    if (CODE_MODE=="YAML") return jsYaml.dump(obj).trim();
+    return pretty ? JSON.stringify(obj, null, "  ") : JSON.stringify(obj);
+}
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
index b19d94bf..e02a3d61 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
@@ -946,6 +946,9 @@ spec-editor {
 .version-selection {
   margin: 1em 0px;
 
+  button {
+    text-align: left;
+  }
   .dropdown {
     // preventing LESS from incorrectly parsing the calc expression
     // using 30px offset for the icon