';
- $('#aggreagteMetricSpecListTable tbody').append(row);
- addClassesToSpecTableRows();
+ $('#aggregateMetricSpecListTable tbody').append(row);
+ addClassesToTableRows('aggregateMetricSpecListTable');
}
/**
@@ -172,94 +177,7 @@ $(document).ready(function () {
$('td:first', row).text(name);
}
- /**
- * Return an object with of the current input values from the spec details panel,
- * namespaced as 'aggregateMetricSpec' for those key-values directly
- * from the AggregateMetricSpecDao and 'specInputs' for those input
- * values contributing to the value of aggregateMetricSpec.spec.
- *
- * @return {object} with namespaces 'aggregateMetricSpec' and 'specInputs'
- */
- function getSpecInputsValues() {
- var specValues = {
- 'aggregateMetricSpec': {
- 'producer_id': $('#producerId').val(),
- 'name': $('#aggregateMetricSpecName').val(),
- 'description': $('#aggregateMetricSpecDescription').val(),
- 'branch': $('#aggregateMetricSpecBranch').val(),
- 'spec': $('#aggregateMetricSpecSpec').val(),
- 'value': $('#aggregateMetricSpecValue').val(),
- 'comparison': $('#aggregateMetricSpecComparison').val()
- },
- 'specInputs': {
- 'metricName': $('#aggregateMetricSpecMetricName').val(),
- 'aggregateMetric': $('#aggregateMetricSpecAggregateMetric').val(),
- 'param': $('#aggregateMetricSpecParam').val()
- }
- };
- return specValues;
- }
-
- /**
- * Utility function to search if a given value exists as an option on the select
- * with the passed in id.
- * @param id the id of the select element
- * @param value the value sought as an option of the select
- */
- function selectValueFound(id, value) {
- var found = false;
- var options = document.getElementById(id).options;
- for (var i = 0; i < options.length; i++) {
- if (value === options[i].value) {
- found = true;
- }
- }
- return found;
- }
-
- /**
- * Populate the input elements with the values from the passed in aggregateMetricSpec,
- * including some validation in case the passed in spec was created out of data that is
- * no longer valid, e.g. a branch name that no longer has scalars tied to the metric_name.
- * @param aggregateMetricSpec object with AggregateMetricSpecDao key value pairs
- */
- function populateSpecInputs(aggregateMetricSpec) {
- $('#aggregateMetricSpecEditId').val(aggregateMetricSpec.aggregate_metric_spec_id);
- $('#aggregateMetricSpecName').val(aggregateMetricSpec.name);
- $('#aggregateMetricSpecDescription').val(aggregateMetricSpec.description);
- $('#aggregateMetricSpecSpec').val(aggregateMetricSpec.spec);
- // Skip value if it is 0 and comparison is empty, this was added as a DB
- // default and wouldn't be allowed by the UI validation logic.
- if (aggregateMetricSpec.value &&
- (aggregateMetricSpec.value != 0 || aggregateMetricSpec.comparison)) {
- $('#aggregateMetricSpecValue').val(aggregateMetricSpec.value);
- }
- if (aggregateMetricSpec.comparison) {
- $('#aggregateMetricSpecComparison').val(aggregateMetricSpec.comparison);
- }
- var specParts = parseMetricSpec(aggregateMetricSpec.spec);
- $('#aggregateMetricSpecParam').val(specParts.param);
- $('#aggregateMetricSpecAggregateMetric').val(specParts.metric);
- var metricNameFound = selectValueFound('aggregateMetricSpecMetricName', specParts.metricName);
- if (!metricNameFound) {
- // Don't set the branch name because the metric name is invalid,
- // and that determines the set of possible branches.
- $('#aggregateMetricSpecValidationError').text("Loaded metric name '"+specParts.metricName+"' is invalid");
- $('img#aggregateMetricSpecSaveLoading').hide();
- } else {
- $('#aggregateMetricSpecMetricName').val(specParts.metricName);
- // Don't need to hide the loading image because updateBranchList will.
- var successCallback = function () {
- var branchNameFound = selectValueFound('aggregateMetricSpecBranch', aggregateMetricSpec.branch);
- if (!branchNameFound) {
- $('#aggregateMetricSpecValidationError').text("Loaded branch '"+aggregateMetricSpec.branch+"' is invalid");
- } else {
- $('#aggregateMetricSpecBranch').val(aggregateMetricSpec.branch);
- }
- };
- updateBranchList(successCallback);
- }
- }
+ // Aggregate Metric Specs main panel handlers
/**
* Handler for Delete action, delete an Aggregate Metric Spec on the server
@@ -267,13 +185,15 @@ $(document).ready(function () {
* to a static parent as the links can be dynamically generated through
* creation of new aggregate metric specs.
*/
- $('#aggreagteMetricSpecListTable').on('click', 'a.removeAggregateMetricSpec', function(){
- var aggregateMetricSpecId = $(event.target).data('aggregate_metric_spec_id');
- var row = $(event.target).closest('tr');
+ $('#aggregateMetricSpecListTable').on('click', 'a.removeAggregateMetricSpec', function(){
+ activateRow($(this));
+
+ var aggregateMetricSpecId = $(this).data('aggregate_metric_spec_id');
+ var row = $(this).closest('tr');
var sCb = function (data) {
row.remove();
$('#aggregateMetricSpecDeleteLoading').hide();
- addClassesToSpecTableRows();
+ addClassesToTableRows('aggregateMetricSpecListTable');
};
$('#aggregateMetricSpecDeleteLoading').show();
aggregatemetricspecRest('DELETE', aggregateMetricSpecId, null, sCb, null, null);
@@ -281,6 +201,7 @@ $(document).ready(function () {
/** Handler for Add action, open the details panel in Create state. */
$('div#addAggregateMetricSpec').click(function () {
+ unhighlightActiveRow();
showDetailsPanel();
});
@@ -290,21 +211,53 @@ $(document).ready(function () {
* to a static parent as the links can be dynamically generated through
* creation of new aggregate metric specs.
*/
- $('#aggreagteMetricSpecListTable').on('click', 'a.editAggregateMetricSpec', function(){
- var aggregateMetricSpecId = $(event.target).data('aggregate_metric_spec_id');
+ $('#aggregateMetricSpecListTable').on('click', 'a.editAggregateMetricSpec', function() {
+ activateRow($(this));
+ var aggregateMetricSpecId = $(this).data('aggregate_metric_spec_id');
showDetailsPanel(true);
- $('img#aggregateMetricSpecSaveLoading').show();
+ $('#aggregateMetricSpecSaveLoading').show();
var successCallback = function (aggregateMetricSpec) {
populateSpecInputs(aggregateMetricSpec);
}
aggregatemetricspecRest('GET', aggregateMetricSpecId, null, successCallback);
});
- /** Handler for Cancel button, hide the details panel. */
- $('input#aggregateMetricSpecCancel').click(function () {
- $('div#aggregateMetricSpecCreateEdit').hide();
+ /** Handler for Alerts button, show the panel to edit the alerted users. */
+ $('#aggregateMetricSpecListTable').on('click', 'a.editAggregateMetricSpecNotificationUsers', function() {
+ activateRow($(this));
+ var amsName = $('td:first', $(this).closest('tr')).text();
+ $('#aggregateMetricUserAlerts').show();
+ var aggregateMetricSpecId = $(this).data('aggregate_metric_spec_id');
+ $('#aggregateMetricUserAlertsSpecName').text(amsName);
+ $('#aggregateMetricSpecAlertValue').val('');
+ $('#aggregateMetricSpecAlertComparison').val('');
+ $('#aggregateMetricUserAlertsSpec').val('');
+ $('#aggregateMetricSpecAlertedUsers').find('tr:gt(0)').remove();
+ $('#aggregateMetricSpecAlertsLoading').show();
+ $('#addAlertUserSearch').val('Start typing a name or email address...');
+ $('#addAlertUserSearchValue').val('init');
+ var successCallback = function (aggregateMetricSpec) {
+ $('#aggregateMetricUserAlertsSpecName').text(aggregateMetricSpec.name);
+ $('#aggregateMetricSpecAlertValue').val(aggregateMetricSpec.value);
+ $('#aggregateMetricSpecAlertComparison').val(aggregateMetricSpec.comparison);
+ $('#aggregateMetricUserAlertsSpec').val(aggregateMetricSpec.spec);
+
+ var jsonMethod = 'midas.tracker.aggregatemetricspecnotifiedusers.list';
+ var args = 'aggregateMetricSpecId=' + aggregateMetricSpecId;
+ callAjaxWebApi(jsonMethod, 'GET', args, function (retVal) {
+ for (var userInd = 0; userInd < retVal.data.length; userInd++) {
+ var user = retVal.data[userInd];
+ addToAlertedUsersTable(user.firstname + ' ' + user.lastname, user.user_id);
+ }
+ addClassesToTableRows('aggregateMetricSpecAlertedUsers');
+ $('#aggregateMetricSpecAlertsLoading').hide();
+ });
+ };
+ aggregatemetricspecRest('GET', aggregateMetricSpecId, null, successCallback);
});
+ //:~ Spec Details Panel
+
/**
* Save an aggregateMetricSpec, either as a new Dao or update an existing one,
* depending on whether aggregateMetricSpecId is passed, will perform validation
@@ -355,7 +308,7 @@ $(document).ready(function () {
}
// Save the AMS on the server.
- $('img#aggregateMetricSpecSaveLoading').show();
+ $('#aggregateMetricSpecSaveLoading').show();
var successCallback = function (aggregateMetricSpec) {
if (aggregateMetricSpecId) {
updateSpecInTable(aggregateMetricSpec);
@@ -363,12 +316,164 @@ $(document).ready(function () {
addToSpecTable(aggregateMetricSpec);
}
$('div#aggregateMetricSpecCreateEdit').hide();
+ unhighlightActiveRow();
}
var method = aggregateMetricSpecId ? 'PUT' : 'POST';
aggregateMetricSpecId = aggregateMetricSpecId ? aggregateMetricSpecId : null;
aggregatemetricspecRest(method, aggregateMetricSpecId, specValues.aggregateMetricSpec, successCallback);
}
+ /**
+ * Displays the loading image, updates the branch list based on
+ * the current metric name, then hides the loading image.
+ * @param successCallback callback on success, not passed any value
+ */
+ function updateBranchList(successCallback) {
+ var producerId = $('#producerId').val();
+ var metricName = $('select#aggregateMetricSpecMetricName').val();
+ $('#aggregateMetricSpecSaveLoading').show();
+
+ var jsonMethod = 'midas.tracker.branchesformetricname.list';
+ var args = 'producerId=' + producerId + '&trendMetricName='+metricName;
+ callAjaxWebApi(jsonMethod, 'GET', args, function (retVal) {
+ $('#aggregateMetricSpecBranch').find('option').remove();
+ var branches = retVal.data;
+ $.each(branches, function (key, value) {
+ $('#aggregateMetricSpecBranch').append('');
+ });
+ $('#aggregateMetricSpecSaveLoading').hide();
+ if (successCallback) { successCallback(); }
+ });
+ }
+
+ /** Clear all spec inputs of any value. */
+ function clearSpecInputs() {
+ $('.amsField').val('');
+ $('#aggregateMetricSpecValidationError').text('');
+ $('#aggregateMetricSpecSpec').val('');
+ $('#aggregateMetricSpecMetricName option:disabled').attr('selected', 'selected');
+ $('#aggregateMetricSpecComparison option:disabled').attr('selected', 'selected');
+ // Remove branches, add the placeholder.
+ $('#aggregateMetricSpecBranch').find('option').remove();
+ $('#aggregateMetricSpecBranch').append('');
+ }
+
+ /** Update display of disabled composite spec input from individual elements. */
+ function updateSpec() {
+ var metricName = $('#aggregateMetricSpecMetricName').val();
+ var metric = $('#aggregateMetricSpecAggregateMetric').val();
+ var param = $('#aggregateMetricSpecParam').val();
+ $('#aggregateMetricSpecSpec').val(metric + "('" + metricName + "', " + param + ")");
+ }
+
+ /**
+ * Parse individual elements from composite spec string.
+ * @param spec string containing the aggregeate metric spec
+ */
+ function parseMetricSpec(spec) {
+ // Expected to be like:
+ // percentile('Optimal distance', 95)
+ var specParts = /(.*)\('(.*)',\s*(.*)\)/.exec(spec);
+ var specParts = {
+ 'metricName': specParts[2],
+ 'metric': specParts[1],
+ 'param': specParts[3]
+ };
+ return specParts;
+ }
+
+ /**
+ * Return an object with the current input values from the spec details panel,
+ * namespaced as 'aggregateMetricSpec' for those key-values directly
+ * from the AggregateMetricSpecDao and 'specInputs' for those input
+ * values contributing to the value of aggregateMetricSpec.spec.
+ *
+ * @return {object} with namespaces 'aggregateMetricSpec' and 'specInputs'
+ */
+ function getSpecInputsValues() {
+ var specValues = {
+ 'aggregateMetricSpec': {
+ 'producer_id': $('#producerId').val(),
+ 'name': $('#aggregateMetricSpecName').val(),
+ 'description': $('#aggregateMetricSpecDescription').val(),
+ 'branch': $('#aggregateMetricSpecBranch').val(),
+ 'spec': $('#aggregateMetricSpecSpec').val(),
+ 'value': $('#aggregateMetricSpecValue').val(),
+ 'comparison': $('#aggregateMetricSpecComparison').val()
+ },
+ 'specInputs': {
+ 'metricName': $('#aggregateMetricSpecMetricName').val(),
+ 'aggregateMetric': $('#aggregateMetricSpecAggregateMetric').val(),
+ 'param': $('#aggregateMetricSpecParam').val()
+ }
+ };
+ return specValues;
+ }
+
+ /**
+ * Populate the input elements with the values from the passed in aggregateMetricSpec,
+ * including some validation in case the passed in spec was created out of data that is
+ * no longer valid, e.g. a branch name that no longer has scalars tied to the metric_name.
+ * @param aggregateMetricSpec object with AggregateMetricSpecDao key value pairs
+ */
+ function populateSpecInputs(aggregateMetricSpec) {
+ $('#aggregateMetricSpecEditId').val(aggregateMetricSpec.aggregate_metric_spec_id);
+ $('#aggregateMetricSpecName').val(aggregateMetricSpec.name);
+ $('#aggregateMetricSpecDescription').val(aggregateMetricSpec.description);
+ $('#aggregateMetricSpecSpec').val(aggregateMetricSpec.spec);
+ // Skip value if it is 0 and comparison is empty, this was added as a DB
+ // default and wouldn't be allowed by the UI validation logic.
+ if (aggregateMetricSpec.value &&
+ (aggregateMetricSpec.value != 0 || aggregateMetricSpec.comparison)) {
+ $('#aggregateMetricSpecValue').val(aggregateMetricSpec.value);
+ }
+ if (aggregateMetricSpec.comparison) {
+ $('#aggregateMetricSpecComparison').val(aggregateMetricSpec.comparison);
+ }
+ var specParts = parseMetricSpec(aggregateMetricSpec.spec);
+ $('#aggregateMetricSpecParam').val(specParts.param);
+ $('#aggregateMetricSpecAggregateMetric').val(specParts.metric);
+ var metricNameFound = selectValueFound('aggregateMetricSpecMetricName', specParts.metricName);
+ if (!metricNameFound) {
+ // Don't set the branch name because the metric name is invalid,
+ // and that determines the set of possible branches.
+ $('#aggregateMetricSpecValidationError').text("Loaded metric name '"+specParts.metricName+"' is invalid");
+ $('#aggregateMetricSpecSaveLoading').hide();
+ } else {
+ $('#aggregateMetricSpecMetricName').val(specParts.metricName);
+ // Don't need to hide the loading image because updateBranchList will.
+ var successCallback = function () {
+ var branchNameFound = selectValueFound('aggregateMetricSpecBranch', aggregateMetricSpec.branch);
+ if (!branchNameFound) {
+ $('#aggregateMetricSpecValidationError').text("Loaded branch '"+aggregateMetricSpec.branch+"' is invalid");
+ } else {
+ $('#aggregateMetricSpecBranch').val(aggregateMetricSpec.branch);
+ }
+ };
+ updateBranchList(successCallback);
+ }
+ }
+
+ // Spec Details panel handlers
+
+ /** Handler for the metric name select. */
+ $('select#aggregateMetricSpecMetricName').change(function () {
+ updateSpec();
+ updateBranchList();
+ });
+
+ /** Handler for aggregate metric select. */
+ $('select#aggregateMetricSpecAggregateMetric').change( function () {
+ updateSpec();
+ });
+
+ /** Handler for aggregate metric param change. */
+ $('input#aggregateMetricSpecParam').on('keyup change', function () {
+ updateSpec();
+ });
+
+ // Spec Details panel button handlers
+
/**
* Handler for Update button:
* update the spec,
@@ -389,19 +494,166 @@ $(document).ready(function () {
saveAggregateMetricSpec();
});
- /** Handler for the metric name select. */
- $('select#aggregateMetricSpecMetricName').change(function () {
- updateSpec();
- updateBranchList();
+ /** Handler for Spec Details Cancel button, hide the details panel. */
+ $('input#aggregateMetricSpecCancel').click(function () {
+ $('div#aggregateMetricSpecCreateEdit').hide();
+ unhighlightActiveRow();
});
- /** Handler for aggregate metric select. */
- $('select#aggregateMetricSpecAggregateMetric').change( function () {
- updateSpec();
+ //:~ Alerted Users panel
+
+ /**
+ * Add a row to the alertedUsers table for the passed in user.
+ * @param string userName
+ * @param string userId
+ */
+ function addToAlertedUsersTable(userName, userId) {
+
+ function createActionLink(qtip, actionClass, imgPath, label) {
+ var actionLink = ' ';
+ actionLink += ' '+label+'';
+ return actionLink;
+ }
+
+ var row = '
' + userName + '
';
+ row += createActionLink('Remove user from alerts', 'removeAlertedUser', '/public/images/icons/close.png', 'Remove alerts');
+ row += '
';
+ $('#aggregateMetricSpecAlertedUsers tbody').append(row);
+ }
+
+ // Alerted Users panel handlers
+
+ /**
+ * Handler for Remove alerted user action, delete the user from being
+ * alerted. The handler is tied to a static parent as the links can be
+ * dynamically generated through creation of new alerts.
+ */
+ $('#aggregateMetricSpecAlertedUsers').on('click', 'a.removeAlertedUser', function() {
+ var row = $(this).closest('tr');
+ row.addClass('activeRow');
+ var aggregateMetricSpecId = $('#aggregateMetricSpecEditId').val();
+ $('#aggregateMetricSpecAlertsLoading').show();
+ var userId = $(this).data('user_id');
+
+ var jsonMethod = 'midas.tracker.aggregatemetricspecnotifieduser.delete';
+ var args = 'aggregateMetricSpecId=' + aggregateMetricSpecId + '&userId=' + userId;
+ callAjaxWebApi(jsonMethod, 'POST', args, function (retVal) {
+ if (retVal.data && retVal.data.user_id == userId) {
+ row.remove();
+ addClassesToTableRows('aggregateMetricSpecAlertedUsers');
+ $('#aggregateMetricSpecAlertsLoading').hide();
+ } else {
+ midas.createNotice('Unexpected return value, check error console', 3000, 'error');
+ console.error(retVal);
+ }
+ });
});
- /** Handler for aggregate metric param change. */
- $('input#aggregateMetricSpecParam').on('keyup change', function () {
- updateSpec();
+ // Live search for users
+ $.widget('custom.catcomplete', $.ui.autocomplete, {
+ _renderMenu: function (ul, items) {
+ 'use strict';
+ var self = this,
+ currentCategory = '',
+ userIds = {};
+
+ $('#aggregateMetricSpecAlertedUsers .actionsList').children('a').each(function (ind, elem) {
+ var userId = $(elem).data('user_id');
+ userIds[userId] = userId;
+ });
+
+ $.each(items, function (index, item) {
+ if (userIds[item.userid]) {
+ // Don't show a user in the list if they are already alerted.
+ return;
+ }
+ if (item.category != currentCategory) {
+ ul.append('