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 2019/07/10 21:19:44 UTC
[incubator-pinot] branch master updated: [TE] frontend -
harleyjj/detection-health - UI for model performance (#4413)
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 e041ec5 [TE] frontend - harleyjj/detection-health - UI for model performance (#4413)
e041ec5 is described below
commit e041ec5439735de81d3f57af91db5b380b9f8062
Author: Harley Jackson <hj...@linkedin.com>
AuthorDate: Wed Jul 10 14:19:37 2019 -0700
[TE] frontend - harleyjj/detection-health - UI for model performance (#4413)
Implements new detection health component for alert overview and preview with alert id
Corrects some comment and naming errors
Adds login redirect to manage/explore and manage/yaml
Removes [Beta] from preview label
---
.../app/pods/components/alert-details/component.js | 2 +
.../app/pods/components/alert-details/template.hbs | 80 +++++++--
.../pods/components/detection-health/component.js | 194 +++++++++++++++++++++
.../pods/components/detection-health/template.hbs | 84 +++++++++
.../app/pods/components/stats-cards/component.js | 42 +++--
.../app/pods/components/stats-cards/template.hbs | 61 +++++--
.../app/pods/components/yaml-editor/template.hbs | 2 +-
.../app/pods/manage/explore/route.js | 37 +++-
.../app/pods/manage/explore/template.hbs | 1 +
.../app/pods/manage/yaml/route.js | 46 ++++-
.../app/pods/manage/yaml/template.hbs | 3 +-
.../app/pods/self-serve/create-alert/template.hbs | 2 +-
.../app/styles/shared/_styles.scss | 118 +++++++++++++
13 files changed, 608 insertions(+), 64 deletions(-)
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
index 4047113..528db3d 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
@@ -93,6 +93,7 @@ export default Component.extend({
granularity: null,
alertYaml: null,
dimensionExploration: null,
+ detectionHealth: null, // result of call to detection/health/{id}, passed in by parent
@@ -615,6 +616,7 @@ export default Component.extend({
}
),
+
/**
* Date types to display in the pills
* @type {Object[]} - array of objects, each of which represents each date pill
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
index 66f9a03..a2f1ea0 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
@@ -54,26 +54,72 @@
predefinedRanges=pill.predefinedRanges
selectAction=(action "onRangeSelection")
}}
- <div class="te-horizontal-cards te-content-block">
- <h4 class="te-self-serve__block-title">
- <label for="select-dimension" class="control-label te-label">
- Alert Performance
- <span>
- <i class="glyphicon glyphicon-question-sign"></i>
- {{#tooltip-on-element class="te-tooltip"}}
- All estimated performance numbers are based on reviewed anomalies.
- {{/tooltip-on-element}}
- </span>
- </label>
- </h4>
- {{!-- Alert anomaly stats cards --}}
- {{stats-cards stats=stats}}
- {{#if repRunStatus}}
- <p class="te-self-serve__block-subtext te-self-serve__block-subtext--normal">Replay in progress. Please check back later...</p>
- {{/if}}
+
+ {{#if alertId}}
+
+ <div class="te-content-block__performance-health-wrapper">
+ <!-- Anomalies, Response Rate, Precision, Recall -->
+ <div class="te-horizontal-cards te-content-block__alert-performance">
+ <h4 class="te-self-serve__block-title">
+ <label for="select-dimension" class="control-label te-label">
+ Alert Performance
+ <span>
+ <i class="glyphicon glyphicon-question-sign"></i>
+ {{#tooltip-on-element class="te-tooltip"}}
+ All estimated performance numbers are based on reviewed anomalies.
+ {{/tooltip-on-element}}
+ </span>
+ </label>
+ </h4>
+ {{!-- Alert anomaly stats cards --}}
+ {{stats-cards stats=stats}}
+ </div>
+
+ <!-- Detection Health -->
+ <div class="te-horizontal-cards te-content-block__detection-health">
+ <h4 class="te-self-serve__block-title">
+ <label for="select-dimension" class="control-label te-label">
+ Detection health
+ <span>
+ <i class="glyphicon glyphicon-question-sign"></i>
+ {{#tooltip-on-element class="te-tooltip"}}
+ See how your detection configuration is performing in detail
+ by clicking 'view details' below{{#if showRules}},
+ and select different rules above the graph
+ to see their respective regression errors{{/if}}.
+ {{/tooltip-on-element}}
+ </span>
+ </label>
+ </h4>
+ {{detection-health
+ health=detectionHealth
+ selectedRule=selectedRule
+ }}
+ </div>
</div>
+ {{else}}
+
+ <!-- Anomalies, Response Rate, Precision, Recall -->
+ <div class="te-horizontal-cards te-content-block">
+ <h4 class="te-self-serve__block-title">
+ <label for="select-dimension" class="control-label te-label">
+ Alert Performance
+ <span>
+ <i class="glyphicon glyphicon-question-sign"></i>
+ {{#tooltip-on-element class="te-tooltip"}}
+ All estimated performance numbers are based on reviewed anomalies.
+ {{/tooltip-on-element}}
+ </span>
+ </label>
+ </h4>
+ {{!-- Alert anomaly stats cards --}}
+ {{stats-cards stats=stats}}
+ </div>
+
+ {{/if}}
+
{{#if isReportSuccess}}
{{#bs-alert type="success" class="te-form__banner te-form__banner--success"}}
<strong>Success:</strong> Anomaly reported for dates <strong>{{reportedRange}}</strong>. Refresh page to see new anomalies...
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/detection-health/component.js b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/component.js
new file mode 100644
index 0000000..82c73aa
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/component.js
@@ -0,0 +1,194 @@
+/**
+ * Detection-Health Component
+ * Displays the health of a given detection, including a tooltip with further details about health
+ * @property {Object} details - [required] details of health status retrieved from endpoint
+ * @example
+ * {{detection-health
+ * health=detectionHealth
+ * selectedRule=selectedRule
+ * }}
+ * @exports detection-health
+ */
+import Component from '@ember/component';
+import { get, computed } from '@ember/object';
+import floatToPercent from 'thirdeye-frontend/utils/float-to-percent';
+import moment from 'moment';
+
+const EXECUTION_TIME_FORMAT = 'MMM DD, YYYY - HH:mm:ss';
+
+export default Component.extend({
+ selectedRule: null, // passed in by parent when relevant
+ classNames: ['te-horizontal-cards__container'],
+ labelMap: {
+ GOOD: '--good',
+ MODERATE: '--average',
+ BAD: '--poor'
+ },
+ statusMap: {
+ GOOD: 'Good',
+ MODERATE: 'Average',
+ BAD: 'Poor',
+ UNKNOWN: 'Unknown'
+ },
+
+ /**
+ * Changes the color of text in Detection Health
+ * @type {String}
+ */
+ tasks: computed(
+ 'health',
+ function() {
+ const health = get(this, 'health');
+ const tasks = [];
+ const taskTypes = ['not run', 'succeeded', 'failed', 'timeout'];
+ const numTasks = [0, 0, 0, 0];
+ if (health && health.detectionTaskStatus && typeof health.detectionTaskStatus === 'object') {
+ if (typeof health.detectionTaskStatus.taskCounts === 'object') {
+ const taskCounts = health.detectionTaskStatus.taskCounts;
+ const keys = ['WAITING', 'COMPLETED', 'FAILED', 'TIMEOUT'];
+ for (let i = 0; i < keys.length; i++) {
+ numTasks[i] = taskCounts[keys[i]];
+ }
+ }
+ }
+ const taskLabels = [null, '--good', '--poor', '--average'];
+ for (let i = 0; i < taskTypes.length; i++) {
+ const task = {
+ title: taskTypes[i],
+ number: numTasks[i],
+ label: taskLabels[i]
+ };
+ tasks.push(task);
+ }
+ return tasks;
+ }
+ ),
+
+ /**
+ * Maps backend values for anomaly coverage status to UI values
+ * @type {String}
+ */
+ anomalyCoverage: computed(
+ 'health',
+ function() {
+ const {
+ health,
+ statusMap,
+ labelMap
+ } = this.getProperties('health', 'statusMap', 'labelMap');
+ const info = {};
+ if (health && health.anomalyCoverageStatus && typeof health.anomalyCoverageStatus === 'object') {
+ info.ratio = floatToPercent(health.anomalyCoverageStatus.anomalyCoverageRatio);
+ info.status = statusMap[health.anomalyCoverageStatus.healthStatus];
+ info.label = labelMap[health.anomalyCoverageStatus.healthStatus];
+ }
+ return info;
+ }
+ ),
+
+ /**
+ * Maps backend values for detection status to UI values
+ * @type {String}
+ */
+ detection: computed(
+ 'health',
+ function() {
+ const {
+ health,
+ statusMap,
+ labelMap
+ } = this.getProperties('health', 'statusMap', 'labelMap');
+ const info = {};
+ if (health && health.detectionTaskStatus && typeof health.detectionTaskStatus === 'object') {
+ info.status = statusMap[health.detectionTaskStatus.healthStatus];
+ info.label = labelMap[health.detectionTaskStatus.healthStatus];
+ }
+ return info;
+ }
+ ),
+
+ /**
+ * Formats execution time for UI display
+ * @type {String}
+ */
+ executionTime: computed(
+ 'health',
+ function() {
+ const health = get(this, 'health');
+ return `${moment(health.executionTime).format(EXECUTION_TIME_FORMAT)}`;
+ }
+ ),
+
+ /**
+ * Maps backend values for overall status to UI values
+ * @type {String}
+ */
+ overall: computed(
+ 'health',
+ function() {
+ const {
+ health,
+ statusMap,
+ labelMap
+ } = this.getProperties('health', 'statusMap', 'labelMap');
+ const info = {};
+ if (health && typeof health === 'object') {
+ info.status = statusMap[health.overallHealth];
+ info.label = labelMap[health.overallHealth];
+ }
+ return info;
+ }
+ ),
+
+ /**
+ * generates regression details for rule selected or first rule if no rule selected
+ * @type {Object}
+ */
+ regressionInfo: computed(
+ 'health',
+ 'selectedRule',
+ function() {
+ const {
+ health,
+ labelMap,
+ selectedRule,
+ statusMap
+ } = this.getProperties('health', 'labelMap', 'selectedRule', 'statusMap');
+ const info = {};
+ info.mape = floatToPercent(NaN); // set default to Nan
+ let rule = selectedRule ? selectedRule.detectorName : null;
+ // 3 possibilities: selectedRule, no selectedRule and rules available, no rules available
+ if (health && health.regressionStatus && typeof health.regressionStatus === 'object') {
+ const regressionStatus = health.regressionStatus;
+ info.status = statusMap[regressionStatus.healthStatus]; // default status will be overall regression status
+ info.label = labelMap[regressionStatus.healthStatus];
+ if (typeof regressionStatus.detectorMapes === 'object' && typeof regressionStatus.detectorHealthStatus === 'object') {
+ // There is a selectedRule
+ if (rule) {
+ info.mape = floatToPercent(regressionStatus.detectorMapes[rule]);
+ info.status = statusMap[regressionStatus.detectorHealthStatus[rule]];
+ info.label = labelMap[regressionStatus.detectorHealthStatus[rule]];
+ info.rule = selectedRule.name;
+ } else { // There is no selectedRule
+ let mapes = [];
+ let ruleHealth = [];
+ if (typeof regressionStatus.detectorMapes === 'object') {
+ mapes = Object.keys(regressionStatus.detectorMapes);
+ }
+ if (typeof regressionStatus.detectorHealthStatus === 'object') {
+ ruleHealth = Object.keys(regressionStatus.detectorHealthStatus);
+ }
+ // There are rules available
+ if (mapes.length > 0 && ruleHealth.length > 0) {
+ info.mape = floatToPercent(regressionStatus.detectorMapes[mapes[0]]);
+ info.status = statusMap[regressionStatus.detectorHealthStatus[mapes[0]]];
+ info.label = labelMap[regressionStatus.detectorHealthStatus[mapes[0]]];
+ info.rule = mapes[0].split(':')[0];
+ }
+ }
+ }
+ }
+ return info;
+ }
+ )
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/detection-health/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/template.hbs
new file mode 100644
index 0000000..d221bb0
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/template.hbs
@@ -0,0 +1,84 @@
+<ul class="te-horizontal-cards__single-card">
+ <li class="te-horizontal-cards__card-title">
+ <label for="select-dimension" class="control-label te-label--no-bottom">
+ 30-day status
+ <span>
+ <i class="glyphicon glyphicon-question-sign"></i>
+ {{#tooltip-on-element class="te-tooltip"}}
+ The overall health status of your detection configuration is based on
+ the most recent 30 days.
+ {{/tooltip-on-element}}
+ </span>
+ </label>
+ </li>
+ <li class="te-horizontal-cards__card-number{{overall.label}}">
+ {{overall.status}}
+ </li>
+ <a role="button" tabindex="0" class=".te-button--link">
+ view details
+ {{#bs-popover placement="bottom" title="Detection health"}}
+ <p>Overall health of the metrics that monitor detection performance.</p>
+
+ <div class="te-horizontal-metrics__container">
+ <ul class="te-horizontal-metrics__metric">
+ <li class="te-horizontal-metrics__number">{{executionTime}}</li>
+ <li class="te-horizontal-metrics__title">Execution Time</li>
+ </ul>
+ </div>
+
+ <label class="control-label te-label">
+ Detection task status
+ </label>
+
+ <div class="te-horizontal-metrics__container">
+ <ul class="te-horizontal-metrics__metric">
+ <li class="te-horizontal-metrics__number{{detection.label}}">{{detection.status}}</li>
+ <li class="te-horizontal-metrics__title">Status</li>
+ </ul>
+
+ {{#each tasks as |task|}}
+ <ul class="te-horizontal-metrics__metric">
+ <li class="te-horizontal-metrics__number{{task.label}}">{{task.number}}</li>
+ <li class="te-horizontal-metrics__title">{{task.title}}</li>
+ </ul>
+ {{/each}}
+ </div>
+
+ <label class="control-label te-label">
+ Anomaly coverage ratio
+ </label>
+
+ <div class="te-horizontal-metrics__container">
+ <ul class="te-horizontal-metrics__metric">
+ <li class="te-horizontal-metrics__number{{anomalyCoverage.label}}">{{anomalyCoverage.status}}</li>
+ <li class="te-horizontal-metrics__title">Status</li>
+ </ul>
+
+ <ul class="te-horizontal-metrics__metric">
+ <li class="te-horizontal-metrics__number">{{anomalyCoverage.ratio}}%</li>
+ <li class="te-horizontal-metrics__title">Last 30 days</li>
+ </ul>
+ </div>
+
+ <p>Adjust alert sensitivity using alert preview.</p>
+
+ <label class="control-label te-label">
+ Regression error{{#if regressionInfo.rule}} for {{regressionInfo.rule}}{{/if}}
+ </label>
+
+ <div class="te-horizontal-metrics__container">
+ <ul class="te-horizontal-metrics__metric">
+ <li class="te-horizontal-metrics__number{{regressionInfo.label}}">{{regressionInfo.status}}</li>
+ <li class="te-horizontal-metrics__title">Status</li>
+ </ul>
+
+ <ul class="te-horizontal-metrics__metric">
+ <li class="te-horizontal-metrics__number">{{regressionInfo.mape}}%</li>
+ <li class="te-horizontal-metrics__title">Percentage of anomalies covered</li>
+ </ul>
+ </div>
+
+ <p>Tune the alert using preview.</p>
+ {{/bs-popover}}
+ </a>
+</ul>
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js
index 0d0ffed..c6f213d 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js
@@ -11,10 +11,18 @@
* @exports stats-cards
*/
import Component from '@ember/component';
-import { set, get } from '@ember/object';
+import { set, get, computed } from '@ember/object';
export default Component.extend({
classNames: ['te-horizontal-cards__container'],
+ statsTransformed: [],
+
+ oneCardOnly: computed(
+ 'statsTransformed',
+ function() {
+ return (this.get('statsTransformed').length < 2);
+ }
+ ),
/**
* Transform the stats array passed to the component
@@ -31,22 +39,22 @@ export default Component.extend({
* @param {Array.Array<String>} - entries of each stats card (i.e. [['entry1', ...], ['entry2', ...], ['entry3], ...])
* @return {Object[]} - array of objects, each of which represents a stats card
* @example
- * [{
- * title: 'title',
- * description: 'description',
- * value: 7,
- * unit: 'digit'
- * }, {
- * title: 'title',
- * description: 'description',
- * value: 87.1,
- * unit: 'percent'
- * }, {
- * title: 'title',
- * description: 'description',
- * value: 87.1,
- * unit: 'percent'
- * }];
+ * [[
+ * 'title',
+ * 'description',
+ * 7,
+ * 'digit'
+ * ], [
+ * 'title',
+ * 'description',
+ * 87.1,
+ * 'percent'
+ * ], [
+ * 'title',
+ * 'description',
+ * 87.1,
+ * 'percent'
+ * ]];
*/
statsBuilder(statsArray) {
const props = ['title', 'description', 'value', 'unit'];
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs
index f1d0de4..da781ff 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs
@@ -1,18 +1,43 @@
-{{#each statsTransformed as |card|}}
- <ul class="te-horizontal-cards__card">
- <li class="te-horizontal-cards__card-title">
- <label for="select-dimension" class="control-label te-label">
- {{card.title}}
- <span>
- <i class="glyphicon glyphicon-question-sign"></i>
- {{#tooltip-on-element class="te-tooltip"}}
- {{card.description}}
- {{/tooltip-on-element}}
- </span>
- </label>
- </li>
- <li class="te-horizontal-cards__card-number">
- {{card.value}}{{#if (eq card.unit 'percent')}}%{{/if}}
- </li>
- </ul>
-{{/each}}
+{{#if oneCardOnly}}
+
+ {{#each statsTransformed as |card|}}
+ <ul class="te-horizontal-cards__single-card">
+ <li class="te-horizontal-cards__card-title">
+ <label for="select-dimension" class="control-label te-label">
+ {{card.title}}
+ <span>
+ <i class="glyphicon glyphicon-question-sign"></i>
+ {{#tooltip-on-element class="te-tooltip"}}
+ {{card.description}}
+ {{/tooltip-on-element}}
+ </span>
+ </label>
+ </li>
+ <li class="te-horizontal-cards__card-number">
+ {{card.value}}{{#if (eq card.unit 'percent')}}%{{/if}}
+ </li>
+ </ul>
+ {{/each}}
+
+{{else}}
+
+ {{#each statsTransformed as |card|}}
+ <ul class="te-horizontal-cards__card">
+ <li class="te-horizontal-cards__card-title">
+ <label for="select-dimension" class="control-label te-label">
+ {{card.title}}
+ <span>
+ <i class="glyphicon glyphicon-question-sign"></i>
+ {{#tooltip-on-element class="te-tooltip"}}
+ {{card.description}}
+ {{/tooltip-on-element}}
+ </span>
+ </label>
+ </li>
+ <li class="te-horizontal-cards__card-number">
+ {{card.value}}{{#if (eq card.unit 'percent')}}%{{/if}}
+ </li>
+ </ul>
+ {{/each}}
+
+{{/if}}
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
index 26487c2..174f690 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
@@ -61,7 +61,7 @@
{{#bs-accordion onChange=(action "changeAccordion") as |acc|}}
{{#acc.item value=preview as |aitem|}}
{{#aitem.title}}
- <section class="dashboard-container__title thirdeye-link-secondary">Preview alert [Beta] {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
+ <section class="dashboard-container__title thirdeye-link-secondary">Preview alert {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
<span class="pull-right"><i class="glyphicon glyphicon-menu-{{if toggleCollapsed "down" "up"}}"></i></span>
</section>
{{/aitem.title}}
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
index 89f00f5..9dc770e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
@@ -11,13 +11,15 @@ import { toastOptions } from 'thirdeye-frontend/utils/constants';
import { formatYamlFilter } from 'thirdeye-frontend/utils/utils';
import yamljs from 'yamljs';
import moment from 'moment';
+import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
-export default Route.extend({
+export default Route.extend(AuthenticatedRouteMixin, {
notifications: service('toast'),
+ analysisRange: [moment().add(1, 'day').subtract(30, 'day').startOf('day').valueOf(), moment().add(1, 'day').startOf('day').valueOf()],
async model(params) {
const alertId = params.alert_id;
- // makes sense to rename this getProps since we are using the get method
+ const analysisRange = get(this, 'analysisRange');
const getProps = {
method: 'get',
headers: { 'content-type': 'application/json' }
@@ -73,6 +75,23 @@ export default Route.extend({
notifications.error('Retrieving alert yaml failed.', error, toastOptions);
}
+ //detection health fetch
+ const executionTime = moment().valueOf(); // grab execution time
+ const healthUrl = `/detection/health/${alertId}?start=${analysisRange[0]}&end=${analysisRange[1]}`;
+ try {
+ const health_result = await fetch(healthUrl, getProps);
+ const health_status = get(health_result, 'status');
+ const health_json = await health_result.json();
+ if (health_status !== 200) {
+ notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+ } else {
+ health_json.executionTime = executionTime; // attach execution time for display
+ set(this, 'detectionHealth', health_json);
+ }
+ } catch (error) {
+ notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+ }
+
//subscription group fetch
const subUrl = `/detection/subscription-groups/${alertId}`;//dropdown of subscription groups
try {
@@ -107,10 +126,24 @@ export default Route.extend({
alertId,
alertData: get(this, 'detectionInfo'),
detectionYaml: get(this, 'rawDetectionYaml'),
+ detectionHealth: get(this, 'detectionHealth'),
subscribedGroups,
metricUrn: get(this, 'metricUrn'),
metricUrnList: get(this, 'metricUrnList') ? get(this, 'metricUrnList') : [],
granularity
});
+ },
+
+ actions: {
+ /**
+ * save session url for transition on login
+ * @method willTransition
+ */
+ willTransition(transition) {
+ //saving session url - TODO: add a util or service - lohuynh
+ if (transition.intent.name && transition.intent.name !== 'logout') {
+ this.set('session.store.fromUrl', {lastIntentTransition: transition});
+ }
+ }
}
});
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs b/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
index fa2e091..5425e24 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
@@ -34,6 +34,7 @@
metricUrnList=model.metricUrnList
granularity=model.granularity
dimensionExploration=model.alertData.dimensionExploration
+ detectionHealth=model.detectionHealth
}}
{{/if}}
</div>
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
index dd99e62..b990418 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
@@ -11,16 +11,19 @@ import yamljs from 'yamljs';
import moment from 'moment';
import { yamlAlertSettings, toastOptions } from 'thirdeye-frontend/utils/constants';
import { formatYamlFilter } from 'thirdeye-frontend/utils/utils';
+import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
const CREATE_GROUP_TEXT = 'Create a new subscription group';
-export default Route.extend({
+export default Route.extend(AuthenticatedRouteMixin, {
anomaliesApiService: service('services/api/anomalies'),
notifications: service('toast'),
+ analysisRange: [moment().add(1, 'day').subtract(30, 'day').startOf('day').valueOf(), moment().add(1, 'day').startOf('day').valueOf()],
async model(params) {
const alertId = params.alert_id;
- const postProps = {
+ const analysisRange = get(this, 'analysisRange');
+ const getProps = {
method: 'get',
headers: { 'content-type': 'application/json' }
};
@@ -29,7 +32,7 @@ export default Route.extend({
//detection alert fetch
const detectionUrl = `/detection/${alertId}`;
try {
- const detection_result = await fetch(detectionUrl, postProps);
+ const detection_result = await fetch(detectionUrl, getProps);
const detection_status = get(detection_result, 'status');
const detection_json = await detection_result.json();
if (detection_status !== 200) {
@@ -59,10 +62,27 @@ export default Route.extend({
notifications.error('Retrieving alert yaml failed.', error, toastOptions);
}
+ //detection health fetch
+ const executionTime = moment().valueOf(); // grab execution time
+ const healthUrl = `/detection/health/${alertId}?start=${analysisRange[0]}&end=${analysisRange[1]}`;
+ try {
+ const health_result = await fetch(healthUrl, getProps);
+ const health_status = get(health_result, 'status');
+ const health_json = await health_result.json();
+ if (health_status !== 200) {
+ notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+ } else {
+ health_json.executionTime = executionTime; // attach execution time for display
+ set(this, 'detectionHealth', health_json);
+ }
+ } catch (error) {
+ notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+ }
+
//subscription group fetch
const subUrl = `/detection/subscription-groups/${alertId}`;//dropdown of subscription groups
try {
- const settings_result = await fetch(subUrl, postProps);
+ const settings_result = await fetch(subUrl, getProps);
const settings_status = get(settings_result, 'status');
const settings_json = await settings_result.json();
if (settings_status !== 200) {
@@ -71,7 +91,7 @@ export default Route.extend({
set(this, 'subscriptionGroups', settings_json);
}
} catch (error) {
- notifications.error('Retrieving subscription groups failed.', error, toastOptions);
+ notifications.error('Retrieving subscription groups failed.', 'Error', toastOptions);
}
let subscribedGroups = "";
@@ -95,6 +115,7 @@ export default Route.extend({
alertId,
alertData: get(this, 'detectionInfo'),
detectionYaml: get (this, 'rawDetectionYaml'),
+ detectionHealth: get (this, 'detectionHealth'),
subscriptionGroups: get(this, 'subscriptionGroups'),
subscribedGroups,
subscriptionGroupNames // all subscription groups as Ember data
@@ -140,7 +161,18 @@ export default Route.extend({
model,
createGroup
});
- }
-
+ },
+ actions: {
+ /**
+ * save session url for transition on login
+ * @method willTransition
+ */
+ willTransition(transition) {
+ //saving session url - TODO: add a util or service - lohuynh
+ if (transition.intent.name && transition.intent.name !== 'logout') {
+ this.set('session.store.fromUrl', {lastIntentTransition: transition});
+ }
+ }
+ }
});
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
index 224269a..90aca1c 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
@@ -38,7 +38,7 @@
{{#bs-accordion onChange=(action "changeAccordion") as |acc|}}
{{#acc.item value=preview as |aitem|}}
{{#aitem.title}}
- <section class="dashboard-container__title thirdeye-link-secondary">Preview alert [Beta] {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
+ <section class="dashboard-container__title thirdeye-link-secondary">Preview alert {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
<span class="pull-right"><i class="glyphicon glyphicon-menu-{{if toggleCollapsed "down" "up"}}"></i></span>
</section>
{{/aitem.title}}
@@ -48,6 +48,7 @@
alertId=model.alertId
alertYaml=detectionYaml
dataIsCurrent=alertDataIsCurrent
+ detectionHealth=model.detectionHealth
}}
{{yield}}
{{/alert-details}}
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 98b8072..7a68854 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
@@ -439,7 +439,7 @@
{{#bs-accordion onChange=(action "changeAccordion") as |acc|}}
{{#acc.item value=preview as |aitem|}}
{{#aitem.title}}
- <section class="dashboard-container__title thirdeye-link-secondary">Preview alert [Beta] {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
+ <section class="dashboard-container__title thirdeye-link-secondary">Preview alert {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
<span class="pull-right"><i class="glyphicon glyphicon-menu-{{if toggleCollapsed "down" "up"}}"></i></span>
</section>
{{/aitem.title}}
diff --git a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
index c39370c..8ff6579 100644
--- a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
@@ -6,6 +6,12 @@ body {
background-color: $te-main-background;
margin: 0;
}
+.popover-title {
+ font-size: 18px;
+ font-weight: normal;
+ color: app-shade(black, 9);
+ background-color: white;
+}
.smaller {
font-size: 19px;
@@ -191,6 +197,37 @@ body {
margin: 10px 0 20px 0;
background: white;
padding: 24px;
+
+ &__alert-performance {
+ display: inline-block;
+ width: calc(80% - 20px);
+ margin-right: 10px;
+ font-family: "Source Sans Pro", sans-serif;
+ background: white;
+ padding: 24px;
+ padding-bottom: 100%;
+ margin-bottom: -100%;
+ border-right: 2px solid app-shade(black, 1);
+ }
+
+ &__detection-health {
+ width: 20%;
+ display: inline-block;
+ float: right;
+ margin-left: 10px;
+ font-family: "Source Sans Pro", sans-serif;
+ background: white;
+ padding: 24px;
+ padding-bottom: 100%;
+ margin-bottom: -100%;
+ }
+
+ &__performance-health-wrapper {
+ overflow: hidden;
+ border-radius: 5px;
+ border: 2px solid app-shade(black, 1);
+ background: white;
+ }
}
.te-hide {
@@ -206,6 +243,12 @@ body {
margin-bottom: 0;
}
+ &--no-bottom {
+ font-weight: normal;
+ color: app-shade(black, 9);
+ margin-bottom: 0;
+ }
+
&--small {
margin-bottom: 2px;
}
@@ -335,6 +378,46 @@ body {
}
}
+.popover {
+ max-width: none;
+}
+
+.te-horizontal-metrics {
+ &__container {
+ display: flex;
+ clear: both;
+ }
+
+ &__metric {
+ list-style: none;
+ display: flex;
+ align-items: stretch;
+ flex-direction: column;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ &__number {
+ color: black;
+ &--good {
+ color: #4B9D27;
+ font-weight: normal;
+ }
+
+ &--average {
+ color: #F5A623;
+ font-weight: normal;
+ }
+
+ &--poor {
+ color: #FF4B51;
+ font-weight: normal;
+ }
+ }
+}
+
.te-horizontal-cards {
&--hidden {
visibility: hidden;
@@ -364,6 +447,23 @@ body {
}
}
+ &__single-card {
+ list-style: none;
+ background-color: white;
+ margin-right: 12px;
+ border-radius: 5px;
+ border: 2px solid app-shade(black, 1);
+ padding: 13px 15px 6px 17px;
+ display: flex;
+ align-items: stretch;
+ flex-direction: column;
+ justify-content: space-between;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
&__card-title {
font-weight: 600;
font-size: 16px;
@@ -427,6 +527,24 @@ body {
&__card-number {
font-size: 28px;
font-weight: normal;
+
+ &--good {
+ color: #4B9D27;
+ font-size: 28px;
+ font-weight: normal;
+ }
+
+ &--average {
+ color: #F5A623;
+ font-size: 28px;
+ font-weight: normal;
+ }
+
+ &--poor {
+ color: #FF4B51;
+ font-size: 28px;
+ font-weight: normal;
+ }
}
&__card-units {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org