Commit 5b5fc537 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '676-notify-about-loss-of-unsaved-changes' into 'master'

Resolve "Notify about loss of unsaved changes"

Closes #676

See merge request minerva/core!749
parents fea10266 bb57fa08
Pipeline #9916 failed with stage
in 31 minutes and 15 seconds
minerva (12.3.0~alpha.0) unstable; urgency=low
* Feature: annotators are more flexible - you can define set of input and
outputs used by annotator (#617)
* Feature: changes in admin panel doesn't require saving - they are saved
automatically (#676)
* Small improvement: validation of the organism and disease id on map upload
added (#618)
* Small improvement: added current username next to logout button in admin
......
......@@ -1074,15 +1074,18 @@ ServerConnector.getProject = function (projectId) {
if (content === null) {
return null;
} else {
var promises = [];
var downloadedProject = new Project(content);
if (self._projectsById[projectId] instanceof Project) {
self._projectsById[projectId].update(downloadedProject);
promises.push(self._projectsById[projectId].update(downloadedProject));
} else {
self._projectsById[projectId] = downloadedProject;
}
project = self._projectsById[projectId];
return self.getModels(projectId).then(function (models) {
return Promise.all(promises).then(function () {
return self.getModels(projectId);
}).then(function (models) {
project.setModels(models);
return self.getOverlays({
projectId: projectId,
......@@ -1138,7 +1141,9 @@ ServerConnector.updateProject = function (project) {
}).then(function () {
return project;
}).catch(function (error) {
return self._processUpdateError(error);
return self.getProject(project.getProjectId()).then(function (project) {
return self._processUpdateError(error);
});
});
};
......@@ -2276,6 +2281,11 @@ ServerConnector.addOverlayFromString = function (name, content) {
});
};
/**
*
* @param {DataOverlay} overlay
* @return {Promise<DataOverlay>}
*/
ServerConnector.updateOverlay = function (overlay) {
var self = this;
var queryParams = {
......@@ -2295,6 +2305,13 @@ ServerConnector.updateOverlay = function (overlay) {
return self.sendPatchRequest(self.updateOverlayUrl(queryParams), filterParams);
};
/**
*
* @param {Object} params
* @param {string} params.projectId
* @param {MapModel} params.model
* @return {Promise}
*/
ServerConnector.updateModel = function (params) {
var self = this;
var model = params.model;
......
......@@ -6,6 +6,7 @@ var ConfigurationType = require('../../ConfigurationType');
var Functions = require('../../Functions');
var GuiConnector = require('../../GuiConnector');
var ValidationError = require('../../ValidationError');
var logger = require('../../logger');
......@@ -16,7 +17,6 @@ var xss = require("xss");
*
* @param {Configuration} [params.configuration]
* @param {HTMLElement} params.element
* @param {Project} params.project
* @param {string} params.panelName
* @param params.parent
*
......@@ -45,10 +45,6 @@ ConfigurationAdminPanel.prototype._createGui = function () {
self.getGuiUtils().initTabContent(self);
$(self.getElement()).on("click", "[name='saveOption']", function () {
var button = this;
return self.saveOption($(button).attr("data")).then(null, GuiConnector.alert);
});
$(self.getElement()).on("input", ".minerva-color-input", function () {
var input = this;
var value = $(input).val();
......@@ -58,6 +54,17 @@ ConfigurationAdminPanel.prototype._createGui = function () {
}
$("[name='edit-color-" + type + "']", self.getElement()).css("background-color", "#" + value);
});
// $("[name='configurationTable']", self.getElement()).on("change", "input", function () {
$(self.getElement()).on("change", "input, textarea", function () {
var input = this;
var type = $(input).attr("data");
var name = $(input).attr("name");
if (name !== undefined && name.indexOf("edit") === 0) {
return self.saveOption(type);
}
});
$(self.getElement()).on("click", ".minerva-color-button", function () {
var button = this;
var value = $(button).css("background-color");
......@@ -72,6 +79,7 @@ ConfigurationAdminPanel.prototype._createGui = function () {
},
hide: function () {
colorPicker.spectrum("destroy");
return self.saveOption(type);
}
});
return new Promise.delay(1).then(function () {
......@@ -107,9 +115,6 @@ ConfigurationAdminPanel.prototype.createOptionsTable = function (options, type)
title: 'Name'
}, {
title: 'Value'
}, {
title: 'Save',
orderable: false
}],
order: [[1, "asc"]]
});
......@@ -202,17 +207,17 @@ ConfigurationAdminPanel.prototype.optionToTableRow = function (option) {
option.getValueType() === "DOUBLE" ||
option.getValueType() === "EMAIL" ||
option.getValueType() === "URL") {
editOption = "<input name='edit-" + option.getType() + "' value='" + value + "'/>";
editOption = "<input name='edit-" + option.getType() + "' data='" + option.getType() + "' value='" + value + "'/>";
} else if (option.getValueType() === "PASSWORD") {
editOption = "<input type='password' name='edit-" + option.getType() + "' autocomplete='new-password' value='" + value + "'/>";
editOption = "<input type='password' name='edit-" + option.getType() + "' autocomplete='new-password' data='" + option.getType() + "' value='" + value + "'/>";
} else if (option.getValueType() === "TEXT") {
editOption = "<textarea name='edit-" + option.getType() + "'>" + xss(value) + "</textarea>";
editOption = "<textarea name='edit-" + option.getType() + "' data='" + option.getType() + "' >" + xss(value) + "</textarea>";
} else if (option.getValueType() === "BOOLEAN") {
var checked = "";
if (value.toLowerCase() === "true") {
checked = " checked ";
}
editOption = "<input type='checkbox' name='edit-" + option.getType() + "' " + checked + " />";
editOption = "<input type='checkbox' name='edit-" + option.getType() + "' " + checked + " data='" + option.getType() + "' />";
} else if (option.getValueType() === "COLOR") {
editOption = "<div>" +
"<input class='minerva-color-input' name='edit-" + option.getType() + "' data='" + option.getType() + "' value='" + value + "'/>" +
......@@ -220,11 +225,10 @@ ConfigurationAdminPanel.prototype.optionToTableRow = function (option) {
"</div>";
} else {
logger.warn("Don't know how to handle: " + option.getValueType());
editOption = "<input name='edit-" + option.getType() + "' value='" + value + "' readonly/>";
editOption = "<input name='edit-" + option.getType() + "' value='" + value + "' readonly data='" + option.getType() + "' />";
}
row[0] = option.getCommonName();
row[1] = editOption;
row[2] = "<button name='saveOption' data='" + option.getType() + "' ><i class='fa fa-save' style='font-size:17px'></i></button>";
return row;
};
......@@ -236,18 +240,24 @@ ConfigurationAdminPanel.prototype.optionToTableRow = function (option) {
ConfigurationAdminPanel.prototype.saveOption = function (type) {
var self = this;
var option;
var value;
var element = $("[name='edit-" + type + "']", self.getElement());
if (element.is(':checkbox')) {
if (element.is(':checked')) {
value = "true";
} else {
value = "false";
}
} else {
value = element.val();
}
GuiConnector.showProcessing();
return self.getServerConnector().getConfiguration().then(function (configuration) {
option = configuration.getOption(type);
var element = $("[name='edit-" + type + "']", self.getElement());
var value;
if (element.is(':checkbox')) {
if (element.is(':checked')) {
value = "true";
} else {
value = "false";
}
} else {
value = element.val();
if (option === undefined) {
return Promise.reject(new ValidationError("Unknown configuration type: " + type));
}
if (option.getValueType() === 'PASSWORD' && value === '') {
return new Promise(function (resolve) {
......@@ -276,17 +286,6 @@ ConfigurationAdminPanel.prototype.saveOption = function (type) {
}
}).then(function (performUpdate) {
if (performUpdate) {
var element = $("[name='edit-" + type + "']", self.getElement());
var value;
if (element.is(':checkbox')) {
if (element.is(':checked')) {
value = "true";
} else {
value = "false";
}
} else {
value = element.val();
}
option.setValue(value);
return self.getServerConnector().updateConfigurationOption(option);
}
......@@ -318,7 +317,7 @@ ConfigurationAdminPanel.prototype.saveOption = function (type) {
}
});
}
});
}).catch(GuiConnector.alert).finally(GuiConnector.hideProcessing);
};
/**
......
......@@ -16,7 +16,7 @@ var guiUtils = new (require('../leftPanel/GuiUtils'))();
/**
*
* @param params
* @param {Object} params
* @param {HTMLElement} params.element
* @param {User} params.user
* @param {Configuration} params.configuration
......@@ -166,6 +166,42 @@ EditUserDialog.prototype.createGeneralTabContent = function () {
menuRow.appendChild(saveUserButton);
menuRow.appendChild(cancelButton);
if (!self.getIsNewUser()) {
$(".minerva-menu-row", result).hide();
}
$(result).on('change', '[name="userName"]', function () {
if (!self.getIsNewUser()) {
self.getUser().setName($(this).val());
return self.updateUser();
}
});
$(result).on('change', '[name="userSurname"]', function () {
if (!self.getIsNewUser()) {
self.getUser().setSurname($(this).val());
return self.updateUser();
}
});
$(result).on('change', '[name="userEmail"]', function () {
if (!self.getIsNewUser()) {
self.getUser().setEmail($(this).val());
return self.updateUser();
}
});
$(result).on('change', '[name^="userPassword"]', function () {
if (!self.getIsNewUser()) {
if (self.getPassword() !== self.getPassword2()) {
$('[name^="userPassword"]', self.getElement()).css("background-color", "red");
} else {
$('[name^="userPassword"]', self.getElement()).css("background-color", "");
self.getUser().setPassword($(this).val());
return self.updateUser();
}
}
});
return result;
};
......@@ -329,6 +365,9 @@ EditUserDialog.prototype.initProjectsTab = function () {
self.getUser().setPrivilege({type: privilege, value: value, objectId: objectId});
}
}
if (!self.getIsNewUser()) {
return self.updateUser();
}
});
$("[name='defaultProjectsRow']", self.getElement()).on("click", "[name='project-privilege-checkbox']", function () {
......@@ -342,6 +381,9 @@ EditUserDialog.prototype.initProjectsTab = function () {
self.getUser().setPrivilege({type: privilege, value: value, objectId: objectId});
}
}
if (!self.getIsNewUser()) {
return self.updateUser();
}
});
};
......@@ -373,6 +415,9 @@ EditUserDialog.prototype.initPrivilegesTab = function () {
self.getUser().setPrivilege({type: privilege, value: value});
}
}
if (!self.getIsNewUser()) {
return self.updateUser();
}
});
$(privilegesTable).on("change", "[name='privilege-int']", function () {
var privilegeType = $(this).attr("data");
......@@ -385,9 +430,14 @@ EditUserDialog.prototype.initPrivilegesTab = function () {
self.getUser().setPrivilege({type: privilege, value: value});
}
}
$(this).css("background-color", "");
if (!self.getIsNewUser()) {
return self.updateUser();
}
} else {
$(this).css("background-color", "red");
}
});
};
/**
......@@ -592,21 +642,30 @@ EditUserDialog.prototype.onSaveClicked = function () {
user.setEmail(self.getEmail());
user.setName(self.getName());
user.setSurname(self.getSurname());
if (self.getIsNewUser()) {
return self.getServerConnector().addUser(user).catch(function (error) {
if (error instanceof ObjectExistsError) {
return Promise.reject(new ValidationError("User with given login already exists"));
}
return Promise.reject(error);
});
} else {
return self.getServerConnector().updateUser(user);
}
return self.getServerConnector().addUser(user).catch(function (error) {
if (error instanceof ObjectExistsError) {
return Promise.reject(new ValidationError("User with given login already exists"));
}
return Promise.reject(error);
});
}).then(function () {
return self.callListeners("onSave", user);
});
};
/**
*
* @returns {Promise}
*/
EditUserDialog.prototype.updateUser = function () {
var self = this;
GuiConnector.showProcessing();
return self.getServerConnector().updateUser(self.getUser()).then(function (user) {
return self.callListeners("onSave", user);
}).catch(GuiConnector.alert).finally(GuiConnector.hideProcessing);
};
/**
*
* @returns {Promise}
......
......@@ -292,7 +292,7 @@ MapsAdminPanel.prototype.setProjects = function (projects) {
for (var i = 0; i < projects.length; i++) {
var project = projects[i];
var rowData = self.projectToTableRow(project, undefined, user);
self.addUpdateListener(project, rowData);
self.addUpdateListener(project);
data.push(rowData);
if (project.getStatus().toLowerCase() !== "ok" && project.getStatus().toLowerCase() !== "failure") {
requireUpdate = true;
......@@ -313,9 +313,8 @@ MapsAdminPanel.prototype.setProjects = function (projects) {
/**
*
* @param {Project} project
* @param {Array} dataTableRow
*/
MapsAdminPanel.prototype.addUpdateListener = function (project, dataTableRow) {
MapsAdminPanel.prototype.addUpdateListener = function (project) {
var self = this;
var listenerName = "PROJECT_LIST_LISTENER";
......@@ -327,10 +326,16 @@ MapsAdminPanel.prototype.addUpdateListener = function (project, dataTableRow) {
}
var listener = function () {
return self.getServerConnector().getLoggedUser().then(function (user) {
self.projectToTableRow(project, dataTableRow, user);
var row = $($("[name='projectsTable']", self.getElement())[0]).DataTable().row("#" + project.getProjectId());
if (row.length > 0) {
row.data(dataTableRow).draw();
var dataTable = $($("[name='projectsTable']", self.getElement())[0]).DataTable();
var length = dataTable.data().length;
for (var i = 0; i < length; i++) {
var row = dataTable.row(i);
var data = row.data();
if (data[0].indexOf(">" + project.getProjectId() + "<")>=0) {
self.projectToTableRow(project, data, user);
row.data(data).draw();
}
}
});
};
......@@ -421,7 +426,7 @@ MapsAdminPanel.prototype.onRefreshClicked = function () {
/**
*
* @param project
* @param {Project} project
* @returns {*}
*/
MapsAdminPanel.prototype.getDialog = function (project) {
......@@ -442,10 +447,6 @@ MapsAdminPanel.prototype.getDialog = function (project) {
});
self._dialogs[project.getProjectId()] = dialog;
return dialog.init().then(function () {
return dialog.addListener("onSave", function () {
return self.onRefreshClicked()
});
}).then(function () {
return dialog;
});
} else {
......
......@@ -50,7 +50,7 @@ PrivilegeType.prototype.setObjectType = function (objectType) {
/**
*
* @returns {string}
* @returns {?null|string}
*/
PrivilegeType.prototype.getObjectType = function () {
return this._objectType;
......
......@@ -30,8 +30,8 @@ describe('ConfigurationAdminPanel', function () {
var mapTab = createConfigurationTab();
return mapTab.init().then(function () {
assert.equal(0, logger.getWarnings().length);
assert.ok($("[name='saveOption']", testDiv).length > 0);
assert.notOk($("[name='saveOption']", testDiv).prop('disabled'));
assert.ok($("[name^='edit-']", testDiv).length > 0);
assert.notOk($("[name^='edit-']", testDiv).prop('disabled'));
return mapTab.destroy();
});
});
......@@ -40,7 +40,7 @@ describe('ConfigurationAdminPanel', function () {
var mapTab = createConfigurationTab();
return mapTab.init().then(function () {
assert.equal(0, logger.getWarnings().length);
assert.equal($("[name='saveOption']", testDiv).length, 0);
assert.equal($("[name^='edit']", testDiv).length, 0);
return mapTab.destroy();
});
});
......
......@@ -60,61 +60,23 @@ describe('EditProjectDialog', function () {
dialog.destroy();
});
});
it('saveOverlay', function () {
helper.loginAsAdmin();
var dialog;
return createDialog().then(function (result) {
dialog = result;
return dialog.init();
}).then(function () {
return dialog.saveOverlay(14081);
}).then(function () {
dialog.destroy();
});
});
it('saveMap', function () {
helper.loginAsAdmin();
var dialog;
return createDialog().then(function (result) {
dialog = result;
return dialog.init();
}).then(function () {
return dialog.saveMap(15781);
}).then(function () {
dialog.destroy();
});
});
it('saveUser', function () {
it('update project rejected', function () {
helper.loginAsAdmin();
var dialog;
var dialog, input;
return createDialog().then(function (result) {
dialog = result;
return dialog.init();
}).then(function () {
return dialog.saveUser("anonymous");
input = $("[name='projectDisease']");
input.val("XYZ");
return input[0].parentNode.onchange();
}).then(function () {
dialog.destroy();
});
});
it('onSaveClicked', function () {
var dialog;
var project;
return ServerConnector.getProject().then(function (result) {
project = result;
project.setVersion("2.01");
return createDialog(project);
}).then(function (result) {
dialog = result;
return dialog.init();
assert.notOk("Error expected");
}).catch(function (e) {
assert.notEqual(input.val(), "XYZ", "After reject the data should be restored");
}).then(function () {
return dialog.onSaveClicked();
}).then(function (result) {
assert.ok(project === result);
dialog.destroy();
return dialog.destroy();
});
});
......
......@@ -69,12 +69,14 @@ describe('MapsAdminPanel', function () {
it('getDialog', function () {
helper.loginAsAdmin();
var mapTab = createMapsAdminPanel();
var project;
return mapTab.init().then(function () {
return ServerConnector.getProject();
}).then(function (project) {
}).then(function (result) {
project = result;
return mapTab.getDialog(project);
}).then(function (dialog) {
assert.ok(dialog.getListeners("onSave").length > 0);
assert.ok(project.getListeners("onreload").length > 0);
return mapTab.destroy();
});
});
......
......@@ -228,6 +228,8 @@ afterEach(function () {
document.body.innerHTML = "";
this.test.error(new Error("Test didn't left clean document. Found: " + content));
} else if ($._data(window, "events").resize) {
var events = $._data(window, "events").resize;
logger.warn(events);
$(window).off("resize");
this.test.error(new Error("Test didn't left clean resize events handlers."));
}
......
......@@ -491,4 +491,27 @@ public abstract class BaseRestImpl {
return result;
}
protected Integer parseInteger(Object value) throws QueryException {
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Double) {
logger.warn("Double will be trunceted to int: " + value);
return ((Double) value).intValue();
} else if (value == null) {
return null;
} else if (value instanceof String) {
if (((String) value).equalsIgnoreCase("")) {
return null;
} else {
try {
return Integer.parseInt((String) value);
} catch (NumberFormatException e) {
throw new QueryException("Invalid integer value: " + value);
}
}
} else {
throw new QueryException("Invalid integer value: " + value);