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 <hjack...@linkedin.com>
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: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to