This is an automated email from the ASF dual-hosted git repository.
xhsun 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 61b2a07 [TE] frontend - harleyjj/aiavailability - implements AI
availability table UI (#4510)
61b2a07 is described below
commit 61b2a0715cd6d3be26691e78611310072885c181
Author: Harley Jackson <[email protected]>
AuthorDate: Fri Aug 9 11:23:13 2019 -0700
[TE] frontend - harleyjj/aiavailability - implements AI availability table
UI (#4510)
---
.../app/pods/aiavailability/controller.js | 102 +++++++++++
.../app/pods/aiavailability/route.js | 186 +++++++++++++++++++++
.../app/pods/aiavailability/template.hbs | 60 +++++++
thirdeye/thirdeye-frontend/app/router.js | 2 +
.../styles/components/range-pill-selectors.scss | 4 +
thirdeye/thirdeye-frontend/app/utils/anomaly.js | 16 +-
.../thirdeye-frontend/app/utils/api/anomaly.js | 13 +-
thirdeye/thirdeye-frontend/app/utils/utils.js | 5 +-
8 files changed, 382 insertions(+), 6 deletions(-)
diff --git a/thirdeye/thirdeye-frontend/app/pods/aiavailability/controller.js
b/thirdeye/thirdeye-frontend/app/pods/aiavailability/controller.js
new file mode 100644
index 0000000..e5a8841
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/aiavailability/controller.js
@@ -0,0 +1,102 @@
+import _ from 'lodash';
+import moment from 'moment';
+import { computed, set } from '@ember/object';
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+import { toIso } from 'thirdeye-frontend/utils/utils';
+
+const DATE_FORMAT = 'MMM DD, YYYY';
+
+export default Controller.extend({
+
+ /**
+ * Make duration service accessible
+ */
+ durationCache: service('services/duration'),
+
+ formattedTime: computed(
+ 'startDate',
+ 'endDate',
+ function() {
+ const {
+ startDate,
+ endDate
+ } = this.getProperties('startDate', 'endDate');
+ const times = {};
+ times.start = `${moment(toIso(startDate)).format(DATE_FORMAT)}`;
+ times.end = `${moment(toIso(endDate)).format(DATE_FORMAT)}`;
+ return times;
+ }
+ ),
+
+ /**
+ * List of anomalies to render as rows
+ * @type {Array}
+ */
+ rows: computed(
+ 'tableData',
+ 'tableHeaders',
+ 'selectedSortMode',
+ function() {
+ const {
+ tableData,
+ selectedSortMode
+ } = this.getProperties('tableData', 'selectedSortMode');
+ let allRows = tableData;
+ if (selectedSortMode) {
+ let [ sortHeader, sortDir ] = selectedSortMode.split(':');
+
+ if (sortDir === 'up') {
+ allRows = _.sortBy(allRows, sortHeader);
+ } else {
+ allRows = _.sortBy(allRows, sortHeader).reverse();
+ }
+ }
+
+ return allRows;
+ }
+ ),
+
+ actions: {
+
+ /**
+ * Sets the new custom date range for anomaly coverage
+ * @method onRangeSelection
+ * @param {Object} rangeOption - the user-selected time range to load
+ */
+ onRangeSelection(rangeOption) {
+ const {
+ start,
+ end,
+ value: duration
+ } = rangeOption;
+ const durationObj = {
+ duration,
+ startDate: moment(start).valueOf(),
+ endDate: moment(end).valueOf()
+ };
+ // Cache the new time range and update page with it
+ this.get('durationCache').setDuration(durationObj);
+ this.transitionToRoute({ queryParams: durationObj });
+ },
+
+ /**
+ * Handle sorting for each sortable table column
+ * @param {String} sortKey - stringified start date
+ */
+ toggleSortDirection(sortKey) {
+ const tableHeaders = this.get('tableHeaders');
+ const propName = 'sortColumn' + sortKey.capitalize() + 'Up' || '';
+ let direction = '';
+ this.toggleProperty(propName);
+ direction = this.get(propName) ? 'up' : 'down';
+ for (let i = 0; i < tableHeaders.length; i++) {
+ if (tableHeaders[i].text === sortKey) {
+ set(tableHeaders[i], 'sort', direction);
+ break;
+ }
+ }
+ this.set('selectedSortMode', `${sortKey}:${direction}`);
+ }
+ }
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/aiavailability/route.js
b/thirdeye/thirdeye-frontend/app/pods/aiavailability/route.js
new file mode 100644
index 0000000..cefbbba
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/aiavailability/route.js
@@ -0,0 +1,186 @@
+/**
+ * Handles the 'aiavailability' route.
+ * @module aiavailability/route
+ * @exports aiavailability model
+ */
+
+import moment from 'moment';
+import Route from '@ember/routing/route';
+import {
+ hash
+} from 'rsvp';
+import {
+ buildDateEod
+} from 'thirdeye-frontend/utils/utils';
+import { setUpTimeRangeOptions } from
'thirdeye-frontend/utils/manage-alert-utils';
+import { getAiAvailability } from 'thirdeye-frontend/utils/anomaly';
+import { inject as service } from '@ember/service';
+
+/**
+ * Time range-related constants
+ */
+const displayDateFormat = 'YYYY-MM-DD HH:mm';
+const defaultDurationObj = {
+ duration: '1w',
+ startDate: buildDateEod(1, 'week').valueOf(),
+ endDate: moment().startOf('day').valueOf()
+};
+
+/**
+ * Setup for query param behavior
+ */
+const queryParamsConfig = {
+ refreshModel: true,
+ replace: true
+};
+
+const detectionConfigId = '116702779'; // detectionConfigId for getting table
data
+
+export default Route.extend({
+ session: service(),
+ queryParams: {
+ duration: queryParamsConfig,
+ startDate: queryParamsConfig,
+ endDate: queryParamsConfig
+ },
+
+ beforeModel(transition) {
+ const { duration, startDate } = transition.queryParams;
+ // Default to 1 week of anomalies to show if no dates present in query
params
+ if (!duration || !startDate) {
+ this.transitionTo({ queryParams: defaultDurationObj });
+ }
+ },
+
+ /**
+ * Model hook for the create alert route.
+ * @method model
+ * @return {Object}
+ */
+ model(transition) {
+ // Get duration data
+ const {
+ duration,
+ startDate,
+ endDate
+ } = transition;
+
+ return hash({
+ // Fetch table data
+ tableData: getAiAvailability(detectionConfigId, startDate, endDate),
+ duration,
+ startDate,
+ endDate
+ });
+ },
+
+ afterModel(model) {
+ this._super(model);
+
+ let tableData = (Array.isArray(model.tableData) && model.tableData.length
> 0) ? model.tableData : [{ 'No Data Returned' : 'N/A'}];
+ let tableHeaders = [...Object.keys(tableData[0]), 'link to RCA'];
+ tableHeaders = tableHeaders.map(header => {
+ return {
+ text: header,
+ sort: 'down'
+ };
+ });
+ // augment response for direct mapping of keys to headers and fields to
table cell
+ const newTableData = [];
+ tableData.forEach(flow => {
+ const anomaliesArray = Object.keys(flow.comment);
+ if (anomaliesArray.length > 0) {
+ anomaliesArray.forEach(anomalyId => {
+ const row = flow;
+ row['link to RCA'] = anomalyId;
+ row.comment = row.comment.anomalyId;
+ // build array of strings to put on DOM
+ row.columns = [];
+ tableHeaders.forEach(header => {
+ const cellValue = row[header.text];
+ if (header.text === 'link to RCA') {
+ const domString = `<a
href="https://thirdeye.corp.linkedin.com/app/#/rootcause?anomalyId=${cellValue}">Comment
of the anomaly</a>`;
+ row.columns.push(domString);
+ } else if (header.text === 'url') {
+ const domString = `<a href="${cellValue}">Flow Link</a>`;
+ row.columns.push(domString);
+ } else {
+ row.columns.push(cellValue);
+ }
+ });
+ newTableData.push(row);
+ });
+ } else {
+ const row = flow;
+ row['link to RCA'] = null;
+ row.comment = null;
+ // build array of strings to put on DOM
+ row.columns = [];
+ tableHeaders.forEach(header => {
+ const cellValue = row[header.text];
+ if (header.text === 'link to RCA') {
+ const domString = 'N/A';
+ row.columns.push(domString);
+ } else if (header.text === 'url') {
+ const domString = `<a href="${cellValue}">Flow Link</a>`;
+ row.columns.push(domString);
+ } else {
+ row.columns.push(cellValue);
+ }
+ });
+ newTableData.push(row);
+ }
+ });
+ Object.assign(model, { tableData: newTableData, tableHeaders });
+ },
+
+ setupController(controller, model) {
+ this._super(controller, model);
+
+ const {
+ startDate,
+ endDate,
+ duration,
+ tableData,
+ tableHeaders
+ } = model;
+
+
+ // Display loading banner
+ controller.setProperties({
+ timeRangeOptions: setUpTimeRangeOptions(['1w', '1m'], duration),
+ activeRangeStart: moment(Number(startDate)).format(displayDateFormat),
+ activeRangeEnd: moment(Number(endDate)).format(displayDateFormat),
+ uiDateFormat: "MMM D, YYYY hh:mm a",
+ timePickerIncrement: 5,
+ // isDataLoading: true,
+ tableData,
+ tableHeaders
+ });
+ },
+
+ 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});
+ }
+ },
+ error() {
+ return true;
+ },
+
+ /**
+ * Refresh route's model.
+ * @method refreshModel
+ * @return {undefined}
+ */
+ refreshModel() {
+ this.refresh();
+ }
+ }
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/aiavailability/template.hbs
b/thirdeye/thirdeye-frontend/app/pods/aiavailability/template.hbs
new file mode 100644
index 0000000..166b39f
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/aiavailability/template.hbs
@@ -0,0 +1,60 @@
+{{#if isDataLoading}}
+ <div class="te-alert-page-pending">
+ <img src="{{rootURL}}assets/images/te-alert-{{if isDataLoadingError
"error" "pending"}}.png" class="te-alert-page-pending__image {{if
isDataLoadingError "te-alert-page-pending__image--error"}}"
alt="alertData.Setup is Processing">
+ <h2 class="te-alert-page-pending__title">{{if isDataLoadingError "Oops,
something went wrong." "Aggregating anomaly performance data..."}}</h2>
+ {{#if (not isDataLoadingError)}}<div
class="te-alert-page-pending__loader"></div>{{/if}}
+ <p class="te-alert-page-pending__text">
+ {{#if isDataLoadingError}}
+ The AI availability data cannot be retrieved.<br/>{{errMsg}}
+ {{else}}
+ Fetching all AI availability data for all alerts may take up to a
minute. <br/>Hang in there!
+ {{/if}}
+ </p>
+ </div>
+{{else}}
+ <div class="manage-alert-tune">
+
+ <div class="range-pill-selectors__ai">
+ {{!-- Date range selector --}}
+ {{range-pill-selectors
+ title="Select Time Range"
+ uiDateFormat=uiDateFormat
+ activeRangeEnd=activeRangeEnd
+ activeRangeStart=activeRangeStart
+ timeRangeOptions=timeRangeOptions
+ timePickerIncrement=timePickerIncrement
+ selectAction=(action "onRangeSelection")
+ }}
+ </div>
+
+ <div class="te-content-block">
+ <h4 class="te-alert-page__subtitle"><strong>AI Availability
Data</strong> from <strong>{{formattedTime.start}}</strong> to
<strong>{{formattedTime.end}}</strong></h4>
+
+ {{!-- Alert anomaly table --}}
+ <table class="te-anomaly-table te-anomaly-table--performance">
+ <thead>
+ <tr class="te-anomaly-table__row te-anomaly-table__head">
+ {{#each tableHeaders as |header|}}
+ <th class="te-anomaly-table__cell-head">
+ <a class="te-anomaly-table__cell-link" {{action
"toggleSortDirection" header.text}}>
+ {{header.text}}
+ <i class="te-anomaly-table__icon
te-anomaly-table__icon--small glyphicon glyphicon-menu-{{header.sort}}"></i>
+ </a>
+ </th>
+ {{/each}}
+ </tr>
+ </thead>
+
+ <tbody>
+ {{#each rows as |row|}}
+ <tr class="te-anomaly-table__row">
+ {{#each row.columns as |column|}}
+ <td class="te-anomaly-table__cell">{{{column}}}</td>
+ {{/each}}
+ </tr>
+ {{/each}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+{{/if}}
diff --git a/thirdeye/thirdeye-frontend/app/router.js
b/thirdeye/thirdeye-frontend/app/router.js
index b62e2be..b222611 100644
--- a/thirdeye/thirdeye-frontend/app/router.js
+++ b/thirdeye/thirdeye-frontend/app/router.js
@@ -18,6 +18,8 @@ Router.map(function() {
this.route('share-dashboard');
});
+ this.route('aiavailability');
+
this.route('anomalies');
this.route('manage', function() {
diff --git
a/thirdeye/thirdeye-frontend/app/styles/components/range-pill-selectors.scss
b/thirdeye/thirdeye-frontend/app/styles/components/range-pill-selectors.scss
index 93235a5..30453b8 100644
--- a/thirdeye/thirdeye-frontend/app/styles/components/range-pill-selectors.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/components/range-pill-selectors.scss
@@ -62,4 +62,8 @@
color: app-shade(black, 7);
display: inline-block;
}
+
+ &__ai {
+ margin-left: 25px;
+ }
}
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomaly.js
b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
index 5d1156b..5b50891 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
@@ -14,7 +14,8 @@ import {
getAnomaliesByAlertIdUrl,
getAnomalyFiltersByTimeRangeUrl,
getAnomalyFiltersByAnomalyIdUrl,
- getBoundsUrl
+ getBoundsUrl,
+ getAiAvailabilityUrl
} from 'thirdeye-frontend/utils/api/anomaly';
/**
@@ -125,6 +126,19 @@ export function getBounds(detectionId, startTime, endTime)
{
}
/**
+ * Get table data for AI Availability
+ * @method getAiAvailability
+ * @param {Number} detectionConfigId - the config id for the table data's alert
+ * @param {Number} startDate - start time of analysis range
+ * @param {Number} endDate - end time of analysis range
+ * @return {Ember.RSVP.Promise}
+ */
+export function getAiAvailability(detectionConfigId, startDate, endDate) {
+ const url = getAiAvailabilityUrl(startDate, endDate);
+ return fetch(url).then(checkStatus);
+}
+
+/**
* Get anomalies for a given detection id over a specified time range
* @method getAnomaliesByAlertId
* @param {Number} alertId - the alert id aka detection config id
diff --git a/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
b/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
index 9dfd386..fc11e39 100644
--- a/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
@@ -65,13 +65,24 @@ export function getAnomalyFiltersByAnomalyIdUrl(startTime,
endTime, anomalyIds)
return
`/anomalies/search/anomalyIds/${startTime}/${endTime}/1?anomalyIds=${encodeURIComponent(anomalyIds)}`;
}
+/**
+ * Returns the url for getting ai availability table
+ * @param {Number} startDate - beginning of time range of interest
+ * @param {Number} endDate - end of time range of interest
+ * @example getAiAvailabilityUrl(1, 1508472700000, 1508472800000) // yields =>
/thirdeye/table?detectionConfigId=1&start=1508472700000&end=1508472800000
+ */
+export function getAiAvailabilityUrl(startDate, endDate) {
+ return
`/thirdeye/table?metricIds=128856623,128856625&start=${startDate}&end=${endDate}&dimensionKeys=grid,flow,project,owner,managers,sla,url`;
+}
+
export const anomalyApiUrls = {
getAnomalyDataUrl,
getAnomaliesForYamlPreviewUrl,
getAnomaliesByAlertIdUrl,
getAnomalyFiltersByTimeRangeUrl,
getAnomalyFiltersByAnomalyIdUrl,
- getBoundsUrl
+ getBoundsUrl,
+ getAiAvailabilityUrl
};
export default {
diff --git a/thirdeye/thirdeye-frontend/app/utils/utils.js
b/thirdeye/thirdeye-frontend/app/utils/utils.js
index 6515ebe..f53aa08 100644
--- a/thirdeye/thirdeye-frontend/app/utils/utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/utils.js
@@ -103,10 +103,7 @@ export function humanizeScore(f) {
* Helps with shorthand for repetitive date generation
*/
export function buildDateEod(unit, type) {
- if (unit === 1) {
- return makeTime().startOf('day');
- }
- return makeTime().subtract(unit-1, type).startOf('day');
+ return makeTime().subtract(unit, type).startOf('day');
}
/**
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]