modules/tracker/public/js/producer/producer.aggregateMetric.js
// Midas Server. Copyright Kitware SAS. Licensed under the Apache License 2.0.
/* global ajaxWebApi */
/* global json */
var midas = midas || {};
$(document).ready(function () {
'use strict';
var activeNotification = null;
var activeSpecNotifications = {};
//:~ Server API interfaces
/**
* Interface to server side midas REST API.
* @param method The HTTP method {'POST'|'PUT'|'DELETE'|'GET'}
* @param resourceId (Required for all except POST)
* @param args an object containing key value pairs for the resource object for PUT calls (Optional except for PUT)
* @param sCb success callback, passed the return value (Optional)
* @param eCb error callback, passed the return value (Optional)
* @param cCb complete callback, passed the return value (Optional)
*/
function midasRest(httpMethod, path, resourceId, args, sCb, eCb, cCb) {
var url = json.global.webroot + path;
if (resourceId) {
url += '/' + resourceId;
}
url += '?useSession=true';
var restCall = {
url: url,
type: httpMethod,
dataType: 'json',
success: function (retVal) {
if (sCb) { sCb(retVal); }
},
error: function (retVal) {
if (eCb) {
eCb(retVal);
} else {
midas.createNotice(retVal.message, 3000, 'error');
}
},
complete: function (retVal) {
if (cCb) { cCb(retVal); }
},
log: $('<p></p>')
};
if (args) {
restCall.data = args;
}
$.ajax(restCall);
}
/**
* Interface to server side AggregateMetricSpec REST API.
* @param method The HTTP method {'POST'|'PUT'|'DELETE'|'GET'}
* @param aggregateMetricSpecId (Required for all except POST)
* @param args an object containing key value pairs for the AggregateMetricSpec object for PUT calls (Optional except for PUT)
* @param sCb success callback, passed the return value (Optional)
* @param eCb error callback, passed the return value (Optional)
* @param cCb complete callback, passed the return value (Optional)
*/
function aggregatemetricspecRest(method, aggregateMetricSpecId, args, sCb, eCb, cCb) {
var path = '/rest/tracker/aggregatemetricspec';
midasRest(method, path, aggregateMetricSpecId, args, sCb, eCb, cCb);
}
/**
* Interface to server side AggregateMetricNotification REST API.
* @param method The HTTP method {'POST'|'PUT'|'DELETE'|'GET'}
* @param aggregateMetricNotificationId (Required for all except POST)
* @param args an object containing key value pairs for the AggregateMetricNotification object for PUT calls (Optional except for PUT)
* @param sCb success callback, passed the return value (Optional)
* @param eCb error callback, passed the return value (Optional)
* @param cCb complete callback, passed the return value (Optional)
*/
function aggregatemetricnotificationRest(method, aggregateMetricNotificationId, args, sCb, eCb, cCb) {
var path = '/rest/tracker/aggregatemetricnotification';
midasRest(method, path, aggregateMetricNotificationId, args, sCb, eCb, cCb);
}
/**
* Additional wrapper around ajaxWebApi.ajax to provide some defaults.
* @param string jsonMethod the midas json method
* @param string httpMethod The HTTP method {'POST'|'PUT'|'DELETE'|'GET'}
* @param string args The args to pass to the API call
* @param function successCb success callback, passed the return value (Optional)
*/
function callAjaxWebApi(jsonMethod, httpMethod, args, successCb) {
ajaxWebApi.ajax({
method: jsonMethod,
type: httpMethod,
args: args,
success: function (retVal) {
if (successCb) { successCb(retVal); }
},
error: function (retVal) {
midas.createNotice(retVal.message, 3000, 'error');
},
complete: function () {},
log: $('<p></p>')
});
}
//:~ Utility functions
/**
* Helper function to assign correct row classes to spec table.
* @param string tableId the DOM id of the table.
*/
function addClassesToTableRows(tableId) {
$('#' + tableId + ' tbody tr').each(function (ind, elem) {
$(this).removeClass('even odd').addClass(ind % 2 ? 'odd' : 'even');
});
}
/**
* 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;
}
//:~ Aggregate Metric Specs main panel
/**
* Reset and display the spec details panel, defaults to create mode.
* @param {bool} true if edit mode, false (the default) for create mode
*/
function showDetailsPanel(editMode) {
clearSpecInputs();
$('div#aggregateMetricSpecCreateEdit').show();
$('#aggregateMetricSpecSaveLoading').hide();
$('div#aggregateMetricSpecSaveState input').prop('disabled', false);
if (editMode) {
$('.aggregateMetricSpecCreate').hide();
$('.aggregateMetricSpecComposition input').prop('disabled', true);
$('.aggregateMetricSpecComposition select').prop('disabled', true);
$('.aggregateMetricSpecEdit').show();
} else {
$('.aggregateMetricSpecComposition input').prop('disabled', false);
$('.aggregateMetricSpecComposition select').prop('disabled', false);
$('.aggregateMetricSpecCreate').show();
$('.aggregateMetricSpecEdit').hide();
}
}
/** Remove highlight from active row. */
function unhighlightActiveRow() {
$('#aggregateMetricSpecListTable tbody tr').each(function (ind, elem) {
$(this).removeClass('activeRow');
$(this).removeClass('activeDeleteRow');
});
$('#aggregateMetricSpecCreateEdit').hide();
$('#aggregateMetricUserAlerts').hide();
$('#aggregateMetricDeleteLoading').hide();
$('#amsDeleteConfirmSaveState').hide();
}
/**
* Highlight the row which houses the current action in the Aggregate Metric Spec table.
* @param element actionLink the anchor element that was clicked.
*/
function activateRow(actionLink, deleteRow) {
var aggregateMetricSpecId = actionLink.data('aggregate_metric_spec_id');
$('#aggregateMetricSpecEditId').val(aggregateMetricSpecId);
unhighlightActiveRow();
if (deleteRow) {
actionLink.closest('tr').addClass('activeDeleteRow');
} else {
actionLink.closest('tr').addClass('activeRow');
}
}
/**
* Add a row to the aggregateMetricSpec table for the passed in aggregateMetricSpec.
* @param object aggregateMetricSpec with AggregateMetricSpecDao key value pairs
*/
function addToSpecTable(aggregateMetricSpec) {
function createActionLink(qtip, actionClass, imgPath, label) {
var actionLink = ' <a qtip="'+qtip+'" class="actionLink aggregateMetricSpecAction '+actionClass+'" data-aggregate_metric_spec_id="' + aggregateMetricSpec.aggregate_metric_spec_id+'">';
actionLink += ' <img class="actionLink" alt="" src="' + json.global.coreWebroot + imgPath +'" /> '+label+'</a>';
return actionLink;
}
var row = '<tr class="aggregateMetricSpecRow"><td class="specName">' + aggregateMetricSpec.name + '</td><td><span class="actionsList">';
row += createActionLink('Edit aggregate metric spec', 'editAggregateMetricSpec', '/public/images/icons/edit.png', 'Edit');
row += createActionLink('Edit user notifications', 'editAggregateMetricSpecNotificationUsers', '/public/images/icons/email_error.png', 'Alerts');
row += createActionLink('Remove aggregate metric spec', 'removeAggregateMetricSpec', '/public/images/icons/close.png', 'Delete');
row += '</tr>';
$('#aggregateMetricSpecListTable tbody').append(row);
addClassesToTableRows('aggregateMetricSpecListTable');
}
/**
* Update an existing aggregateMetricSpec in the spec table, with the properties
* of the passed in aggergateMetricSpec, currently only updates the name.
* @param aggregateMetricSpec object with AggregateMetricSpecDao key value pairs
*/
function updateSpecInTable(aggregateMetricSpec) {
var amsId = aggregateMetricSpec.aggregate_metric_spec_id;
var name = aggregateMetricSpec.name;
// Get the row of this spec in the table.
var row = $("td").find("[data-aggregate_metric_spec_id='" + amsId + "']").first().closest('tr');
// Get the first cell of that row and output the spec's name.
$('td:first', row).text(name);
}
// Aggregate Metric Specs main panel handlers
/**
* Handler for Delete action, delete an Aggregate Metric Spec on the server
* and remove it from the table. The handler is tied
* to a static parent as the links can be dynamically generated through
* creation of new aggregate metric specs.
*/
$('#aggregateMetricSpecListTable').on('click', 'a.removeAggregateMetricSpec', function(){
activateRow($(this), true);
$('#amsDeleteConfirmSaveState').show();
var aggregateMetricSpecId = $(this).data('aggregate_metric_spec_id');
var row = $(this).closest('tr');
// successCallback for delete ajax request.
var sCb = function (data) {
row.remove();
$('#aggregateMetricDeleteLoading').hide();
$('#amsDeleteConfirmSaveState input').prop('disabled', false);
$('#amsDeleteConfirmSaveState').hide();
addClassesToTableRows('aggregateMetricSpecListTable');
};
/** Handler for Confirm delete action, deletes the Spec. */
$('input#amsDeleteConfirmDelete').off('click').on('click', function() {
$('#amsDeleteConfirmSaveState input').prop('disabled', true);
$('#aggregateMetricDeleteLoading').show();
aggregatemetricspecRest('DELETE', aggregateMetricSpecId, null, sCb, null, null);
});
});
/** Handler for Cancel delete action, removes the deletion warning. */
$('input#amsDeleteCancelDelete').on('click', function() {
unhighlightActiveRow();
$('#amsDeleteConfirmSaveState').hide();
});
/** Handler for Add action, open the details panel in Create state. */
$('div#addAggregateMetricSpec').click(function () {
$('#amsDeleteConfirm').remove();
unhighlightActiveRow();
showDetailsPanel();
});
/**
* Handler for Edit action, open the details panel in Edit state after
* loading the details of the Aggregate Metric Spec. The handler is tied
* to a static parent as the links can be dynamically generated through
* creation of new aggregate metric specs.
*/
$('#aggregateMetricSpecListTable').on('click', 'a.editAggregateMetricSpec', function() {
activateRow($(this));
var aggregateMetricSpecId = $(this).data('aggregate_metric_spec_id');
showDetailsPanel(true);
$('#aggregateMetricSpecSaveLoading').show();
var successCallback = function (aggregateMetricSpec) {
populateSpecInputs(aggregateMetricSpec);
}
aggregatemetricspecRest('GET', aggregateMetricSpecId, null, successCallback);
});
/** Handler for Alerts button, show the panel to edit the alerted users. */
$('#aggregateMetricSpecListTable').on('click', 'a.editAggregateMetricSpecNotificationUsers', function() {
activateRow($(this));
unhighlightActiveAlertRow();
var amsName = $('td:first', $(this).closest('tr')).text();
$('#aggregateMetricUserAlerts').show();
var aggregateMetricSpecId = $(this).data('aggregate_metric_spec_id');
$('#aggregateMetricUserAlertsSpecName').text(amsName);
$('#aggregateMetricUserAlertsBranch').text('');
$('#aggregateMetricSpecAlertValue').val('');
$('#aggregateMetricSpecAlertComparison').val('');
$('#aggregateMetricUserAlertsSpec').val('');
$('#aggregateMetricSpecAlertedUsers').find('tr:gt(0)').remove();
$('#aggregateMetricNotificationsTable').find('tr:gt(0)').remove();
$('#aggregateMetricNotificationRemoveLoading').show();
$('#addAlertUserSearch').val('Start typing a name or email address...');
$('#addAlertUserSearchValue').val('init');
var successCallback = function (aggregateMetricSpec) {
$('#aggregateMetricUserAlertsSpecName').text(aggregateMetricSpec.name);
$('#aggregateMetricUserAlertsSpec').val(aggregateMetricSpec.spec);
var jsonMethod = 'midas.tracker.aggregatemetricspecnotifications.list';
var args = 'aggregateMetricSpecId=' + aggregateMetricSpecId;
callAjaxWebApi(jsonMethod, 'GET', args, function (retVal) {
activeSpecNotifications = {};
for (var notifInd = 0; notifInd < retVal.data.length; notifInd++) {
var notification = retVal.data[notifInd];
addToNotificationTable(notification.notification);
activeSpecNotifications[notification.notification.aggregate_metric_notification_id] = notification;
}
addClassesToTableRows('aggregateMetricNotificationsTable');
$('#aggregateMetricNotificationRemoveLoading').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
* on input fields before saving, and will update or add the spec to the spec table.
* @param aggregateMetricSpecId if passed the id of an existing spec to update
*/
function saveAggregateMetricSpec(aggregateMetricSpecId) {
$('div#aggregateMetricSpecSaveState input').prop('disabled', true);
$('#aggregateMetricSpecValidationError').text('');
var specValues = getSpecInputsValues();
// Validate the inputs.
// Order matters, so return after the first invalid field found.
var requiredFields = [
{'value': specValues.aggregateMetricSpec.name, 'name': 'Name'},
{'value': specValues.specInputs.metricName, 'name': 'Metric name'},
{'value': specValues.specInputs.aggregateMetric, 'name': 'Aggregate metric'},
{'value': specValues.specInputs.param, 'name': 'Param (percentile)'}
];
for (var i = 0; i < requiredFields.length; i++) {
var value = requiredFields[i].value;
var name = requiredFields[i].name;
if (!value || value === '') {
$('#aggregateMetricSpecValidationError').text(name + ' is a required field');
$('div#aggregateMetricSpecSaveState input').prop('disabled', false);
return;
}
}
var param = specValues.specInputs.param;
if (!$.isNumeric(param) || param < 0 || param > 100) {
$('#aggregateMetricSpecValidationError').text('Param (percentile) must be >= 0 and <= 100');
$('div#aggregateMetricSpecSaveState input').prop('disabled', false);
return;
}
// Save the AMS on the server.
$('#aggregateMetricSpecSaveLoading').show();
var successCallback = function (aggregateMetricSpec) {
if (aggregateMetricSpecId) {
updateSpecInTable(aggregateMetricSpec);
} else {
addToSpecTable(aggregateMetricSpec);
}
$('div#aggregateMetricSpecCreateEdit').hide();
unhighlightActiveRow();
}
var method = aggregateMetricSpecId ? 'PUT' : 'POST';
aggregateMetricSpecId = aggregateMetricSpecId ? aggregateMetricSpecId : null;
aggregatemetricspecRest(method, aggregateMetricSpecId, specValues.aggregateMetricSpec, successCallback);
}
/** Clear all spec inputs of any value. */
function clearSpecInputs() {
$('.amsField').val('');
$('#aggregateMetricSpecValidationError').text('');
$('#aggregateMetricSpecSpec').val('');
$('#aggregateMetricSpecMetricName option:disabled').attr('selected', 'selected');
}
/** 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(),
'spec': $('#aggregateMetricSpecSpec').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.
* @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);
var specParts = parseMetricSpec(aggregateMetricSpec.spec);
$('#aggregateMetricSpecParam').val(specParts.param);
$('#aggregateMetricSpecAggregateMetric').val(specParts.metric);
var metricNameFound = selectValueFound('aggregateMetricSpecMetricName', specParts.metricName);
if (!metricNameFound) {
$('#aggregateMetricSpecValidationError').text("Loaded metric name '"+specParts.metricName+"' is invalid");
} else {
$('#aggregateMetricSpecMetricName').val(specParts.metricName);
}
$('#aggregateMetricSpecSaveLoading').hide();
}
// Spec Details panel handlers
/** Handler for the metric name select. */
$('select#aggregateMetricSpecMetricName').change(function () {
updateSpec();
});
/** 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,
* adjust its name in the spec listing table,
* hide the details panel.
*/
$('input#aggregateMetricSpecUpdate').click(function () {
saveAggregateMetricSpec($('#aggregateMetricSpecEditId').val());
});
/**
* Handler for Create button:
* create the new spec,
* add it to the spec listing table,
* hide the details panel.
*/
$('input#aggregateMetricSpecCreate').click(function () {
saveAggregateMetricSpec();
});
/** Handler for Spec Details Cancel button, hide the details panel. */
$('input#aggregateMetricSpecCancel').click(function () {
$('div#aggregateMetricSpecCreateEdit').hide();
unhighlightActiveRow();
});
//:~ Notification/Alerts panel
/**
* Add a row to the notifications table for a given spec, based on the
* passed in AggregateMetricNotification object.
* @param aggregateMetricNotification object with AggregateMetricNotificationDao key value pairs
*/
function addToNotificationTable(notification) {
var notificationId = notification.aggregate_metric_notification_id;
function createActionLink(qtip, actionClass, imgPath, label) {
var actionLink = ' <a qtip="'+qtip+'" class="actionLink notificationAction '+actionClass+'" data-aggregate_metric_notification_id="' + notificationId+'">';
actionLink += ' <img class="actionLink" alt="" src="' + json.global.coreWebroot + imgPath +'" /> '+label+'</a>';
return actionLink;
}
var row = '<tr id=><td id="amnBranch'+notificationId+'" class="aggregateMetricNotificationBranch">' + notification.branch + '</td>';
row += '<td id="amnComparison'+notificationId+'" class="aggregateMetricNotificationComparison">' + notification.comparison + '</td>';
row += '<td id="amnValue'+notificationId+'" class="aggregateMetricNotificationValue">' + notification.value + '</td><td><span class="actionsList">';
row += createActionLink('Edit notification', 'editAggregateMetricNotification', '/public/images/icons/edit.png', 'Edit');
row += createActionLink('Edit users', 'editAggregateMetricNotificationUsers', '/public/images/icons/email_error.png', 'Users');
row += createActionLink('Remove notification', 'removeAggregateMetricNotification', '/public/images/icons/close.png', 'Delete');
row += '</tr>';
$('#aggregateMetricNotificationsTable tbody').append(row);
}
/** Remove highlight from active notification row. */
function unhighlightActiveAlertRow() {
$('#aggregateMetricNotificationsTable tbody tr').each(function (ind, elem) {
$(this).removeClass('activeRow');
});
$('#aggregateMetricNotificationEdit').hide();
$('#aggregateMetricNotificationUsers').hide();
$('#aggregateMetricNotificationCancel').show();
}
/**
* Highlight the alert row which houses the current action in the Notifications table,
* sets the activeNotification closure variable to the alert in the row.
* @param element actionLink the anchor element that was clicked.
*/
function activateAlertRow(actionLink) {
unhighlightActiveAlertRow();
$('#aggregateMetricNotificationValidationError').text('');
actionLink.closest('tr').addClass('activeRow');
$('#aggregateMetricNotificationCancel').hide();
var notificationId = actionLink.data('aggregate_metric_notification_id');
activeNotification = activeSpecNotifications[notificationId];
}
/**
* Validates the current notification input fields, whether for an edited
* notification or a new one.
* @return bool indicating whether validation succeeded.
*/
function validateNotification() {
var branch = $('#aggregateMetricNotificationBranch').val();
if (branch === null || branch === '') {
$('#aggregateMetricNotificationValidationError').text('branch cannot be empty');
return false;
}
var comparison = $('#aggregateMetricNotificationComparison').val();
if (comparison === null || comparison === '') {
$('#aggregateMetricNotificationValidationError').text('comparison cannot be empty');
return false;
}
var value = $('#aggregateMetricNotificationValue').val();
if (value === null || value === '' || !$.isNumeric(value)) {
$('#aggregateMetricNotificationValidationError').text('value must be numeric');
return false;
}
$('#aggregateMetricNotificationValidationError').text('');
return true;
}
/**
* Deactivate the current notification and hide any notification details panels.
*/
function hideNotificationDetailPanel() {
$('.editAlert').hide();
$('.createAlert').hide();
$('#aggregateMetricNotificationSaveLoading').hide();
$('div#aggregateMetricNotificationSaveState input').prop('disabled', false);
unhighlightActiveAlertRow();
$('#aggregateMetricNotificationEdit').hide();
$('#aggregateMetricNotificationCancel').show();
}
// Notification panel handlers
// Notifications and Alerts are considered equivalent, "Alert" is shorter
/**
* Handler for edit notification users action, activates the notification
* and loads any users tied to the notification to display them in the
* users table.
* The handler is tied to a static parent as the links can be
* dynamically generated through creation of new alerts.
*/
$('#aggregateMetricNotificationsTable').on('click', 'a.editAggregateMetricNotificationUsers', function() {
activateAlertRow($(this));
$('#aggregateMetricNotificationUsers').show();
$('#aggregateMetricSpecAlertedUsers').find('tr:gt(0)').remove();
for(var userInd = 0; userInd < activeNotification.users.length; userInd += 1) {
var user = activeNotification.users[userInd];
addToAlertedUsersTable(user.firstname +' '+user.lastname, user.user_id);
}
addClassesToTableRows('aggregateMetricSpecAlertedUsers');
});
/**
* Handler for edit notification action, activates the notification
* and loads the notification into fields such that it can be edited.
* The handler is tied to a static parent as the links can be
* dynamically generated through creation of new alerts.
*/
$('#aggregateMetricNotificationsTable').on('click', 'a.editAggregateMetricNotification', function() {
activateAlertRow($(this));
$('#aggregateMetricNotificationEdit').show();
$('#aggregateMetricNotificationBranch').val(activeNotification.notification.branch);
$('#aggregateMetricNotificationValue').val(activeNotification.notification.value);
$('#aggregateMetricNotificationComparison').val(activeNotification.notification.comparison);
$('#aggregateMetricNotificationCreate').hide();
$('#aggregateMetricNotificationUpdate').show();
$('.editAlert').show();
$('.createAlert').hide();
});
/**
* Handler for Remove notification action, delete the notification and
* any associated alerted users.
* The handler is tied to a static parent as the links can be
* dynamically generated through creation of new alerts.
*/
$('#aggregateMetricNotificationsTable').on('click', 'a.removeAggregateMetricNotification', function() {
hideNotificationDetailPanel();
var row = $(this).closest('tr');
activateAlertRow($(this));
var notificationId = $(this).data('aggregate_metric_notification_id');
$('#aggregateMetricNotificationRemoveLoading').show();
aggregatemetricnotificationRest('DELETE', notificationId, {}, function (retVal) {
row.remove();
$('#aggregateMetricNotificationRemoveLoading').hide();
delete activeSpecNotifications[notificationId];
unhighlightActiveAlertRow();
addClassesToTableRows('aggregateMetricNotificationsTable');
});
});
/**
* Handler for adding a new notification to the active Spec.
*/
$('#addAggregateMetricNotification').click(function () {
unhighlightActiveAlertRow();
$('#aggregateMetricNotificationUpdate').hide();
$('#aggregateMetricNotificationBranch').val('');
$('#aggregateMetricNotificationComparison').val('');
$('#aggregateMetricNotificationValue').val('');
$('#aggregateMetricNotificationCreate').show();
$('#aggregateMetricNotificationEdit').show();
$('div#aggregateMetricNotificationSaveState input').prop('disabled', false);
$('#aggregateMetricNotificationValidationError').text('');
$('#aggregateMetricNotificationSaveState').show();
$('#aggregateMetricNotificationCancel').hide();
$('.editAlert').hide();
$('.createAlert').show();
});
// Notification panel button handlers
/**
* Handler for Cancel button:
* hide any notification detail panel and de-activate any notification row.
*/
$('#aggregateMetricNotificationCancel').click(function () {
$('#aggregateMetricUserAlerts').hide();
unhighlightActiveAlertRow();
unhighlightActiveRow();
hideNotificationDetailPanel();
});
/**
* Update the active notification with the input fields.
*/
$('input#aggregateMetricNotificationUpdate').click(function () {
if (validateNotification()) {
$('div#aggregateMetricNotificationSaveState input').prop('disabled', true);
$('#aggregateMetricNotificationSaveLoading').show();
var notificationUpdates = {
branch: $('#aggregateMetricNotificationBranch').val(),
comparison: $('#aggregateMetricNotificationComparison').val(),
value: $('#aggregateMetricNotificationValue').val()
};
var notificationId = activeNotification.notification.aggregate_metric_notification_id;
aggregatemetricnotificationRest('PUT', notificationId, notificationUpdates, function (retVal) {
activeSpecNotifications[notificationId].notification = retVal;
$('#aggregateMetricNotificationsTable td#amnBranch'+notificationId).html(retVal.branch);
$('#aggregateMetricNotificationsTable td#amnValue'+notificationId).html(retVal.value);
$('#aggregateMetricNotificationsTable td#amnComparison'+notificationId).html(retVal.comparison);
hideNotificationDetailPanel();
});
}
});
/**
* Cancel editing or creating a notification.
*/
$('#aggregateMetricNotificationSaveCancel').click(function () {
hideNotificationDetailPanel();
});
/**
* Create a new notification with the input fields tied to the active Spec.
*/
$('input#aggregateMetricNotificationCreate').click(function () {
if (validateNotification()) {
$('div#aggregateMetricNotificationSaveState input').prop('disabled', true);
var notificationProperties = {
aggregate_metric_spec_id: $('#aggregateMetricSpecEditId').val(),
branch: $('#aggregateMetricNotificationBranch').val(),
comparison: $('#aggregateMetricNotificationComparison').val(),
value: $('#aggregateMetricNotificationValue').val()
};
$('#aggregateMetricNotificationSaveLoading').show();
aggregatemetricnotificationRest('POST', null, notificationProperties, function (retVal) {
hideNotificationDetailPanel();
addToNotificationTable(retVal);
activeSpecNotifications[retVal.aggregate_metric_notification_id] = {
notification: retVal,
users: []
};
addClassesToTableRows('aggregateMetricNotificationsTable');
});
}
});
//:~ 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 = ' <a qtip="'+qtip+'" class="actionLink alertedUsersAction '+actionClass+'" data-user_id="' + userId+'">';
actionLink += ' <img class="actionLink" alt="" src="' + json.global.coreWebroot + imgPath +'" /> '+label+'</a>';
return actionLink;
}
var row = '<tr><td class="userName">' + userName + '</td><td><span class="actionsList">';
row += createActionLink('Remove user from alerts', 'removeAlertedUser', '/public/images/icons/close.png', 'Remove alert');
row += '</tr>';
$('#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');
$('#aggregateMetricSpecAlertsLoading').show();
var userId = $(this).data('user_id');
var jsonMethod = 'midas.tracker.aggregatemetricspecnotifieduser.delete';
var args = 'aggregateMetricNotificationId=' + activeNotification.notification.aggregate_metric_notification_id + '&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);
}
});
});
// 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('<li class="search-category">' + item.category + '</li>');
currentCategory = item.category;
}
self._renderItemData(ul, item);
});
}
});
var alertUserSearchCache = {},
lastAlertUserShareXhr;
$('#addAlertUserSearch').catcomplete({
minLength: 2,
delay: 10,
source: function (request, response) {
'use strict';
var term = request.term;
if (term in alertUserSearchCache) {
response(alertUserSearchCache[term]);
return;
}
$('#aggregateMetricSpecAlertsLoading').show();
lastAlertUserShareXhr = $.getJSON($('.webroot').val() + '/search/live?userSearch=true&allowEmail',
request, function (data, status, xhr) {
$('#aggregateMetricSpecAlertsLoading').hide();
alertUserSearchCache[term] = data;
if (xhr === lastAlertUserShareXhr) {
response(data);
}
});
}, // end source
select: function (event, ui) {
'use strict';
$('#aggregateMetricSpecAlertsLoading').show();
var userId = ui.item.userid;
var userName = ui.item.value;
var jsonMethod = 'midas.tracker.aggregatemetricspecnotifieduser.create';
var args = 'aggregateMetricNotificationId=' + activeNotification.notification.aggregate_metric_notification_id + '&userId=' + userId;
callAjaxWebApi(jsonMethod, 'POST', args, function (retVal) {
if (retVal.data && retVal.data.user_id == userId) {
activeNotification.users.push(retVal.data);
addToAlertedUsersTable(userName, userId);
addClassesToTableRows('aggregateMetricSpecAlertedUsers');
$('#addAlertUserSearchValue').val('init');
$('#aggregateMetricSpecAlertsLoading').hide();
} else {
midas.createNotice('Unexpected return value, check error console', 3000, 'error');
console.error(retVal);
}
});
} // end select
});
$('#addAlertUserSearch').focus(function () {
'use strict';
if ($('#addAlertUserSearchValue').val() == 'init') {
$('#addAlertUserSearchValue').val($(this).val());
$(this).val('');
}
}).focusout(function () {
'use strict';
if ($(this).val() == '') {
$(this).val($('#addAlertUserSearchValue').val());
$('#addAlertUserSearchValue').val('init');
}
});
// Alerted Users panel button handlers
/** Handler for Alerts Users Done button, hide the alerts panel. */
$('input#aggregateMetricSpecUserAlertsDone').click(function () {
hideNotificationDetailPanel();
});
// Initialize the dialog now that it is loaded.
// Parse json content
// jQuery 1.8 has weird bugs when using .html() here, use the old-style innerHTML here
var trackerJson = $.parseJSON($('div.trackerJsonContent')[0].innerHTML);
// Create table rows.
for (var amsInd = 0; amsInd < trackerJson.aggregateMetricSpecs.length; amsInd++) {
addToSpecTable(trackerJson.aggregateMetricSpecs[amsInd]);
}
// Initialize the table row classess.
addClassesToTableRows('aggregateMetricSpecListTable');
});