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

apucher pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 0146e5f  [TE] frontend - harleyjj/create-alert - YAML autocomplete for 
create … (#3617)
0146e5f is described below

commit 0146e5fc82ea3c466b5c3a30618a8456a76cd686
Author: Harley Jackson <[email protected]>
AuthorDate: Tue Dec 18 10:21:24 2018 -0800

    [TE] frontend - harleyjj/create-alert - YAML autocomplete for create … 
(#3617)
    
    The autocomplete works for metrics only until a metric has been chosen. 
Then it replaces the whole text field in the editor and enables autocompletion 
for dimensions and filters (dimensions and filters will only be offered for the 
autocompleted metric, and they will only replace the string instead of the 
whole text field).
    
    It might be better to merge it after we have the componentized YAML 
editort, but I just thought I'd put the PR up for code review.
---
 .../app/pods/self-serve/create-alert/controller.js | 151 ++++++++++++++++++++-
 .../app/pods/self-serve/create-alert/template.hbs  |  15 +-
 .../app/styles/pods/self-serve/create-alert.scss   |   6 +
 thirdeye/thirdeye-frontend/app/utils/constants.js  |  88 ++++++++++--
 thirdeye/thirdeye-frontend/ember-cli-build.js      |   7 +
 thirdeye/thirdeye-frontend/package.json            |   4 +-
 6 files changed, 255 insertions(+), 16 deletions(-)

diff --git 
a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/controller.js 
b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/controller.js
index 3fd0819..2f609cf 100644
--- a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/controller.js
@@ -6,10 +6,11 @@
 import { reads } from '@ember/object/computed';
 import { inject as service } from '@ember/service';
 import RSVP from "rsvp";
+import yamljs from 'yamljs';
 import fetch from 'fetch';
 import moment from 'moment';
 import Controller from '@ember/controller';
-import { computed, set, get, getWithDefault } from '@ember/object';
+import { computed, set, get, getWithDefault, getProperties } from 
'@ember/object';
 import { task, timeout } from 'ember-concurrency';
 import {
   isPresent,
@@ -29,7 +30,7 @@ import {
   getTopDimensions
 } from 'thirdeye-frontend/utils/manage-alert-utils';
 import config from 'thirdeye-frontend/config/environment';
-import { yamlAlertProps } from 'thirdeye-frontend/utils/constants';
+import { yamlAlertProps, yamIt } from 'thirdeye-frontend/utils/constants';
 
 export default Controller.extend({
 
@@ -62,10 +63,19 @@ export default Controller.extend({
   dimensionCount: 7,
   availableDimensions: 0,
   metricLookupCache: [],
+  filtersCache: {},
+  dimensionsCache: [],
+  noResultsArray: [{
+    value: 'abcdefghijklmnopqrstuvwxyz0123456789',
+    caption: 'No Results',
+    snippet: ''
+  }],
   metricHelpMailto: `mailto:${config.email}?subject=Metric Onboarding Request 
(non-additive UMP or derived)`,
   isForm: true,
   disableYamlSave: true,
   yamlAlertProps,
+  currentMetric: null,
+  isYamlParseable: true,
 
   /**
    * Component property initial settings
@@ -731,6 +741,102 @@ export default Controller.extend({
   ),
 
   /**
+   * Calls api's for specific metric's autocomplete
+   * @method _loadAutocompleteById
+   * @return Promise
+   */
+   _loadAutocompleteById(metricId) {
+     const promiseHash = {
+       filters: fetch(selfServeApiGraph.metricFilters(metricId)).then(res => 
checkStatus(res, 'get', true)),
+       dimensions: 
fetch(selfServeApiGraph.metricDimensions(metricId)).then(res => 
checkStatus(res, 'get', true))
+     };
+     return RSVP.hash(promiseHash);
+   },
+
+  /**
+   * Get autocomplete suggestions from relevant api
+   * @method _buildYamlSuggestions
+   * @return Promise
+   */
+   _buildYamlSuggestions(currentMetric, yamlAsObject, prefix, noResultsArray, 
filtersCache, dimensionsCache) {
+    // holds default result to return if all checks fail
+    let defaultReturn = Promise.resolve(noResultsArray);
+    // when metric is being autocompleted, entire text field will be replaced 
and metricId stored in editor
+    if (yamlAsObject.metric === prefix) {
+      return fetch(selfServeApiCommon.metricAutoComplete(prefix))
+        .then(checkStatus)
+        .then(metrics => {
+          if (metrics && metrics.length > 0) {
+            return metrics.map(metric => {
+              const [dataset, metricname] = metric.alias.split('::');
+              return {
+                value: metricname,
+                caption: metric.alias,
+                metricname,
+                dataset,
+                id: metric.id,
+                completer:{
+                insertMatch: (editor, data) => {
+                  editor.setValue(yamIt(data.metricname, data.dataset));
+                  editor.metricId = data.id;
+                  //editor.completer.insertMatch({value: data.value});
+                  // editor.insert('abc');
+                }
+              }}
+            })
+          }
+          return noResultsArray
+        })
+        .catch(() => {
+          return noResultsArray;
+        });
+    }
+    // if a currentMetric has been stored, we can check autocomplete filters 
and dimensions
+    if (currentMetric) {
+      const dimensionValues = yamlAsObject.dimensionExploration.dimensions;
+      const filterTypes = typeof yamlAsObject.filters === "object" ? 
Object.keys(yamlAsObject.filters) : [];
+      if (Array.isArray(dimensionValues) && dimensionValues.includes(prefix)) {
+        if (dimensionsCache.length > 0) {
+          // wraps result in Promise.resolve because return of Promise is 
expected by yamlSuggestions
+          return Promise.resolve(dimensionsCache.map(dimension => {
+            return {
+              value: dimension,
+            };
+          }));
+        }
+      }
+      let filterKey = '';
+      let i = 0;
+      while (i < filterTypes.length) {
+        if (filterTypes[i] === prefix){
+          i = filterTypes.length;
+          // wraps result in Promise.resolve because return of Promise is 
expected by yamlSuggestions
+          return Promise.resolve(Object.keys(filtersCache).map(filterType => {
+            return {
+              value: `${filterType}:`,
+              caption: `${filterType}:`,
+              snippet: filterType
+            };
+          }));
+        }
+        if (Array.isArray(yamlAsObject.filters[filterTypes[i]]) && 
yamlAsObject.filters[filterTypes[i]].includes(prefix)) {
+          filterKey = filterTypes[i];
+        }
+        i++;
+      }
+      if (filterKey) {
+        // wraps result in Promise.resolve because return of Promise is 
expected by yamlSuggestions
+        return Promise.resolve(filtersCache[filterKey].map(filterParam => {
+          return {
+            value: filterParam
+          }
+        }));
+      }
+    }
+    return defaultReturn;
+  },
+
+  /**
    * Reset the form... clear all important fields
    * @method clearAll
    * @return {undefined}
@@ -774,6 +880,46 @@ export default Controller.extend({
    * Actions for create alert form view
    */
   actions: {
+    /**
+     * returns array of suggestions for Yaml editor autocompletion
+     */
+    yamlSuggestions(editor, session, position, prefix) {
+      const {
+        alertYamlContent,
+        noResultsArray
+      } = getProperties(this, 'alertYamlContent', 'noResultsArray');
+      let yamlAsObject = {}
+      try {
+        yamlAsObject = yamljs.parse(alertYamlContent);
+        set(this, 'isYamlParseable', true);
+      }
+      catch(err){
+        set(this, 'isYamlParseable', false);
+        return noResultsArray;
+      }
+      // if editor.metricId field contains a value, metric was just chosen.  
Populate caches for filters and dimensions
+      if(editor.metricId){
+        const currentMetric = set(this, 'currentMetric', editor.metricId);
+        editor.metricId = '';
+        return get(this, '_loadAutocompleteById')(currentMetric)
+          .then(resultObj => {
+            const { filters, dimensions } = resultObj;
+            this.setProperties({
+              dimensionsCache: dimensions,
+              filtersCache: filters
+            });
+          })
+          .then(() => {
+            return get(this, '_buildYamlSuggestions')(currentMetric, 
yamlAsObject, prefix, noResultsArray, get(this, 'filtersCache'), get(this, 
'dimensionsCache'))
+              .then(results => results);
+          })
+      }
+      const currentMetric = get(this, 'currentMetric');
+      // deals with no metricId, which could be autocomplete for metric or for 
filters and dimensions already cached
+      return get(this, '_buildYamlSuggestions')(currentMetric, yamlAsObject, 
prefix, noResultsArray, get(this, 'filtersCache'), get(this, 'dimensionsCache'))
+        .then(results => results);
+
+    },
 
     /**
      * Clears YAML content, disables 'save changes' button, and moves to form
@@ -782,6 +928,7 @@ export default Controller.extend({
       set(this, 'disableYamlSave', true);
       set(this, 'alertYamlContent', null);
       set(this, 'isForm', true);
+      set(this, 'currentMetric', null);
     },
 
     /**
diff --git 
a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs 
b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs
index 817ff0a..f7ad481 100644
--- a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs
@@ -10,9 +10,12 @@
     onToggle=(action (mut isForm))
     as |toggle|}}
       {{#toggle.label value=isForm}}
-        <span class="te-label te-label--flush">{{if isForm 'YAML' 
'Form'}}</span>
+        <span class="te-label te-label--flush">YAML</span>
       {{/toggle.label}}
       {{toggle.switch theme='ios' onLabel='diff on' offLabel='diff off'}}
+      {{#toggle.label value=isForm}}
+        <span class="te-label te-label--flush">Form</span>
+      {{/toggle.label}}
   {{/x-toggle}}
 </h1>
 <main class="alert-create card-container card-container--padded te-form">
@@ -420,12 +423,14 @@
   {{else}}
     <fieldset class="te-form__section te-form__section--first row">
       <div class="col-xs-12">
-        <legend class="te-form__section-title">Define alert in YAML</legend>
+        <legend class="te-form__section-title">Define anomaly detection in 
YAML</legend>
         {{ember-ace
           lines=35
           value=currentYamlValues
           update=(action 'onYMLSelector')
           mode="ace/mode/yaml"
+          suggestCompletions=(action 'yamlSuggestions')
+          enableLiveAutocompletion=true
         }}
       </div>
       {{!-- TOD: save for Alert settings
@@ -439,6 +444,12 @@
         }}
       </div> --}}
     </fieldset>
+    {{#unless isYamlParseable}}
+      <div class="yaml alert alert-warning fade in">
+        Could not parse YAML.  If you want autocomplete for filter name,
+        please insert a colon and type before the colon
+      </div>
+    {{/unless}}
     <fieldset class="te-form__section-submit">
       {{bs-button
         defaultText="Cancel"
diff --git 
a/thirdeye/thirdeye-frontend/app/styles/pods/self-serve/create-alert.scss 
b/thirdeye/thirdeye-frontend/app/styles/pods/self-serve/create-alert.scss
index 208e729..34664e3 100644
--- a/thirdeye/thirdeye-frontend/app/styles/pods/self-serve/create-alert.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/pods/self-serve/create-alert.scss
@@ -51,3 +51,9 @@
     }
   }
 }
+
+.te-label {
+  &--flush {
+    margin-left: 10px;
+  }
+}
diff --git a/thirdeye/thirdeye-frontend/app/utils/constants.js 
b/thirdeye/thirdeye-frontend/app/utils/constants.js
index cc00fbc..5ec1129 100644
--- a/thirdeye/thirdeye-frontend/app/utils/constants.js
+++ b/thirdeye/thirdeye-frontend/app/utils/constants.js
@@ -11,7 +11,7 @@ export default {
 
 
 export const yamlAlertProps = `# give a name for this detection
-detectionName: name_of_the_detection 
+detectionName: name_of_the_detection
 # the metric to detect the anomalies
 metric: metric_name
 # the data set name for this metric
@@ -24,26 +24,26 @@ dimensionExploration:
   # Create an alert for each dimension value in the dimension
   dimensions:
     - dimensionName
-    
-  # (Optional) only create alert for the dimension value if the contribution 
to 
-  # overall metric is above the threshold 
+
+  # (Optional) only create alert for the dimension value if the contribution to
+  # overall metric is above the threshold
   minContribution: 0.05
-  
+
   # (Optional) only create alert for the top k dimension values
   k: 10
 
-# (Optional) Filter the metric by 
+# (Optional) Filter the metric by
 filters:
   dimensionName1:
      - dimensionValue1
      - dimensionValue2
   dimensionName2:
      - dimensionValue3
-     
+
 # configure rules of anomaly detection
 rules:
 # configure the first rule
-- # configure the detection rule. ThirdEye will detect anomalies based on the 
+- # configure the detection rule. ThirdEye will detect anomalies based on the
   # detection rules.
   detection:
       # give a name for the detection rule
@@ -51,11 +51,11 @@ rules:
       # ThirdEye rule type
       type: PERCENTAGE_RULE
       # parameters for this rule
-      params: 
+      params:
         offset: wo1w
         change: 0.1
- 
-  # (Optional) configure the exclusion rule. (Exclude the anomalies you don't 
+
+  # (Optional) configure the exclusion rule. (Exclude the anomalies you don't
   # want to see but detected by the detection rule above)
   filter:
     - name: filter_rule_1
@@ -73,3 +73,69 @@ rules:
         pattern: UP
 
 `;
+
+export const yamIt = function(metric, dataset){
+  return `# give a name for this detection
+detectionName: name_of_the_detection
+# the metric to detect the anomalies
+metric: ${metric}
+# the data set name for this metric
+dataset: ${dataset}
+# ThirdEye pipeline type. Just fill in Composite
+pipelineType: Composite
+
+# (Optional) Config dimension exploration
+dimensionExploration:
+  # Create an alert for each dimension value in the dimension
+  dimensions:
+    - dimensionName
+
+  # (Optional) only create alert for the dimension value if the contribution to
+  # overall metric is above the threshold
+  minContribution: 0.05
+
+  # (Optional) only create alert for the top k dimension values
+  k: 10
+
+# (Optional) Filter the metric by
+filters:
+  dimensionName1:
+     - dimensionValue1
+     - dimensionValue2
+  dimensionName2:
+     - dimensionValue3
+
+# configure rules of anomaly detection
+rules:
+# configure the first rule
+- # configure the detection rule. ThirdEye will detect anomalies based on the
+  # detection rules.
+  detection:
+      # give a name for the detection rule
+    - name: detection_rule_1
+      # ThirdEye rule type
+      type: PERCENTAGE_RULE
+      # parameters for this rule
+      params:
+        offset: wo1w
+        change: 0.1
+
+  # (Optional) configure the exclusion rule. (Exclude the anomalies you don't
+  # want to see but detected by the detection rule above)
+  filter:
+    - name: filter_rule_1
+      type: ABSOLUTE_CHANGE_FILTER
+      params:
+        threshold: 10000
+
+# configure more rule if you'd like to
+- detection:
+    - name: detection_rule_2
+      type: ABSOLUTE_CHANGE_RULE
+      params:
+        offset: wo1w
+        change: 1000000
+        pattern: UP
+
+`;
+};
diff --git a/thirdeye/thirdeye-frontend/ember-cli-build.js 
b/thirdeye/thirdeye-frontend/ember-cli-build.js
index b49b3ba..6149e37 100644
--- a/thirdeye/thirdeye-frontend/ember-cli-build.js
+++ b/thirdeye/thirdeye-frontend/ember-cli-build.js
@@ -68,6 +68,13 @@ module.exports = function(defaults) {
   // jspdf and html2canvas assets for PDF
   app.import('node_modules/jspdf/dist/jspdf.min.js');
 
+  // imports yamljs node module as commonjs
+  app.import('node_modules/yamljs/index.js', {
+    using: [
+      { transformation: 'cjs', as: 'yamljs'}
+    ]
+  });
+
   // Use `app.import` to add additional libraries to the generated
   // output files.
 
diff --git a/thirdeye/thirdeye-frontend/package.json 
b/thirdeye/thirdeye-frontend/package.json
index ef83479..af2129d 100644
--- a/thirdeye/thirdeye-frontend/package.json
+++ b/thirdeye/thirdeye-frontend/package.json
@@ -32,6 +32,7 @@
     "ember-cli": "~3.4.0",
     "ember-cli-app-version": "^3.0.0",
     "ember-cli-babel": "^6.6.0",
+    "ember-cli-cjs-transform": "^1.3.0",
     "ember-cli-daterangepicker": "^0.5.1",
     "ember-cli-dependency-checker": "^2.0.0",
     "ember-cli-eslint": "^4.2.1",
@@ -89,6 +90,7 @@
     "ember-cryptojs-shim": "^4.3.0",
     "ember-simple-tree": "^0.4.2",
     "ion-rangeslider": "^2.2.0",
-    "moment": "^2.18.1"
+    "moment": "^2.18.1",
+    "yamljs": "^0.3.0"
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to