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 2018/08/20 09:39:22 UTC

[02/12] brooklyn-ui git commit: initial JS poc of custom config widget

initial JS poc of custom config widget

all the hard angular stuff working, but hard-coded.  now just need to wire it to real tags.


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/commit/29ba70e1
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/tree/29ba70e1
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/diff/29ba70e1

Branch: refs/heads/master
Commit: 29ba70e1936367a4e226823add0a4272252b0922
Parents: f9d239e
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Aug 6 22:59:11 2018 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Aug 6 22:59:11 2018 +0100

----------------------------------------------------------------------
 docs/customizations.md                          |  8 +++
 .../suggestion-dropdown.html                    | 48 +++++++++++++++
 .../custom-config-widget/suggestion-dropdown.js | 44 ++++++++++++++
 .../suggestion-dropdown.less                    | 30 +++++++++
 .../spec-editor/spec-editor.directive.js        | 64 +++++++++++++++++++-
 .../app/components/spec-editor/spec-editor.less | 26 +++++++-
 .../spec-editor/spec-editor.template.html       | 22 +++++--
 ui-modules/blueprint-composer/app/index.js      |  6 +-
 ui-modules/blueprint-composer/app/index.less    |  1 +
 9 files changed, 238 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/docs/customizations.md
----------------------------------------------------------------------
diff --git a/docs/customizations.md b/docs/customizations.md
index 4458a88..9912317 100644
--- a/docs/customizations.md
+++ b/docs/customizations.md
@@ -11,3 +11,11 @@ There are a number of points where UI default choices can be overridden:
   items for the palette can come from Brooklyn and/or other sources, with other sources converted to "virtual items" that
   can extend existing Brooklyn items
 
+* Composer - Custom Config Widgets: special widgets to use for config keys can be specified in a registered type's
+  definition as a map tag, for example for the demo widget `suggestion-dropout` included we might have:
+      '{ ui-composer-hints: { config-widgets: [ { key: start.timeout, suggestion-values: [ 30s, 2m, 5m, 30m, 2h ],
+         widget: suggestion-dropdown, label-collapsed: fail after, label-expanded: Fail if not successful within } ] } }`;
+  widgets should be registered as angular directives using the standard naming conventions (e.g. suggestionDropdownDirective),
+  as done for that directive in app/index.js and app/index.less.
+
+

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.html
----------------------------------------------------------------------
diff --git a/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.html b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.html
new file mode 100644
index 0000000..c511dfe
--- /dev/null
+++ b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.html
@@ -0,0 +1,48 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<div class="custom-config-widget suggestion-dropdown config-flex-row">
+    <label class="control-label" for="{{item.name}}">
+        <span class="label-spec-configuration">{{item.label || item.name}}</span>
+        <span class="info-spec-configuration">
+            <i class="fa fa-fw fa-info-circle" popover-trigger="'mouseenter'"
+                popover-title="{{item.label || item.name}}"
+                uib-popover-template="'ConfigInfoTemplate.html'"
+                popover-class="spec-editor-popover" popover-placement="top-left" popover-append-to-body="true"></i>
+            </span>
+    </label>
+
+    <span class="label-rhs-buttons">
+        <span class="spacer"> </span>
+        <span class="custom-widget-enable-button custom-widget-active">
+            <i class="fa fa-fw fa-sun-o" ng-click="toggleCustomConfigWidgetMode(item, 'disabled')" aria-hidden="true" title="{{getCustomConfigWidgetModeTitle(item)}} [{{item.name}}]"></i>
+            <span class="sr-only">{{getCustomConfigWidgetModeTitle(item)}} [{{item.name}}]</span>
+        </span>
+    </span>
+
+    <div class="control-value" ng-class="{ 'inline-control': item.widgetMode==='boolean', 'unset': !defined(config[item.name]), 'has-default': defined(item.defaultValue) && item.defaultValue!=null }">
+        <div class="hide-when-collapsed">
+            <a ng-click="test()">Extra information</a>
+        </div>
+        <div>
+            <span class="hide-when-expanded">Custom widget: </span>
+            {{ config[item.name] }}
+        </div>
+    </div>
+
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js
----------------------------------------------------------------------
diff --git a/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js
new file mode 100644
index 0000000..d36c801
--- /dev/null
+++ b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.js
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ 
+import angular from 'angular';
+import template from './suggestion-dropdown.html';
+
+const MODULE_NAME = 'brooklyn.components.custom-config-widget.suggestion-dropdown';
+
+angular.module(MODULE_NAME, [])
+    .directive('suggestionDropdown', ['$rootScope', suggestionDropdownDirective]);
+
+export default MODULE_NAME;
+
+export function suggestionDropdownDirective($rootScope) {
+    return {
+        restrict: 'E',
+        scope: {
+            item: '='
+        },
+        template: template,
+        link: link,
+    };
+
+    function link(scope) {
+        scope.$parent.copyScopeForCustomConfigWidget(scope);
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.less
----------------------------------------------------------------------
diff --git a/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.less b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.less
new file mode 100644
index 0000000..c080a1b
--- /dev/null
+++ b/ui-modules/blueprint-composer/app/components/custom-config-widget/suggestion-dropdown.less
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ 
+suggestion-dropdown {
+
+    width: 100%;
+    
+    .control-value {
+        // for widgets that are text, this aligns it with the label when collapsed and gives nice spacing when expanded.
+        // (for widgets that are dropdowns etc this should be omitted)
+        padding-top: 2px;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.directive.js
----------------------------------------------------------------------
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 7a581b7..a0eeba8 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
@@ -33,7 +33,7 @@ const ANY_MEMBERSPEC_REGEX = /(^.*[m,M]ember[s,S]pec$)/;
 const REPLACED_DSL_ENTITYSPEC = '___brooklyn:entitySpec';
 
 angular.module(MODULE_NAME, [onEnter, autoGrow, blurOnEnter, brooklynDslEditor, brooklynDslViewer])
-    .directive('specEditor', ['$rootScope', '$filter', '$log', '$sce', '$timeout', '$document', 'blueprintService', specEditorDirective])
+    .directive('specEditor', ['$rootScope', '$templateCache', '$injector', '$sanitize', '$filter', '$log', '$sce', '$timeout', '$document', 'blueprintService', specEditorDirective])
     .filter('specEditorConfig', specEditorConfigFilter)
     .filter('specEditorType', specEditorTypeFilter);
 
@@ -70,7 +70,7 @@ export const CONFIG_FILTERS = [
     }
 ];
 
-export function specEditorDirective($rootScope, $filter, $log, $sce, $timeout, $document, blueprintService) {
+export function specEditorDirective($rootScope, $templateCache, $injector, $sanitize, $filter, $log, $sce, $timeout, $document, blueprintService) {
     return {
         restrict: 'E',
         scope: {
@@ -106,6 +106,10 @@ export function specEditorDirective($rootScope, $filter, $log, $sce, $timeout, $
                 codeModeActive: {},
                 codeModeForced: {},
                 codeModeError: {},
+                customWidget: { 
+                    "defaultDisplayName": { mode: "full", widget: "known-widget-missing" }, 
+                    "download.url": { mode: "full", widget: "suggestion-dropdown" }, 
+                },
             },
             location: {
                 open: false
@@ -514,7 +518,7 @@ export function specEditorDirective($rootScope, $filter, $log, $sce, $timeout, $
                 // local config changed, make sure model is updated too
                 setModelFromLocalConfig();
             }
-        }
+        };
         scope.getJsonModeTitle = (itemName) => {
             if (!scope.state.config.codeModeActive[itemName]) {
                 return "Treat this value as a JSON-encoded object ["+itemName+"]";
@@ -525,6 +529,60 @@ export function specEditorDirective($rootScope, $filter, $log, $sce, $timeout, $
                 return "Edit in simple mode, unwrapping JSON if possible ["+itemName+"]";
             }
         }
+        scope.getCustomConfigWidgetMode = (item) => {
+            var mode = (scope.state.config.customWidget[item.name] || {})["mode"] || null;
+            if (mode) {
+                if ((scope.state.config.customWidget[item.name] || {})["error"]) return null;
+            }
+            return mode;
+        } ;
+        scope.toggleCustomConfigWidgetMode = (item, newval) => {
+            if (!scope.state.config.customWidget[item.name]) {
+                $log.error('Custom widget mode should not be toggled when not available: '+item.name);
+                return null;
+            }
+            if (newval) scope.state.config.customWidget[item.name].mode = newval;
+            else {
+                if (scope.state.config.customWidget[item.name].mode === "disabled") {
+                    // TODO not always full
+                    scope.state.config.customWidget[item.name].mode = "full"
+                } else {
+                    scope.state.config.customWidget[item.name].mode = "full"
+                }
+            }
+        }
+        scope.getCustomConfigWidgetModeTitle = (item) => {
+            if (!scope.state.config.customWidget[item.name]) {
+                // shouldn't be visible
+                return "(custom widget not available)";
+            }
+            if (scope.state.config.customWidget[item.name].mode === "disabled") return "Enable custom widget";
+            else return "Disable custom widget";
+        };
+        scope.copyScopeForCustomConfigWidget = (descendantScope) => {
+            descendantScope.toggleCustomConfigWidgetMode = scope.toggleCustomConfigWidgetMode;
+            descendantScope.getCustomConfigWidgetModeTitle = scope.getCustomConfigWidgetModeTitle;
+            descendantScope.defined = scope.defined;
+            descendantScope.config = scope.config;
+            descendantScope.state = scope.state;
+            descendantScope.copyScopeForCustomConfigWidget = scope.copyScopeForCustomConfigWidget;
+        };
+        scope.getCustomConfigWidgetTemplate = (item) => {
+            var widgetName = $sanitize(scope.state.config.customWidget[item.name].widget);
+            var templateName = 'custom-config-widget-'+widgetName;
+            if (!$templateCache.get(templateName)) {
+                var widgetDirective = widgetName.replace(/(-[a-z])/g, function($1){return $1[1].toUpperCase();})+'Directive';
+                if ($injector.has(widgetDirective)) {
+                    $templateCache.put(templateName, '<'+widgetName+' item="item">foo</'+widgetName+'>');
+                } else {
+                    $log.error('Missing directive '+widgetDirective+' for custom widget for '+item.name+'; falling back to default widget');
+                    scope.state.config.customWidget[item.name].error = "Missing directive";
+                    templateName = "error-" + templateName;
+                    $templateCache.put(templateName, '<i>Widget '+widgetName+' missing</i>');
+                }
+            }
+            return templateName;
+        };
         
         scope.isDsl = (key, index) => {
             let val = scope.model.config.get(key);

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.less
----------------------------------------------------------------------
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 1c14c92..233d937 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
@@ -272,12 +272,23 @@ spec-editor {
       margin-right: 0.5em;
       .remove-affordance();
     }
+    span.rhs-buttons-subspan {
+      display: flex;
+    }
     .json-config i {
         margin-top: 2px; //nudge down to make aligned
     }
-    .dsl-open-wizard-from-toolbar, .dsl-manual-override-editor, .json-config {
+    .dsl-open-wizard-from-toolbar, .dsl-manual-override-editor, .json-config, .custom-widget-enable-button {
         .toolbar-button-affordance()
     }
+    .custom-widget-enable-button.custom-widget-active {
+        i {
+            color: @brand-primary;
+        }
+        &:hover i {
+            color: @gray-light;
+        }
+    }
     .dsl-manual-override-editor.dsl-viewer-manual-override,
     .json-config.code-mode-active {
         i {
@@ -327,8 +338,11 @@ spec-editor {
       }
     }
     .form-group {
-        display: flex;
-        flex-flow: row wrap;
+        &, .config-flex-row, .custom-widget-full {
+            display: flex;
+            flex-flow: row wrap;
+            width: 100%;
+        }
         .control-label {
             flex: 0 0 auto;
             order: 10;
@@ -406,6 +420,9 @@ spec-editor {
             }
         }
         &:not(:focus-within):not(:focus) {
+            .hide-when-collapsed {
+                display: none;
+            }
             input {
                 // the RHS is square if DSL button hidden; simplest fix is to make LHS square too
                 border-radius: 0;
@@ -426,6 +443,9 @@ spec-editor {
         }
     }
     .form-group:focus, .form-group:focus-within {
+        .hide-when-expanded {
+            display: none;
+        }
         .control-value:not(.inline-control) {
             flex-basis: 100%;
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
----------------------------------------------------------------------
diff --git a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
index b305651..cc3e11e 100644
--- a/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
+++ b/ui-modules/blueprint-composer/app/components/spec-editor/spec-editor.template.html
@@ -131,12 +131,20 @@
 
         <form name="formSpecConfig" novalidate class="lightweight">
             <div ng-repeat="item in (filteredItems = (model.miscData.get('config') | specEditorConfig:state.config.filter.values:model.config | filter:{name:state.config.search} | orderBy:+priority)) track by item.name ">
-                <div class="form-group" ng-class="{'has-error': (model.issues | filter:{ref: item.name}:true).length > 0, 'used': config[item.name] !== undefined}" ng-switch="getConfigWidgetMode(item)" tabindex="1"
+                <div class="form-group" ng-class="{'has-error': (model.issues | filter:{ref: item.name}:true).length > 0, 'used': config[item.name] !== undefined}" 
+                        ng-switch="getConfigWidgetMode(item)" 
+                        tabindex="1"
                         auto-focus="state.config.focus === item.name"
                         auto-focus-listen-to-window="true"
                         auto-focus-not-if-within="true"
                         ng-focus="recordFocus(item)"
                         on-enter="advanceControlInFormGroup">
+                  <div ng-if="getCustomConfigWidgetMode(item) === 'full'" class="custom-widget-full">
+                    <ng-include src="getCustomConfigWidgetTemplate(item)" class="custom-widget-full"/>
+                  </div>
+                  
+                  <!-- have to hide it; excluding it conditionally via if doesn't play nice with switch -->
+                  <div ng-show="getCustomConfigWidgetMode(item) !== 'full'" class="config-flex-row">
                     <label class="control-label" for="{{item.name}}">
                         <span class="label-spec-configuration">{{item.label || item.name}}</span>
                         <span class="info-spec-configuration">
@@ -149,11 +157,15 @@
                     
                     <span class="label-rhs-buttons">
                         <span class="spacer"> </span>
+                        <span class="custom-widget-enable-button" ng-if="getCustomConfigWidgetMode(item) !== null">
+                            <i class="fa fa-fw fa-sun-o" ng-click="toggleCustomConfigWidgetMode(item)" aria-hidden="true" title="{{getCustomConfigWidgetModeTitle(item)}} [{{item.name}}]"></i>
+                            <span class="sr-only">{{getCustomConfigWidgetModeTitle(item)}} [{{item.name}}]</span>
+                        </span>
                         <span class="json-config" ng-class="{'code-mode-active': state.config.codeModeActive[item.name], 'code-mode-forced': state.config.codeModeForced[item.name]}" ng-if="isCodeModeAvailable(item)">
-                            <i class="fa fa-fw fa-code" ng-click="codeModeClick(item)" aria-hidden="true" title="{{getJsonModeTitle(item.name)}}"></i>
-                            <span class="sr-only">{{getJsonModeTitle(item.name)}}</span>
+                            <i class="fa fa-fw fa-code" ng-click="codeModeClick(item)" aria-hidden="true" title="{{getJsonModeTitle(item.name)}} [{{item.name}}]"></i>
+                            <span class="sr-only">{{getJsonModeTitle(item.name)}} [{{item.name}}]</span>
                         </span>
-                        <span ng-if="isDsl(item.name)">
+                        <span ng-if="isDsl(item.name)" class="rhs-buttons-subspan">
                             <span class="dsl-open-wizard-from-toolbar" ng-if="!state.config.dslViewerManualOverride[item.name]">
                                 <i class="fa fa-fw fa-bolt" ui-sref="main.graphical.edit.dsl({entityId: model._id, for: item.name})" aria-hidden="true" title="Open in DSL editor [{{item.name}}]"></i>
                                 <span class="sr-only">Open in DSL editor [{{item.name}}]</span>
@@ -309,6 +321,8 @@
                     </div>
                     
                     <small ng-repeat="issue in model.issues | filter:{ref: item.name}:true" class="help-block issue" ng-bind-html="issue.message"></small>
+                  </div>
+
                 </div>
             </div>
         </form>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/index.js
----------------------------------------------------------------------
diff --git a/ui-modules/blueprint-composer/app/index.js b/ui-modules/blueprint-composer/app/index.js
index f7a55b2..a970dc2 100755
--- a/ui-modules/blueprint-composer/app/index.js
+++ b/ui-modules/blueprint-composer/app/index.js
@@ -48,6 +48,7 @@ import {
     catalogSelectorSortFilter
 } from "components/catalog-selector/catalog-selector.directive";
 import customActionDirective from "./components/custom-action/custom-action.directive";
+import customConfigSuggestionDropdown from "components/custom-config-widget/suggestion-dropdown";
 import {onErrorDirective} from "components/catalog-selector/on-error.directive";
 import {breadcrumbsDirective} from "components/breacrumbs/breadcrumbs.directive";
 import {recursionHelperFactory} from "components/factories/recursion-helper.factory";
@@ -71,7 +72,10 @@ import {graphicalEditDslState, dslParamLabelFilter} from "views/main/graphical/e
 import bottomSheet from "brooklyn-ui-utils/bottom-sheet/bottom-sheet";
 import stackViewer from 'angular-java-stack-viewer';
 
-angular.module('app', [ngAnimate, ngResource, ngCookies, ngClipboard, uiRouter, 'ui.router.state.events', brCore, brServerStatus, brAutoFocus, brIconGenerator, brInterstitialSpinner, brooklynModuleLinks, brooklynUserManagement, brYamlEditor, brUtils, brSpecEditor, brooklynCatalogSaver, brooklynApi, bottomSheet, stackViewer, brDragndrop, customActionDirective, paletteApiProvider])
+angular.module('app', [ngAnimate, ngResource, ngCookies, ngClipboard, uiRouter, 'ui.router.state.events', brCore, 
+        brServerStatus, brAutoFocus, brIconGenerator, brInterstitialSpinner, brooklynModuleLinks, brooklynUserManagement, 
+        brYamlEditor, brUtils, brSpecEditor, brooklynCatalogSaver, brooklynApi, bottomSheet, stackViewer, brDragndrop, 
+        customActionDirective, customConfigSuggestionDropdown, paletteApiProvider])
     .directive('designer', ['$log', '$state', '$q', 'iconGenerator', 'catalogApi', 'blueprintService', 'brSnackbar', 'paletteDragAndDropService', designerDirective])
     .directive('onError', onErrorDirective)
     .directive('catalogSelector', catalogSelectorDirective)

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/29ba70e1/ui-modules/blueprint-composer/app/index.less
----------------------------------------------------------------------
diff --git a/ui-modules/blueprint-composer/app/index.less b/ui-modules/blueprint-composer/app/index.less
index 5baf074..1562b40 100644
--- a/ui-modules/blueprint-composer/app/index.less
+++ b/ui-modules/blueprint-composer/app/index.less
@@ -31,6 +31,7 @@
 @import "components/spec-editor/spec-editor";
 @import "components/catalog-selector/catalog-selector";
 @import "components/breacrumbs/breadcrumbs";
+@import "components/custom-config-widget/suggestion-dropdown";
 @import "components/blueprint-data-manager/blueprint-data-manager.style.less";
 @import "components/catalog-saver/catalog-saver.less";
 @import "components/dsl-editor/dsl-editor";