You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ji...@apache.org on 2021/01/08 18:35:13 UTC
[incubator-pinot] branch master updated: [TE]frontend - Add support
for Group Constituents and Entity Metric components (#6421)
This is an automated email from the ASF dual-hosted git repository.
jihao 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 3d4c98d [TE]frontend - Add support for Group Constituents and Entity Metric components (#6421)
3d4c98d is described below
commit 3d4c98d0b669b2abb6a9e47bd5be98098e56ef75
Author: Tejas Ajmera <33...@users.noreply.github.com>
AuthorDate: Fri Jan 8 10:34:56 2021 -0800
[TE]frontend - Add support for Group Constituents and Entity Metric components (#6421)
Additionally, this PR also does the integration for being able to perform multi-level drilldown for Composite Anomalies. The Breadcrumb navigation is activated as well.
---
.../app/mocks/compositeAnomalies.js | 188 ++++++++++-----------
.../components/composite-anomalies/component.js | 52 +++++-
.../composite-anomalies/data-table/template.hbs | 24 +++
.../entity-metrics-anomalies/component.js | 86 ++++++++++
.../entity-metrics-anomalies/template.hbs | 1 +
.../group-constituents-anomalies/component.js | 81 +++++++++
.../group-constituents-anomalies/template.hbs | 1 +
.../parent-anomalies/component.js | 64 ++-----
.../parent-anomalies/template.hbs | 21 +--
.../components/composite-anomalies/template.hbs | 11 +-
.../anomalies-list/template.hbs | 2 +-
.../criticality/template.hbs | 3 +
.../current-predicted/template.hbs | 6 +
.../dimensions/template.hbs | 3 +
.../group-name/component.js | 10 ++
.../group-name/template.hbs | 3 +
.../composite-anomalies-table/metric/template.hbs | 3 +
.../start-duration/component.js | 10 ++
.../start-duration/template.hbs | 6 +-
.../explore/composite-anomalies/controller.js | 10 ++
.../explore/composite-anomalies/template.hbs | 9 +-
.../app/styles/components/breadcrumb-list.scss | 2 +
.../app/styles/components/te-anomaly-table.scss | 1 +
.../styles/pods/custom/parent-anomalies-table.scss | 11 +-
.../app/styles/shared/_styles.scss | 4 +
.../app/utils/anomalies-tree-parser.js | 43 +++--
.../app/utils/composite-anomalies.js | 48 ++++++
.../composite-anomalies/component-test.js | 2 +-
.../entity-metrics-anomalies/component-test.js | 72 ++++++++
.../group-constituents-anomalies/component-test.js | 81 +++++++++
.../parent-anomalies/component-test.js | 12 +-
.../tests/unit/utils/anomalies-tree-parser-test.js | 181 ++++++++++++--------
32 files changed, 778 insertions(+), 273 deletions(-)
diff --git a/thirdeye/thirdeye-frontend/app/mocks/compositeAnomalies.js b/thirdeye/thirdeye-frontend/app/mocks/compositeAnomalies.js
index e81c0cf..b02f94f 100644
--- a/thirdeye/thirdeye-frontend/app/mocks/compositeAnomalies.js
+++ b/thirdeye/thirdeye-frontend/app/mocks/compositeAnomalies.js
@@ -5,8 +5,8 @@ export const mockData = [
endTime: 1599721200000,
feedback: null,
properties: {
- detectorComponentName: "grouper_one:GROUPER",
- subEntityName: "entity_one"
+ detectorComponentName: 'grouper_one:GROUPER',
+ subEntityName: 'entity_one'
},
children: [
{
@@ -14,44 +14,44 @@ export const mockData = [
startTime: 1599462000000,
endTime: 1599721200000,
feedback: null,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
properties: {
- groupScore: "6.189942819613212",
- detectorComponentName: "detection_grouper:ANOMALY_SUMMARIZE",
- subEntityName: "group_entity_one",
- groupKey: "groupConstituentOne"
+ groupScore: '6.189942819613212',
+ detectorComponentName: 'detection_grouper:ANOMALY_SUMMARIZE',
+ subEntityName: 'group_entity_one',
+ groupKey: 'groupConstituentOne'
},
children: [
{
id: 3,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentOne#",
- feature_section: "groupConstituentOne",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentOne#',
+ feature_section: 'groupConstituentOne',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
},
{
id: 4,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentOne#",
- feature_section: "groupConstituentOne",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentOne#',
+ feature_section: 'groupConstituentOne',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
}
@@ -62,44 +62,44 @@ export const mockData = [
startTime: 1599462000000,
endTime: 1599721200000,
feedback: null,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
properties: {
- groupScore: "6.189942819613212",
- detectorComponentName: "detection_grouper:ANOMALY_SUMMARIZE",
- subEntityName: "group_entity_one",
- groupKey: "groupConstituentTwo"
+ groupScore: '6.189942819613212',
+ detectorComponentName: 'detection_grouper:ANOMALY_SUMMARIZE',
+ subEntityName: 'group_entity_one',
+ groupKey: 'groupConstituentTwo'
},
children: [
{
id: 6,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentTwo#",
- feature_section: "groupConstituentTwo",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentTwo#',
+ feature_section: 'groupConstituentTwo',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
},
{
id: 7,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentTwo#",
- feature_section: "groupConstituentTwo",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentTwo#',
+ feature_section: 'groupConstituentTwo',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
}
@@ -109,45 +109,45 @@ export const mockData = [
id: 8,
startTime: 1599462000000,
endTime: 1599721200000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
properties: {
- groupScore: "6.189942819613212",
- detectorComponentName: "detection_grouper:ANOMALY_SUMMARIZE",
- subEntityName: "group_entity_two",
- groupKey: "groupConstituentOne"
+ groupScore: '6.189942819613212',
+ detectorComponentName: 'detection_grouper:ANOMALY_SUMMARIZE',
+ subEntityName: 'group_entity_two',
+ groupKey: 'groupConstituentOne'
},
children: [
{
id: 9,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentOne#",
- feature_section: "groupConstituentOne",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentOne#',
+ feature_section: 'groupConstituentOne',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
},
{
id: 10,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentOne#",
- feature_section: "groupConstituentOne",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentOne#',
+ feature_section: 'groupConstituentOne',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
}
@@ -157,45 +157,45 @@ export const mockData = [
id: 11,
startTime: 1599462000000,
endTime: 1599721200000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
properties: {
- groupScore: "6.189942819613212",
- detectorComponentName: "detection_grouper:ANOMALY_SUMMARIZE",
- subEntityName: "group_entity_two",
- groupKey: "groupConstituentTwo"
+ groupScore: '6.189942819613212',
+ detectorComponentName: 'detection_grouper:ANOMALY_SUMMARIZE',
+ subEntityName: 'group_entity_two',
+ groupKey: 'groupConstituentTwo'
},
children: [
{
id: 12,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentTwo#",
- feature_section: "groupConstituentTwo",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentTwo#',
+ feature_section: 'groupConstituentTwo',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
},
{
id: 13,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentTwo#",
- feature_section: "groupConstituentTwo",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentTwo#',
+ feature_section: 'groupConstituentTwo',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
startTime: 1599462000000,
endTime: 1599548400000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: []
}
@@ -205,31 +205,31 @@ export const mockData = [
id: 15,
startTime: 1599462000000,
endTime: 1599462000000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: [],
- metric: "metric_one"
+ metric: 'metric_one'
},
{
id: 16,
startTime: 1599462000000,
endTime: 1599462000000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: [],
- metric: "metric_one"
+ metric: 'metric_one'
},
{
id: 17,
startTime: 1599462000000,
endTime: 1599462000000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: [],
- metric: "metric_two"
+ metric: 'metric_two'
},
{
id: 18,
@@ -243,11 +243,11 @@ export const mockData = [
id: 19,
startTime: 1599462000000,
endTime: 1599462000000,
- avgCurrentVal: 32,
- avgBaselineVal: 33,
+ avgCurrentVal: 4,
+ avgBaselineVal: 2,
feedback: null,
children: [],
- metric: "metric_three"
+ metric: 'metric_three'
}
]
}
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js
index 461754f..b7e43f0 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js
@@ -1,9 +1,11 @@
import Component from '@ember/component';
import { A as EmberArray } from '@ember/array';
-import { parseRoot } from 'thirdeye-frontend/utils/anomalies-tree-parser';
+import { parseRoot, parseSubtree } from 'thirdeye-frontend/utils/anomalies-tree-parser';
+import pubSub from 'thirdeye-frontend/utils/pub-sub';
export default Component.extend({
+ data: EmberArray(),
/**
* @public
* @templateProps
@@ -32,6 +34,14 @@ export default Component.extend({
*/
onBreadcrumbClick: () => {},
+ /**
+ * The callback function to invoke when anomaly is drilled down
+ *
+ * @public
+ * @type {Function}
+ */
+ onDrilldown: () => {},
+
/** Internal states */
/**
@@ -42,6 +52,10 @@ export default Component.extend({
*/
breadcrumbList: EmberArray(),
+ onAnomalyDrilldown(anomalyId) {
+ this.set('data', parseSubtree(anomalyId, this.anomalies));
+ },
+
/**
* Ember component life hook.
*
@@ -53,17 +67,44 @@ export default Component.extend({
/**
* Ember component life hook.
- * Call the tree parser to fetch the component information for the root level
+ * Call the tree parser to fetch the component information for the root level and
+ * subscribe to drilldown updates.
*
* @override
*/
didReceiveAttrs() {
this._super(...arguments);
- const { breadcrumbInfo } = parseRoot(this.alertId, this.anomalies);
+ //Initial Processing
+ const { output, breadcrumbInfo } = parseRoot(this.alertId, this.anomalies);
this.breadcrumbList.push(breadcrumbInfo);
+ this.set('data', output);
+
+ //Processing on each drilldown
+ const subscription = pubSub.subscribe('onAnomalyDrilldown', (anomalyId) => {
+ const { output, breadcrumbInfo } = parseSubtree(anomalyId, this.anomalies);
+
+ this.set('breadcrumbList', [...this.breadcrumbList, breadcrumbInfo]);
+ this.set('data', output);
+
+ this.onDrilldown();
+ });
+
+ this.set('subscription', subscription);
},
+
+ /**
+ * Ember component life hook.
+ * Delete the callback subscribed to for anomaly drilldowns
+ *
+ * @override
+ */
+ willDestroyElement() {
+ this.subscription.unSubscribe();
+ this._super(...arguments);
+ },
+
/**
* Event handlers
*/
@@ -78,7 +119,10 @@ export default Component.extend({
* @param {Number} index
* The index of the breadcrumb that was clicked
*/
- onBreadcrumbClick({ isRoot = false }, index) {
+ onBreadcrumbClick({ id, isRoot = false }, index) {
+ const { output } = isRoot ? parseRoot(id, this.anomalies) : parseSubtree(id, this.anomalies);
+
+ this.set('data', output);
this.set('breadcrumbList', this.breadcrumbList.splice(0, index + 1));
this.onBreadcrumbClick(isRoot);
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/data-table/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/data-table/template.hbs
new file mode 100644
index 0000000..3031f27
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/data-table/template.hbs
@@ -0,0 +1,24 @@
+<div class="panel-group">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h4 class="panel-title">
+ {{title}}
+ </h4>
+ </div>
+ {{#if tableData.length}}
+ <div class="panel-body">
+ {{models-table
+ data=tableData
+ columns=tableColumns
+ showGlobalFilter=false
+ showColumnsDropdown=false
+ customClasses=customClasses
+ }}
+ </div>
+ {{else}}
+ <p class="composite-anomalies-no-records">
+ {{noRecords}}
+ </p>
+ {{/if}}
+ </div>
+</div>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/component.js
new file mode 100644
index 0000000..29402a7
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/component.js
@@ -0,0 +1,86 @@
+/*
+ * Entity Metric Anomalies Component
+ *
+ * Display a table containing entity metric anomalies
+ * @module composite-anomalies/entity-metric-anomalies
+ * @property {string} title - Heading to use on the table
+ * @property {object[]} data - [required] array of composite anomalies objects
+ *
+ * @example
+ * {{composite-anomalies/entity-metric-anomalies title=<title> data=<data>}}
+ *
+ * @exports composite-anomalies/entity-metric-anomalies
+ */
+
+import Component from '@ember/component';
+import { computed } from '@ember/object';
+import { A as EmberArray } from '@ember/array';
+
+import moment from 'moment';
+import config from 'thirdeye-frontend/config/environment';
+import { getAnomaliesStartDuration, getFeedback } from 'thirdeye-frontend/utils/composite-anomalies';
+
+const TABLE_COLUMNS = [
+ {
+ component: 'custom/composite-anomalies-table/start-duration',
+ propertyName: 'startDuration',
+ title: `Start / Duration (${moment.tz([2012, 5], config.timeZone).format('z')})`
+ },
+ {
+ component: 'custom/composite-anomalies-table/metric',
+ propertyName: 'metric',
+ title: 'Metric'
+ },
+ {
+ component: 'custom/composite-anomalies-table/dimensions',
+ propertyName: 'dimensions',
+ title: 'Dimension'
+ },
+ {
+ component: 'custom/composite-anomalies-table/current-predicted',
+ propertyName: 'currentPredicted',
+ title: 'Current / Predicted'
+ },
+ {
+ component: 'custom/composite-anomalies-table/resolution',
+ propertyName: 'feedback',
+ title: 'Feedback'
+ },
+ {
+ component: 'custom/anomalies-table/investigation-link',
+ propertyName: 'id',
+ title: 'Investigate'
+ }
+];
+
+const CUSTOM_TABLE_CLASSES = {
+ table: 'composite-anomalies-table'
+};
+
+export default Component.extend({
+ data: EmberArray(),
+ tagName: 'section',
+ customClasses: CUSTOM_TABLE_CLASSES,
+ title: 'Metrics Anomalies',
+ noRecords: 'No Entity Metrics found',
+ tableData: computed('data', function () {
+ const computedTableData = [];
+
+ if (this.data && this.data.length > 0) {
+ this.data.map((d) => {
+ const row = {
+ id: d.id,
+ startDuration: getAnomaliesStartDuration(d.startTime, d.endTime, false),
+ metric: d.metric,
+ dimensions: d.dimensions,
+ currentPredicted: d.currentPredicted,
+ feedback: getFeedback(d.feedback)
+ };
+ computedTableData.push(row);
+ });
+ }
+
+ return computedTableData;
+ }),
+ tableColumns: TABLE_COLUMNS
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/template.hbs
new file mode 100644
index 0000000..4c67aa6
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/template.hbs
@@ -0,0 +1 @@
+{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/component.js
new file mode 100644
index 0000000..2a868d4
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/component.js
@@ -0,0 +1,81 @@
+/*
+ * Group Constituents Anomalies Component
+ *
+ * Display a table containing group constituents anomalies
+ * @module composite-anomalies/group-constituents-anomalies
+ * @property {string} title - Heading to use on the table
+ * @property {object[]} data - [required] array of composite anomalies objects
+ *
+ * @example
+ * {{composite-anomalies/group-constituents-anomalies title=<title> data=<data>}}
+ *
+ * @exports composite-anomalies/group-constituents-anomalies
+ */
+
+import Component from '@ember/component';
+import { computed } from '@ember/object';
+import { A as EmberArray } from '@ember/array';
+
+import moment from 'moment';
+import config from 'thirdeye-frontend/config/environment';
+import { getAnomaliesStartDuration, getFeedback } from 'thirdeye-frontend/utils/composite-anomalies';
+
+const TABLE_COLUMNS = [
+ {
+ component: 'custom/composite-anomalies-table/start-duration',
+ propertyName: 'startDuration',
+ title: `Start / Duration (${moment.tz([2012, 5], config.timeZone).format('z')})`
+ },
+ {
+ component: 'custom/composite-anomalies-table/group-name',
+ propertyName: 'groupName',
+ title: 'Group Name'
+ },
+ {
+ component: 'custom/composite-anomalies-table/criticality',
+ propertyName: 'criticalityScore',
+ title: 'Criticality Score'
+ },
+ {
+ component: 'custom/composite-anomalies-table/current-predicted',
+ propertyName: 'currentPredicted',
+ title: 'Current / Predicted'
+ },
+ {
+ component: 'custom/composite-anomalies-table/resolution',
+ propertyName: 'feedback',
+ title: 'Feedback'
+ }
+];
+
+const CUSTOM_TABLE_CLASSES = {
+ table: 'composite-anomalies-table'
+};
+
+export default Component.extend({
+ data: EmberArray(),
+ tagName: 'section',
+ customClasses: CUSTOM_TABLE_CLASSES,
+ title: 'Entity', // Default Header if no title is passed in.
+ noRecords: 'No Groups found',
+ tableData: computed('data', function () {
+ const computedTableData = [];
+
+ if (this.data && this.data.length > 0) {
+ this.data.map((d) => {
+ const row = {
+ anomalyId: d.id,
+ startDuration: getAnomaliesStartDuration(d.startTime, d.endTime, false),
+ groupName: d.groupName,
+ criticalityScore: d.criticality,
+ currentPredicted: d.currentPredicted,
+ feedback: getFeedback(d.feedback)
+ };
+ computedTableData.push(row);
+ });
+ }
+
+ return computedTableData;
+ }),
+ tableColumns: TABLE_COLUMNS
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/template.hbs
new file mode 100644
index 0000000..4c67aa6
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/template.hbs
@@ -0,0 +1 @@
+{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js
index 2bbf542..de9049e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js
@@ -18,32 +18,34 @@ import { A as EmberArray } from '@ember/array';
import moment from 'moment';
import config from 'thirdeye-frontend/config/environment';
-import * as anomalyUtil from 'thirdeye-frontend/utils/anomaly';
+import { getAnomaliesStartDuration, getFeedback } from 'thirdeye-frontend/utils/composite-anomalies';
const TABLE_COLUMNS = [
{
- template: 'custom/composite-anomalies-table/start-duration',
+ component: 'custom/composite-anomalies-table/start-duration',
propertyName: 'startDuration',
title: `Start / Duration (${moment.tz([2012, 5], config.timeZone).format('z')})`
},
{
template: 'custom/composite-anomalies-table/anomalies-list',
- propertyName: 'details',
+ propertyName: 'anomaliesDetails',
title: 'Anomalies details'
},
{
component: 'custom/composite-anomalies-table/resolution',
propertyName: 'feedback',
- title: 'Feedback '
+ title: 'Feedback'
}
];
+const CUSTOM_TABLE_CLASSES = {
+ table: 'composite-anomalies-table'
+};
+
export default Component.extend({
data: EmberArray(),
tagName: 'section',
- customClasses: {
- table: 'composite-anomalies-table'
- },
+ customClasses: CUSTOM_TABLE_CLASSES,
title: 'Composite Anomalies', // Default Header if no title is passed in.
noRecords: 'No Composite Anomalies found',
tableData: computed('data', function () {
@@ -52,9 +54,10 @@ export default Component.extend({
if (this.data && this.data.length > 0) {
this.data.map((d) => {
const row = {
- startDuration: this.getAnomaliesStartDuration(d.startTime, d.endTime),
+ anomalyId: d.id,
+ startDuration: getAnomaliesStartDuration(d.startTime, d.endTime, true),
anomaliesDetails: this.getAnomaliesDetails(d.details),
- feedback: this.getFeedback(d.feedback)
+ feedback: getFeedback(d.feedback)
};
computedTableData.push(row);
});
@@ -63,55 +66,18 @@ export default Component.extend({
}),
tableColumns: TABLE_COLUMNS,
/*
- * convert anomaly 'start' and 'end' into an object to be used by template: 'custom/composite-animalies-table/start-duration'
- *
- * @param {Number} start
- * The start time in milliseconds.
- * @param {Number} end
- * The end time in milliseconds
- *
- * @returns {Object}
- * Description of the object.
- */
- getAnomaliesStartDuration: (start, end) => {
- return {
- startTime: start,
- endTime: end,
- duration: moment.duration(end - start).asHours() + ' hours'
- };
- },
- /*
- * convert list of anonalies object in to an array of objects to be used by template: 'custom/composite-animalies-table/anomalies-list'
+ * Convert list of anonalies object in to an array of objects to be used by template: 'custom/composite-animalies-table/anomalies-list'
*
* @param {Object} details
- * The object containing the list of anomalies and their count.
+ * The object containing the list of anomalies and their count.
*
* @returns {Array}
- * Description of the object.
+ * Description of the object.
*/
getAnomaliesDetails: (anomalies) => {
return Object.entries(anomalies).reduce((anomalyList, anomalyDetails) => {
anomalyList.push({ name: anomalyDetails[0], count: anomalyDetails[1] });
return anomalyList;
}, []);
- },
- /*
- * return feedbackObject with pre-selected 'feedback' to be use in the feedback dropdown
- *
- * @param {String} feedback
- * The object containing the list of anomalies and their count.
- *
- * @returns {Object} feedbackObject
- * Description of the object.
- */
- getFeedback: (feedback) => {
- const selectedFeedback = feedback
- ? anomalyUtil.anomalyResponseObj.find((f) => f.value === feedback)
- : anomalyUtil.anomalyResponseObj[0];
-
- return {
- options: anomalyUtil.anomalyResponseObj.mapBy('name'),
- selected: selectedFeedback ? selectedFeedback.name : anomalyUtil.anomalyResponseObj[0].name
- };
}
});
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs
index ea66b01..4c67aa6 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs
@@ -1,20 +1 @@
-<div class="panel-group">
- <div class="panel panel-default">
- <div class="panel-heading">
- <h4 class="panel-title">{{title}}</h4>
- </div>
- {{#if tableData.length}}
- <div class="panel-body">
- {{models-table
- data=tableData
- columns=tableColumns
- showGlobalFilter=false
- showColumnsDropdown=false
- customClasses=customClasses
- }}
- </div>
- {{else}}
- <p class="composite-anomalies-no-records">{{noRecords}}</p>
- {{/if}}
- </div>
-</div>
\ No newline at end of file
+{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/template.hbs
index a37c87f..9b64c90 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/template.hbs
@@ -1,8 +1,9 @@
{{yield}}
-
<section>
- {{breadcrumb-list
- items=breadcrumbList
- onBreadcrumbClick=(action "onBreadcrumbClick")
- }}
+ {{breadcrumb-list items=breadcrumbList onBreadcrumbClick=(action "onBreadcrumbClick")}}
</section>
+<section>
+ {{#each data as |cmpt|}}
+ {{component cmpt.componentPath title=cmpt.title data=cmpt.data}}
+ {{/each}}
+</section>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/anomalies-list/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/anomalies-list/template.hbs
index 0f64c45..26c6c47 100644
--- a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/anomalies-list/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/anomalies-list/template.hbs
@@ -1,3 +1,3 @@
{{#each record.anomaliesDetails as |anomalyDetails| }}
- <p class="details">{{anomalyDetails.name}} ({{anomalyDetails.count}})</p>
+ <p class="te-anomaly-table__details te-label te-label--small">{{anomalyDetails.name}} ({{anomalyDetails.count}})</p>
{{/each}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/criticality/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/criticality/template.hbs
new file mode 100644
index 0000000..7a3d230
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/criticality/template.hbs
@@ -0,0 +1,3 @@
+<p class="te-label te-label--small te-anomaly-table__criticality">
+ {{record.criticalityScore}}
+</p>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/current-predicted/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/current-predicted/template.hbs
new file mode 100644
index 0000000..e0bd32f
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/current-predicted/template.hbs
@@ -0,0 +1,6 @@
+<p class="te-anomaly-table__current-baseline">
+ {{record.currentPredicted.current}}/{{record.currentPredicted.predicted}}
+</p>
+<p class="te-anomaly-table__value-label te-anomaly-table__value-label--dash te-anomaly-table__deviation-percent te-anomaly-table__value-label--{{calculate-direction record.currentPredicted.deviation}}">
+ ({{record.currentPredicted.deviationPercent}})
+</p>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/dimensions/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/dimensions/template.hbs
new file mode 100644
index 0000000..a2142c1
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/dimensions/template.hbs
@@ -0,0 +1,3 @@
+{{#each-in record.dimensions as |key value|}}
+ <p class="te-label te-label--small te-anomaly-table__dimension">{{key}}:{{value}}</p>
+{{/each-in}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/group-name/component.js b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/group-name/component.js
new file mode 100644
index 0000000..adbb6d4
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/group-name/component.js
@@ -0,0 +1,10 @@
+import Component from '@ember/component';
+import pubSub from 'thirdeye-frontend/utils/pub-sub';
+
+export default Component.extend({
+ actions: {
+ drilldownGroupConstituent(anomalyId) {
+ pubSub.publish('onAnomalyDrilldown', anomalyId);
+ }
+ }
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/group-name/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/group-name/template.hbs
new file mode 100644
index 0000000..323da0c
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/group-name/template.hbs
@@ -0,0 +1,3 @@
+<p class="te-label te-label--small te-anomaly-table__link te-anomaly-table__group" onclick={{action "drilldownGroupConstituent" record.anomalyId}}>
+ {{record.groupName}}
+</p>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/metric/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/metric/template.hbs
new file mode 100644
index 0000000..f24d5d8
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/metric/template.hbs
@@ -0,0 +1,3 @@
+<p class="te-label te-label--small te-anomaly-table__metric">
+ {{record.metric}}
+</p>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/start-duration/component.js b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/start-duration/component.js
new file mode 100644
index 0000000..1a2713a
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/start-duration/component.js
@@ -0,0 +1,10 @@
+import Component from '@ember/component';
+import pubSub from 'thirdeye-frontend/utils/pub-sub';
+
+export default Component.extend({
+ actions: {
+ drilldownAnomaly(anomalyId) {
+ pubSub.publish('onAnomalyDrilldown', anomalyId);
+ }
+ }
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/start-duration/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/start-duration/template.hbs
index 51fce82..0d2f4b1 100644
--- a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/start-duration/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/start-duration/template.hbs
@@ -1,8 +1,8 @@
-<label class="te-label te-label--small te-anomaly-table__link">
- <span class="start-time">{{moment-format record.startDuration.startTime "MMM Do, h:mm z"}}</span>
+<label class="te-label te-label--small {{if record.startDuration.showLink "te-anomaly-table__link" "te-label--disable-click"}}">
+ <span class="start-time" onclick={{action "drilldownAnomaly" record.anomalyId}}>{{moment-format record.startDuration.startTime "MMM Do, h:mm z"}}</span>
{{#tooltip-on-element}}
Start: {{moment-format record.startDuration.startTime "MMM Do, h:mm z"}}<br />
End: {{moment-format record.startDuration.endTime "MMM Do, h:mm z"}}
{{/tooltip-on-element}}
</label>
-<p class="duration">{{record.startDuration.duration}}</p>
\ No newline at end of file
+<p class="te-anomaly-table__duration">{{record.startDuration.duration}}</p>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/controller.js b/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/controller.js
index 02d2c19..4251b84 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/controller.js
@@ -149,6 +149,16 @@ export default Controller.extend({
*/
onBreadcrumbClick(isRoot = false) {
this.set('rootLevel', isRoot);
+ },
+
+ /**
+ * Function to disable the ability to select dates since user is not on root level
+ *
+ * @param {Boolean} isRoot
+ * Tracks if we are working on root level of the tree
+ */
+ onDrilldown() {
+ this.set('rootLevel', false);
}
}
});
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/template.hbs
index e38db1f..06969f5 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/composite-anomalies/template.hbs
@@ -53,8 +53,13 @@
<div class="spinner-wrapper spinner-wrapper--card">
{{ember-spinner lines=30 radius=20 length=0 width=10 opacity=0 trail=75 color="blue"}}
</div>
- {{else}}
- {{composite-anomalies anomalies=anomalies alertId=model.alertId onBreadcrumbClick=(action "onBreadcrumbClick")}}
+ {{else}}
+ {{composite-anomalies
+ anomalies=anomalies
+ alertId=model.alertId
+ onBreadcrumbClick=(action "onBreadcrumbClick")
+ onDrilldown=(action "onDrilldown")
+ }}
{{/if}}
</section>
</section>
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/breadcrumb-list.scss b/thirdeye/thirdeye-frontend/app/styles/components/breadcrumb-list.scss
index 34d9f76..e87b29b 100644
--- a/thirdeye/thirdeye-frontend/app/styles/components/breadcrumb-list.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/components/breadcrumb-list.scss
@@ -11,9 +11,11 @@
padding-left: 10px;
font-size: 14px;
font-weight: 400;
+ cursor:pointer;
}
&__title:last-of-type {
font-weight: 700;
+ cursor: auto;
}
}
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/te-anomaly-table.scss b/thirdeye/thirdeye-frontend/app/styles/components/te-anomaly-table.scss
index d2d0ff5..d00e0fa 100644
--- a/thirdeye/thirdeye-frontend/app/styles/components/te-anomaly-table.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/components/te-anomaly-table.scss
@@ -176,6 +176,7 @@
font-weight: 600;
letter-spacing: .03em;
font-size: 15px;
+ cursor: pointer;
&--glyph {
margin-left: 10px;
diff --git a/thirdeye/thirdeye-frontend/app/styles/pods/custom/parent-anomalies-table.scss b/thirdeye/thirdeye-frontend/app/styles/pods/custom/parent-anomalies-table.scss
index 258a86a..ff2ee95 100644
--- a/thirdeye/thirdeye-frontend/app/styles/pods/custom/parent-anomalies-table.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/pods/custom/parent-anomalies-table.scss
@@ -6,10 +6,13 @@
}
td, th {
padding: 10px 10px 10px 20px;
-
- .details, .duration {
- margin: 0;
- }
+ }
+ &__multi-line-column {
+ margin: 0;
+ color: black;
+ }
+ &__duration {
+ margin: 0;
}
margin-bottom: 20px;
}
diff --git a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
index 2041630..58f98bc 100644
--- a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
@@ -334,6 +334,10 @@ body {
&--dark {
color: app-shade(black, 0.85);
}
+
+ &--disable-click {
+ pointer-events: none;
+ }
}
.te-input {
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js b/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js
index af02b31..72d3128 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js
@@ -2,21 +2,22 @@ import { isEmpty } from '@ember/utils';
import { set } from '@ember/object';
import moment from 'moment';
import { BREADCRUMB_TIME_DISPLAY_FORMAT } from 'thirdeye-frontend/utils/constants';
+import { humanizeFloat, humanizeChange } from 'thirdeye-frontend/utils/utils';
const CLASSIFICATIONS = {
METRICS: {
KEY: 'metrics',
- COMPONENT_PATH: 'entity-metrics',
+ COMPONENT_PATH: 'composite-anomalies/entity-metrics-anomalies',
DEFAULT_TITLE: 'Metric Anomalies'
},
GROUPS: {
KEY: 'groups',
- COMPONENT_PATH: 'entity-groups',
+ COMPONENT_PATH: 'composite-anomalies/group-constituents-anomalies',
DEFAULT_TITLE: 'ENTITY:'
},
ENTITIES: {
KEY: 'entities',
- COMPONENT_PATH: 'parent-anomalies',
+ COMPONENT_PATH: 'composite-anomalies/parent-anomalies',
DEFAULT_TITLE: 'Entity'
}
};
@@ -162,8 +163,14 @@ const setMetricsBucket = (buckets, anomaly, metric) => {
endTime,
metric,
feedback,
- current,
- predicted
+ currentPredicted: {
+ current: humanizeFloat(current),
+ predicted: humanizeFloat(predicted),
+ deviation: Number((current - predicted) / predicted),
+ get deviationPercent() {
+ return humanizeChange(this.deviation);
+ }
+ }
};
if (isEmpty(data)) {
@@ -211,8 +218,14 @@ const setGroupsBucket = (buckets, anomaly, subEntityName, groupName) => {
endTime,
feedback,
criticality,
- current,
- predicted
+ currentPredicted: {
+ current: humanizeFloat(current),
+ predicted: humanizeFloat(predicted),
+ deviation: Number((current - predicted) / predicted),
+ get deviationPercent() {
+ return humanizeChange(this.deviation);
+ }
+ }
};
if ([groupKey] in buckets) {
@@ -362,7 +375,7 @@ const parseGroupAnomaly = (input) => {
const output = [];
const data = [];
const {
- GROUPS: { DEFAULT_TITLE, COMPONENT_PATH }
+ METRICS: { DEFAULT_TITLE, COMPONENT_PATH }
} = CLASSIFICATIONS;
const {
id,
@@ -393,8 +406,14 @@ const parseGroupAnomaly = (input) => {
feedback,
metric,
dimensions,
- current,
- predicted
+ currentPredicted: {
+ current: humanizeFloat(current),
+ predicted: humanizeFloat(predicted),
+ deviation: Number((current - predicted) / predicted),
+ get deviationPercent() {
+ return humanizeChange(this.deviation);
+ }
+ }
});
}
@@ -515,8 +534,8 @@ export const parseRoot = (explorationId, input) => {
* The anomaly id
* @param {Array<Object> or Object} input
* The tree structure hosting the anomaly referenced by the id.
- * -If the entire tree is being passed, it would in array form
- * -If a subtree is being passed, it would be in object form
+ * -If the entire tree is being passed, it would be in an array form
+ * -If a subtree is being passed, it would be in an object form
*
* @return {Object}
* The breadcrumb info and data for instantiating tables at any level in tree
diff --git a/thirdeye/thirdeye-frontend/app/utils/composite-anomalies.js b/thirdeye/thirdeye-frontend/app/utils/composite-anomalies.js
new file mode 100644
index 0000000..b08d764
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/utils/composite-anomalies.js
@@ -0,0 +1,48 @@
+/*
+ * Util functions to structure the data to be passed into individual columns
+ */
+
+import moment from 'moment';
+import * as anomalyUtil from 'thirdeye-frontend/utils/anomaly';
+
+/*
+ * Convert anomaly 'start' and 'end' into an object
+ *
+ * @param {Number} start
+ * The start time in milliseconds.
+ * @param {Number} end
+ * The end time in milliseconds
+ * @param {Boolean} showLink
+ * Indicates whether to hyperlink the start duration
+ *
+ * @returns {Object}
+ * Description of the object.
+ */
+export const getAnomaliesStartDuration = (start, end, showLink = false) => {
+ return {
+ startTime: start,
+ endTime: end,
+ duration: moment.duration(end - start).asHours() + ' hours',
+ showLink
+ };
+};
+
+/*
+ * Return feedbackObject with pre-selected 'feedback' to be use in the feedback dropdown
+ *
+ * @param {String} feedback
+ * The object containing the list of anomalies and their count.
+ *
+ * @returns {Object} feedbackObject
+ * Description of the object.
+ */
+export const getFeedback = (feedback) => {
+ const selectedFeedback = feedback
+ ? anomalyUtil.anomalyResponseObj.find((f) => f.value === feedback)
+ : anomalyUtil.anomalyResponseObj[0];
+
+ return {
+ options: anomalyUtil.anomalyResponseObj.mapBy('name'),
+ selected: selectedFeedback ? selectedFeedback.name : anomalyUtil.anomalyResponseObj[0].name
+ };
+};
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/component-test.js
index 9dae98b..53637a7 100644
--- a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/component-test.js
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/component-test.js
@@ -16,5 +16,5 @@ test('it renders', function (assert) {
this.render(hbs`{{composite-anomalies alertId=alertId anomalies=anomalies}}`);
- assert.equal(this.$().text().trim(), 'Alert Anomalies');
+ assert.ok(this.$().text().trim().includes('Alert Anomalies'));
});
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/entity-metrics-anomalies/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/entity-metrics-anomalies/component-test.js
new file mode 100644
index 0000000..21b4da5
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/entity-metrics-anomalies/component-test.js
@@ -0,0 +1,72 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+import * as anomalyUtil from 'thirdeye-frontend/utils/anomaly';
+
+moduleForComponent(
+ 'composite-anomalies/entity-metrics-anomalies',
+ 'Integration | Component | composite anomalies/entity metrics anomalies',
+ {
+ integration: true
+ }
+);
+
+test('it renders', function (assert) {
+ this.setProperties({
+ tableTitle: 'ENTITY',
+ tableData: [
+ {
+ id: 9,
+ startTime: 1599462000000,
+ endTime: 1599721200000,
+ feedback: null,
+ metric: 'metric_four',
+ dimensions: {
+ feature_name: 'groupConstituentOne#',
+ feature_section: 'groupConstituentOne',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
+ },
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
+ }
+ ],
+ feedbackOptionNames: anomalyUtil.anomalyResponseObj.mapBy('name'),
+ feedbackOptionValues: anomalyUtil.anomalyResponseObj.mapBy('value')
+ });
+
+ this.render(hbs`
+ {{composite-anomalies/entity-metrics-anomalies title=tableTitle data=tableData}}
+ `);
+
+ assert.equal(this.$('.panel-title').html().trim(), this.tableTitle);
+
+ assert.equal(this.$('.te-anomaly-table__duration')[0].innerHTML, '72 hours');
+
+ assert.equal(this.$('.te-anomaly-table__metric')[0].innerHTML.trim(), 'metric_four');
+
+ assert.equal(this.$('.te-anomaly-table__dimension')[0].innerHTML.trim(), 'feature_name:groupConstituentOne#');
+ assert.equal(this.$('.te-anomaly-table__dimension')[1].innerHTML.trim(), 'feature_section:groupConstituentOne');
+ assert.equal(this.$('.te-anomaly-table__dimension')[2].innerHTML.trim(), 'dimension_three:True');
+ assert.equal(this.$('.te-anomaly-table__dimension')[3].innerHTML.trim(), 'use_case:DESKTOP');
+
+ assert.equal(this.$('.te-anomaly-table__current-baseline')[0].innerHTML.trim(), '4.00/2.00');
+ assert.equal(this.$('.te-anomaly-table__deviation-percent')[0].innerHTML.trim(), '(+100.0%)');
+
+ assert.equal(this.$('.ember-power-select-selected-item').html().trim(), this.feedbackOptionNames[0]);
+
+ // Check other values based on feedback
+
+ this.feedbackOptionNames.forEach((option, index) => {
+ this.tableData[0].feedback = this.feedbackOptionValues[index];
+
+ this.render(hbs`
+ {{composite-anomalies/entity-metrics-anomalies data=tableData}}
+ `);
+
+ assert.equal(this.$('.ember-power-select-selected-item').html().trim(), option);
+ });
+});
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/group-constituents-anomalies/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/group-constituents-anomalies/component-test.js
new file mode 100644
index 0000000..ddd8279
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/group-constituents-anomalies/component-test.js
@@ -0,0 +1,81 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+import * as anomalyUtil from 'thirdeye-frontend/utils/anomaly';
+
+moduleForComponent(
+ 'composite-anomalies/group-constituent-anomalies',
+ 'Integration | Component | composite anomalies/group constituent anomalies',
+ {
+ integration: true
+ }
+);
+
+test('it renders', function (assert) {
+ this.setProperties({
+ tableTitle: 'ENTITY:group_entity_one',
+ tableData: [
+ {
+ id: 2,
+ groupName: 'groupConstituentOne',
+ startTime: 1599462000000,
+ endTime: 1599721200000,
+ feedback: null,
+ criticality: '6.189942819613212',
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
+ },
+ {
+ id: 5,
+ groupName: 'groupConstituentTwo',
+ startTime: 1599462000000,
+ endTime: 1599721200000,
+ feedback: null,
+ criticality: '1.213451',
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
+ }
+ ],
+ feedbackOptionNames: anomalyUtil.anomalyResponseObj.mapBy('name'),
+ feedbackOptionValues: anomalyUtil.anomalyResponseObj.mapBy('value')
+ });
+
+ this.render(hbs`
+ {{composite-anomalies/group-constituents-anomalies title=tableTitle data=tableData}}
+ `);
+
+ assert.equal(this.$('.panel-title').html().trim(), this.tableTitle);
+
+ assert.equal(this.$('.te-anomaly-table__duration')[0].innerHTML, '72 hours');
+ assert.equal(this.$('.te-anomaly-table__duration')[1].innerHTML, '72 hours');
+
+ assert.equal(this.$('.te-anomaly-table__group')[0].innerHTML.trim(), 'groupConstituentOne');
+ assert.equal(this.$('.te-anomaly-table__group')[1].innerHTML.trim(), 'groupConstituentTwo');
+
+ assert.equal(this.$('.te-anomaly-table__criticality')[0].innerHTML.trim(), '6.189942819613212');
+ assert.equal(this.$('.te-anomaly-table__criticality')[1].innerHTML.trim(), '1.213451');
+
+ assert.equal(this.$('.te-anomaly-table__current-baseline')[0].innerHTML.trim(), '4.00/2.00');
+ assert.equal(this.$('.te-anomaly-table__deviation-percent')[0].innerHTML.trim(), '(+100.0%)');
+
+ assert.equal(this.$('.ember-power-select-selected-item').html().trim(), this.feedbackOptionNames[0]);
+
+ // Check other values based on feedback
+
+ this.feedbackOptionNames.forEach((option, index) => {
+ this.tableData[0].feedback = this.feedbackOptionValues[index];
+
+ this.render(hbs`
+ {{composite-anomalies/group-constituents-anomalies data=tableData}}
+ `);
+
+ assert.equal(this.$('.ember-power-select-selected-item').html().trim(), option);
+ });
+});
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js
index ebb08d8..1c8dbc9 100644
--- a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js
@@ -36,23 +36,23 @@ test('it renders', function (assert) {
this.render(hbs`{{composite-anomalies/parent-anomalies}}`);
- assert.equal(this.$('h4.panel-title').html(), this.tableTitle);
- assert.equal(this.$('p.composite-anomalies-no-records').html(), this.noAnmalies);
+ assert.equal(this.$('h4.panel-title').html().trim(), this.tableTitle);
+ assert.equal(this.$('p.composite-anomalies-no-records').html().trim(), this.noAnmalies);
this.render(hbs`
{{composite-anomalies/parent-anomalies title=tableTitle}}
`);
- assert.equal(this.$('.panel-title').html(), this.tableTitle);
- assert.equal(this.$('.composite-anomalies-no-records').html(), this.noAnmalies);
+ assert.equal(this.$('.panel-title').html().trim(), this.tableTitle);
+ assert.equal(this.$('.composite-anomalies-no-records').html().trim(), this.noAnmalies);
this.render(hbs`
{{composite-anomalies/parent-anomalies data=tableData}}
`);
// assert.equal($('.start-time').html(), 'Sep 7th, 12:00 ');
- assert.equal(this.$('.duration').html(), '72 hours');
- assert.equal(this.$('.details').html(), 'oe_viral_detection (2)');
+ assert.equal(this.$('.te-anomaly-table__duration').html(), '72 hours');
+ assert.equal(this.$('.te-anomaly-table__details').html(), 'oe_viral_detection (2)');
assert.equal(this.$('.ember-power-select-selected-item').html().trim(), this.feedbackOptionNames[0]);
// Check other values based on feedback
diff --git a/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js b/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js
index bc6ad54..61fc716 100644
--- a/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js
+++ b/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js
@@ -1,24 +1,21 @@
-import { mockData } from "thirdeye-frontend/mocks/compositeAnomalies";
-import { module, test } from "qunit";
-import {
- parseRoot,
- parseSubtree
-} from "thirdeye-frontend/utils/anomalies-tree-parser";
-
-module("Unit | Utility | Anomalies tree parser utils", function() {
- test("it parses root level parent anomalies correctly", function(assert) {
+import { mockData } from 'thirdeye-frontend/mocks/compositeAnomalies';
+import { module, test } from 'qunit';
+import { parseRoot, parseSubtree } from 'thirdeye-frontend/utils/anomalies-tree-parser';
+
+module('Unit | Utility | Anomalies tree parser utils', function () {
+ test('it parses root level parent anomalies correctly', function (assert) {
const explorationId = 121;
const { breadcrumbInfo, output } = parseRoot(explorationId, mockData);
const expectedBreadcrumbInfo = {
- title: "Alert Anomalies",
+ title: 'Alert Anomalies',
isRoot: true,
id: 121
};
const expectedOutput = [
{
- componentPath: "parent-anomalies",
+ componentPath: 'composite-anomalies/parent-anomalies',
data: [
{
id: 1,
@@ -34,7 +31,7 @@ module("Unit | Utility | Anomalies tree parser utils", function() {
}
}
],
- title: "Entity"
+ title: 'Entity'
}
];
@@ -42,7 +39,7 @@ module("Unit | Utility | Anomalies tree parser utils", function() {
assert.deepEqual(output, expectedOutput);
});
- test("it drills down a composite anomaly correctly - level 1 drilldown example", function(assert) {
+ test('it drills down a composite anomaly correctly - level 1 drilldown example', function (assert) {
const anomalyId = 1;
const {
breadcrumbInfo: { id, isRoot },
@@ -51,93 +48,121 @@ module("Unit | Utility | Anomalies tree parser utils", function() {
const expectedOutput = [
{
- componentPath: "entity-groups",
- title: "ENTITY:group_entity_one",
+ componentPath: 'composite-anomalies/group-constituents-anomalies',
+ title: 'ENTITY:group_entity_one',
data: [
{
id: 2,
- groupName: "groupConstituentOne",
+ groupName: 'groupConstituentOne',
startTime: 1599462000000,
endTime: 1599721200000,
feedback: null,
- criticality: "6.189942819613212",
- current: 32,
- predicted: 33
+ criticality: '6.189942819613212',
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
},
{
id: 5,
- groupName: "groupConstituentTwo",
+ groupName: 'groupConstituentTwo',
startTime: 1599462000000,
endTime: 1599721200000,
feedback: null,
- criticality: "6.189942819613212",
- current: 32,
- predicted: 33
+ criticality: '6.189942819613212',
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
}
]
},
{
- componentPath: "entity-groups",
- title: "ENTITY:group_entity_two",
+ componentPath: 'composite-anomalies/group-constituents-anomalies',
+ title: 'ENTITY:group_entity_two',
data: [
{
id: 8,
- groupName: "groupConstituentOne",
+ groupName: 'groupConstituentOne',
startTime: 1599462000000,
endTime: 1599721200000,
feedback: null,
- criticality: "6.189942819613212",
- current: 32,
- predicted: 33
+ criticality: '6.189942819613212',
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
},
{
id: 11,
- groupName: "groupConstituentTwo",
+ groupName: 'groupConstituentTwo',
startTime: 1599462000000,
endTime: 1599721200000,
feedback: null,
- criticality: "6.189942819613212",
- current: 32,
- predicted: 33
+ criticality: '6.189942819613212',
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
}
]
},
{
- componentPath: "entity-metrics",
- title: "Metric Anomalies",
+ componentPath: 'composite-anomalies/entity-metrics-anomalies',
+ title: 'Metric Anomalies',
data: [
{
id: 15,
startTime: 1599462000000,
endTime: 1599462000000,
- metric: "metric_one",
+ metric: 'metric_one',
feedback: null,
- current: 32,
- predicted: 33
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
},
{
id: 16,
startTime: 1599462000000,
endTime: 1599462000000,
- metric: "metric_one",
+ metric: 'metric_one',
feedback: null,
- current: 32,
- predicted: 33
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
},
{
id: 17,
startTime: 1599462000000,
endTime: 1599462000000,
- metric: "metric_two",
+ metric: 'metric_two',
feedback: null,
- current: 32,
- predicted: 33
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
}
]
},
{
- componentPath: "parent-anomalies",
- title: "Entity",
+ componentPath: 'composite-anomalies/parent-anomalies',
+ title: 'Entity',
data: [
{
id: 18,
@@ -157,56 +182,64 @@ module("Unit | Utility | Anomalies tree parser utils", function() {
assert.notEqual(
isRoot,
true,
- "Breadcrumb state should be indicating we are not doing root level parsing of the tree"
+ 'Breadcrumb state should be indicating we are not doing root level parsing of the tree'
);
//output tests
assert.deepEqual(output, expectedOutput);
});
- test("it drills down an ananomaly grouped by anomaly summarize grouper correctly - level 2 drilldown example", function(assert) {
+ test('it drills down an ananomaly grouped by anomaly summarize grouper correctly - level 2 drilldown example', function (assert) {
const anomalyId = 8;
const { breadcrumbInfo, output } = parseSubtree(anomalyId, mockData);
const expectedBreadcrumbInfo = {
- title: "group_entity_two/groupConstituentOne",
+ title: 'group_entity_two/groupConstituentOne',
id: 8
};
const expectedOutput = [
{
- componentPath: "entity-groups",
- title: "ENTITY:",
+ componentPath: 'composite-anomalies/entity-metrics-anomalies',
+ title: 'Metric Anomalies',
data: [
{
id: 9,
startTime: 1599462000000,
endTime: 1599548400000,
feedback: null,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentOne#",
- feature_section: "groupConstituentOne",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentOne#',
+ feature_section: 'groupConstituentOne',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
- current: 32,
- predicted: 33
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
},
{
id: 10,
startTime: 1599462000000,
endTime: 1599548400000,
feedback: null,
- metric: "metric_four",
+ metric: 'metric_four',
dimensions: {
- feature_name: "groupConstituentOne#",
- feature_section: "groupConstituentOne",
- dimension_three: "True",
- use_case: "DESKTOP"
+ feature_name: 'groupConstituentOne#',
+ feature_section: 'groupConstituentOne',
+ dimension_three: 'True',
+ use_case: 'DESKTOP'
},
- current: 32,
- predicted: 33
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
}
]
}
@@ -216,7 +249,7 @@ module("Unit | Utility | Anomalies tree parser utils", function() {
assert.deepEqual(output, expectedOutput);
});
- test("it drills down a composite anomaly correctly - level 2 drilldown example (composite anomaly within a composite anomaly)", function(assert) {
+ test('it drills down a composite anomaly correctly - level 2 drilldown example (composite anomaly within a composite anomaly)', function (assert) {
const anomalyId = 18;
const {
breadcrumbInfo: { id, isRoot },
@@ -225,17 +258,21 @@ module("Unit | Utility | Anomalies tree parser utils", function() {
const expectedOutput = [
{
- componentPath: "entity-metrics",
- title: "Metric Anomalies",
+ componentPath: 'composite-anomalies/entity-metrics-anomalies',
+ title: 'Metric Anomalies',
data: [
{
id: 19,
startTime: 1599462000000,
endTime: 1599462000000,
- metric: "metric_three",
+ metric: 'metric_three',
feedback: null,
- current: 32,
- predicted: 33
+ currentPredicted: {
+ current: '4.00',
+ predicted: '2.00',
+ deviation: 1,
+ deviationPercent: '+100.0%'
+ }
}
]
}
@@ -246,7 +283,7 @@ module("Unit | Utility | Anomalies tree parser utils", function() {
assert.notEqual(
isRoot,
true,
- "Breadcrumb state should be indicating we are not doing root level parsing of the tree"
+ 'Breadcrumb state should be indicating we are not doing root level parsing of the tree'
);
//output tests
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org