mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-21 23:46:21 +08:00
Merge branch 'gc_SCI_7442' of github.com:G-Chubinidze/scinote-web into gc_SCI_7442
This commit is contained in:
commit
3c23465114
|
@ -1,7 +1,10 @@
|
|||
/* global dropdownSelector */
|
||||
|
||||
(function() {
|
||||
function initNewMyModuleModal() {
|
||||
let experimentWrapper = '.experiment-new-my_module';
|
||||
let newMyModuleModal = '#new-my-module-modal';
|
||||
let myModuleUserSelector = '#my_module_user_ids';
|
||||
|
||||
// Modal's submit handler function
|
||||
$(experimentWrapper)
|
||||
|
@ -10,12 +13,19 @@
|
|||
})
|
||||
.on('ajax:error', newMyModuleModal, function(ev, data) {
|
||||
$(this).renderFormErrors('my_module', data.responseJSON);
|
||||
});
|
||||
|
||||
$(experimentWrapper)
|
||||
.on('ajax:success', '.new-my-module-button', function(ev, data) {
|
||||
})
|
||||
.on('submit', newMyModuleModal, function() {
|
||||
// To submit correct assigned user ids to new modal
|
||||
// Clear default selected user in dropdown
|
||||
$(`${myModuleUserSelector} option[value=${$('#new-my-module-modal').data('user-id')}]`)
|
||||
.prop('selected', false);
|
||||
$.map(dropdownSelector.getValues(myModuleUserSelector), function(val) {
|
||||
$(`${myModuleUserSelector} option[value=${val}]`).prop('selected', true);
|
||||
});
|
||||
})
|
||||
.on('ajax:success', '.new-my-module-button', function(ev, result) {
|
||||
// Add and show modal
|
||||
$(experimentWrapper).append($.parseHTML(data.html));
|
||||
$(experimentWrapper).append($.parseHTML(result.html));
|
||||
$(newMyModuleModal).modal('show');
|
||||
$(newMyModuleModal).find("input[type='text']").focus();
|
||||
|
||||
|
@ -23,6 +33,28 @@
|
|||
$(newMyModuleModal).on('hidden.bs.modal', function() {
|
||||
$(newMyModuleModal).remove();
|
||||
});
|
||||
|
||||
// initiaize user assing dropdown menu
|
||||
dropdownSelector.init(myModuleUserSelector, {
|
||||
closeOnSelect: true,
|
||||
labelHTML: true,
|
||||
tagClass: 'my-module-user-tags',
|
||||
tagLabel: (data) => {
|
||||
return `<img class="img-responsive block-inline" src="${data.params.avatar_url}" alt="${data.label}"/>
|
||||
<span style="user-full-name block-inline">${data.label}</span>`;
|
||||
},
|
||||
optionLabel: (data) => {
|
||||
if (data.params.avatar_url) {
|
||||
return `<span class="global-avatar-container" style="margin-top: 10px">
|
||||
<img src="${data.params.avatar_url}" alt="${data.label}"/></span>
|
||||
<span style="margin-left: 10px">${data.label}</span>`;
|
||||
}
|
||||
|
||||
return data.label;
|
||||
}
|
||||
});
|
||||
|
||||
dropdownSelector.selectValues(myModuleUserSelector, $('#new-my-module-modal').data('user-id'));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global I18n GLOBAL_CONSTANTS InfiniteScroll filterDropdown dropdownSelector HelperModule */
|
||||
/* global I18n GLOBAL_CONSTANTS InfiniteScroll initBSTooltips filterDropdown dropdownSelector HelperModule */
|
||||
|
||||
var ExperimnetTable = {
|
||||
permissions: ['editable', 'archivable', 'restorable', 'moveable'],
|
||||
|
@ -8,6 +8,7 @@ var ExperimnetTable = {
|
|||
selectedMyModules: [],
|
||||
activeFilters: {},
|
||||
filters: [], // Filter {name: '', init(), closeFilter(), apply(), active(), clearFilter()}
|
||||
myModulesCurrentSort: '',
|
||||
pageSize: GLOBAL_CONSTANTS.DEFAULT_ELEMENTS_PER_PAGE,
|
||||
getUrls: function(id) {
|
||||
return $(`.table-row[data-id="${id}"]`).data('urls');
|
||||
|
@ -20,15 +21,19 @@ var ExperimnetTable = {
|
|||
$(placeholder).insertAfter($(this.table).find('.table-body'));
|
||||
},
|
||||
appendRows: function(result) {
|
||||
$.each(result, (id, data) => {
|
||||
$.each(result, (_j, data) => {
|
||||
let row;
|
||||
|
||||
// Checkbox selector
|
||||
let row = `
|
||||
<div class="table-body-cell">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox my-module-selector" data-my-module="${id}">
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
</div>`;
|
||||
row = `
|
||||
<div class="table-body-cell">
|
||||
<div class="sci-checkbox-container">
|
||||
<div class="loading-overlay"></div>
|
||||
<input type="checkbox" class="sci-checkbox my-module-selector" data-my-module="${data.id}">
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Task columns
|
||||
$.each(data.columns, (_i, cell) => {
|
||||
let hidden = '';
|
||||
|
@ -53,10 +58,50 @@ var ExperimnetTable = {
|
|||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
$(`<div class="table-row" data-urls='${JSON.stringify(data.urls)}' data-id="${id}">${row}</div>`)
|
||||
|
||||
let tableRowClass = `table-row ${data.provisioning_status === 'in_progress' ? 'table-row-provisioning' : ''}`;
|
||||
$(`<div class="${tableRowClass}" data-urls='${JSON.stringify(data.urls)}' data-id="${data.id}">${row}</div>`)
|
||||
.appendTo(`${this.table} .table-body`);
|
||||
});
|
||||
},
|
||||
initDueDatePicker: function(data) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
$.each(data, (id, _) => {
|
||||
let element = `#calendarDueDate${id}`;
|
||||
let dueDateContainer = $(element).closest('#dueDateContainer');
|
||||
let dateText = $(element).closest('.date-text');
|
||||
let clearDate = $(element).closest('.datetime-container').find('.clear-date');
|
||||
|
||||
$(element).on('dp.change', function() {
|
||||
$.ajax({
|
||||
url: dueDateContainer.data('update-url'),
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { my_module: { due_date: $(element).val() } },
|
||||
success: function(result) {
|
||||
dueDateContainer.find('#dueDateLabelContainer').html(result.table_due_date_label.html);
|
||||
dateText.data('due-status', result.table_due_date_label.due_status);
|
||||
|
||||
if ($(result.table_due_date_label.html).data('due-date')) {
|
||||
clearDate.addClass('open');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(element).on('dp.hide', function() {
|
||||
dateText.attr('data-original-title', dateText.data('due-status'));
|
||||
clearDate.removeClass('open');
|
||||
});
|
||||
|
||||
$(element).on('dp.show', function() {
|
||||
dateText.attr('data-original-title', '').tooltip('hide');
|
||||
if (dueDateContainer.find('.due-date-label').data('due-date')) {
|
||||
clearDate.addClass('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
initMyModuleActions: function() {
|
||||
$(this.table).on('show.bs.dropdown', '.my-module-menu', (e) => {
|
||||
let menu = $(e.target).find('.dropdown-menu');
|
||||
|
@ -70,6 +115,41 @@ var ExperimnetTable = {
|
|||
e.preventDefault();
|
||||
this.archiveMyModules(e.target.href, e.target.dataset.id);
|
||||
});
|
||||
|
||||
|
||||
$(this.table).on('click', '.restore-my-module', (e) => {
|
||||
e.preventDefault();
|
||||
this.restoreMyModules(e.target.href, e.target.dataset.id);
|
||||
});
|
||||
|
||||
$(this.table).on('click', '.duplicate-my-module', (e) => {
|
||||
e.preventDefault();
|
||||
this.duplicateMyModules($('#duplicateTasks').data('url'), e.target.dataset.id);
|
||||
});
|
||||
|
||||
$(this.table).on('click', '.move-my-module', (e) => {
|
||||
e.preventDefault();
|
||||
this.openMoveModulesModal([e.target.dataset.id]);
|
||||
});
|
||||
|
||||
$(this.table).on('click', '.edit-my-module', (e) => {
|
||||
e.preventDefault();
|
||||
$('#modal-edit-module').modal('show');
|
||||
$('#modal-edit-module').data('id', e.target.dataset.id);
|
||||
$('#edit-module-name-input').val($(`#taskName${$('#modal-edit-module').data('id')}`).data('full-name'));
|
||||
});
|
||||
},
|
||||
initDuplicateMyModules: function() {
|
||||
$('#duplicateTasks').on('click', (e) => {
|
||||
this.duplicateMyModules(e.target.dataset.url, this.selectedMyModules);
|
||||
});
|
||||
},
|
||||
duplicateMyModules: function(url, ids) {
|
||||
$.post(url, { my_module_ids: ids }, () => {
|
||||
this.loadTable();
|
||||
}).error((data) => {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
},
|
||||
initArchiveMyModules: function() {
|
||||
$('#archiveTask').on('click', (e) => {
|
||||
|
@ -84,6 +164,14 @@ var ExperimnetTable = {
|
|||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
},
|
||||
initRestoreMyModules: function() {
|
||||
$('#restoreTask').on('click', (e) => {
|
||||
this.restoreMyModules(e.target.dataset.url, this.selectedMyModules);
|
||||
});
|
||||
},
|
||||
restoreMyModules: function(url, ids) {
|
||||
$.post(url, { my_modules_ids: ids, view: 'table' });
|
||||
},
|
||||
initAccessModal: function() {
|
||||
$('#manageTaskAccess').on('click', () => {
|
||||
$(`.table-row[data-id="${this.selectedMyModules[0]}"] .open-access-modal`).click();
|
||||
|
@ -92,23 +180,25 @@ var ExperimnetTable = {
|
|||
initRenameModal: function() {
|
||||
$('#editTask').on('click', () => {
|
||||
$('#modal-edit-module').modal('show');
|
||||
$('#edit-module-name-input').val($(`#taskName${this.selectedMyModules[0]}`).data('full-name'));
|
||||
$('#modal-edit-module').data('id', this.selectedMyModules[0]);
|
||||
$('#edit-module-name-input').val($(`#taskName${$('#modal-edit-module').data('id')}`).data('full-name'));
|
||||
});
|
||||
$('#modal-edit-module').on('click', 'button[data-action="confirm"]', () => {
|
||||
let id = $('#modal-edit-module').data('id');
|
||||
let newValue = $('#edit-module-name-input').val();
|
||||
|
||||
if (newValue === $(`#taskName${this.selectedMyModules[0]}`).data('full-name')) {
|
||||
if (newValue === $(`#taskName${id}`).data('full-name')) {
|
||||
$('#modal-edit-module').modal('hide');
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
url: this.getUrls(this.selectedMyModules[0]).name_update,
|
||||
url: this.getUrls(id).name_update,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { my_module: { name: $('#edit-module-name-input').val() } },
|
||||
success: () => {
|
||||
$(`#taskName${this.selectedMyModules[0]}`).text(newValue);
|
||||
$(`#taskName${this.selectedMyModules[0]}`).data('full-name', newValue);
|
||||
$(`#taskName${id}`).text(newValue);
|
||||
$(`#taskName${id}`).data('full-name', newValue);
|
||||
$('#edit-module-name-input').closest('.sci-input-container').removeClass('error');
|
||||
$('#modal-edit-module').modal('hide');
|
||||
},
|
||||
|
@ -133,7 +223,13 @@ var ExperimnetTable = {
|
|||
$(`
|
||||
<div class="user-container">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox user-selector" value="${user.value}">
|
||||
<input type="checkbox"
|
||||
class="sci-checkbox user-selector"
|
||||
${user.params.designated ? 'checked' : ''}
|
||||
value="${user.value}"
|
||||
data-assign-url="${user.params.assign_url}"
|
||||
data-unassign-url="${user.params.unassign_url}"
|
||||
>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
|
@ -147,6 +243,85 @@ var ExperimnetTable = {
|
|||
});
|
||||
});
|
||||
});
|
||||
$(this.table).on('click', '.dropdown-menu', (e) => {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
$(this.table).on('change keyup', '.assign-users-dropdown .user-search', (e) => {
|
||||
let query = e.target.value;
|
||||
let usersList = $(e.target).closest('.dropdown-menu').find('.user-container');
|
||||
$.each(usersList, (_i, user) => {
|
||||
$(user).removeClass('hidden');
|
||||
if (query.length && !$(user).find('.user-name').text().toLowerCase()
|
||||
.includes(query.toLowerCase())) {
|
||||
$(user).addClass('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
$(this.table).on('change', '.assign-users-dropdown .user-selector', (e) => {
|
||||
let checkbox = e.target;
|
||||
if (checkbox.checked) {
|
||||
$.post(checkbox.dataset.assignUrl, {
|
||||
table: true,
|
||||
user_my_module: {
|
||||
my_module_id: $(checkbox).closest('.table-row').data('id'),
|
||||
user_id: checkbox.value
|
||||
}
|
||||
}, (result) => {
|
||||
checkbox.dataset.unassignUrl = result.unassign_url;
|
||||
$(checkbox).closest('.table-row').find('.assigned-users-container')
|
||||
.replaceWith($(result.html).find('.assigned-users-container'));
|
||||
}).error((data) => {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.errors, 'danger');
|
||||
});
|
||||
} else {
|
||||
$.ajax({
|
||||
url: checkbox.dataset.unassignUrl,
|
||||
method: 'DELETE',
|
||||
data: { table: true },
|
||||
success: (result) => {
|
||||
$(checkbox).closest('.table-row').find('.assigned-users-container')
|
||||
.replaceWith($(result.html).find('.assigned-users-container'));
|
||||
},
|
||||
error: (data) => {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.errors, 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
initMoveModulesModal: function() {
|
||||
$('#moveTask').on('click', () => {
|
||||
this.openMoveModulesModal(this.selectedMyModules);
|
||||
});
|
||||
},
|
||||
openMoveModulesModal: function(ids) {
|
||||
let table = $(this.table);
|
||||
$.get(table.data('move-modules-modal-url'), (modalData) => {
|
||||
if ($('#modal-move-modules').length > 0) {
|
||||
$('#modal-move-modules').replaceWith(modalData.html);
|
||||
} else {
|
||||
$('#experimentTable').append(modalData.html);
|
||||
}
|
||||
$('#modal-move-modules').on('shown.bs.modal', function() {
|
||||
$(this).find('.selectpicker').selectpicker().focus();
|
||||
});
|
||||
$('#modal-move-modules').on('click', 'button[data-action="confirm"]', () => {
|
||||
let moveParams = {
|
||||
to_experiment_id: $('#modal-move-modules').find('.selectpicker').val(),
|
||||
my_module_ids: ids
|
||||
};
|
||||
$.post(table.data('move-modules-url'), moveParams, (data) => {
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
this.loadTable();
|
||||
}).error((data) => {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
$('#modal-move-modules').modal('hide');
|
||||
});
|
||||
$('#modal-move-modules').modal('show');
|
||||
});
|
||||
},
|
||||
checkActionPermission: function(permission) {
|
||||
let allMyModules;
|
||||
|
@ -229,7 +404,8 @@ var ExperimnetTable = {
|
|||
$.each($('.table-display-modal .fa-eye-slash'), (_i, column) => {
|
||||
$(column).parent().removeClass('visible');
|
||||
});
|
||||
$('.experiment-table')[0].style.setProperty('--columns-count', $('.table-display-modal .fa-eye').length + 1);
|
||||
$('.experiment-table')[0].style
|
||||
.setProperty('--columns-count', $('.table-display-modal .fa-eye:not(.disabled)').length + 1);
|
||||
|
||||
$('.table-display-modal').on('click', '.column-container .fas', (e) => {
|
||||
let icon = $(e.target);
|
||||
|
@ -247,7 +423,8 @@ var ExperimnetTable = {
|
|||
// Update columns on backend - $.post('', { columns: visibleColumns }, () => {});
|
||||
$.post($('.table-display-modal').data('column-state-url'), { columns: visibleColumns }, () => {});
|
||||
|
||||
$('.experiment-table')[0].style.setProperty('--columns-count', $('.table-display-modal .fa-eye').length + 1);
|
||||
$('.experiment-table')[0].style
|
||||
.setProperty('--columns-count', $('.table-display-modal .fa-eye:not(.disabled)').length + 1);
|
||||
});
|
||||
},
|
||||
initNewTaskModal: function(table) {
|
||||
|
@ -255,6 +432,18 @@ var ExperimnetTable = {
|
|||
table.loadTable();
|
||||
});
|
||||
},
|
||||
initSorting: function(table) {
|
||||
$('#sortMenuDropdown a').click(function() {
|
||||
if (table.myModulesCurrentSort !== $(this).data('sort')) {
|
||||
$('#sortMenuDropdown a').removeClass('selected');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
table.myModulesCurrentSort = $(this).data('sort');
|
||||
table.loadTable();
|
||||
$(this).addClass('selected');
|
||||
$('#sortMenu').dropdown('toggle');
|
||||
}
|
||||
});
|
||||
},
|
||||
initFilters: function() {
|
||||
this.filterDropdown = filterDropdown.init();
|
||||
let $experimentFilter = $('#experimentTable .my-modules-filters');
|
||||
|
@ -291,11 +480,16 @@ var ExperimnetTable = {
|
|||
});
|
||||
},
|
||||
loadTable: function() {
|
||||
var tableParams = {
|
||||
filters: this.activeFilters,
|
||||
sort: this.myModulesCurrentSort
|
||||
};
|
||||
var dataUrl = $(this.table).data('my-modules-url');
|
||||
this.loadPlaceholder();
|
||||
$.get(dataUrl, { filters: this.activeFilters }, (result) => {
|
||||
$.get(dataUrl, tableParams, (result) => {
|
||||
$(this.table).find('.table-row').remove();
|
||||
this.appendRows(result.data);
|
||||
this.initDueDatePicker(result.data);
|
||||
InfiniteScroll.init(this.table, {
|
||||
url: dataUrl,
|
||||
eventTarget: window,
|
||||
|
@ -305,30 +499,75 @@ var ExperimnetTable = {
|
|||
lastPage: !result.next_page,
|
||||
customResponse: (response) => {
|
||||
this.appendRows(response.data);
|
||||
this.initDueDatePicker(response.data);
|
||||
},
|
||||
customParams: (params) => {
|
||||
return { ...params, ...{ filters: this.activeFilters } };
|
||||
return { ...params, ...tableParams };
|
||||
}
|
||||
});
|
||||
|
||||
initBSTooltips();
|
||||
this.initProvisioningStatusPolling();
|
||||
});
|
||||
},
|
||||
initProvisioningStatusPolling: function() {
|
||||
let provisioningStatusUrls = $('.table-row-provisioning').toArray()
|
||||
.map((u) => $(u).data('urls').provisioning_status);
|
||||
|
||||
this.provisioningMyModulesCount = provisioningStatusUrls.length;
|
||||
|
||||
if (this.provisioningMyModulesCount > 0) this.pollProvisioningStatuses(provisioningStatusUrls);
|
||||
},
|
||||
pollProvisioningStatuses: function(provisioningStatusUrls) {
|
||||
let remainingUrls = [];
|
||||
|
||||
provisioningStatusUrls.forEach((url) => {
|
||||
jQuery.ajax({
|
||||
url: url,
|
||||
success: (data) => {
|
||||
if (data.provisioning_status === 'in_progress') remainingUrls.push(url);
|
||||
},
|
||||
async: false
|
||||
});
|
||||
});
|
||||
|
||||
if (remainingUrls.length > 0) {
|
||||
setTimeout(() => {
|
||||
this.pollProvisioningStatuses(remainingUrls);
|
||||
}, 10000);
|
||||
} else {
|
||||
HelperModule.flashAlertMsg(
|
||||
I18n.t('experiments.duplicate_tasks.success', { count: this.provisioningMyModulesCount }),
|
||||
'success'
|
||||
);
|
||||
this.loadTable();
|
||||
}
|
||||
},
|
||||
init: function() {
|
||||
this.initSelector();
|
||||
this.initSelectAllCheckbox();
|
||||
this.initFilters();
|
||||
this.initSorting(this);
|
||||
this.loadTable();
|
||||
this.initRenameModal();
|
||||
this.initAccessModal();
|
||||
this.initDuplicateMyModules();
|
||||
this.initMoveModulesModal();
|
||||
this.initArchiveMyModules();
|
||||
this.initManageColumnsModal();
|
||||
this.initNewTaskModal(this);
|
||||
this.initMyModuleActions();
|
||||
this.updateExperimentToolbar();
|
||||
this.initRestoreMyModules();
|
||||
this.initManageUsersDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
ExperimnetTable.render.task_name = function(data) {
|
||||
if (data.provisioning_status === 'in_progress') {
|
||||
return `<span data-full-name="${data.name}">${data.name}</span>`;
|
||||
}
|
||||
|
||||
return `<a href="${data.url}" id="taskName${data.id}" data-full-name="${data.name}">${data.name}</a>`;
|
||||
};
|
||||
|
||||
|
@ -339,7 +578,7 @@ ExperimnetTable.render.id = function(data) {
|
|||
};
|
||||
|
||||
ExperimnetTable.render.due_date = function(data) {
|
||||
return data;
|
||||
return data.data;
|
||||
};
|
||||
|
||||
ExperimnetTable.render.archived = function(data) {
|
||||
|
@ -359,49 +598,7 @@ ExperimnetTable.render.status = function(data) {
|
|||
};
|
||||
|
||||
ExperimnetTable.render.assigned = function(data) {
|
||||
let users = '';
|
||||
|
||||
$.each(data.users, (i, user) => {
|
||||
users += `
|
||||
<span class="avatar-container" style="z-index: ${5 - i}">
|
||||
<img src=${user.image_url} title=${user.title}>
|
||||
</span>
|
||||
`;
|
||||
});
|
||||
|
||||
if (data.count > 4) {
|
||||
users += `
|
||||
<span class="more-users avatar-container" title="${data.more_users_title}">
|
||||
+${data.count - 4}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
if (data.create_url) {
|
||||
users += `
|
||||
<span class="new-user avatar-container">
|
||||
<i class="fas fa-plus"></i>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div ref="dropdown"
|
||||
class="assign-users-dropdown dropdown"
|
||||
>
|
||||
<div class="assigned-users-container" data-toggle="dropdown" >
|
||||
${users}
|
||||
</div>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<div class="sci-input-container left-icon">
|
||||
<input type="text" class="sci-input-field" placeholder="${I18n.t('experiments.table.search')}"></input>
|
||||
<i class="fas fa-search"></i>
|
||||
</div>
|
||||
<div class="users-list" data-list-url="${data.list_url}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return data.html;
|
||||
};
|
||||
|
||||
ExperimnetTable.render.tags = function(data) {
|
||||
|
@ -469,6 +666,36 @@ ExperimnetTable.filters.push({
|
|||
}
|
||||
});
|
||||
|
||||
ExperimnetTable.filters.push({
|
||||
name: 'archived_on_from',
|
||||
init: () => {},
|
||||
closeFilter: () => {},
|
||||
apply: ($container) => {
|
||||
return ExperimnetTable.selectDate($('.archived-on-filter .from-date', $container));
|
||||
},
|
||||
active: (value) => { return value; },
|
||||
clearFilter: ($container) => {
|
||||
if ($('.archived-on-filter .from-date', $container).data('DateTimePicker')) {
|
||||
$('.archived-on-filter .from-date', $container).data('DateTimePicker').clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ExperimnetTable.filters.push({
|
||||
name: 'archived_on_to',
|
||||
init: () => {},
|
||||
closeFilter: () => {},
|
||||
apply: ($container) => {
|
||||
return ExperimnetTable.selectDate($('.archived-on-filter .to-date', $container));
|
||||
},
|
||||
active: (value) => { return value; },
|
||||
clearFilter: ($container) => {
|
||||
if ($('.archived-on-filter .to-date', $container).data('DateTimePicker')) {
|
||||
$('.archived-on-filter .to-date', $container).data('DateTimePicker').clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ExperimnetTable.filters.push({
|
||||
name: 'assigned_users',
|
||||
init: ($container) => {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
dt.data('DateTimePicker').show();
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-toggle="clear-date-time-picker"]', function() {
|
||||
$(document).on('mousedown', '[data-toggle="clear-date-time-picker"]', function() {
|
||||
let dt = $(`#${$(this).data('target')}`);
|
||||
if (!dt.data('DateTimePicker')) dt.datetimepicker({ useCurrent: false });
|
||||
dt.data('DateTimePicker').clear();
|
||||
|
|
|
@ -21,3 +21,28 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#new-my-module-modal {
|
||||
.form-control {
|
||||
border-color: $color-silver-chalice;
|
||||
}
|
||||
|
||||
.my-module-user-tags {
|
||||
img {
|
||||
border-radius: 50%;
|
||||
display: inline;
|
||||
margin-right: .5em;
|
||||
max-height: 20px;
|
||||
max-width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.datetime-picker-container {
|
||||
width: 45%;
|
||||
|
||||
.fa-calendar-alt {
|
||||
color: $color-volcano !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,22 @@
|
|||
--toolbar-height: 4.5em;
|
||||
position: relative;
|
||||
|
||||
.title-row {
|
||||
.header-actions {
|
||||
&.experiment-header {
|
||||
column-gap: .25em;
|
||||
}
|
||||
|
||||
.sort-task-menu {
|
||||
&:not(.archived) {
|
||||
[data-view-mode="archived"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.experiment-table-container {
|
||||
height: calc(100vh - var(--content-header-size) - var(--navbar-height) - var(--toolbar-height));
|
||||
overflow: auto;
|
||||
|
@ -69,6 +85,31 @@
|
|||
display: contents;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-row-provisioning {
|
||||
.loading-overlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sci-checkbox-container {
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
|
||||
.loading-overlay::after {
|
||||
background-size: 1.5em;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.sci-checkbox,
|
||||
.sci-checkbox-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-body-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -137,7 +178,7 @@
|
|||
}
|
||||
|
||||
.users-list {
|
||||
max-height: 400px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
@ -148,6 +189,10 @@
|
|||
|
||||
.user-avatar {
|
||||
padding: 0 .75em;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,6 +369,59 @@
|
|||
min-width: 16px;
|
||||
}
|
||||
|
||||
.datetime-container {
|
||||
width: 100%;
|
||||
|
||||
.clear-date {
|
||||
cursor: pointer;
|
||||
left: 90%;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
visibility: hidden;
|
||||
width: 16px;
|
||||
z-index: 999;
|
||||
|
||||
&.open {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.date-text {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
.alert-yellow {
|
||||
color: $brand-warning;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.alert-red {
|
||||
color: $brand-danger;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.datetime-picker-container {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
.calendar-due-date {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.date-text[data-editable=true] {
|
||||
background-color: $color-concrete;
|
||||
border-radius: 4px;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.open-comments-sidebar {
|
||||
display: contents;
|
||||
margin-bottom: 0;
|
||||
|
@ -374,16 +472,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.datetime-picker-container {
|
||||
width: 45%;
|
||||
|
||||
.fa-calendar-alt {
|
||||
color: $color-volcano !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.task_name-column span {
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
|
||||
|
||||
.table-display-modal {
|
||||
.column-container {
|
||||
align-items: center;
|
||||
|
@ -402,6 +494,11 @@
|
|||
.fas {
|
||||
cursor: pointer;
|
||||
margin-right: 1em;
|
||||
|
||||
&.disabled {
|
||||
color: $color-alto;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.task_name {
|
||||
|
|
|
@ -49,10 +49,6 @@
|
|||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
||||
&.experiment-header {
|
||||
column-gap: .25em;
|
||||
}
|
||||
}
|
||||
|
||||
.view-switch {
|
||||
|
|
|
@ -12,7 +12,7 @@ class ExperimentsController < ApplicationController
|
|||
before_action :check_read_permissions, except: %i(edit archive clone move new create archive_group restore_group)
|
||||
before_action :check_canvas_read_permissions, only: %i(canvas)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_manage_permissions, only: %i(edit)
|
||||
before_action :check_manage_permissions, only: %i(edit batch_clone_my_modules)
|
||||
before_action :check_update_permissions, only: %i(update)
|
||||
before_action :check_archive_permissions, only: :archive
|
||||
before_action :check_clone_permissions, only: %i(clone_modal clone)
|
||||
|
@ -90,15 +90,19 @@ class ExperimentsController < ApplicationController
|
|||
|
||||
def table
|
||||
redirect_to module_archive_experiment_path(@experiment) if @experiment.archived_branch?
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_mode = params[:view_mode] || 'active'
|
||||
@current_sort = view_state.state.dig('my_modules', view_mode, 'sort') || 'atoz'
|
||||
|
||||
@project = @experiment.project
|
||||
@active_modules = @experiment.my_modules.active.order(:name)
|
||||
@my_module_visible_table_columns = current_user.settings['visible_my_module_table_columns'].presence || []
|
||||
end
|
||||
|
||||
def load_table
|
||||
my_modules = @experiment.my_modules
|
||||
my_modules = @experiment.my_modules.readable_by_user(current_user)
|
||||
my_modules = params[:view_mode] == 'archived' ? my_modules.archived : my_modules.active
|
||||
render json: Experiments::TableViewService.new(my_modules, current_user, params).call
|
||||
render json: Experiments::TableViewService.new(@experiment, my_modules, current_user, params).call
|
||||
end
|
||||
|
||||
def edit
|
||||
|
@ -281,6 +285,43 @@ class ExperimentsController < ApplicationController
|
|||
render json: { message: message, path: path }, status: status
|
||||
end
|
||||
|
||||
def move_modules_modal
|
||||
@experiments = @experiment.project.experiments.active.where.not(id: @experiment)
|
||||
.managable_by_user(current_user).order(name: :asc)
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'move_modules_modal.html.erb'
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def move_modules
|
||||
modules_to_move = {}
|
||||
dst_experiment = @experiment.project.experiments.find(params[:to_experiment_id])
|
||||
return render_403 unless can_manage_experiment?(dst_experiment)
|
||||
|
||||
@experiment.with_lock do
|
||||
params[:my_module_ids].each do |id|
|
||||
my_module = @experiment.my_modules.find(id)
|
||||
return render_403 unless can_move_my_module?(my_module)
|
||||
|
||||
modules_to_move[id] = dst_experiment.id
|
||||
end
|
||||
@experiment.move_modules(modules_to_move, current_user)
|
||||
render json: { message: t('experiments.table.modal_move_modules.success_flash',
|
||||
experiment: sanitize_input(dst_experiment.name)) }
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
render json: {
|
||||
message: t('experiments.table.modal_move_modules.error_flash', experiment: sanitize_input(dst_experiment.name))
|
||||
}, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def module_archive
|
||||
@project = @experiment.project
|
||||
@my_modules = @experiment.archived_branch? ? @experiment.my_modules : @experiment.my_modules.archived
|
||||
|
@ -366,6 +407,32 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def batch_clone_my_modules
|
||||
MyModule.transaction do
|
||||
@my_modules =
|
||||
@experiment.my_modules
|
||||
.readable_by_user(current_user)
|
||||
.where(id: params[:my_module_ids])
|
||||
|
||||
@my_modules.find_each do |my_module|
|
||||
new_my_module = my_module.dup
|
||||
new_my_module.update!(
|
||||
{
|
||||
provisioning_status: :in_progress,
|
||||
name: my_module.next_clone_name
|
||||
}.merge(new_my_module.get_new_position)
|
||||
)
|
||||
MyModules::CopyContentJob.perform_later(current_user, my_module.id, new_my_module.id)
|
||||
end
|
||||
end
|
||||
|
||||
render(
|
||||
json: {
|
||||
provisioning_status_urls: @my_modules.map { |m| provisioning_status_my_module_url(m) }
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_experiment
|
||||
|
|
|
@ -5,16 +5,17 @@ class MyModulesController < ApplicationController
|
|||
include Rails.application.routes.url_helpers
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ApplicationHelper
|
||||
include MyModulesHelper
|
||||
|
||||
before_action :load_vars, except: %i(restore_group save_table_state)
|
||||
before_action :load_vars, except: %i(restore_group create new save_table_state)
|
||||
before_action :load_experiment, only: %i(create new)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_archive_permissions, only: %i(update)
|
||||
before_action :check_manage_permissions, only: %i(
|
||||
create description due_date update_description update_protocol_description update_protocol
|
||||
)
|
||||
before_action :check_read_permissions, except: %i(
|
||||
update update_description update_protocol_description restore_group save_table_state
|
||||
description due_date update_description update_protocol_description update_protocol
|
||||
)
|
||||
before_action :check_read_permissions, except: %i(create new update update_description
|
||||
update_protocol_description restore_group save_table_state)
|
||||
before_action :check_update_state_permissions, only: :update_state
|
||||
before_action :set_inline_name_editing, only: %i(protocols results activities archive)
|
||||
before_action :load_experiment_my_modules, only: %i(protocols results activities archive)
|
||||
|
@ -23,9 +24,12 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def new
|
||||
@my_module = @experiment.my_modules.new
|
||||
assigned_users = User.where(id: @experiment.user_assignments.select(:user_id))
|
||||
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'my_modules/modals/new_modal.html.erb', locals: { view_mode: params[:view_mode] }
|
||||
partial: 'my_modules/modals/new_modal.html.erb', locals: { view_mode: params[:view_mode],
|
||||
users: assigned_users }
|
||||
)
|
||||
}
|
||||
end
|
||||
|
@ -233,6 +237,11 @@ class MyModulesController < ApplicationController
|
|||
partial: 'my_modules/card_due_date_label.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
),
|
||||
table_due_date_label: {
|
||||
html: render_to_string(partial: 'experiments/table_due_date_label.html.erb',
|
||||
locals: { my_module: @my_module, user: current_user }),
|
||||
due_status: my_module_due_status(@my_module)
|
||||
},
|
||||
module_header_due_date: render_to_string(
|
||||
partial: 'my_modules/module_header_due_date.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
|
@ -377,7 +386,12 @@ class MyModulesController < ApplicationController
|
|||
else
|
||||
flash[:error] = t('my_modules.restore_group.error_flash')
|
||||
end
|
||||
redirect_to module_archive_experiment_path(experiment)
|
||||
|
||||
if params[:view] == 'table'
|
||||
redirect_to table_experiment_path(experiment, view_mode: :archived)
|
||||
else
|
||||
redirect_to module_archive_experiment_path(experiment)
|
||||
end
|
||||
end
|
||||
|
||||
def update_state
|
||||
|
@ -414,6 +428,10 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def provisioning_status
|
||||
render json: { provisioning_status: @my_module.provisioning_status }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
|
@ -426,6 +444,11 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def load_experiment
|
||||
@experiment = Experiment.preload(user_assignments: %i(user user_role)).find_by(id: params[:id])
|
||||
render_404 unless @experiment
|
||||
end
|
||||
|
||||
def load_experiment_my_modules
|
||||
@experiment_my_modules = if @my_module.experiment.archived_branch?
|
||||
@my_module.experiment.my_modules.order(:name)
|
||||
|
|
|
@ -61,14 +61,22 @@ class UserMyModulesController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
user: {
|
||||
id: @um.user.id,
|
||||
full_name: @um.user.full_name,
|
||||
avatar_url: avatar_path(@um.user, :icon_small),
|
||||
user_module_id: @um.id
|
||||
}, status: :ok
|
||||
}
|
||||
if params[:table]
|
||||
render json: {
|
||||
html: render_to_string(partial: 'experiments/assigned_users.html.erb',
|
||||
locals: { my_module: @my_module, user: current_user }),
|
||||
unassign_url: my_module_user_my_module_path(@my_module, @um)
|
||||
}
|
||||
else
|
||||
render json: {
|
||||
user: {
|
||||
id: @um.user.id,
|
||||
full_name: @um.user.full_name,
|
||||
avatar_url: avatar_path(@um.user, :icon_small),
|
||||
user_module_id: @um.id
|
||||
}, status: :ok
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -88,7 +96,14 @@ class UserMyModulesController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {}, status: :ok
|
||||
if params[:table]
|
||||
render json: {
|
||||
html: render_to_string(partial: 'experiments/assigned_users.html.erb',
|
||||
locals: { my_module: @my_module, user: current_user })
|
||||
}
|
||||
else
|
||||
render json: {}, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -107,20 +122,33 @@ class UserMyModulesController < ApplicationController
|
|||
.joins("LEFT OUTER JOIN user_my_modules ON user_my_modules.user_id = users.id "\
|
||||
"AND user_my_modules.my_module_id = #{@my_module.id}")
|
||||
.search(false, params[:query])
|
||||
.order(:full_name)
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.select('users.*')
|
||||
.select('users.*', 'user_my_modules.id as user_my_module_id')
|
||||
.select('CASE WHEN user_my_modules.id IS NOT NULL '\
|
||||
'THEN true ELSE false END as designated')
|
||||
|
||||
users = users.map do |user|
|
||||
{
|
||||
next if params[:skip_assigned] && user.designated
|
||||
|
||||
user_hash = {
|
||||
value: user.id,
|
||||
label: sanitize_input(user.full_name),
|
||||
params: { avatar_url: avatar_path(user, :icon_small), designated: user.designated }
|
||||
params: {
|
||||
avatar_url: avatar_path(user, :icon_small),
|
||||
designated: user.designated,
|
||||
assign_url: my_module_user_my_modules_path(@my_module)
|
||||
}
|
||||
}
|
||||
|
||||
if user.designated
|
||||
user_hash[:params][:unassign_url] = my_module_user_my_module_path(@my_module, user.user_my_module_id)
|
||||
end
|
||||
|
||||
user_hash
|
||||
end
|
||||
|
||||
render json: users
|
||||
render json: users.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -100,4 +100,14 @@ module MyModulesHelper
|
|||
my_module.experiment.project.archived_on
|
||||
end
|
||||
end
|
||||
|
||||
def my_module_due_status(my_module, datetime = DateTime.current)
|
||||
if my_module.is_overdue?(datetime)
|
||||
I18n.t('my_modules.details.overdue')
|
||||
elsif my_module.is_one_day_prior?(datetime)
|
||||
I18n.t('my_modules.details.due_soon')
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
15
app/jobs/my_modules/copy_content_job.rb
Normal file
15
app/jobs/my_modules/copy_content_job.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MyModules
|
||||
class CopyContentJob < ApplicationJob
|
||||
def perform(user, source_my_module_id, target_my_module_id)
|
||||
MyModule.transaction do
|
||||
target_my_module = MyModule.find(target_my_module_id)
|
||||
MyModule.find(source_my_module_id).copy_content(user, target_my_module)
|
||||
target_my_module.update!(provisioning_status: :done)
|
||||
end
|
||||
rescue StandardError => _e
|
||||
target_my_module.update(provisioning_status: :failed)
|
||||
end
|
||||
end
|
||||
end
|
19
app/models/concerns/cloneable.rb
Normal file
19
app/models/concerns/cloneable.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Cloneable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def next_clone_name
|
||||
raise NotImplementedError, "Cloneable model must implement the '.parent' method!" unless respond_to?(:parent)
|
||||
|
||||
clone_label = I18n.t('general.clone_label')
|
||||
last_clone_number =
|
||||
parent.public_send(self.class.table_name)
|
||||
.select("substring(#{self.class.table_name}.name, '(?:^#{clone_label} )(\\d+)')::int AS clone_number")
|
||||
.where('name ~ ?', "^#{clone_label} \\d+ - #{name}$")
|
||||
.order(clone_number: :asc)
|
||||
.last&.clone_number
|
||||
|
||||
"#{clone_label} #{(last_clone_number || 0) + 1} - #{name}".truncate(Constants::NAME_MAX_LENGTH)
|
||||
end
|
||||
end
|
|
@ -9,8 +9,10 @@ class Experiment < ApplicationRecord
|
|||
include ArchivableModel
|
||||
include SearchableModel
|
||||
include SearchableByNameModel
|
||||
include ViewableModel
|
||||
include PermissionCheckableModel
|
||||
include Assignable
|
||||
include Cloneable
|
||||
|
||||
before_save -> { report_elements.destroy_all }, if: -> { !new_record? && project_id_changed? }
|
||||
|
||||
|
@ -93,6 +95,23 @@ class Experiment < ApplicationRecord
|
|||
joins(:project).where(project: { team: teams })
|
||||
end
|
||||
|
||||
def default_view_state
|
||||
{
|
||||
my_modules: {
|
||||
active: { sort: 'atoz' },
|
||||
archived: { sort: 'atoz' }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def validate_view_state(view_state)
|
||||
if %w(atoz ztoa due_first due_last).exclude?(view_state.state.dig('my_modules', 'active', 'sort')) ||
|
||||
%w(atoz ztoa due_first due_last
|
||||
archived_old archived_new).exclude?(view_state.state.dig('my_modules', 'archived', 'sort'))
|
||||
view_state.errors.add(:state, :wrong_state)
|
||||
end
|
||||
end
|
||||
|
||||
def connections
|
||||
Connection.joins(
|
||||
'LEFT JOIN my_modules AS inputs ON input_id = inputs.id'
|
||||
|
@ -224,11 +243,13 @@ class Experiment < ApplicationRecord
|
|||
.with_granted_permissions(current_user, ProjectPermissions::EXPERIMENTS_CREATE)
|
||||
end
|
||||
|
||||
def permission_parent
|
||||
def parent
|
||||
project
|
||||
end
|
||||
|
||||
private
|
||||
def permission_parent
|
||||
project
|
||||
end
|
||||
|
||||
# Archive all modules. Receives an array of module integer IDs
|
||||
# and current user.
|
||||
|
@ -520,6 +541,8 @@ class Experiment < ApplicationRecord
|
|||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_activity(type_of, current_user, my_module)
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
|
|
|
@ -9,10 +9,12 @@ class MyModule < ApplicationRecord
|
|||
include TinyMceImages
|
||||
include PermissionCheckableModel
|
||||
include Assignable
|
||||
include Cloneable
|
||||
|
||||
attr_accessor :transition_error_rollback
|
||||
|
||||
enum state: Extends::TASKS_STATES
|
||||
enum provisioning_status: { done: 0, in_progress: 1, failed: 2 }
|
||||
|
||||
before_validation :archiving_and_restoring_extras, on: :update, if: :archived_changed?
|
||||
before_save -> { report_elements.destroy_all }, if: -> { !new_record? && experiment_id_changed? }
|
||||
|
@ -124,6 +126,10 @@ class MyModule < ApplicationRecord
|
|||
joins(experiment: :project).where(experiment: { projects: { team: teams } })
|
||||
end
|
||||
|
||||
def parent
|
||||
experiment
|
||||
end
|
||||
|
||||
def navigable?
|
||||
!experiment.archived? && experiment.navigable?
|
||||
end
|
||||
|
@ -381,25 +387,29 @@ class MyModule < ApplicationRecord
|
|||
|
||||
clone.assign_user(current_user)
|
||||
|
||||
copy_content(current_user, clone)
|
||||
|
||||
clone
|
||||
end
|
||||
|
||||
def copy_content(current_user, target_my_module)
|
||||
# Remove the automatically generated protocol,
|
||||
# & clone the protocol instead
|
||||
clone.protocol.destroy
|
||||
clone.reload
|
||||
target_my_module.protocol.destroy
|
||||
target_my_module.reload
|
||||
|
||||
# Update the cloned protocol if neccesary
|
||||
clone_tinymce_assets(clone, clone.experiment.project.team)
|
||||
clone.protocols << protocol.deep_clone_my_module(self, current_user)
|
||||
clone.reload
|
||||
clone_tinymce_assets(clone, target_my_module.experiment.project.team)
|
||||
target_my_module.protocols << protocol.deep_clone_my_module(self, current_user)
|
||||
target_my_module.reload
|
||||
|
||||
# fixes linked protocols
|
||||
clone.protocols.each do |protocol|
|
||||
target_my_module.protocols.each do |protocol|
|
||||
next unless protocol.linked?
|
||||
|
||||
protocol.updated_at = protocol.parent_updated_at
|
||||
protocol.save
|
||||
end
|
||||
|
||||
clone
|
||||
end
|
||||
|
||||
# Find an empty position for the restored module. It's
|
||||
|
|
|
@ -21,7 +21,7 @@ module Experiments
|
|||
|
||||
ActiveRecord::Base.transaction do
|
||||
@c_exp = Experiment.new(
|
||||
name: find_uniq_name,
|
||||
name: @exp.next_clone_name,
|
||||
description: @exp.description,
|
||||
created_by: @user,
|
||||
last_modified_by: @user,
|
||||
|
@ -54,15 +54,6 @@ module Experiments
|
|||
|
||||
private
|
||||
|
||||
def find_uniq_name
|
||||
experiment_names = @project.experiments.map(&:name)
|
||||
format = 'Clone %d - %s'
|
||||
free_index = 1
|
||||
free_index += 1 while experiment_names
|
||||
.include?(format(format, free_index, @exp.name))
|
||||
format(format, free_index, @exp.name).truncate(Constants::NAME_MAX_LENGTH)
|
||||
end
|
||||
|
||||
def valid?
|
||||
unless @exp && @project && @user
|
||||
@errors[:invalid_arguments] =
|
||||
|
|
|
@ -7,7 +7,10 @@ module Experiments
|
|||
include CommentHelper
|
||||
include ProjectsHelper
|
||||
include InputSanitizeHelper
|
||||
include BootstrapFormHelper
|
||||
include MyModulesHelper
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
COLUMNS = %i(
|
||||
task_name
|
||||
|
@ -32,20 +35,24 @@ module Experiments
|
|||
experiment: :project
|
||||
}
|
||||
|
||||
def initialize(my_modules, user, params)
|
||||
def initialize(experiment, my_modules, user, params)
|
||||
@my_modules = my_modules
|
||||
@page = params[:page] || 1
|
||||
@user = user
|
||||
@filters = params[:filters] || []
|
||||
@params = params
|
||||
initialize_table_sorting(experiment)
|
||||
end
|
||||
|
||||
def call
|
||||
result = {}
|
||||
result = []
|
||||
my_module_list = @my_modules
|
||||
@filters.each do |name, value|
|
||||
my_module_list = __send__("#{name}_filter", my_module_list, value) if value.present?
|
||||
end
|
||||
|
||||
my_module_list = sort_records(my_module_list)
|
||||
|
||||
my_module_list = my_module_list.includes(PRELOAD)
|
||||
.select('my_modules.*')
|
||||
.group('my_modules.id')
|
||||
|
@ -64,15 +71,20 @@ module Experiments
|
|||
experiment = my_module.experiment
|
||||
project = experiment.project
|
||||
|
||||
result[my_module.id] = {
|
||||
columns: prepared_my_module,
|
||||
urls: {
|
||||
permissions: permissions_my_module_path(my_module),
|
||||
actions_dropdown: actions_dropdown_my_module_path(my_module),
|
||||
name_update: my_module_path(my_module),
|
||||
access: edit_access_permissions_project_experiment_my_module_path(project, experiment, my_module)
|
||||
}
|
||||
}
|
||||
result.push({ id: my_module.id,
|
||||
columns: prepared_my_module,
|
||||
provisioning_status: my_module.provisioning_status,
|
||||
urls: {
|
||||
permissions: permissions_my_module_path(my_module),
|
||||
actions_dropdown: actions_dropdown_my_module_path(my_module),
|
||||
name_update: my_module_path(my_module),
|
||||
restore: restore_my_modules_experiment_path(experiment),
|
||||
provisioning_status:
|
||||
my_module.provisioning_status == 'in_progress' &&
|
||||
provisioning_status_my_module_url(my_module),
|
||||
access: edit_access_permissions_project_experiment_my_module_path(project,
|
||||
experiment, my_module)
|
||||
} })
|
||||
end
|
||||
|
||||
{
|
||||
|
@ -87,6 +99,7 @@ module Experiments
|
|||
{
|
||||
id: my_module.id,
|
||||
name: my_module.name,
|
||||
provisioning_status: my_module.provisioning_status,
|
||||
url: protocols_my_module_path(my_module)
|
||||
}
|
||||
end
|
||||
|
@ -98,11 +111,14 @@ module Experiments
|
|||
end
|
||||
|
||||
def due_date_presenter(my_module)
|
||||
if my_module.due_date
|
||||
I18n.l(my_module.due_date, format: :full_date)
|
||||
else
|
||||
''
|
||||
end
|
||||
{
|
||||
id: my_module.id,
|
||||
data: ApplicationController.renderer.render(
|
||||
partial: 'experiments/table_due_date.html.erb',
|
||||
locals: { my_module: my_module,
|
||||
user: @user }
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def archived_presenter(my_module)
|
||||
|
@ -132,25 +148,10 @@ module Experiments
|
|||
end
|
||||
|
||||
def assigned_presenter(my_module)
|
||||
users = my_module.designated_users
|
||||
result = {
|
||||
count: users.length,
|
||||
users: []
|
||||
}
|
||||
users[0..3].each do |user|
|
||||
result[:users].push({
|
||||
image_url: avatar_path(user, :icon_small),
|
||||
title: user.full_name
|
||||
})
|
||||
end
|
||||
|
||||
result[:more_users_title] = users[4..].map(&:full_name).join('
') if users.length > 4
|
||||
result[:list_url] = search_my_module_user_my_module_path(my_module, my_module_id: my_module.id)
|
||||
if can_manage_my_module_users?(@user, my_module)
|
||||
result[:create_url] = my_module_user_my_modules_path(my_module_id: my_module.id)
|
||||
end
|
||||
|
||||
result
|
||||
{ html: ApplicationController.renderer.render(
|
||||
partial: 'experiments/assigned_users.html.erb',
|
||||
locals: { my_module: my_module, user: @user }
|
||||
) }
|
||||
end
|
||||
|
||||
def tags_presenter(my_module)
|
||||
|
@ -181,12 +182,51 @@ module Experiments
|
|||
my_modules.where('my_modules.due_date <= ?', value)
|
||||
end
|
||||
|
||||
def archived_on_from_filter(my_modules, value)
|
||||
my_modules.where('my_modules.archived_on >= ?', value)
|
||||
end
|
||||
|
||||
def archived_on_to_filter(my_modules, value)
|
||||
my_modules.where('my_modules.archived_on <= ?', value)
|
||||
end
|
||||
|
||||
def assigned_users_filter(my_modules, value)
|
||||
my_modules.joins(:user_my_modules).where(user_my_modules: { user_id: value })
|
||||
end
|
||||
|
||||
def statuses_filter(my_modules, value)
|
||||
my_modules.where('my_module_status_id IN (?)', value)
|
||||
my_modules.where(my_module_status_id: value)
|
||||
end
|
||||
|
||||
def initialize_table_sorting(experiment)
|
||||
@view_state = experiment.current_view_state(@user)
|
||||
@view_mode = @params[:view_mode] || 'active'
|
||||
@sort = @view_state.state.dig('my_modules', @view_mode, 'sort') || 'atoz'
|
||||
if @params[:sort] && @sort != @params[:sort] && %w(due_first due_last atoz ztoa
|
||||
archived_old archived_new).include?(@params[:sort])
|
||||
@view_state.state['my_modules'].merge!(Hash[@view_mode, { 'sort': @params[:sort] }.stringify_keys])
|
||||
@view_state.save!
|
||||
@sort = @view_state.state.dig('my_modules', @view_mode, 'sort')
|
||||
end
|
||||
end
|
||||
|
||||
def sort_records(records)
|
||||
case @sort
|
||||
when 'due_first'
|
||||
records.order(:due_date)
|
||||
when 'due_last'
|
||||
records.order(Arel.sql("COALESCE(due_date, DATE '1900-01-01') DESC"))
|
||||
when 'atoz'
|
||||
records.order(:name)
|
||||
when 'ztoa'
|
||||
records.order(name: :desc)
|
||||
when 'archived_old'
|
||||
records.order(Arel.sql('COALESCE(my_modules.archived_on, my_modules.archived_on) ASC'))
|
||||
when 'archived_new'
|
||||
records.order(Arel.sql('COALESCE(my_modules.archived_on, my_modules.archived_on) DESC'))
|
||||
else
|
||||
records
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
28
app/views/experiments/_assigned_users.html.erb
Normal file
28
app/views/experiments/_assigned_users.html.erb
Normal file
|
@ -0,0 +1,28 @@
|
|||
<% users = my_module.designated_users.order(:full_name) %>
|
||||
<div ref="dropdown" class="assign-users-dropdown dropdown">
|
||||
<div class="assigned-users-container" data-toggle="dropdown">
|
||||
<% users[0..3].each_with_index do |user, i| %>
|
||||
<span class="avatar-container" style="z-index: <%= 5 - i %>">
|
||||
<%= image_tag avatar_path(user, :icon_small), title: user.full_name %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% if users.length > 4 %>
|
||||
<span class="more-users avatar-container" title="<%= sanitize_input(users[4..].map(&:full_name).join('
')) %>">
|
||||
+<%= users.length - 4 %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% if can_manage_my_module_users?(user, my_module) %>
|
||||
<span class="new-user avatar-container">
|
||||
<i class="fas fa-plus"></i>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="dropdown-menu">
|
||||
<div class="sci-input-container left-icon">
|
||||
<input type="text" class="sci-input-field user-search" placeholder="<%= I18n.t('experiments.table.search') %>">
|
||||
<i class="fas fa-search"></i>
|
||||
</div>
|
||||
<div class="users-list" data-list-url="<%= search_my_module_user_my_module_path(my_module, my_module_id: my_module.id) %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
31
app/views/experiments/_move_modules_modal.html.erb
Normal file
31
app/views/experiments/_move_modules_modal.html.erb
Normal file
|
@ -0,0 +1,31 @@
|
|||
<div class="modal fade" id="modal-move-modules" tabindex="-1" role="dialog" aria-labelledby="modal-move-modules-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="modal-move-modules-label"><%= t('experiments.table.modal_move_modules.title') %></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<% if @experiments.present? %>
|
||||
<%= bootstrap_form_tag do |f| %>
|
||||
<%= f.select :experiment_id, @experiments.collect { |e| [ e.name, e.id ] }, {}, class: "form-control selectpicker", 'data-role': 'clear' %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div>
|
||||
<em>
|
||||
<%= t('experiments.table.modal_move_modules.no_experiments') %>
|
||||
</em>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t('general.cancel') %></button>
|
||||
<% if @experiments.present? %>
|
||||
<button type="button" class="btn btn-primary" data-action="confirm">
|
||||
<%= t('experiments.table.modal_move_modules.confirm') %>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -63,6 +63,25 @@
|
|||
<% end %>
|
||||
<% if action_name == 'table' %>
|
||||
<%= render partial: 'table_filters.html.erb' %>
|
||||
|
||||
<div class="dropdown sort-menu">
|
||||
<button class="btn btn-light icon-btn dropdown-toggle" type="button" id="sortMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fas fa-sort-amount-up"></i>
|
||||
</button>
|
||||
<ul id="sortMenuDropdown" class="dropdown-menu sort-task-menu <%= params[:view_mode] %> dropdown-menu-right" aria-labelledby="sortMenu">
|
||||
<% %w(atoz ztoa due_first due_last archived_old archived_new).each_with_index do |sort, i| %>
|
||||
<% if i.even? && i.positive? %>
|
||||
<li class="divider" <%= i > 3 ? 'data-view-mode=archived' : '' %>></li>
|
||||
<% end %>
|
||||
<li <%= %w(archived_new archived_old).include?(sort) ? 'data-view-mode=archived' : '' %>>
|
||||
<a class="<%= 'selected' if @current_sort == sort %>"
|
||||
data-sort="<%= sort %>" >
|
||||
<%= t("general.sort.#{sort}_html") %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,11 +11,14 @@
|
|||
<p><%= t("experiments.table.column_display_modal.description") %></p>
|
||||
<% Experiments::TableViewService::COLUMNS.each do |col| %>
|
||||
<div class="column-container <%= col %> visible">
|
||||
<% unless col == :task_name %>
|
||||
<% if col == :archived && params[:view_mode] != 'archived' %>
|
||||
<i class="fas fa-eye disabled" data-column="<%= col %>"></i>
|
||||
<% elsif col != :task_name %>
|
||||
<i class="fas fa-<%= col.to_s.in?(@my_module_visible_table_columns) ? 'eye' : 'eye-slash' %>" data-column="<%= col %>"></i>
|
||||
<% end %>
|
||||
<%= t("experiments.table.column_display_modal.#{col}") %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
32
app/views/experiments/_table_due_date.html.erb
Normal file
32
app/views/experiments/_table_due_date.html.erb
Normal file
|
@ -0,0 +1,32 @@
|
|||
<% due_date_editable = can_update_my_module_due_date?(user, my_module)%>
|
||||
<% due_status = my_module_due_status(my_module) %>
|
||||
|
||||
<div id="dueDateContainer" class="datetime-container"
|
||||
data-update-url="<%= my_module_path(my_module, user, format: :json) %>">
|
||||
<span class="date-text" data-editable="<%= due_date_editable %>"
|
||||
data-toggle="tooltip" data-placement="top" title="<%= due_status %>" data-due-status="<%= due_status %>">
|
||||
<span id="dueDateLabelContainer" class="view-block" >
|
||||
<%= render partial: "experiments/table_due_date_label.html.erb" ,
|
||||
locals: { my_module: my_module, user: user } %>
|
||||
</span>
|
||||
<% if due_date_editable %>
|
||||
<div class="datetime-picker-container" id="due-date">
|
||||
<input id="calendarDueDate<%= my_module.id %>"
|
||||
type="datetime"
|
||||
data-toggle='date-time-picker'
|
||||
class="form-control calendar-input calendar-due-date"
|
||||
readonly
|
||||
placeholder="<%= t('my_modules.details.no_due_date_placeholder') %>"
|
||||
data-date-format="<%= datetime_picker_format_full %>"
|
||||
data-date-locale="<%= I18n.locale %>"
|
||||
data-date-use-current="false"
|
||||
data-date-orientation="left"
|
||||
value="<%= my_module.due_date ? l(my_module.due_date, format: :full) : '' %>"/>
|
||||
</div>
|
||||
<div class="fas fa-times-circle clear-date"
|
||||
data-toggle='clear-date-time-picker'
|
||||
data-target='calendarDueDate<%= my_module.id %>'>
|
||||
</div>
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
17
app/views/experiments/_table_due_date_label.html.erb
Normal file
17
app/views/experiments/_table_due_date_label.html.erb
Normal file
|
@ -0,0 +1,17 @@
|
|||
<span class="due-date-label" data-due-date="<%= my_module.due_date.present? %>" >
|
||||
<% if my_module.is_one_day_prior? %>
|
||||
<%= l(my_module.due_date, format: :full_date) %>
|
||||
<span class="fas fa-exclamation-triangle <%= get_task_alert_color(my_module) %>"></span>
|
||||
<% elsif my_module.is_overdue? %>
|
||||
<%= l(my_module.due_date, format: :full_date) %>
|
||||
<span class="fas fa-exclamation-triangle <%= get_task_alert_color(my_module) %>"></span>
|
||||
<% elsif my_module.due_date %>
|
||||
<%= l(my_module.due_date, format: :full_date) %>
|
||||
<% elsif can_update_my_module_due_date?(user, my_module) %>
|
||||
<a href='#'>
|
||||
<%= t('my_modules.details.no_due_date_placeholder') %>
|
||||
</a>
|
||||
<% else %>
|
||||
<%= t('my_modules.details.no_due_date') %>
|
||||
<% end %>
|
||||
</span>
|
|
@ -5,6 +5,8 @@
|
|||
} do %>
|
||||
<%= render partial: 'shared/filter_dropdown/text_search', locals: {container_class: 'task-name-filter', label_text: t('experiments.table.filters.name')} %>
|
||||
<%= render partial: 'shared/filter_dropdown/datetime_search', locals: {container_class: 'due-date-filter', label: t('experiments.table.filters.due_date'), view_mode: nil } %>
|
||||
<%= render partial: 'shared/filter_dropdown/datetime_search', locals: {container_class: 'archived-on-filter', label: t("filters_modal.archived_on.label"), view_mode: 'archived' } if params[:view_mode] == 'archived' %>
|
||||
|
||||
<div class="select-block status-container">
|
||||
<label><%= t('experiments.table.filters.status') %></label>
|
||||
<select class="status-filter"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<li class="divider-label"><%= t("experiments.table.my_module_actions.title") %></li>
|
||||
<% if can_manage_my_module?(my_module) %>
|
||||
<li>
|
||||
<a href="" class="edit-my-module">
|
||||
<a href="" class="edit-my-module" data-id="<%= my_module.id %>">
|
||||
<i class="fas fa-pen"></i>
|
||||
<%= t("experiments.table.my_module_actions.edit") %>
|
||||
</a>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<% end %>
|
||||
<% if can_manage_experiment?(my_module.experiment) && my_module.active? %>
|
||||
<li>
|
||||
<a href="" class="duplicate-my-module">
|
||||
<a href="" class="duplicate-my-module" data-id="<%= my_module.id %>">
|
||||
<i class="fas fa-copy"></i>
|
||||
<%= t("experiments.table.my_module_actions.duplicate") %>
|
||||
</a>
|
||||
|
@ -17,7 +17,7 @@
|
|||
<% end %>
|
||||
<% if can_move_my_module?(my_module) %>
|
||||
<li>
|
||||
<a href="" class="move-my-module">
|
||||
<a href="" class="move-my-module" data-id="<%= my_module.id %>">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
<%= t("experiments.table.my_module_actions.move") %>
|
||||
</a>
|
||||
|
@ -37,3 +37,11 @@
|
|||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can_restore_my_module?(my_module) %>
|
||||
<li>
|
||||
<a href="<%= restore_my_modules_experiment_path(my_module.experiment) %>" class="restore-my-module" data-id="<%= my_module.id %>">
|
||||
<i class="fas fa-undo"></i>
|
||||
<%= t("experiments.table.my_module_actions.restore") %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<%= t("experiments.table.toolbar.edit") %>
|
||||
</button>
|
||||
<% if can_manage_experiment?(@experiment) %>
|
||||
<button id="duplicateTask" class="btn btn-light multiple-object-action hidden only-active">
|
||||
<button id="duplicateTasks" class="btn btn-light multiple-object-action hidden only-active" data-url="<%= batch_clone_my_modules_experiment_path(@experiment) %>">
|
||||
<i class="fas fa-copy"></i>
|
||||
<%= t("experiments.table.toolbar.duplicate") %>
|
||||
</button>
|
||||
|
@ -32,6 +32,10 @@
|
|||
<i class="fas fa-archive"></i>
|
||||
<%= t("experiments.table.toolbar.archive") %>
|
||||
</button>
|
||||
<button id="restoreTask" class="btn btn-light multiple-object-action hidden only-archive" data-url="<%= restore_my_modules_experiment_path(@experiment) %>" data-for="restorable">
|
||||
<i class="fas fa-undo"></i>
|
||||
<%= t("experiments.table.toolbar.restore") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toolbar-right-block">
|
||||
<button id="taskDataDisplay" class="btn btn-light" data-toggle="modal" data-target="#tableDisplayModal">
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
<div class="experiment-table"
|
||||
style="--columns-count: <%= Experiments::TableViewService::COLUMNS.length%>"
|
||||
data-my-modules-url="<%= load_table_experiment_path(@experiment, view_mode: params[:view_mode]) %>"
|
||||
data-move-modules-modal-url="<%= move_modules_modal_experiment_path(@experiment) %>"
|
||||
data-move-modules-url="<%= move_modules_experiment_path(@experiment) %>"
|
||||
>
|
||||
<div class="table-header">
|
||||
<div class="table-header-cell select-all-checkboxes">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<div class="modal" id="new-my-module-modal" tabindex="-1" role="dialog" data-create-url="<%= modules_experiment_path(@experiment) %>">
|
||||
<div class="modal" id="new-my-module-modal" tabindex="-1" role="dialog"
|
||||
data-create-url="<%= modules_experiment_path(@experiment) %>" data-user-id="<%= current_user.id %>">
|
||||
<%= bootstrap_form_for @my_module, url: modules_experiment_path(@experiment), remote: true do |f| %>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
@ -35,6 +36,26 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
|
||||
<%= f.select 'user_ids',
|
||||
options_for_select(users.map{ |user|
|
||||
[
|
||||
user.full_name,
|
||||
user.id,
|
||||
{'data-params' => {avatar_url: avatar_path(user, :icon_small) }.to_json}
|
||||
]
|
||||
}),
|
||||
{
|
||||
id: 'new-modal-assigned-users-selector',
|
||||
label: t('experiments.canvas.new_my_module_modal.assigned_users')
|
||||
}, {
|
||||
:multiple => true
|
||||
} %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
'data-project-id': my_module.experiment.project_id,
|
||||
'data-placeholder': t('my_modules.details.no_assigned_users'),
|
||||
'data-users-create-url': my_module_user_my_modules_path(my_module_id: my_module.id),
|
||||
'data-ajax-url': search_my_module_user_my_module_path(my_module),
|
||||
'data-ajax-url': search_my_module_user_my_module_path(my_module, skip_assigned: true),
|
||||
'data-update-module-users-url': my_module_user_my_modules_url(my_module),
|
||||
'data-view-mode': !can_manage_my_module_designated_users?(my_module)
|
||||
} %>
|
||||
|
|
|
@ -951,6 +951,9 @@ en:
|
|||
no_start_date_placeholder: "+ Add starting date"
|
||||
due_date: "Due date:"
|
||||
no_due_date_placeholder: "+ Add due date"
|
||||
overdue: "Overdue"
|
||||
due_soon: "Due soon"
|
||||
no_due_date: "not set"
|
||||
assigned_users: "Assigned to:"
|
||||
no_assigned_users: "+ Assign task to a project member"
|
||||
tags: "Tags:"
|
||||
|
@ -1282,6 +1285,8 @@ en:
|
|||
success_flash: 'Successfully duplicated experiment %{experiment} as template.'
|
||||
error_flash: 'Could not duplicate the experiment as template.'
|
||||
current_project: '(current project)'
|
||||
duplicate_tasks:
|
||||
success: 'Successfully duplicated %{count} task(s) as template.'
|
||||
move:
|
||||
modal_title: 'Move experiment %{experiment}'
|
||||
notice: 'Moving is possible to projects, where you have permissions to create experiments and tasks.'
|
||||
|
@ -1305,6 +1310,12 @@ en:
|
|||
assigned_html: 'Assigned to'
|
||||
tags_html: 'Tags'
|
||||
comments_html: '<i class="fas fa-comment"></i>'
|
||||
modal_move_modules:
|
||||
title: "Move task(s) to experiment"
|
||||
confirm: "Move"
|
||||
no_experiments: "No experiments to move this task to."
|
||||
success_flash: "Successfully moved task(s) to experiment %{experiment}."
|
||||
error_flash: "Failed to move task(s) to experiment %{experiment}."
|
||||
column_display_modal:
|
||||
title: 'Task data display'
|
||||
description: 'Click the eye buttons to hide or show columns in the table'
|
||||
|
@ -1322,6 +1333,7 @@ en:
|
|||
new: 'New task'
|
||||
edit: 'Edit'
|
||||
archive: 'Archive'
|
||||
restore: 'Restore'
|
||||
move: 'Move'
|
||||
duplicate: 'Duplicate'
|
||||
manage_access: 'Manage access'
|
||||
|
@ -1333,6 +1345,7 @@ en:
|
|||
move: 'Move to another experiment'
|
||||
access: 'Task access'
|
||||
archive: 'Archive'
|
||||
restore: 'Restore'
|
||||
filters:
|
||||
name: 'Task name'
|
||||
due_date: 'Due date'
|
||||
|
@ -1364,6 +1377,7 @@ en:
|
|||
name_placeholder: "e.g. My task"
|
||||
due_date: "Due date (optional)"
|
||||
due_date_placeholder: "+ Add due date"
|
||||
assigned_users: "Assign task to (optional)"
|
||||
tags: "Add tags (optional)"
|
||||
create: "Create"
|
||||
enter_placeholder: 'Enter...'
|
||||
|
@ -3194,6 +3208,7 @@ en:
|
|||
create: 'Create'
|
||||
change: "Change"
|
||||
remove: "Remove"
|
||||
clone_label: "Clone"
|
||||
# In order to use the strings 'yes' and 'no' as keys, you need to wrap them with quotes
|
||||
'yes': "Yes"
|
||||
'no': "No"
|
||||
|
@ -3210,6 +3225,8 @@ en:
|
|||
ztoa_html: "<i class=\"fas fa-sort-alpha-up\"></i> Name Z to A"
|
||||
archived_new_html: "<span class=\"fa-stack\"><i class=\"fas fa-long-arrow-alt-up\"></i><i class=\"fas fa-archive\"></i></span>Archived last"
|
||||
archived_old_html: "<span class=\"fa-stack\"><i class=\"fas fa-long-arrow-alt-down\"></i><i class=\"fas fa-archive\"></i></span>Archived first"
|
||||
due_first_html: "<i class=\"fas fa-sort-numeric-up\"></i> Due first"
|
||||
due_last_html: "<i class=\"fas fa-sort-numeric-up\"></i> Due last"
|
||||
sort_new:
|
||||
new: "Newest"
|
||||
old: "Oldest"
|
||||
|
|
|
@ -357,6 +357,8 @@ Rails.application.routes.draw do
|
|||
get 'actions_dropdown'
|
||||
get :table
|
||||
get :load_table
|
||||
get :move_modules_modal
|
||||
post :move_modules
|
||||
get 'canvas' # Overview/structure for single experiment
|
||||
# AJAX-loaded canvas edit mode (from canvas)
|
||||
get 'canvas/edit', to: 'canvas#edit'
|
||||
|
@ -379,6 +381,7 @@ Rails.application.routes.draw do
|
|||
get 'sidebar'
|
||||
get :assigned_users_to_tasks
|
||||
post :archive_my_modules
|
||||
post :batch_clone_my_modules
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -390,6 +393,7 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
get :permissions
|
||||
get :actions_dropdown
|
||||
get :provisioning_status
|
||||
end
|
||||
resources :my_module_tags, path: '/tags', only: [:index, :create, :destroy] do
|
||||
collection do
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProvisioningStatusToMyModules < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :my_modules, :provisioning_status, :integer
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue