mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 06:35:56 +08:00
Merge branch 'develop' into jg_sci_2228
This commit is contained in:
commit
aa1520bfb8
|
@ -1,6 +1,6 @@
|
|||
ruby:
|
||||
config_file: .rubocop.yml
|
||||
version: 0.75.0
|
||||
version: 0.83.0
|
||||
|
||||
eslint:
|
||||
enabled: true
|
||||
|
|
|
@ -6,6 +6,7 @@ AllCops:
|
|||
Exclude:
|
||||
- "vendor/**/*"
|
||||
- "db/schema.rb"
|
||||
NewCops: enable
|
||||
UseCache: false
|
||||
TargetRubyVersion: 2.6
|
||||
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -37,6 +37,7 @@ gem 'jsonapi-renderer', '~> 0.2.2'
|
|||
gem 'jwt', '~> 1.5'
|
||||
gem 'kaminari'
|
||||
gem 'rack-attack'
|
||||
gem 'rack-cors'
|
||||
|
||||
# JS datetime library, requirement of datetime picker
|
||||
gem 'momentjs-rails', '~> 2.17.1'
|
||||
|
@ -123,7 +124,7 @@ group :development, :test do
|
|||
gem 'pry-rails'
|
||||
gem 'rails-controller-testing'
|
||||
gem 'rspec-rails', '>= 4.0.0.beta2'
|
||||
gem 'rubocop', '>= 0.75.0', require: false
|
||||
gem 'rubocop', '= 0.83.0', require: false
|
||||
gem 'rubocop-performance'
|
||||
gem 'rubocop-rails'
|
||||
gem 'timecop'
|
||||
|
|
31
Gemfile.lock
31
Gemfile.lock
|
@ -17,7 +17,7 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: https://github.com/biosistemika/yomu
|
||||
revision: 8845246f3e6a6cbc49b902cd4b908ba70553cbdd
|
||||
revision: 063b855d672e9dd9de1e6e585b349a9b63e120c3
|
||||
branch: master
|
||||
specs:
|
||||
yomu (0.2.4)
|
||||
|
@ -110,7 +110,7 @@ GEM
|
|||
ajax-datatables-rails (0.3.1)
|
||||
railties (>= 3.1)
|
||||
aspector (0.14.0)
|
||||
ast (2.4.0)
|
||||
ast (2.4.1)
|
||||
auto_strip_attributes (2.5.0)
|
||||
activerecord (>= 4.0)
|
||||
autoprefixer-rails (9.7.0)
|
||||
|
@ -287,7 +287,6 @@ GEM
|
|||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.13, < 3)
|
||||
iniparse (1.4.4)
|
||||
jaro_winkler (1.5.4)
|
||||
jbuilder (2.9.1)
|
||||
activesupport (>= 4.2.0)
|
||||
jmespath (1.4.0)
|
||||
|
@ -338,9 +337,9 @@ GEM
|
|||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
method_source (0.9.2)
|
||||
mime-types (3.3)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.0904)
|
||||
mime-types-data (3.2020.0512)
|
||||
mimemagic (0.3.5)
|
||||
mini_magick (4.9.5)
|
||||
mini_mime (1.0.2)
|
||||
|
@ -387,9 +386,9 @@ GEM
|
|||
overcommit (0.49.1)
|
||||
childprocess (>= 0.6.3, < 2.0)
|
||||
iniparse (~> 1.4)
|
||||
parallel (1.19.1)
|
||||
parser (2.6.5.0)
|
||||
ast (~> 2.4.0)
|
||||
parallel (1.19.2)
|
||||
parser (2.7.1.4)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.1.4)
|
||||
pg_search (2.3.0)
|
||||
activerecord (>= 4.2)
|
||||
|
@ -410,6 +409,8 @@ GEM
|
|||
rack (2.2.3)
|
||||
rack-attack (6.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-proxy (0.6.5)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
|
@ -463,6 +464,7 @@ GEM
|
|||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rexml (3.2.4)
|
||||
rgl (0.5.6)
|
||||
lazy_priority_queue (~> 0.1.0)
|
||||
stream (~> 0.5.2)
|
||||
|
@ -492,13 +494,13 @@ GEM
|
|||
rspec-mocks (~> 3.8)
|
||||
rspec-support (~> 3.8)
|
||||
rspec-support (3.8.2)
|
||||
rubocop (0.78.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
rubocop (0.83.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
parser (>= 2.7.0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
rexml
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-performance (1.5.1)
|
||||
rubocop (>= 0.71.0)
|
||||
rubocop-rails (2.4.0)
|
||||
|
@ -569,7 +571,7 @@ GEM
|
|||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
underscore-rails (1.8.3)
|
||||
unicode-display_width (1.6.0)
|
||||
unicode-display_width (1.7.0)
|
||||
uniform_notifier (1.12.1)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
|
@ -666,6 +668,7 @@ DEPENDENCIES
|
|||
pry-rails
|
||||
puma
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails (~> 6.0.0)
|
||||
rails-controller-testing
|
||||
rails_12factor
|
||||
|
@ -676,7 +679,7 @@ DEPENDENCIES
|
|||
rotp
|
||||
rqrcode
|
||||
rspec-rails (>= 4.0.0.beta2)
|
||||
rubocop (>= 0.75.0)
|
||||
rubocop (= 0.83.0)
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
ruby-graphviz (~> 1.2)
|
||||
|
|
|
@ -1,31 +1,43 @@
|
|||
/* global dropdownSelector I18n animateSpinner PerfectSb InfiniteScroll */
|
||||
/* global dropdownSelector animateSpinner PerfectSb InfiniteScroll */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
var DasboardCurrentTasksWidget = (function() {
|
||||
var sortFilter = '.curent-tasks-filters .sort-filter';
|
||||
var viewFilter = '.curent-tasks-filters .view-filter';
|
||||
var projectFilter = '.curent-tasks-filters .project-filter';
|
||||
var experimentFilter = '.curent-tasks-filters .experiment-filter';
|
||||
var sortFilter = '.current-tasks-filters .sort-filter';
|
||||
var statusFilter = '.current-tasks-filters .view-filter';
|
||||
var projectFilter = '.current-tasks-filters .project-filter';
|
||||
var experimentFilter = '.current-tasks-filters .experiment-filter';
|
||||
|
||||
function generateTasksListHtml(json, container) {
|
||||
$.each(json.data, (i, task) => {
|
||||
var currentTaskItem = ` <a class="current-task-item" href="${task.link}">
|
||||
<div class="current-task-breadcrumbs">${task.project}<span class="slash">/</span>${task.experiment}</div>
|
||||
<div class="item-row">
|
||||
<div class="task-name">${task.name}</div>
|
||||
<div class="task-due-date ${task.state.class} ${task.due_date ? '' : 'hidden'}">
|
||||
<i class="fas fa-calendar-day"></i> ${I18n.t('dashboard.current_tasks.due_date', { date: task.due_date })}
|
||||
</div>
|
||||
<div class="task-progress-container ${task.state.class}">
|
||||
<div class="task-progress" style="padding-left: ${task.steps_precentage}%"></div>
|
||||
<div class="task-progress-label">${task.state.text}</div>
|
||||
</div>
|
||||
<div class="task-name row-border">${task.name}</div>
|
||||
<div class="task-due-date row-border ${task.due_date.state}">
|
||||
<span class="${task.due_date.text ? '' : 'hidden'}">
|
||||
<i class="fas fa-calendar-day"></i> ${task.due_date.text}
|
||||
</span>
|
||||
</div>
|
||||
<div class="task-status-container row-border">
|
||||
<span class="task-status" style="background:${task.status_color}">${task.status_name}</span>
|
||||
</div>
|
||||
</a>`;
|
||||
$(container).append(currentTaskItem);
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultStatusValues() {
|
||||
// Select uncompleted status values
|
||||
var values = [];
|
||||
$(statusFilter).find('option').each(function(_, option) {
|
||||
if ($(option).data('completionConsequence')) {
|
||||
return false;
|
||||
}
|
||||
values.push(option.value);
|
||||
return this;
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
function initInfiniteScroll() {
|
||||
InfiniteScroll.init('.current-tasks-list', {
|
||||
url: $('.current-tasks-list').data('tasksListUrl'),
|
||||
|
@ -36,7 +48,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
params.project_id = dropdownSelector.getValues(projectFilter);
|
||||
params.experiment_id = dropdownSelector.getValues(experimentFilter);
|
||||
params.sort = dropdownSelector.getValues(sortFilter);
|
||||
params.view = dropdownSelector.getValues(viewFilter);
|
||||
params.statuses = dropdownSelector.getValues(statusFilter);
|
||||
params.query = $('.current-tasks-widget .task-search-field').val();
|
||||
params.mode = $('.current-tasks-navbar .active').data('mode');
|
||||
return params;
|
||||
|
@ -47,8 +59,53 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
function filtersEnabled() {
|
||||
return dropdownSelector.getValues(experimentFilter)
|
||||
|| dropdownSelector.getValues(projectFilter)
|
||||
|| $('.current-tasks-widget .task-search-field').val().length > 0
|
||||
|| dropdownSelector.getValues(viewFilter) !== 'uncompleted';
|
||||
|| $('.current-tasks-widget .task-search-field').val().length > 0;
|
||||
}
|
||||
|
||||
function filterStateSave() {
|
||||
var teamId = $('.current-tasks-filters').data('team-id');
|
||||
var filterState = {
|
||||
sort: dropdownSelector.getValues(sortFilter),
|
||||
statuses: dropdownSelector.getValues(statusFilter),
|
||||
project_id: dropdownSelector.getData(projectFilter),
|
||||
experiment_id: dropdownSelector.getData(experimentFilter),
|
||||
mode: $('.current-tasks-navbar .active').data('mode')
|
||||
};
|
||||
|
||||
if (filterState) {
|
||||
localStorage.setItem('current_tasks_filters_per_team/' + teamId, JSON.stringify(filterState));
|
||||
}
|
||||
}
|
||||
|
||||
function filterStateLoad() {
|
||||
var teamId = $('.current-tasks-filters').data('team-id');
|
||||
var filterState = localStorage.getItem('current_tasks_filters_per_team/' + teamId);
|
||||
var parsedFilterState;
|
||||
var allStatusValues = $.map($(statusFilter).find('option'), function(option) {
|
||||
return option.value;
|
||||
});
|
||||
|
||||
if (filterState) {
|
||||
try {
|
||||
parsedFilterState = JSON.parse(filterState);
|
||||
dropdownSelector.selectValues(sortFilter, parsedFilterState.sort);
|
||||
// Check if saved statuses are valid
|
||||
if (parsedFilterState.statuses.every(status => allStatusValues.includes(status))) {
|
||||
dropdownSelector.selectValues(statusFilter, parsedFilterState.statuses);
|
||||
} else {
|
||||
dropdownSelector.selectValues(statusFilter, getDefaultStatusValues());
|
||||
}
|
||||
dropdownSelector.setData(projectFilter, parsedFilterState.project_id);
|
||||
dropdownSelector.setData(experimentFilter, parsedFilterState.experiment_id);
|
||||
// Select saved navbar state
|
||||
$('.current-tasks-navbar .navbar-link').removeClass('active');
|
||||
$('.current-tasks-navbar').find(`[data-mode='${parsedFilterState.mode}']`).addClass('active');
|
||||
} catch (e) {
|
||||
dropdownSelector.selectValues(statusFilter, getDefaultStatusValues());
|
||||
}
|
||||
} else {
|
||||
dropdownSelector.selectValues(statusFilter, getDefaultStatusValues());
|
||||
}
|
||||
}
|
||||
|
||||
function loadCurrentTasksList(newList) {
|
||||
|
@ -57,7 +114,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
project_id: dropdownSelector.getValues(projectFilter),
|
||||
experiment_id: dropdownSelector.getValues(experimentFilter),
|
||||
sort: dropdownSelector.getValues(sortFilter),
|
||||
view: dropdownSelector.getValues(viewFilter),
|
||||
statuses: dropdownSelector.getValues(statusFilter),
|
||||
query: $('.current-tasks-widget .task-search-field').val(),
|
||||
mode: $('.current-tasks-navbar .active').data('mode')
|
||||
};
|
||||
|
@ -81,11 +138,12 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
}
|
||||
|
||||
function initFilters() {
|
||||
$('.curent-tasks-filters .clear-button').click((e) => {
|
||||
$('.current-tasks-filters .clear-button').click((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropdownSelector.selectValue(sortFilter, 'due_date');
|
||||
dropdownSelector.selectValue(viewFilter, 'uncompleted');
|
||||
|
||||
dropdownSelector.selectValues(sortFilter, 'due_date');
|
||||
dropdownSelector.selectValues(statusFilter, getDefaultStatusValues());
|
||||
dropdownSelector.clearData(projectFilter);
|
||||
dropdownSelector.clearData(experimentFilter);
|
||||
});
|
||||
|
@ -98,12 +156,9 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
disableSearch: true
|
||||
});
|
||||
|
||||
dropdownSelector.init(viewFilter, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
dropdownSelector.init(statusFilter, {
|
||||
selectAppearance: 'simple',
|
||||
disableSearch: true
|
||||
optionClass: 'checkbox-icon'
|
||||
});
|
||||
|
||||
dropdownSelector.init(projectFilter, {
|
||||
|
@ -138,25 +193,27 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
}
|
||||
});
|
||||
|
||||
$('.curent-tasks-filters').click((e) => {
|
||||
$('.current-tasks-filters').click((e) => {
|
||||
// Prevent filter window close
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropdownSelector.closeDropdown(sortFilter);
|
||||
dropdownSelector.closeDropdown(viewFilter);
|
||||
dropdownSelector.closeDropdown(statusFilter);
|
||||
dropdownSelector.closeDropdown(projectFilter);
|
||||
dropdownSelector.closeDropdown(experimentFilter);
|
||||
});
|
||||
|
||||
$('.curent-tasks-filters .apply-filters').click((e) => {
|
||||
$('.curent-tasks-filters').dropdown('toggle');
|
||||
$('.current-tasks-filters .apply-filters').click((e) => {
|
||||
$('.current-tasks-filters').dropdown('toggle');
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
loadCurrentTasksList(true);
|
||||
filterStateSave();
|
||||
});
|
||||
|
||||
$('.filter-container').on('hide.bs.dropdown', () => {
|
||||
loadCurrentTasksList(true);
|
||||
filterStateSave();
|
||||
$('.current-tasks-list').removeClass('disabled');
|
||||
});
|
||||
|
||||
|
@ -170,6 +227,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
$(this).parent().find('.navbar-link').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
loadCurrentTasksList(true);
|
||||
filterStateSave();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -179,13 +237,13 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
if ($('.current-tasks-widget').length) {
|
||||
initNavbar();
|
||||
initFilters();
|
||||
initSearch();
|
||||
filterStateLoad();
|
||||
loadCurrentTasksList();
|
||||
initInfiniteScroll();
|
||||
}
|
||||
|
|
|
@ -14,16 +14,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
function initExpandCollapseButton() {
|
||||
$('.ga-activities-list').on('hidden.bs.collapse', function(ev) {
|
||||
$(ev.target.dataset.buttonLink)
|
||||
.find('.fas').removeClass('fa-chevron-down').addClass('fa-chevron-right');
|
||||
});
|
||||
$('.ga-activities-list').on('shown.bs.collapse', function(ev) {
|
||||
$(ev.target.dataset.buttonLink)
|
||||
.find('.fas').removeClass('fa-chevron-right').addClass('fa-chevron-down');
|
||||
});
|
||||
}
|
||||
function initShowMoreButton() {
|
||||
var moreButton = $('.btn-more-activities');
|
||||
moreButton.on('click', function(ev) {
|
||||
|
@ -70,6 +60,5 @@
|
|||
}
|
||||
|
||||
initExpandCollapseAllButtons();
|
||||
initExpandCollapseButton();
|
||||
initShowMoreButton();
|
||||
}());
|
||||
|
|
|
@ -1,388 +1,425 @@
|
|||
/* global I18n dropdownSelector */
|
||||
/* global I18n dropdownSelector HelperModule animateSpinner */
|
||||
/* eslint-disable no-use-before-define */
|
||||
(function() {
|
||||
const STATUS_POLLING_INTERVAL = 5000;
|
||||
|
||||
function initTaskCollapseState() {
|
||||
let taskView = '.my-modules-protocols-index';
|
||||
let taskSection = '.task-section-caret';
|
||||
let taskId = $(taskView).data('task-id');
|
||||
function initTaskCollapseState() {
|
||||
let taskView = '.my-modules-protocols-index';
|
||||
let taskSection = '.task-section-caret';
|
||||
let taskId = $(taskView).data('task-id');
|
||||
|
||||
function collapseStateSave() {
|
||||
$(taskView).on('click', taskSection, function() {
|
||||
let collapsed = $(this).attr('aria-expanded');
|
||||
let taskSectionType = $(this).attr('aria-controls');
|
||||
function collapseStateSave() {
|
||||
$(taskView).on('click', taskSection, function() {
|
||||
let collapsed = $(this).attr('aria-expanded');
|
||||
let taskSectionType = $(this).attr('aria-controls');
|
||||
|
||||
if (collapsed === 'true') {
|
||||
localStorage.setItem('task_section_collapsed/' + taskId + '/' + taskSectionType, collapsed);
|
||||
} else {
|
||||
localStorage.removeItem('task_section_collapsed/' + taskId + '/' + taskSectionType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function collapseStateLoad() {
|
||||
$(taskSection).each(function() {
|
||||
let taskSectionType = $(this).attr('aria-controls');
|
||||
var collapsed = localStorage.getItem('task_section_collapsed/' + taskId + '/' + taskSectionType);
|
||||
|
||||
if (JSON.parse(collapsed)) {
|
||||
$('#' + taskSectionType).collapse('hide');
|
||||
}
|
||||
$(this).closest('.task-section').removeClass('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
collapseStateSave();
|
||||
collapseStateLoad();
|
||||
}
|
||||
|
||||
function updateStartDate() {
|
||||
let updateUrl = $('#startDateContainer').data('update-url');
|
||||
let val = $('#calendarStartDate').val();
|
||||
$.ajax({
|
||||
url: updateUrl,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { my_module: { started_on: val } },
|
||||
success: function(result) {
|
||||
$('#startDateLabelContainer').html(result.start_date_label);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bind ajax for editing due dates
|
||||
function initStartDatePicker() {
|
||||
$('#calendarStartDate').on('dp.change', function() {
|
||||
updateStartDate();
|
||||
});
|
||||
}
|
||||
|
||||
function updateDueDate() {
|
||||
let updateUrl = $('#dueDateContainer').data('update-url');
|
||||
let val = $('#calendarDueDate').val();
|
||||
$.ajax({
|
||||
url: updateUrl,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { my_module: { due_date: val } },
|
||||
success: function(result) {
|
||||
$('#dueDateLabelContainer').html(result.due_date_label);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bind ajax for editing due dates
|
||||
function initDueDatePicker() {
|
||||
$('#calendarDueDate').on('dp.change', function() {
|
||||
updateDueDate();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Bind ajax for editing tags
|
||||
function bindEditTagsAjax() {
|
||||
var manageTagsModal = null;
|
||||
var manageTagsModalBody = null;
|
||||
|
||||
// Initialize reloading of manage tags modal content after posting new
|
||||
// tag.
|
||||
function initAddTagForm() {
|
||||
manageTagsModalBody.find('.add-tag-form')
|
||||
.submit(function() {
|
||||
var selectOptions = manageTagsModalBody.find('#new_my_module_tag .dropdown-menu li').length;
|
||||
if (selectOptions === 0 && this.id === 'new_my_module_tag') return false;
|
||||
return true;
|
||||
})
|
||||
.on('ajax:success', function(e, data) {
|
||||
var newTag;
|
||||
initTagsModalBody(data);
|
||||
newTag = $('#manage-module-tags-modal .list-group-item').last();
|
||||
dropdownSelector.addValue('#module-tags-selector', {
|
||||
value: newTag.data('tag-id'),
|
||||
label: newTag.data('name'),
|
||||
params: {
|
||||
color: newTag.data('color')
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize edit tag & remove tag functionality from my_module links.
|
||||
function initTagRowLinks() {
|
||||
manageTagsModalBody.find('.edit-tag-link')
|
||||
.on('click', function() {
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
var editDiv = $(li.find('div.tag-edit'));
|
||||
|
||||
// Revert all rows to their original states
|
||||
manageTagsModalBody.find('li.list-group-item').each(function() {
|
||||
var li2 = $(this);
|
||||
li2.css('background-color', li2.data('color'));
|
||||
li2.find('.edit-tag-form').clearFormErrors();
|
||||
li2.find('input[type=text]').val(li2.data('name'));
|
||||
});
|
||||
|
||||
// Hide all other edit divs, show all show divs
|
||||
manageTagsModalBody.find('div.tag-edit').hide();
|
||||
manageTagsModalBody.find('div.tag-show').show();
|
||||
|
||||
editDiv.find('input[type=text]').val(li.data('name'));
|
||||
editDiv.find('.edit-tag-color').colorselector('setColor', li.data('color'));
|
||||
|
||||
li.find('div.tag-show').hide();
|
||||
editDiv.show();
|
||||
});
|
||||
manageTagsModalBody.find('div.tag-edit .dropdown-colorselector > .dropdown-menu li a')
|
||||
.on('click', function() {
|
||||
// Change background of the <li>
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
li.css('background-color', $this.data('value'));
|
||||
});
|
||||
manageTagsModalBody.find('.remove-tag-link')
|
||||
.on('ajax:success', function(e, data) {
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
manageTagsModalBody.find('.delete-tag-form')
|
||||
.on('ajax:success', function(e, data) {
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
manageTagsModalBody.find('.edit-tag-form')
|
||||
.on('ajax:success', function(e, data) {
|
||||
var newTag;
|
||||
initTagsModalBody(data);
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
newTag = $('#manage-module-tags-modal .list-group-item[data-tag-id=' + this.dataset.tagId + ']');
|
||||
dropdownSelector.addValue('#module-tags-selector', {
|
||||
value: newTag.data('tag-id'),
|
||||
label: newTag.data('name'),
|
||||
params: {
|
||||
color: newTag.data('color')
|
||||
}
|
||||
}, true);
|
||||
})
|
||||
.on('ajax:error', function(e, data) {
|
||||
$(this).renderFormErrors('tag', data.responseJSON);
|
||||
});
|
||||
manageTagsModalBody.find('.cancel-tag-link')
|
||||
.on('click', function() {
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
|
||||
li.css('background-color', li.data('color'));
|
||||
li.find('.edit-tag-form').clearFormErrors();
|
||||
|
||||
li.find('div.tag-edit').hide();
|
||||
li.find('div.tag-show').show();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize ajax listeners and elements style on modal body. This
|
||||
// function must be called when modal body is changed.
|
||||
function initTagsModalBody(data) {
|
||||
manageTagsModalBody.html(data.html);
|
||||
manageTagsModalBody.find('.selectpicker').selectpicker();
|
||||
initAddTagForm();
|
||||
initTagRowLinks();
|
||||
}
|
||||
|
||||
manageTagsModal = $('#manage-module-tags-modal');
|
||||
manageTagsModalBody = manageTagsModal.find('.modal-body');
|
||||
|
||||
// Reload tags HTML element when modal is closed
|
||||
manageTagsModal.on('hide.bs.modal', function() {
|
||||
var tagsEl = $('#module-tags');
|
||||
|
||||
// Load HTML
|
||||
$.ajax({
|
||||
url: tagsEl.attr('data-module-tags-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var newOptions = $(data.html_module_header).find('option');
|
||||
$('#module-tags-selector').find('option').remove();
|
||||
$(newOptions).appendTo('#module-tags-selector').change();
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove modal content when modal window is closed.
|
||||
manageTagsModal.on('hidden.bs.modal', function() {
|
||||
manageTagsModalBody.html('');
|
||||
});
|
||||
// initialize my_module tab remote loading
|
||||
$('.edit-tags-link')
|
||||
.on('ajax:before', function() {
|
||||
manageTagsModal.modal('show');
|
||||
})
|
||||
.on('ajax:success', function(e, data) {
|
||||
$('#manage-module-tags-modal-module').text(data.my_module.name);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
}
|
||||
|
||||
// Sets callback for completing/uncompleting task
|
||||
function applyTaskCompletedCallBack() {
|
||||
$("[data-action='complete-task'], [data-action='uncomplete-task']")
|
||||
.on('click', function() {
|
||||
var button = $(this);
|
||||
$.ajax({
|
||||
url: button.data('link-url'),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.completed === true) {
|
||||
button.attr('data-action', 'uncomplete-task');
|
||||
button.find('.btn')
|
||||
.removeClass('btn-primary').addClass('btn-default');
|
||||
} else {
|
||||
button.attr('data-action', 'complete-task');
|
||||
button.find('.btn')
|
||||
.removeClass('btn-default').addClass('btn-primary');
|
||||
}
|
||||
$('#dueDateContainer').html(data.module_header_due_date);
|
||||
initDueDatePicker();
|
||||
$('.task-state-label').html(data.module_state_label);
|
||||
button.find('button').replaceWith(data.new_btn);
|
||||
},
|
||||
error: function() {
|
||||
if (collapsed === 'true') {
|
||||
localStorage.setItem('task_section_collapsed/' + taskId + '/' + taskSectionType, collapsed);
|
||||
} else {
|
||||
localStorage.removeItem('task_section_collapsed/' + taskId + '/' + taskSectionType);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initTagsSelector() {
|
||||
var myModuleTagsSelector = '#module-tags-selector';
|
||||
function collapseStateLoad() {
|
||||
$(taskSection).each(function() {
|
||||
let taskSectionType = $(this).attr('aria-controls');
|
||||
var collapsed = localStorage.getItem('task_section_collapsed/' + taskId + '/' + taskSectionType);
|
||||
|
||||
dropdownSelector.init(myModuleTagsSelector, {
|
||||
closeOnSelect: true,
|
||||
tagClass: 'my-module-white-tags',
|
||||
tagStyle: (data) => {
|
||||
return `background: ${data.params.color}`;
|
||||
},
|
||||
customDropdownIcon: () => {
|
||||
return '';
|
||||
},
|
||||
optionLabel: (data) => {
|
||||
if (data.value > 0) {
|
||||
return `<span class="my-module-tags-color" style="background:${data.params.color}"></span>
|
||||
${data.label}`;
|
||||
if (JSON.parse(collapsed)) {
|
||||
$('#' + taskSectionType).collapse('hide');
|
||||
}
|
||||
$(this).closest('.task-section').removeClass('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
collapseStateSave();
|
||||
collapseStateLoad();
|
||||
}
|
||||
|
||||
function updateStartDate() {
|
||||
let updateUrl = $('#startDateContainer').data('update-url');
|
||||
let val = $('#calendarStartDate').val();
|
||||
$.ajax({
|
||||
url: updateUrl,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { my_module: { started_on: val } },
|
||||
success: function(result) {
|
||||
$('#startDateLabelContainer').html(result.start_date_label);
|
||||
},
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
}
|
||||
return `<span class="my-module-tags-color"></span>
|
||||
${data.label + ' '}
|
||||
<span class="my-module-tags-create-new"> (${I18n.t('my_modules.details.create_new_tag')})</span>`;
|
||||
},
|
||||
onOpen: function() {
|
||||
$('.select-container .edit-button-container').removeClass('hidden');
|
||||
},
|
||||
onClose: function() {
|
||||
$('.select-container .edit-button-container').addClass('hidden');
|
||||
},
|
||||
onSelect: function() {
|
||||
var selectElement = $(myModuleTagsSelector);
|
||||
var lastTag = selectElement.next().find('.ds-tags').last();
|
||||
var lastTagId = lastTag.find('.tag-label').data('ds-tag-id');
|
||||
var newTag;
|
||||
});
|
||||
}
|
||||
|
||||
if (lastTagId > 0) {
|
||||
newTag = { my_module_tag: { tag_id: lastTagId } };
|
||||
$.post(selectElement.data('update-module-tags-url'), newTag)
|
||||
.fail(function() {
|
||||
dropdownSelector.removeValue(myModuleTagsSelector, lastTagId, '', true);
|
||||
});
|
||||
} else {
|
||||
newTag = {
|
||||
tag: {
|
||||
name: lastTag.find('.tag-label').html(),
|
||||
project_id: selectElement.data('project-id'),
|
||||
color: null
|
||||
},
|
||||
my_module_id: selectElement.data('module-id'),
|
||||
simple_creation: true
|
||||
};
|
||||
$.post(selectElement.data('tags-create-url'), newTag, function(result) {
|
||||
dropdownSelector.removeValue(myModuleTagsSelector, 0, '', true);
|
||||
dropdownSelector.addValue(myModuleTagsSelector, {
|
||||
value: result.tag.id,
|
||||
label: result.tag.name,
|
||||
// Bind ajax for editing due dates
|
||||
function initStartDatePicker() {
|
||||
$('#calendarStartDate').on('dp.change', function() {
|
||||
updateStartDate();
|
||||
});
|
||||
}
|
||||
|
||||
function updateDueDate() {
|
||||
let updateUrl = $('#dueDateContainer').data('update-url');
|
||||
let val = $('#calendarDueDate').val();
|
||||
$.ajax({
|
||||
url: updateUrl,
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { my_module: { due_date: val } },
|
||||
success: function(result) {
|
||||
$('#dueDateLabelContainer').html(result.due_date_label);
|
||||
},
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bind ajax for editing due dates
|
||||
function initDueDatePicker() {
|
||||
$('#calendarDueDate').on('dp.change', function() {
|
||||
updateDueDate();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Bind ajax for editing tags
|
||||
function bindEditTagsAjax() {
|
||||
var manageTagsModal = null;
|
||||
var manageTagsModalBody = null;
|
||||
|
||||
// Initialize reloading of manage tags modal content after posting new
|
||||
// tag.
|
||||
function initAddTagForm() {
|
||||
manageTagsModalBody.find('.add-tag-form')
|
||||
.submit(function() {
|
||||
var selectOptions = manageTagsModalBody.find('#new_my_module_tag .dropdown-menu li').length;
|
||||
if (selectOptions === 0 && this.id === 'new_my_module_tag') return false;
|
||||
return true;
|
||||
})
|
||||
.on('ajax:success', function(e, data) {
|
||||
var newTag;
|
||||
initTagsModalBody(data);
|
||||
newTag = $('#manage-module-tags-modal .list-group-item').last();
|
||||
dropdownSelector.addValue('#module-tags-selector', {
|
||||
value: newTag.data('tag-id'),
|
||||
label: newTag.data('name'),
|
||||
params: {
|
||||
color: result.tag.color
|
||||
color: newTag.data('color')
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
},
|
||||
onUnSelect: (id) => {
|
||||
$.post(`${$(myModuleTagsSelector).data('update-module-tags-url')}/${id}/destroy_by_tag_id`);
|
||||
dropdownSelector.closeDropdown(myModuleTagsSelector);
|
||||
}
|
||||
}).getContainer(myModuleTagsSelector).addClass('my-module-tags-container');
|
||||
}
|
||||
|
||||
function initAssignedUsersSelector() {
|
||||
var manageUsersModal = $('#manage-module-users-modal');
|
||||
var manageUsersModalBody = manageUsersModal.find('.modal-body');
|
||||
// Initialize edit tag & remove tag functionality from my_module links.
|
||||
function initTagRowLinks() {
|
||||
manageTagsModalBody.find('.edit-tag-link')
|
||||
.on('click', function() {
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
var editDiv = $(li.find('div.tag-edit'));
|
||||
|
||||
// Initialize users editing modal remote loading
|
||||
function initUsersEditLink() {
|
||||
$('.task-details').on('ajax:success', '.manage-users-link', function(e, data) {
|
||||
manageUsersModal.modal('show');
|
||||
manageUsersModal.find('#manage-module-users-modal-module').text(data.my_module.name);
|
||||
// Revert all rows to their original states
|
||||
manageTagsModalBody.find('li.list-group-item').each(function() {
|
||||
var li2 = $(this);
|
||||
li2.css('background-color', li2.data('color'));
|
||||
li2.find('.edit-tag-form').clearFormErrors();
|
||||
li2.find('input[type=text]').val(li2.data('name'));
|
||||
});
|
||||
|
||||
// Hide all other edit divs, show all show divs
|
||||
manageTagsModalBody.find('div.tag-edit').hide();
|
||||
manageTagsModalBody.find('div.tag-show').show();
|
||||
|
||||
editDiv.find('input[type=text]').val(li.data('name'));
|
||||
editDiv.find('.edit-tag-color').colorselector('setColor', li.data('color'));
|
||||
|
||||
li.find('div.tag-show').hide();
|
||||
editDiv.show();
|
||||
});
|
||||
manageTagsModalBody.find('div.tag-edit .dropdown-colorselector > .dropdown-menu li a')
|
||||
.on('click', function() {
|
||||
// Change background of the <li>
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
li.css('background-color', $this.data('value'));
|
||||
});
|
||||
manageTagsModalBody.find('.remove-tag-link')
|
||||
.on('ajax:success', function(e, data) {
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
manageTagsModalBody.find('.delete-tag-form')
|
||||
.on('ajax:success', function(e, data) {
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
manageTagsModalBody.find('.edit-tag-form')
|
||||
.on('ajax:success', function(e, data) {
|
||||
var newTag;
|
||||
initTagsModalBody(data);
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
newTag = $('#manage-module-tags-modal .list-group-item[data-tag-id=' + this.dataset.tagId + ']');
|
||||
dropdownSelector.addValue('#module-tags-selector', {
|
||||
value: newTag.data('tag-id'),
|
||||
label: newTag.data('name'),
|
||||
params: {
|
||||
color: newTag.data('color')
|
||||
}
|
||||
}, true);
|
||||
})
|
||||
.on('ajax:error', function(e, data) {
|
||||
$(this).renderFormErrors('tag', data.responseJSON);
|
||||
});
|
||||
manageTagsModalBody.find('.cancel-tag-link')
|
||||
.on('click', function() {
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
|
||||
li.css('background-color', li.data('color'));
|
||||
li.find('.edit-tag-form').clearFormErrors();
|
||||
|
||||
li.find('div.tag-edit').hide();
|
||||
li.find('div.tag-show').show();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize ajax listeners and elements style on modal body. This
|
||||
// function must be called when modal body is changed.
|
||||
function initTagsModalBody(data) {
|
||||
manageTagsModalBody.html(data.html);
|
||||
manageTagsModalBody.find('.selectpicker').selectpicker();
|
||||
initAddTagForm();
|
||||
initTagRowLinks();
|
||||
}
|
||||
|
||||
manageTagsModal = $('#manage-module-tags-modal');
|
||||
manageTagsModalBody = manageTagsModal.find('.modal-body');
|
||||
|
||||
// Reload tags HTML element when modal is closed
|
||||
manageTagsModal.on('hide.bs.modal', function() {
|
||||
var tagsEl = $('#module-tags');
|
||||
|
||||
// Load HTML
|
||||
$.ajax({
|
||||
url: tagsEl.attr('data-module-tags-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var newOptions = $(data.html_module_header).find('option');
|
||||
$('#module-tags-selector').find('option').remove();
|
||||
$(newOptions).appendTo('#module-tags-selector').change();
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove modal content when modal window is closed.
|
||||
manageTagsModal.on('hidden.bs.modal', function() {
|
||||
manageTagsModalBody.html('');
|
||||
});
|
||||
// initialize my_module tab remote loading
|
||||
$('.edit-tags-link')
|
||||
.on('ajax:before', function() {
|
||||
manageTagsModal.modal('show');
|
||||
})
|
||||
.on('ajax:success', function(e, data) {
|
||||
$('#manage-module-tags-modal-module').text(data.my_module.name);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
}
|
||||
|
||||
function checkStatusState() {
|
||||
$.getJSON($('.status-flow-dropdown').data('status-check-url'), (statusData) => {
|
||||
if (statusData.status_changing) {
|
||||
setTimeout(() => { checkStatusState(); }, STATUS_POLLING_INTERVAL);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyTaskStatusChangeCallBack() {
|
||||
if ($('.status-flow-dropdown').data('status-changing')) {
|
||||
setTimeout(() => { checkStatusState(); }, STATUS_POLLING_INTERVAL);
|
||||
}
|
||||
$('.task-flows').on('click', '#dropdownTaskFlowList > li[data-state-id]', function() {
|
||||
var list = $('#dropdownTaskFlowList');
|
||||
var item = $(this);
|
||||
animateSpinner();
|
||||
$.ajax({
|
||||
url: list.data('link-url'),
|
||||
beforeSend: function(e, ajaxSettings) {
|
||||
if (item.data('beforeSend') instanceof Function) {
|
||||
return item.data('beforeSend')(item, ajaxSettings)
|
||||
}
|
||||
return true
|
||||
},
|
||||
type: 'PATCH',
|
||||
data: { my_module: { status_id: item.data('state-id') } },
|
||||
error: function(e) {
|
||||
animateSpinner(null, false);
|
||||
if (e.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('my_module_statuses.update_status.error.no_permission'), 'danger');
|
||||
} else if (e.status === 422) {
|
||||
HelperModule.flashAlertMsg(e.responseJSON.errors, 'danger');
|
||||
} else {
|
||||
HelperModule.flashAlertMsg('error', 'danger');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initTagsSelector() {
|
||||
var myModuleTagsSelector = '#module-tags-selector';
|
||||
|
||||
dropdownSelector.init(myModuleTagsSelector, {
|
||||
closeOnSelect: true,
|
||||
tagClass: 'my-module-white-tags',
|
||||
tagStyle: (data) => {
|
||||
return `background: ${data.params.color}`;
|
||||
},
|
||||
customDropdownIcon: () => {
|
||||
return '';
|
||||
},
|
||||
optionLabel: (data) => {
|
||||
if (data.value > 0) {
|
||||
return `<span class="my-module-tags-color" style="background:${data.params.color}"></span>
|
||||
${data.label}`;
|
||||
}
|
||||
return `<span class="my-module-tags-color"></span>
|
||||
${data.label + ' '}
|
||||
<span class="my-module-tags-create-new"> (${I18n.t('my_modules.details.create_new_tag')})</span>`;
|
||||
},
|
||||
onOpen: function() {
|
||||
$('.select-container .edit-button-container').removeClass('hidden');
|
||||
},
|
||||
onClose: function() {
|
||||
$('.select-container .edit-button-container').addClass('hidden');
|
||||
},
|
||||
onSelect: function() {
|
||||
var selectElement = $(myModuleTagsSelector);
|
||||
var lastTag = selectElement.next().find('.ds-tags').last();
|
||||
var lastTagId = lastTag.find('.tag-label').data('ds-tag-id');
|
||||
var newTag;
|
||||
|
||||
if (lastTagId > 0) {
|
||||
newTag = { my_module_tag: { tag_id: lastTagId } };
|
||||
$.post(selectElement.data('update-module-tags-url'), newTag)
|
||||
.fail(function(response) {
|
||||
dropdownSelector.removeValue(myModuleTagsSelector, lastTagId, '', true);
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newTag = {
|
||||
tag: {
|
||||
name: lastTag.find('.tag-label').html(),
|
||||
project_id: selectElement.data('project-id'),
|
||||
color: null
|
||||
},
|
||||
my_module_id: selectElement.data('module-id'),
|
||||
simple_creation: true
|
||||
};
|
||||
$.post(selectElement.data('tags-create-url'), newTag, function(result) {
|
||||
dropdownSelector.removeValue(myModuleTagsSelector, 0, '', true);
|
||||
dropdownSelector.addValue(myModuleTagsSelector, {
|
||||
value: result.tag.id,
|
||||
label: result.tag.name,
|
||||
params: {
|
||||
color: result.tag.color
|
||||
}
|
||||
}, true);
|
||||
}).fail(function() {
|
||||
dropdownSelector.removeValue(myModuleTagsSelector, lastTagId, '', true);
|
||||
});
|
||||
}
|
||||
},
|
||||
onUnSelect: (id) => {
|
||||
$.post(`${$(myModuleTagsSelector).data('update-module-tags-url')}/${id}/destroy_by_tag_id`)
|
||||
.success(function() {
|
||||
dropdownSelector.closeDropdown(myModuleTagsSelector);
|
||||
})
|
||||
.fail(function(r) {
|
||||
if (r.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
}).getContainer(myModuleTagsSelector).addClass('my-module-tags-container');
|
||||
}
|
||||
|
||||
function initAssignedUsersSelector() {
|
||||
var manageUsersModal = $('#manage-module-users-modal');
|
||||
var manageUsersModalBody = manageUsersModal.find('.modal-body');
|
||||
|
||||
// Initialize users editing modal remote loading
|
||||
function initUsersEditLink() {
|
||||
$('.task-details').on('ajax:success', '.manage-users-link', function(e, data) {
|
||||
manageUsersModal.modal('show');
|
||||
manageUsersModal.find('#manage-module-users-modal-module').text(data.my_module.name);
|
||||
initUsersModalBody(data);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize ajax listeners and elements style on modal body.
|
||||
// This function must be called when modal body is changed.
|
||||
function initUsersModalBody(data) {
|
||||
manageUsersModalBody.html(data.html);
|
||||
manageUsersModalBody.find('.selectpicker').selectpicker();
|
||||
}
|
||||
|
||||
// Initialize reloading manage user modal content after posting new user
|
||||
manageUsersModalBody.on('ajax:success', '.add-user-form', function(e, data) {
|
||||
initUsersModalBody(data);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize ajax listeners and elements style on modal body.
|
||||
// This function must be called when modal body is changed.
|
||||
function initUsersModalBody(data) {
|
||||
manageUsersModalBody.html(data.html);
|
||||
manageUsersModalBody.find('.selectpicker').selectpicker();
|
||||
}
|
||||
|
||||
// Initialize reloading manage user modal content after posting new user
|
||||
manageUsersModalBody.on('ajax:success', '.add-user-form', function(e, data) {
|
||||
initUsersModalBody(data);
|
||||
});
|
||||
|
||||
// Initialize remove user from my_module links
|
||||
manageUsersModalBody.on('ajax:success', '.remove-user-link', function(e, data) {
|
||||
initUsersModalBody(data);
|
||||
});
|
||||
|
||||
// Reload users HTML element when modal is closed
|
||||
manageUsersModal.on('hide.bs.modal', function() {
|
||||
var usersEl = $('.task-assigned-users');
|
||||
// Load HTML to refresh users
|
||||
$.ajax({
|
||||
url: usersEl.attr('data-module-users-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
$('.task-assigned-users').replaceWith(data.html);
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
}
|
||||
// Initialize remove user from my_module links
|
||||
manageUsersModalBody.on('ajax:success', '.remove-user-link', function(e, data) {
|
||||
initUsersModalBody(data);
|
||||
});
|
||||
});
|
||||
|
||||
// Remove users modal content when modal window is closed.
|
||||
manageUsersModal.on('hidden.bs.modal', function() {
|
||||
manageUsersModalBody.html('');
|
||||
});
|
||||
// Reload users HTML element when modal is closed
|
||||
manageUsersModal.on('hide.bs.modal', function() {
|
||||
var usersEl = $('.task-assigned-users');
|
||||
// Load HTML to refresh users
|
||||
$.ajax({
|
||||
url: usersEl.attr('data-module-users-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
$('.task-assigned-users').replaceWith(data.html);
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
initUsersEditLink();
|
||||
}
|
||||
// Remove users modal content when modal window is closed.
|
||||
manageUsersModal.on('hidden.bs.modal', function() {
|
||||
manageUsersModalBody.html('');
|
||||
});
|
||||
|
||||
initTaskCollapseState();
|
||||
applyTaskCompletedCallBack();
|
||||
initTagsSelector();
|
||||
bindEditTagsAjax();
|
||||
initStartDatePicker();
|
||||
initDueDatePicker();
|
||||
initAssignedUsersSelector();
|
||||
initUsersEditLink();
|
||||
}
|
||||
|
||||
initTaskCollapseState();
|
||||
applyTaskStatusChangeCallBack();
|
||||
initTagsSelector();
|
||||
bindEditTagsAjax();
|
||||
initStartDatePicker();
|
||||
initDueDatePicker();
|
||||
initAssignedUsersSelector();
|
||||
}());
|
||||
|
|
|
@ -11,16 +11,24 @@ var selectedRow = null;
|
|||
|
||||
function initEditMyModuleDescription() {
|
||||
var viewObject = $('#my_module_description_view');
|
||||
viewObject.on('click', function() {
|
||||
viewObject.on('click', function(e) {
|
||||
if ($(e.target).hasClass('record-info-link')) return;
|
||||
TinyMCE.init('#my_module_description_textarea');
|
||||
}).on('click', 'a', function(e) {
|
||||
if ($(this).hasClass('record-info-link')) return;
|
||||
e.stopPropagation();
|
||||
});
|
||||
TinyMCE.initIfHasDraft(viewObject);
|
||||
}
|
||||
|
||||
function initEditProtocolDescription() {
|
||||
var viewObject = $('#protocol_description_view');
|
||||
viewObject.on('click', function() {
|
||||
viewObject.on('click', function(e) {
|
||||
if ($(e.target).hasClass('record-info-link')) return;
|
||||
TinyMCE.init('#protocol_description_textarea', refreshProtocolStatusBar);
|
||||
}).on('click', 'a', function(e) {
|
||||
if ($(this).hasClass('record-info-link')) return;
|
||||
e.stopPropagation();
|
||||
});
|
||||
TinyMCE.initIfHasDraft(viewObject);
|
||||
}
|
||||
|
@ -361,11 +369,13 @@ function loadFromRepository() {
|
|||
// Simply reload page
|
||||
location.reload();
|
||||
},
|
||||
error: function(ev) {
|
||||
// Display error message in alert()
|
||||
alert(ev.responseJSON.message);
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
} else {
|
||||
alert(response.responseJSON.message);
|
||||
}
|
||||
|
||||
// Hide modal
|
||||
modal.modal('hide');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -453,7 +453,8 @@ var MyModuleRepositories = (function() {
|
|||
|
||||
FULL_VIEW_MODAL.on('show.bs.modal', function() {
|
||||
FULL_VIEW_MODAL.find('.table-container').empty();
|
||||
FULL_VIEW_MODAL.find('.repository-name').empty();
|
||||
FULL_VIEW_MODAL.find('.repository-title').empty();
|
||||
FULL_VIEW_MODAL.find('.repository-version').empty();
|
||||
updateFullViewRowsCount('');
|
||||
});
|
||||
}
|
||||
|
@ -518,29 +519,31 @@ var MyModuleRepositories = (function() {
|
|||
|
||||
function updateFullViewRowsCount(value) {
|
||||
FULL_VIEW_MODAL.data('rows-count', value);
|
||||
FULL_VIEW_MODAL.find('.repository-name').attr('data-rows-count', value);
|
||||
FULL_VIEW_MODAL.find('.repository-version').attr('data-rows-count', value);
|
||||
}
|
||||
|
||||
function renderFullViewRepositoryName(name, snapshotDate, assignMode) {
|
||||
var title;
|
||||
var repositoryName = name || FULL_VIEW_MODAL.find('.repository-name').data('repository-name');
|
||||
var version;
|
||||
var repositoryName = name || FULL_VIEW_MODAL.find('.repository-title').data('repository-name');
|
||||
|
||||
if (assignMode) {
|
||||
title = I18n.t('my_modules.repository.full_view.assign_modal_header', {
|
||||
repository_name: repositoryName
|
||||
});
|
||||
version = '';
|
||||
} else if (snapshotDate) {
|
||||
title = I18n.t('my_modules.repository.full_view.modal_snapshot_header', {
|
||||
repository_name: repositoryName,
|
||||
title = repositoryName;
|
||||
version = I18n.t('my_modules.repository.full_view.modal_snapshot_header', {
|
||||
snaphot_date: snapshotDate
|
||||
});
|
||||
} else {
|
||||
title = I18n.t('my_modules.repository.full_view.modal_live_header', {
|
||||
repository_name: repositoryName
|
||||
});
|
||||
title = repositoryName;
|
||||
version = I18n.t('my_modules.repository.full_view.modal_live_header');
|
||||
}
|
||||
FULL_VIEW_MODAL.find('.repository-name').data('repository-name', repositoryName);
|
||||
FULL_VIEW_MODAL.find('.repository-name').html(title);
|
||||
FULL_VIEW_MODAL.find('.repository-title').data('repository-name', repositoryName);
|
||||
FULL_VIEW_MODAL.find('.repository-title').html(title);
|
||||
FULL_VIEW_MODAL.find('.repository-version').html(version);
|
||||
}
|
||||
|
||||
function initRepoistoryAssignView() {
|
||||
|
@ -635,9 +638,13 @@ var MyModuleRepositories = (function() {
|
|||
updateFullViewRowsCount(data.rows_count);
|
||||
renderFullViewAssignButtons();
|
||||
},
|
||||
error: function(data) {
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
} else {
|
||||
HelperModule.flashAlertMsg(response.responseJSON.flash, 'danger');
|
||||
}
|
||||
UPDATE_REPOSITORY_MODAL.modal('hide');
|
||||
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
||||
SELECTED_ROWS = {};
|
||||
FULL_VIEW_TABLE.ajax.reload(null, false);
|
||||
}
|
||||
|
|
|
@ -47,17 +47,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
function applyCollapseLinkCallBack() {
|
||||
$('.panel-collapse')
|
||||
.on('shown.bs.collapse hidden.bs.collapse', function() {
|
||||
var collapseIcon = $(this).closest('.panel').find('.collapse-result-icon');
|
||||
var collapsed = $(this).closest('.panel').find('.result-panel-collapse-link').hasClass('collapsed');
|
||||
// Toggle collapse button
|
||||
collapseIcon.toggleClass('fa-caret-up', !collapsed);
|
||||
collapseIcon.toggleClass('fa-caret-down', collapsed);
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle editing buttons
|
||||
function toggleResultEditButtons(show) {
|
||||
if (show) {
|
||||
|
@ -80,10 +69,6 @@
|
|||
// Expand all results
|
||||
function expandAllResults() {
|
||||
$('.result .panel-collapse').collapse('show');
|
||||
$(document).find('span.collapse-result-icon').each(function() {
|
||||
$(this).addClass('fa-caret-up');
|
||||
$(this).removeClass('fa-caret-down');
|
||||
});
|
||||
$(document).find('div.step-result-hot-table').each(function() {
|
||||
renderTable(this);
|
||||
});
|
||||
|
@ -91,10 +76,6 @@
|
|||
|
||||
function expandResult(result) {
|
||||
$('.panel-collapse', result).collapse('show');
|
||||
$(result).find('span.collapse-result-icon').each(function() {
|
||||
$(this).addClass('fa-caret-up');
|
||||
$(this).removeClass('fa-caret-down');
|
||||
});
|
||||
renderTable($(result).find('div.step-result-hot-table'));
|
||||
animateSpinner(null, false);
|
||||
}
|
||||
|
@ -135,7 +116,6 @@
|
|||
$.each($('#results').find('.result'), function() {
|
||||
initFormSubmitLinks($(this));
|
||||
ResultAssets.applyEditResultAssetCallback();
|
||||
applyCollapseLinkCallBack();
|
||||
applyCreateWopiFileCallback();
|
||||
toggleResultEditButtons(true);
|
||||
FilePreviewModal.init();
|
||||
|
@ -208,15 +188,11 @@
|
|||
function init() {
|
||||
initHandsOnTables($(document));
|
||||
expandAllResults();
|
||||
applyCollapseLinkCallBack();
|
||||
applyCreateWopiFileCallback();
|
||||
|
||||
$(function() {
|
||||
$('#results-collapse-btn').click(function() {
|
||||
$('.result .panel-collapse').collapse('hide');
|
||||
$(document).find('span.collapse-result-icon')
|
||||
.addClass('fa-caret-down')
|
||||
.removeClass('fa-caret-square-up');
|
||||
});
|
||||
|
||||
$('#results-expand-btn').click(expandAllResults);
|
||||
|
@ -233,7 +209,6 @@
|
|||
let publicAPI = Object.freeze({
|
||||
init: init,
|
||||
initHandsOnTables: initHandsOnTables,
|
||||
applyCollapseLinkCallBack: applyCollapseLinkCallBack,
|
||||
toggleResultEditButtons: toggleResultEditButtons,
|
||||
expandResult: expandResult,
|
||||
processResult: processResult,
|
||||
|
|
16
app/assets/javascripts/my_modules/status_flow.js
Normal file
16
app/assets/javascripts/my_modules/status_flow.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* global animateSpinner */
|
||||
|
||||
(function() {
|
||||
$('.task-flows').on('click', '#viewTaskFlow', function() {
|
||||
$('#statusFlowModal').modal('show');
|
||||
});
|
||||
|
||||
$('#statusFlowModal').on('show.bs.modal', function() {
|
||||
var $modalBody = $(this).find('.modal-body');
|
||||
animateSpinner($modalBody);
|
||||
$.get($(this).data('status-flow-url'), function(result) {
|
||||
animateSpinner($modalBody, false);
|
||||
$modalBody.html(result.html);
|
||||
});
|
||||
});
|
||||
}());
|
|
@ -176,10 +176,6 @@ function expandAllSteps() {
|
|||
$(document).find("[data-role='step-hot-table']").each(function() {
|
||||
renderTable($(this));
|
||||
});
|
||||
$(document).find('span.collapse-step-icon').each(function() {
|
||||
$(this).addClass('fa-caret-square-up');
|
||||
$(this).removeClass('fa-caret-square-down');
|
||||
});
|
||||
}
|
||||
|
||||
function handleFormSubmit(modal) {
|
||||
|
|
|
@ -22,8 +22,12 @@ var ProtocolRepositoryHeader = (function() {
|
|||
|
||||
function initEditDescription() {
|
||||
var viewObject = $('#protocol_description_view');
|
||||
viewObject.on('click', function() {
|
||||
viewObject.on('click', function(e) {
|
||||
if ($(e.target).hasClass('record-info-link')) return;
|
||||
TinyMCE.init('#protocol_description_textarea');
|
||||
}).on('click', 'a', function(e) {
|
||||
if ($(this).hasClass('record-info-link')) return;
|
||||
e.stopPropagation();
|
||||
});
|
||||
TinyMCE.initIfHasDraft(viewObject);
|
||||
}
|
||||
|
|
|
@ -439,79 +439,78 @@ function updateButtons() {
|
|||
var archiveBtn = $("[data-action='archive']");
|
||||
var restoreBtn = $("[data-action='restore']");
|
||||
var exportBtn = $("[data-action='export']");
|
||||
var row = $("tr[data-row-id='" + rowsSelected[0] + "']");
|
||||
var rows = [];
|
||||
|
||||
if (rowsSelected.length == 1) {
|
||||
if (rowsSelected.length === 1) {
|
||||
// 1 ROW SELECTED
|
||||
var row = $("tr[data-row-id='" + rowsSelected[0] + "']");
|
||||
|
||||
if (row.is("[data-can-edit]")) {
|
||||
editBtn.removeAttr("disabled");
|
||||
editBtn.off("click").on("click", function() { editSelectedProtocol(); });
|
||||
if (row.is('[data-can-edit]')) {
|
||||
editBtn.removeClass('disabled hidden');
|
||||
editBtn.off('click').on('click', function() { editSelectedProtocol(); });
|
||||
} else {
|
||||
editBtn.attr("disabled", "disabled");
|
||||
editBtn.off("click");
|
||||
editBtn.removeClass('hidden').addClass('disabled');
|
||||
editBtn.off('click');
|
||||
}
|
||||
if (row.is("[data-can-clone]")) {
|
||||
cloneBtn.removeAttr("disabled");
|
||||
cloneBtn.off("click").on("click", function() { cloneSelectedProtocol(); });
|
||||
if (row.is('[data-can-clone]')) {
|
||||
cloneBtn.removeClass('disabled hidden');
|
||||
cloneBtn.off('click').on('click', function() { cloneSelectedProtocol(); });
|
||||
} else {
|
||||
cloneBtn.attr("disabled", "disabled");
|
||||
cloneBtn.off("click");
|
||||
cloneBtn.removeClass('hidden').addClass('disabled');
|
||||
cloneBtn.off('click');
|
||||
}
|
||||
if (row.is("[data-can-make-private]")) {
|
||||
makePrivateBtn.removeAttr("disabled");
|
||||
makePrivateBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
if (row.is('[data-can-make-private]')) {
|
||||
makePrivateBtn.removeClass('disabled hidden');
|
||||
makePrivateBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
makePrivateBtn.attr("disabled", "disabled");
|
||||
makePrivateBtn.off("click");
|
||||
makePrivateBtn.removeClass('hidden').addClass('disabled');
|
||||
makePrivateBtn.off('click');
|
||||
}
|
||||
if (row.is("[data-can-publish]")) {
|
||||
publishBtn.removeAttr("disabled");
|
||||
publishBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
if (row.is('[data-can-publish]')) {
|
||||
publishBtn.removeClass('disabled hidden');
|
||||
publishBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
publishBtn.attr("disabled", "disabled");
|
||||
publishBtn.off("click");
|
||||
publishBtn.removeClass('hidden').addClass('disabled');
|
||||
publishBtn.off('click');
|
||||
}
|
||||
if (row.is("[data-can-archive]")) {
|
||||
archiveBtn.removeAttr("disabled");
|
||||
archiveBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
if (row.is('[data-can-archive]')) {
|
||||
archiveBtn.removeClass('disabled hidden');
|
||||
archiveBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
archiveBtn.attr("disabled", "disabled");
|
||||
archiveBtn.off("click");
|
||||
archiveBtn.removeClass('hidden').addClass('disabled');
|
||||
archiveBtn.off('click');
|
||||
}
|
||||
if (row.is("[data-can-restore]")) {
|
||||
restoreBtn.removeAttr("disabled");
|
||||
restoreBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
if (row.is('[data-can-restore]')) {
|
||||
restoreBtn.removeClass('disabled hidden');
|
||||
restoreBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
restoreBtn.attr("disabled", "disabled");
|
||||
restoreBtn.off("click");
|
||||
restoreBtn.removeClass('hidden').addClass('disabled');
|
||||
restoreBtn.off('click');
|
||||
}
|
||||
if (row.is("[data-can-export]")) {
|
||||
exportBtn.removeAttr("disabled");
|
||||
exportBtn.off("click").on("click", function() { exportProtocols(rowsSelected); });
|
||||
if (row.is('[data-can-export]')) {
|
||||
exportBtn.removeClass('disabled hidden');
|
||||
exportBtn.off('click').on('click', function() { exportProtocols(rowsSelected); });
|
||||
} else {
|
||||
exportBtn.attr("disabled", "disabled");
|
||||
exportBtn.off("click");
|
||||
exportBtn.removeClass('hidden').addClass('disabled');
|
||||
exportBtn.off('click');
|
||||
}
|
||||
} else if (rowsSelected.length === 0) {
|
||||
// 0 ROWS SELECTED
|
||||
editBtn.attr("disabled", "disabled");
|
||||
editBtn.off("click");
|
||||
cloneBtn.attr("disabled", "disabled");
|
||||
cloneBtn.off("click");
|
||||
makePrivateBtn.attr("disabled", "disabled");
|
||||
makePrivateBtn.off("click");
|
||||
publishBtn.attr("disabled", "disabled");
|
||||
publishBtn.off("click");
|
||||
archiveBtn.attr("disabled", "disabled");
|
||||
archiveBtn.off("click");
|
||||
restoreBtn.attr("disabled", "disabled");
|
||||
restoreBtn.off("click");
|
||||
exportBtn.attr("disabled", "disabled");
|
||||
exportBtn.off("click");
|
||||
editBtn.addClass('disabled hidden');
|
||||
editBtn.off('click');
|
||||
cloneBtn.addClass('disabled hidden');
|
||||
cloneBtn.off('click');
|
||||
makePrivateBtn.addClass('disabled hidden');
|
||||
makePrivateBtn.off('click');
|
||||
publishBtn.addClass('disabled hidden');
|
||||
publishBtn.off('click');
|
||||
archiveBtn.addClass('disabled hidden');
|
||||
archiveBtn.off('click');
|
||||
restoreBtn.addClass('disabled hidden');
|
||||
restoreBtn.off('click');
|
||||
exportBtn.addClass('disabled hidden');
|
||||
exportBtn.off('click');
|
||||
} else {
|
||||
// > 1 ROWS SELECTED
|
||||
var rows = [];
|
||||
_.each(rowsSelected, function(rowId) {
|
||||
rows.push($("tr[data-row-id='" + rowId + "']")[0]);
|
||||
});
|
||||
|
@ -519,44 +518,44 @@ function updateButtons() {
|
|||
|
||||
// Only enable button if all selected rows can
|
||||
// be published/archived/restored/exported
|
||||
editBtn.attr("disabled", "disabled");
|
||||
editBtn.off("click");
|
||||
cloneBtn.attr("disabled", "disabled");
|
||||
cloneBtn.off("click");
|
||||
if (!rows.is(":not([data-can-make-private])")) {
|
||||
makePrivateBtn.removeAttr("disabled");
|
||||
makePrivateBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
editBtn.removeClass('hidden').addClass('disabled');
|
||||
editBtn.off('click');
|
||||
cloneBtn.removeClass('hidden').addClass('disabled');
|
||||
cloneBtn.off('click');
|
||||
if (!rows.is(':not([data-can-make-private])')) {
|
||||
makePrivateBtn.removeClass('disabled hidden');
|
||||
makePrivateBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
makePrivateBtn.attr("disabled", "disabled");
|
||||
makePrivateBtn.off("click");
|
||||
makePrivateBtn.removeClass('hidden').addClass('disabled');
|
||||
makePrivateBtn.off('click');
|
||||
}
|
||||
if (!rows.is(":not([data-can-publish])")) {
|
||||
publishBtn.removeAttr("disabled");
|
||||
publishBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
if (!rows.is(':not([data-can-publish])')) {
|
||||
publishBtn.removeClass('disabled hidden');
|
||||
publishBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
publishBtn.attr("disabled", "disabled");
|
||||
publishBtn.off("click");
|
||||
publishBtn.removeClass('hidden').addClass('disabled');
|
||||
publishBtn.off('click');
|
||||
}
|
||||
if (!rows.is(":not([data-can-archive])")) {
|
||||
archiveBtn.removeAttr("disabled");
|
||||
archiveBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
if (!rows.is(':not([data-can-archive])')) {
|
||||
archiveBtn.removeClass('disabled hidden');
|
||||
archiveBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
archiveBtn.attr("disabled", "disabled");
|
||||
archiveBtn.off("click");
|
||||
archiveBtn.removeClass('hidden').addClass('disabled');
|
||||
archiveBtn.off('click');
|
||||
}
|
||||
if (!rows.is(":not([data-can-restore])")) {
|
||||
restoreBtn.removeAttr("disabled");
|
||||
restoreBtn.off("click").on("click", function() { processMoveButtonClick($(this)); });
|
||||
if (!rows.is(':not([data-can-restore])')) {
|
||||
restoreBtn.removeClass('disabled hidden');
|
||||
restoreBtn.off('click').on('click', function() { processMoveButtonClick($(this)); });
|
||||
} else {
|
||||
restoreBtn.attr("disabled", "disabled");
|
||||
restoreBtn.off("click");
|
||||
restoreBtn.removeClass('hidden').addClass('disabled');
|
||||
restoreBtn.off('click');
|
||||
}
|
||||
if (!rows.is(":not([data-can-export])")) {
|
||||
exportBtn.removeAttr("disabled");
|
||||
exportBtn.off("click").on("click", function() { exportProtocols(rowsSelected); });
|
||||
if (!rows.is(':not([data-can-export])')) {
|
||||
exportBtn.removeClass('disabled hidden');
|
||||
exportBtn.off('click').on('click', function() { exportProtocols(rowsSelected); });
|
||||
} else {
|
||||
exportBtn.attr("disabled", "disabled");
|
||||
exportBtn.off("click");
|
||||
exportBtn.removeClass('hidden').addClass('disabled');
|
||||
exportBtn.off('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,38 +24,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Complete mymodule
|
||||
function complete_my_module_actions() {
|
||||
var modal = $('#completed-task-modal');
|
||||
|
||||
modal.find('[data-action="complete"]')
|
||||
.off().on().click(function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
$.ajax({
|
||||
url: modal.data('url'),
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
var task_button = $("[data-action='complete-task']");
|
||||
task_button.attr('data-action', 'uncomplete-task');
|
||||
task_button.find('.btn')
|
||||
.removeClass('btn-toggle').addClass('btn-default');
|
||||
$('.task-due-date').html(data.module_header_due_date);
|
||||
$('.task-state-label').html(data.module_state_label);
|
||||
task_button
|
||||
.find('button')
|
||||
.html('<span class="fas fa-times"></span> ' +
|
||||
data.task_button_title);
|
||||
modal.modal('hide');
|
||||
},
|
||||
error: function() {
|
||||
modal.modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Sets callback for completing/uncompleting step
|
||||
function applyStepCompletedCallBack() {
|
||||
// First, remove old event handlers, as we use turbolinks
|
||||
|
@ -77,25 +45,20 @@
|
|||
|
||||
button = step.find("[data-action='complete-step']");
|
||||
button.attr("data-action", "uncomplete-step");
|
||||
button.find(".btn").removeClass("btn-toggle").addClass("btn-default");
|
||||
button.find("button").html('<span class="fas fa-times"></span> ' + data.new_title);
|
||||
|
||||
if (data.task_ready_to_complete) {
|
||||
$('#completed-task-modal').modal('show');
|
||||
complete_my_module_actions();
|
||||
}
|
||||
button.html('<span class="fas fa-times"></span> ' + data.new_title);
|
||||
}
|
||||
else {
|
||||
step.addClass("not-completed").removeClass("completed");
|
||||
|
||||
button = step.find("[data-action='uncomplete-step']");
|
||||
button.attr("data-action", "complete-step");
|
||||
button.find(".btn").removeClass("btn-default").addClass("btn-toggle");
|
||||
button.find("button").html('<span class="fas fa-check"></span> ' + data.new_title);
|
||||
button.html('<span class="fas fa-check"></span> ' + data.new_title);
|
||||
}
|
||||
},
|
||||
error: function (data) {
|
||||
console.log ("error");
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -157,6 +120,11 @@
|
|||
tinyMCE.editors.step_description_textarea.remove();
|
||||
TinyMCE.init('#step_description_textarea');
|
||||
});
|
||||
})
|
||||
.on("ajax:error", function(e, response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -166,44 +134,24 @@
|
|||
if ($.isEmptyObject(data)) return;
|
||||
|
||||
let $step = $(this).closest('.step');
|
||||
let stepUpPosition = data.step_up_position;
|
||||
let stepDownPosition = data.step_down_position;
|
||||
let $stepDown, $stepUp;
|
||||
let steps = $('#steps').find('.step');
|
||||
$('#steps').append($.map(data.steps_order, function(step_data) {
|
||||
let step = $('#steps').find(`.step[data-id=${step_data.id}]`);
|
||||
step.find('.step-number').html(`${step_data.position + 1}.`);
|
||||
return step;
|
||||
}));
|
||||
|
||||
switch ($(this).data('direction')) {
|
||||
case 'up':
|
||||
$stepDown = $step.prev('.step');
|
||||
$stepUp = $step;
|
||||
break;
|
||||
case 'down':
|
||||
$stepDown = $step;
|
||||
$stepUp = $step.next('.step');
|
||||
}
|
||||
|
||||
// Switch position of top and bottom steps
|
||||
if (!_.isUndefined($stepDown) && !_.isUndefined($stepUp)) {
|
||||
$stepDown.insertAfter($stepUp);
|
||||
$stepDown.find('.step-number').html(`${stepDownPosition + 1}.`);
|
||||
$stepUp.find('.step-number').html(`${stepUpPosition + 1}.`);
|
||||
$('html, body').animate({ scrollTop: $step.offset().top - window.innerHeight / 2 });
|
||||
}
|
||||
$('html, body').animate({ scrollTop: $step.offset().top - window.innerHeight / 2 });
|
||||
|
||||
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
|
||||
})
|
||||
.on("ajax:error", function(e, xhr) {
|
||||
if (xhr.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyCollapseLinkCallBack() {
|
||||
$(".step-panel-collapse-link")
|
||||
.on("ajax:success", function() {
|
||||
var collapseIcon = $(this).find(".collapse-step-icon");
|
||||
var collapsed = $(this).hasClass("collapsed");
|
||||
// Toggle collapse button
|
||||
collapseIcon.toggleClass("fa-chevron-up", !collapsed);
|
||||
collapseIcon.toggleClass("fa-chevron-down", collapsed);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function formCallback($form) {
|
||||
$form
|
||||
.on("fields_added.nested_form_fields", function(e, param) {
|
||||
|
@ -387,7 +335,6 @@
|
|||
applyStepCompletedCallBack();
|
||||
applyEditCallBack();
|
||||
applyMoveStepCallBack();
|
||||
applyCollapseLinkCallBack();
|
||||
initDeleteStep();
|
||||
TinyMCE.highlight();
|
||||
}
|
||||
|
@ -518,8 +465,11 @@
|
|||
});
|
||||
|
||||
},
|
||||
error: function() {
|
||||
newStepHandler();
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
animateSpinner(null, false);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -648,17 +598,10 @@
|
|||
$(document).find("[data-role='step-hot-table']").each(function() {
|
||||
renderTable($(this));
|
||||
});
|
||||
$(document).find("span.collapse-step-icon").each(function() {
|
||||
$(this).addClass("fa-chevron-up");
|
||||
$(this).removeClass("fa-chevron-down");
|
||||
});
|
||||
}
|
||||
|
||||
function expandStep(step) {
|
||||
$('.panel-collapse', step).collapse('show');
|
||||
$(step).find("span.collapse-step-icon")
|
||||
.addClass("fa-chevron-up")
|
||||
.removeClass("fa-chevron-down");
|
||||
$(step).find("div.step-result-hot-table").each(function() {
|
||||
renderTable($(this));
|
||||
});
|
||||
|
@ -706,10 +649,6 @@
|
|||
$(function () {
|
||||
$("[data-action='collapse-steps']").click(function () {
|
||||
$('.step .panel-collapse').collapse('hide');
|
||||
$(document).find("span.collapse-step-icon").each(function() {
|
||||
$(this).addClass("fa-chevron-down");
|
||||
$(this).removeClass("fa-chevron-up");
|
||||
});
|
||||
});
|
||||
$("[data-action='expand-steps']").click(expandAllSteps);
|
||||
});
|
||||
|
|
|
@ -114,14 +114,14 @@
|
|||
var editReportButton = $('#edit-report-btn');
|
||||
var deleteReportsButton = $('#delete-reports-btn');
|
||||
if (CHECKED_REPORTS.length === 0) {
|
||||
editReportButton.addClass("disabled");
|
||||
deleteReportsButton.addClass("disabled");
|
||||
editReportButton.addClass('disabled hidden');
|
||||
deleteReportsButton.addClass('disabled hidden');
|
||||
} else if (CHECKED_REPORTS.length === 1) {
|
||||
editReportButton.removeClass("disabled");
|
||||
deleteReportsButton.removeClass("disabled");
|
||||
editReportButton.removeClass('disabled hidden');
|
||||
deleteReportsButton.removeClass('disabled hidden');
|
||||
} else {
|
||||
editReportButton.addClass("disabled");
|
||||
deleteReportsButton.removeClass("disabled");
|
||||
editReportButton.removeClass('hidden').addClass('disabled');
|
||||
deleteReportsButton.removeClass('disabled hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,6 @@
|
|||
initFormSubmitLinks($newResult);
|
||||
$(this).remove();
|
||||
applyEditResultAssetCallback();
|
||||
Results.applyCollapseLinkCallBack();
|
||||
|
||||
Results.toggleResultEditButtons(true);
|
||||
Results.expandResult($newResult);
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
$(this).remove();
|
||||
|
||||
applyEditResultTableCallback();
|
||||
Results.applyCollapseLinkCallBack();
|
||||
Results.initHandsOnTables($result);
|
||||
Results.toggleResultEditButtons(true);
|
||||
Results.expandResult($result);
|
||||
|
|
|
@ -73,7 +73,6 @@
|
|||
initFormSubmitLinks(newResult);
|
||||
$(this).remove();
|
||||
applyEditResultTextCallback();
|
||||
Results.applyCollapseLinkCallBack();
|
||||
Results.toggleResultEditButtons(true);
|
||||
Results.expandResult(newResult);
|
||||
TinyMCE.destroyAll();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "initInlineEditing" }]*/
|
||||
/* global SmartAnnotation */
|
||||
/* global SmartAnnotation HelperModule I18n */
|
||||
|
||||
var inlineEditing = (function() {
|
||||
const SIDEBAR_ITEM_TYPES = ['project', 'experiment', 'my_module', 'repository'];
|
||||
|
@ -103,6 +103,9 @@ var inlineEditing = (function() {
|
|||
},
|
||||
error: function(response) {
|
||||
var error = response.responseJSON[fieldToUpdate];
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
if (!error) error = response.responseJSON.errors[fieldToUpdate];
|
||||
container.addClass('error');
|
||||
container.find('.error-block').html(error.join(', '));
|
||||
|
|
241
app/assets/javascripts/sitewide/atwho_res.js
Normal file
241
app/assets/javascripts/sitewide/atwho_res.js
Normal file
|
@ -0,0 +1,241 @@
|
|||
/* global _ */
|
||||
|
||||
var SmartAnnotation = (function() {
|
||||
'use strict';
|
||||
|
||||
// stop the user annotation popover on click propagation
|
||||
function atwhoStopPropagation(element) {
|
||||
$(element).on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function SetAtWho(field) {
|
||||
var FilterTypeEnum = Object.freeze({
|
||||
USER: { tag: 'users', dataUrl: $(document.body).attr('data-atwho-users-url') },
|
||||
TASK: { tag: 'sa-tasks', dataUrl: $(document.body).attr('data-atwho-task-url') },
|
||||
PROJECT: { tag: 'sa-projects', dataUrl: $(document.body).attr('data-atwho-project-url') },
|
||||
EXPERIMENT: { tag: 'sa-experiments', dataUrl: $(document.body).attr('data-atwho-experiment-url') },
|
||||
REPOSITORY: { tag: 'sa-repositories', dataUrl: $(document.body).attr('data-atwho-rep-items-url') },
|
||||
MENU: { tag: 'menu', dataUrl: $(document.body).attr('data-atwho-menu-items') }
|
||||
});
|
||||
var DEFAULT_SEARCH_FILTER = FilterTypeEnum.REPOSITORY;
|
||||
|
||||
function matchHighlighter(html, query) {
|
||||
var $html = $(html);
|
||||
var $liText = $html.find('.item-text');
|
||||
if ($liText.length === 0 || !query) return html;
|
||||
|
||||
$.each($liText, function(i, item) {
|
||||
$(item).html($(item).text().replace(new RegExp(query.split(' ').join('|'), 'gi'),
|
||||
'<span class="atwho-highlight">$&</span>'));
|
||||
});
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Generates suggestion dropdown filter
|
||||
function generateFilterMenu() {
|
||||
var menu = '';
|
||||
$.ajax({
|
||||
async: false,
|
||||
dataType: 'json',
|
||||
url: $(document.body).attr('data-atwho-repositories-url'),
|
||||
success: function(data) {
|
||||
menu = data.html;
|
||||
}
|
||||
});
|
||||
return menu;
|
||||
}
|
||||
|
||||
function atWhoSettings(at) {
|
||||
return {
|
||||
at: at,
|
||||
callbacks: {
|
||||
remoteFilter: function(query, callback) {
|
||||
var $currentAtWho = $(`.atwho-view[data-at-who-id=${$(field).attr('data-smart-annotation')}]`);
|
||||
var filterType;
|
||||
var params = { query: query };
|
||||
filterType = FilterTypeEnum[$currentAtWho.find('.tab-pane.active').data('object-type')];
|
||||
if (!filterType) {
|
||||
callback([{ name: '' }]);
|
||||
return false;
|
||||
}
|
||||
if (filterType.tag === 'sa-repositories') {
|
||||
let repositoryTab = $currentAtWho.find('[data-object-type="REPOSITORY"]');
|
||||
let activeRepository = repositoryTab.find('.btn-primary');
|
||||
if (activeRepository.length) {
|
||||
params.repository_id = activeRepository.data('object-id');
|
||||
}
|
||||
}
|
||||
$.getJSON(filterType.dataUrl, params, function(data) {
|
||||
localStorage.setItem('smart_annotation_states/teams/' + data.team, JSON.stringify({
|
||||
tag: filterType.tag,
|
||||
repository: data.repository
|
||||
}));
|
||||
|
||||
callback(data.res);
|
||||
|
||||
if (data.repository) {
|
||||
$currentAtWho.find(`.repository-object[data-object-id="${data.repository}"]`)
|
||||
.addClass('btn-primary').removeClass('btn-light');
|
||||
}
|
||||
});
|
||||
return true;
|
||||
},
|
||||
tplEval: function(_tpl, items) {
|
||||
var $items = $(items.name);
|
||||
$items.find('li').data('item-data', { 'atwho-at': at }); // Emulate at.js insertContentFor method
|
||||
return $items;
|
||||
},
|
||||
highlighter: function(li, query) {
|
||||
return matchHighlighter(li, query);
|
||||
},
|
||||
beforeInsert: function(value, li) {
|
||||
return `[#${li.attr('data-name')}~${li.attr('data-type')}~${li.attr('data-id')}]`;
|
||||
},
|
||||
matcher: function(flag, subtext, shouldStartWithSpace) {
|
||||
var a;
|
||||
var y;
|
||||
var match;
|
||||
var regexp;
|
||||
var cleanedFlag = flag.replace(/[-[]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
||||
if (shouldStartWithSpace) {
|
||||
cleanedFlag = '(?:^|\\s)' + cleanedFlag;
|
||||
}
|
||||
a = decodeURI('%C3%80');
|
||||
y = decodeURI('%C3%BF');
|
||||
regexp = new RegExp(`${cleanedFlag}$|${cleanedFlag}(\\S[A-Za-z${a}-${y}0-9_/:\\s+-]*)$|${cleanedFlag}(\\S[^\\x00-\\xff]*)$`, 'gi');
|
||||
match = regexp.exec(subtext);
|
||||
if (match) {
|
||||
return match[1] || '';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
headerTpl: generateFilterMenu(),
|
||||
startWithSpace: true,
|
||||
acceptSpaceBar: true,
|
||||
displayTimeout: 120000
|
||||
};
|
||||
}
|
||||
|
||||
function init() {
|
||||
$(field)
|
||||
.on('shown.atwho', function() {
|
||||
var $currentAtWho = $('.atwho-view[style]:not(.old)');
|
||||
var atWhoId = $currentAtWho.find('.atwho-header-res').data('at-who-key');
|
||||
$currentAtWho.addClass('old').attr('data-at-who-id', atWhoId);
|
||||
$(field).attr('data-smart-annotation', atWhoId);
|
||||
|
||||
$currentAtWho.find('.tab-button').off().on('shown.bs.tab', function() {
|
||||
$(field).click().focus();
|
||||
$(this).closest('.nav-tabs').find('.tab-button').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
});
|
||||
$currentAtWho.find('.repository-object').off().on('click', function() {
|
||||
$(this).parent().find('.repository-object').removeClass('btn-primary')
|
||||
.addClass('btn-light');
|
||||
$(this).addClass('btn-primary').removeClass('btn-light');
|
||||
$(field).click().focus();
|
||||
});
|
||||
|
||||
if ($currentAtWho.find('.tab-pane.active').length === 0) {
|
||||
let filterType = DEFAULT_SEARCH_FILTER.tag;
|
||||
let teamId = $currentAtWho.find('.atwho-header-res').data('team-id');
|
||||
let remeberedState = localStorage.getItem('smart_annotation_states/teams/' + teamId);
|
||||
if (remeberedState) {
|
||||
try {
|
||||
remeberedState = JSON.parse(remeberedState);
|
||||
filterType = remeberedState.tag;
|
||||
$currentAtWho.find(`.repository-object[data-object-id=${remeberedState.repository}]`)
|
||||
.addClass('btn-primary');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
$currentAtWho.find(`.${filterType}`).click();
|
||||
}
|
||||
})
|
||||
.on('reposition.atwho', function(event, flag, query) {
|
||||
let inputFieldLeft = query.$inputor.offset().left;
|
||||
if (inputFieldLeft > $(window).width()) {
|
||||
let leftPosition;
|
||||
if (inputFieldLeft < flag.left + $(window).scrollLeft()) {
|
||||
leftPosition = inputFieldLeft;
|
||||
} else {
|
||||
leftPosition = flag.left + $(window).scrollLeft();
|
||||
}
|
||||
query.$el.find('.atwho-view').css('left', leftPosition + 'px');
|
||||
}
|
||||
if ($('.repository-show').length) {
|
||||
query.$el.find('.atwho-view').css('top', flag.top + 'px');
|
||||
}
|
||||
})
|
||||
.atwho({
|
||||
at: '@',
|
||||
callbacks: {
|
||||
remoteFilter: function(query, callback) {
|
||||
$.getJSON(FilterTypeEnum.USER.dataUrl, { query: query }, function(data) {
|
||||
callback(data.users);
|
||||
});
|
||||
},
|
||||
tplEval: function(_tpl, items) {
|
||||
var $items = $(items.name);
|
||||
$items.find('li').data('item-data', { 'atwho-at': '@' }); // Emulate at.js insertContentFor method
|
||||
return $items;
|
||||
},
|
||||
highlighter: function(li, query) {
|
||||
return matchHighlighter(li, query);
|
||||
},
|
||||
beforeInsert: function(value, li) {
|
||||
return `[@${li.attr('data-full-name')}~${li.attr('data-id')}]`;
|
||||
}
|
||||
},
|
||||
startsWithSpace: true,
|
||||
acceptSpaceBar: true,
|
||||
displayTimeout: 120000
|
||||
})
|
||||
.atwho(atWhoSettings('#'));
|
||||
// .atwho(atWhoSettings('task#', FilterTypeEnum.TASK)) Waiting for better times
|
||||
// .atwho(atWhoSettings('project#', FilterTypeEnum.PROJECT))
|
||||
// .atwho(atWhoSettings('experiment#', FilterTypeEnum.EXPERIMENT))
|
||||
// .atwho(atWhoSettings('sample#', FilterTypeEnum.REPOSITORY));
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
}
|
||||
// Closes the atwho popup * needed in repositories to close the popup
|
||||
// if nothing is selected and the user leaves the form *
|
||||
function closePopup() {
|
||||
$('.atwho-header-res').find('.fa-times').click();
|
||||
}
|
||||
|
||||
function initialize(field) {
|
||||
var atWho = new SetAtWho(field);
|
||||
atWho.init();
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
init: initialize,
|
||||
preventPropagation: atwhoStopPropagation,
|
||||
closePopup: closePopup
|
||||
});
|
||||
}());
|
||||
|
||||
|
||||
// initialize the smart annotations
|
||||
(function() {
|
||||
$(document).on('focus', '[data-atwho-edit]', function() {
|
||||
if (_.isUndefined($(this).data('atwho'))) {
|
||||
SmartAnnotation.init(this);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.atwho-view .dismiss', function() {
|
||||
$(this).closest('.atwho-view').hide();
|
||||
});
|
||||
}());
|
|
@ -1,520 +0,0 @@
|
|||
var SmartAnnotation = (function() {
|
||||
'use strict';
|
||||
|
||||
// utilities
|
||||
var Util = (function() {
|
||||
// helper method that binds show/hidden action
|
||||
function showHideBinding() {
|
||||
$.each(['show', 'hide'], function (i, ev) {
|
||||
var el = $.fn[ev];
|
||||
$.fn[ev] = function () {
|
||||
this.trigger(ev);
|
||||
return el.apply(this, arguments);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var publicApi = {
|
||||
showHideBinding: showHideBinding
|
||||
};
|
||||
|
||||
return publicApi;
|
||||
})();
|
||||
|
||||
// stop the user annotation popover on click propagation
|
||||
function atwhoStopPropagation(element) {
|
||||
$(element).on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function setAtWho(field) {
|
||||
var FilterTypeEnum = Object.freeze({
|
||||
USER: {tag: "users",
|
||||
dataUrl: $(document.body).attr('data-atwho-users-url')},
|
||||
TASK: {tag: "tsk",
|
||||
dataUrl: $(document.body).attr('data-atwho-task-url')},
|
||||
PROJECT: {tag: "prj",
|
||||
dataUrl: $(document.body).attr('data-atwho-project-url')},
|
||||
EXPERIMENT: {tag: "exp",
|
||||
dataUrl: $(document.body).attr('data-atwho-experiment-url')},
|
||||
REPOSITORY: {tag: "rep",
|
||||
dataUrl: $(document.body).attr('data-atwho-rep-items-url')},
|
||||
MENU: {tag: "menu",
|
||||
dataUrl: $(document.body).attr('data-atwho-menu-items')}
|
||||
});
|
||||
var prevAt,
|
||||
// Default selected filter when using '#'
|
||||
DEFAULT_SEARCH_FILTER = FilterTypeEnum.REPOSITORY,
|
||||
atWhoUpdating = false;
|
||||
|
||||
var defaultRepId;
|
||||
|
||||
// helper methods for AtWho callback
|
||||
function _templateEval(_tpl, map) {
|
||||
var res;
|
||||
try {
|
||||
if (map.no_results) {
|
||||
res = noResultsTemplate();
|
||||
} else {
|
||||
res = generateTemplate(map);
|
||||
}
|
||||
} catch (_error) {
|
||||
res = '';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function _matchHighlighter(li, query, filterType) {
|
||||
var $li, re;
|
||||
|
||||
function highlight(el, sel, re) {
|
||||
var prevVal, newVal;
|
||||
prevVal = el.find(sel).html();
|
||||
newVal = prevVal.replace(re, '<strong>$&</strong>');
|
||||
el.find(sel).html(newVal);
|
||||
}
|
||||
|
||||
if (!query || $(li).data('no-results')) {
|
||||
return li;
|
||||
}
|
||||
|
||||
$li = $(li);
|
||||
re = new RegExp(query, 'gi');
|
||||
// search_filter is not passed for the user
|
||||
if(filterType) {
|
||||
highlight($li, '[data-val=name]', re);
|
||||
} else {
|
||||
highlight($li, '[data-val=full-name]', re);
|
||||
highlight($li, '[data-val=email]', re);
|
||||
}
|
||||
|
||||
return $li[0].outerHTML
|
||||
}
|
||||
|
||||
function _generateInputTag(value, li) {
|
||||
var res = '';
|
||||
res += '[#' + li.attr('data-name');
|
||||
res += '~' + li.attr('data-type');
|
||||
res += '~' + li.attr('data-id') + ']';
|
||||
return res;
|
||||
}
|
||||
|
||||
// initialise dropdown dismiss button
|
||||
function initDismissButton($currentAtWho) {
|
||||
$currentAtWho.find('.dismiss').off('click')
|
||||
.on('click', function() {
|
||||
$(field).atwho('destroy');
|
||||
init();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize or update dropdown header buttons
|
||||
function updateHeaderButtons(query, filterType) {
|
||||
var $currentAtWho = $('.atwho-view[style]');
|
||||
initDismissButton($currentAtWho);
|
||||
|
||||
// Update the selected filter button when changing smart annotation type
|
||||
$currentAtWho.find('[data-filter]')
|
||||
.removeClass('btn-primary')
|
||||
.addClass('btn-default');
|
||||
if(filterType.tag === 'rep') {
|
||||
$currentAtWho.find('[data-rep-id="' + filterType.repository_id + '"]')
|
||||
.removeClass('btn-default')
|
||||
.addClass('btn-primary');
|
||||
} else {
|
||||
$currentAtWho.find('[data-filter="' + filterType.tag + '"]')
|
||||
.removeClass('btn-default')
|
||||
.addClass('btn-primary');
|
||||
}
|
||||
|
||||
// Update the selected filter button when clicking on one of them
|
||||
$currentAtWho.find('[data-filter]').off()
|
||||
.on('click', function(e) {
|
||||
if($(this).hasClass('btn-primary')) {
|
||||
return;
|
||||
}
|
||||
var $selectedBtn = $(this);
|
||||
var $prevBtn = $selectedBtn.closest('.atwho-header-res')
|
||||
.children('.btn-primary');
|
||||
$selectedBtn.removeClass('btn-default').addClass('btn-primary');
|
||||
$prevBtn.removeClass('btn-primary').addClass('btn-default');
|
||||
|
||||
// Updates query and dropdown elements; focuses input
|
||||
$(field).click().focus();
|
||||
});
|
||||
}
|
||||
|
||||
// Generates suggestion dropdown filter
|
||||
function generateFilterMenu(active, res_data) {
|
||||
var rep_buttons = '';
|
||||
$.ajax({
|
||||
async: false,
|
||||
dataType: 'json',
|
||||
url: $(document.body).attr('data-atwho-repositories-url'),
|
||||
success: function(data) {
|
||||
$.each(data['repositories'], function(id, name) {
|
||||
if(defaultRepId === undefined){
|
||||
defaultRepId = id;
|
||||
}
|
||||
rep_buttons += '<button data-filter="rep" data-rep-id="' + id +
|
||||
'" class="btn btn-xs btn-primary">' + name + '</button>';
|
||||
});
|
||||
}
|
||||
});
|
||||
var header = '<div class="atwho-header-res">' +
|
||||
'<button data-filter="prj" class="btn btn-xs btn-primary' +
|
||||
'">Projects</button>' +
|
||||
'<button data-filter="exp" class="btn btn-xs btn-primary' +
|
||||
'">Experiments</button>' +
|
||||
'<button data-filter="tsk" class="btn btn-xs btn-primary' +
|
||||
'">Tasks</button>' +
|
||||
rep_buttons +
|
||||
'<div class="dismiss">' +
|
||||
'<span class="fas fa-times"></span>' +
|
||||
'</div>' +
|
||||
'<div class="help">' +
|
||||
'<div>' +
|
||||
'<strong><%= I18n.t("atwho.users.navigate_1") %></strong> ' +
|
||||
'<%= I18n.t("atwho.users.navigate_2") %>' +
|
||||
'</div>' +
|
||||
'<div><strong><%= I18n.t("atwho.users.confirm_1") %></strong> ' +
|
||||
'<%= I18n.t("atwho.users.confirm_2") %>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<strong><%= I18n.t("atwho.users.dismiss_1") %></strong> ' +
|
||||
'<%= I18n.t("atwho.users.dismiss_2") %>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
function noResultsTemplate() {
|
||||
var res = '<div class="atwho-no-results" data-no-results="1">';
|
||||
res += '<span><%= I18n.t("atwho.no_results") %></span>';
|
||||
res += '</div>';
|
||||
return res;
|
||||
}
|
||||
|
||||
// Generates resources list items
|
||||
function generateTemplate(map) {
|
||||
var res = '';
|
||||
res += '<li class="atwho-li atwho-li-res" data-name="' +
|
||||
truncateLongString(map.name,
|
||||
<%= Constants::NAME_TRUNCATION_LENGTH %>) +
|
||||
'" data-id="' + map.id + '" data-type="' +
|
||||
map.type + '">';
|
||||
switch(map.type) {
|
||||
case 'tsk':
|
||||
res += '<span data-type class="res-type">' + map.type + '</span>';
|
||||
break;
|
||||
case 'prj':
|
||||
res += '<span data-type class="res-type">' + map.type + '</span>';
|
||||
break;
|
||||
case 'exp':
|
||||
res += '<span data-type class="res-type">' + map.type + '</span>';
|
||||
break;
|
||||
case 'rep_item':
|
||||
res += '<span data-type class="res-type">' +
|
||||
map.repository_tag + '</span>';
|
||||
break;
|
||||
}
|
||||
|
||||
res += ' ';
|
||||
res += '<span data-val="name" class="res-name">';
|
||||
res += truncateLongString(map.name,
|
||||
<%= Constants::NAME_TRUNCATION_LENGTH %>);
|
||||
res += '</span>';
|
||||
if(map.archived) {
|
||||
res += '<%= I18n.t("atwho.res.archived") %></span>';
|
||||
} else {
|
||||
res += '</span>';
|
||||
}
|
||||
res += ' ';
|
||||
|
||||
switch (map.type) {
|
||||
case 'tsk':
|
||||
|
||||
res += '<span class="res-description">< ' + map.experimentName +
|
||||
' < ' + map.projectName + '</span>';
|
||||
break;
|
||||
case 'exp':
|
||||
res += '<span class="res-description">< ' + map.projectName + '</span>';
|
||||
break;
|
||||
}
|
||||
|
||||
res += '</li>';
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hackish wrapper function to make AtWho work when switching between
|
||||
* multiple AtWho instances (e.g. from # to task#).
|
||||
*
|
||||
* Prevents second execution of AtWho update callback, triggered when user
|
||||
* switches to different AtWho instance (e.g. from # to task#), which causes
|
||||
* both of them to be called. In such case, AtWhO modal needs to be
|
||||
* rerendered.
|
||||
*/
|
||||
function atWhoSwitchHack(filterTypeTag, remoteFilterCb) {
|
||||
if(atWhoUpdating || (!$(field).length && _.isUndefined(filterTypeTag))) {
|
||||
setTimeout(function() {
|
||||
$(field).click();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
atWhoUpdating = true;
|
||||
setTimeout(function() {
|
||||
remoteFilterCb();
|
||||
atWhoUpdating = false;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function atWhoSettings(at, defaultFilterType) {
|
||||
return {
|
||||
at: at,
|
||||
callbacks: {
|
||||
remoteFilter: function(query, callback) {
|
||||
var $currentAtWho = $('.atwho-view[style]');
|
||||
var filterTypeTag = $currentAtWho
|
||||
.find('.btn-primary')
|
||||
.data('filter');
|
||||
|
||||
atWhoSwitchHack(filterTypeTag, function() {
|
||||
var filterType;
|
||||
if (_.isUndefined(filterTypeTag)) {
|
||||
// Switched smart annotation type (i.e. changed input)
|
||||
filterType = defaultFilterType;
|
||||
} else {
|
||||
// Switched filtering type (i.e. different filter button
|
||||
// pressed; works also for specific annotation types, e.g.
|
||||
// task#, and coverts to the correct annotation type on confirm)
|
||||
$.each(FilterTypeEnum, function(k, v) {
|
||||
if (v.tag == filterTypeTag) {
|
||||
filterType = FilterTypeEnum[k];
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (prevAt != at) {
|
||||
// Switching smart annotation type (i.e. chaned input)
|
||||
|
||||
prevAt = at;
|
||||
filterType = defaultFilterType;
|
||||
// Hide current AtWho
|
||||
$currentAtWho.removeAttr("style");
|
||||
}
|
||||
|
||||
var params = { query: query };
|
||||
|
||||
if(filterType.tag === 'rep') {
|
||||
params.repository_id = $currentAtWho
|
||||
.find('.btn-primary')
|
||||
.data('rep-id');
|
||||
if(params.repository_id === undefined) {
|
||||
params.repository_id = defaultRepId;
|
||||
}
|
||||
filterType.repository_id = params.repository_id;
|
||||
}
|
||||
|
||||
$.getJSON(
|
||||
filterType.dataUrl,
|
||||
params,
|
||||
function(data) {
|
||||
// Updates dropdown
|
||||
if (data.res.length < 1) {
|
||||
callback([{no_results: 1}]);
|
||||
} else {
|
||||
callback(data.res);
|
||||
}
|
||||
|
||||
updateHeaderButtons(query, filterType);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
sorter: function(query, items, _searchKey) {
|
||||
// Sorting is already done on server-side
|
||||
return items;
|
||||
},
|
||||
tplEval: function(_tpl, map) {
|
||||
return _templateEval(_tpl, map);
|
||||
},
|
||||
highlighter: function(li, query) {
|
||||
return _matchHighlighter(li, query, true);
|
||||
},
|
||||
beforeInsert: function(value, li) {
|
||||
return _generateInputTag(value, li);
|
||||
},
|
||||
matcher:function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
|
||||
var _a, _y, match, regexp, space;
|
||||
flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
||||
if (should_startWithSpace) {
|
||||
flag = '(?:^|\\s)' + flag;
|
||||
}
|
||||
_a = decodeURI("%C3%80");
|
||||
_y = decodeURI("%C3%BF");
|
||||
regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_/:\\s\+\-\]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
|
||||
match = regexp.exec(subtext);
|
||||
if (match) {
|
||||
return match[2] || match[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
headerTpl: generateFilterMenu(defaultFilterType),
|
||||
limit: <%= Constants::ATWHO_SEARCH_LIMIT %>,
|
||||
startWithSpace: true,
|
||||
acceptSpaceBar: true,
|
||||
displayTimeout: 120000
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
$(field)
|
||||
.on("reposition.atwho", function(event, flag, query) {
|
||||
let inputFieldLeft = query.$inputor.offset().left;
|
||||
if (inputFieldLeft > $(window).width()) {
|
||||
let leftPosition;
|
||||
if (inputFieldLeft < flag.left + $(window).scrollLeft()) {
|
||||
leftPosition = inputFieldLeft;
|
||||
} else {
|
||||
leftPosition = flag.left + $(window).scrollLeft();
|
||||
}
|
||||
query.$el.find('.atwho-view').css('left', leftPosition + 'px');
|
||||
}
|
||||
if ($('.repository-show').length) {
|
||||
query.$el.find('.atwho-view').css('top', flag.top + 'px');
|
||||
}
|
||||
})
|
||||
.atwho({
|
||||
at: '@',
|
||||
callbacks: {
|
||||
remoteFilter: function(query, callback) {
|
||||
$.getJSON(
|
||||
FilterTypeEnum.USER.dataUrl,
|
||||
{query: query},
|
||||
function(data) {
|
||||
if (data.users.length < 1) {
|
||||
callback([{no_results: 1}]);
|
||||
} else {
|
||||
callback(data.users);
|
||||
}
|
||||
initDismissButton($('.atwho-view[style]'));
|
||||
}
|
||||
);
|
||||
},
|
||||
sorter: function(query, items, _searchKey) {
|
||||
// Sorting is already done on server-side
|
||||
return items;
|
||||
},
|
||||
tplEval: function(_tpl, map) {
|
||||
var res;
|
||||
try {
|
||||
if (map.no_results) {
|
||||
res = noResultsTemplate();
|
||||
} else {
|
||||
res = '';
|
||||
res += '<li class="atwho-li atwho-li-user" ';
|
||||
res += 'data-id="' + map.id + '" ';
|
||||
res += 'data-full-name="' + map.full_name + '">';
|
||||
res += '<span class="global-avatar-container"><img src="' + map.img_url + '" class="avatar" /></span>';
|
||||
res += '<span data-val="full-name">';
|
||||
res += map.full_name;
|
||||
res += '</span>';
|
||||
res += '<small>';
|
||||
res += ' ';
|
||||
res += '·';
|
||||
res += ' ';
|
||||
res += '<span data-val="email">';
|
||||
res += map.email;
|
||||
res += '</span>';
|
||||
res += '</small>';
|
||||
res += '</li>';
|
||||
}
|
||||
} catch (_error) {
|
||||
res = '';
|
||||
}
|
||||
return res;
|
||||
},
|
||||
highlighter: function(li, query) {
|
||||
return _matchHighlighter(li, query);
|
||||
},
|
||||
beforeInsert: function(value, li) {
|
||||
var res = '';
|
||||
res += '[@' + li.attr('data-full-name');
|
||||
res += '~' + li.attr('data-id') + ']';
|
||||
return res;
|
||||
}
|
||||
},
|
||||
headerTpl:
|
||||
'<div class="atwho-header-res">' +
|
||||
'<div class="title-user"><%= I18n.t("atwho.users.title") %></div>' +
|
||||
'<div class="help">' +
|
||||
'<div>' +
|
||||
'<strong><%= I18n.t("atwho.users.navigate_1") %></strong> ' +
|
||||
'<%= I18n.t("atwho.users.navigate_2") %>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<strong><%= I18n.t("atwho.users.confirm_1") %></strong> ' +
|
||||
'<%= I18n.t("atwho.users.confirm_2") %>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<strong><%= I18n.t("atwho.users.dismiss_1") %></strong> ' +
|
||||
'<%= I18n.t("atwho.users.dismiss_2") %>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="dismiss">' +
|
||||
'<span class="fas fa-times"></span>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
limit: <%= Constants::ATWHO_SEARCH_LIMIT %>,
|
||||
startsWithSpace: true,
|
||||
acceptSpaceBar: true,
|
||||
displayTimeout: 120000
|
||||
})
|
||||
.atwho(atWhoSettings('#', DEFAULT_SEARCH_FILTER))
|
||||
// .atwho(atWhoSettings('task#', FilterTypeEnum.TASK)) Waiting for better times
|
||||
// .atwho(atWhoSettings('project#', FilterTypeEnum.PROJECT))
|
||||
// .atwho(atWhoSettings('experiment#', FilterTypeEnum.EXPERIMENT));
|
||||
}
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
}
|
||||
// Closes the atwho popup * needed in repositories to close the popup
|
||||
// if nothing is selected and the user leaves the form *
|
||||
function closePopup() {
|
||||
$('.atwho-header-res').find('.fa-times').click();
|
||||
}
|
||||
|
||||
function initialize(field) {
|
||||
var atWho = new setAtWho(field);
|
||||
atWho.init();
|
||||
}
|
||||
|
||||
var publicApi = Object.freeze({
|
||||
init: initialize,
|
||||
preventPropagation: atwhoStopPropagation,
|
||||
closePopup: closePopup
|
||||
});
|
||||
|
||||
return publicApi;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
// initialize the smart annotations
|
||||
(function initSmartAnnotation() {
|
||||
$(document).on('focus', '[data-atwho-edit]', function() {
|
||||
if(_.isUndefined($(this).data('atwho'))) {
|
||||
SmartAnnotation.init(this);
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -1,4 +1,4 @@
|
|||
/* global inlineEditing PerfectScrollbar */
|
||||
/* global inlineEditing PerfectScrollbar HelperModule I18n */
|
||||
/* eslint-disable no-restricted-globals, no-alert */
|
||||
var Comments = (function() {
|
||||
function changeCounter(comment, value) {
|
||||
|
@ -49,7 +49,11 @@ var Comments = (function() {
|
|||
$this.closest('.comment-container').remove();
|
||||
},
|
||||
error: (error) => {
|
||||
alert(error.responseJSON.errors.message);
|
||||
if (error.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
} else {
|
||||
alert(error.responseJSON.errors.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -85,6 +89,9 @@ var Comments = (function() {
|
|||
$el.find('textarea').focus().blur();
|
||||
})
|
||||
.error((error) => {
|
||||
if (error.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
errorField.html(error.responseJSON.errors.message);
|
||||
newButton.disable = false;
|
||||
});
|
||||
|
|
|
@ -434,7 +434,6 @@
|
|||
$.each($('[data-container="new-reports"]').find('.result'), function() {
|
||||
initFormSubmitLinks($(this));
|
||||
ResultAssets.applyEditResultAssetCallback();
|
||||
Results.applyCollapseLinkCallBack();
|
||||
Results.toggleResultEditButtons(true);
|
||||
FilePreviewModal.init();
|
||||
Comments.init();
|
||||
|
|
|
@ -91,6 +91,9 @@ var dropdownSelector = (function() {
|
|||
|
||||
// Get data in JSON from field
|
||||
function getCurrentData(container) {
|
||||
if (!container.find('.data-field').val()) {
|
||||
return '';
|
||||
}
|
||||
return JSON.parse(container.find('.data-field').val());
|
||||
}
|
||||
|
||||
|
@ -179,8 +182,19 @@ var dropdownSelector = (function() {
|
|||
}
|
||||
|
||||
// Add selected option to value
|
||||
function addSelectedOption(selector, container) {
|
||||
setData(selector, [convertOptionToJson($(selector).find('option:selected')[0])], true);
|
||||
function addSelectedOptions(selector, container) {
|
||||
var selectedOptions = [];
|
||||
var optionSelector = selector.data('config').noEmptyOption ? 'option:selected' : 'option[data-selected=true]';
|
||||
$.each($(selector).find(optionSelector), function(i, option) {
|
||||
selectedOptions.push(convertOptionToJson(option));
|
||||
if (selector.data('config').singleSelect) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!selectedOptions.length) return false;
|
||||
|
||||
setData(selector, selectedOptions, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare custom dropdown icon
|
||||
|
@ -422,8 +436,8 @@ var dropdownSelector = (function() {
|
|||
}
|
||||
|
||||
// Select default value
|
||||
if (config.noEmptyOption && config.singleSelect) {
|
||||
addSelectedOption(selectElement, dropdownContainer);
|
||||
if (!selectElement.data('ajax-url')) {
|
||||
addSelectedOptions(selectElement, dropdownContainer);
|
||||
}
|
||||
|
||||
// Enable simple mode for dropdown selector
|
||||
|
@ -849,17 +863,20 @@ var dropdownSelector = (function() {
|
|||
return this;
|
||||
},
|
||||
|
||||
// Select value
|
||||
selectValue: function(selector, value) {
|
||||
var $selector;
|
||||
// Select values
|
||||
selectValues: function(selector, values) {
|
||||
var $selector = $(selector);
|
||||
var option;
|
||||
var valuesArray = [].concat(values);
|
||||
var options = [];
|
||||
|
||||
if ($(selector).length === 0) return false;
|
||||
|
||||
$selector = $(selector);
|
||||
option = $selector.find(`option[value="${value}"]`)[0];
|
||||
setData($selector, [convertOptionToJson(option)]);
|
||||
if ($selector.length === 0) return false;
|
||||
|
||||
valuesArray.forEach(function(value) {
|
||||
option = $selector.find(`option[value="${value}"]`)[0];
|
||||
options.push(convertOptionToJson(option));
|
||||
});
|
||||
setData($selector, options);
|
||||
return this;
|
||||
},
|
||||
|
||||
|
|
|
@ -168,6 +168,10 @@ var MarvinJsEditorApi = (function() {
|
|||
$(marvinJsModal).modal('hide');
|
||||
FilePreviewModal.init();
|
||||
config.button.dataset.inProgress = false;
|
||||
}).error((response) => {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -196,6 +200,11 @@ var MarvinJsEditorApi = (function() {
|
|||
}
|
||||
$(marvinJsModal).modal('hide');
|
||||
config.button.dataset.inProgress = false;
|
||||
},
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
5
app/assets/javascripts/sitewide/tiny_mce.js
vendored
5
app/assets/javascripts/sitewide/tiny_mce.js
vendored
|
@ -1,4 +1,4 @@
|
|||
/* global _ hljs tinyMCE SmartAnnotation I18n GLOBAL_CONSTANTS */
|
||||
/* global _ hljs tinyMCE SmartAnnotation I18n GLOBAL_CONSTANTS HelperModule */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var TinyMCE = (function() {
|
||||
|
@ -278,6 +278,9 @@ var TinyMCE = (function() {
|
|||
var model = editor.getElement().dataset.objectType;
|
||||
$(this).renderFormErrors(model, data.responseJSON);
|
||||
editor.setProgressState(0);
|
||||
if (data.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
|
||||
// Init Cancel button
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
@import url(https://fonts.googleapis.com/css?family=Lato:400,400i,700&subset=latin-ext);
|
||||
|
||||
//==============================================================================
|
||||
// Colors
|
||||
//==============================================================================
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
margin-right: 4px;
|
||||
width: 36px;
|
||||
|
||||
.curent-tasks-filters {
|
||||
.current-tasks-filters {
|
||||
padding: 0;
|
||||
width: 230px;
|
||||
|
||||
|
@ -133,28 +133,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
.current-tasks-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.current-tasks-list-wrapper {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.current-tasks-list {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content max-content;
|
||||
padding: 0 1em;
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.current-task-item {
|
||||
border-bottom: $border-tertiary;
|
||||
color: $color-volcano;
|
||||
padding: 6px;
|
||||
display: contents;
|
||||
text-decoration: none;
|
||||
|
||||
.current-task-breadcrumbs {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
line-height: 14px;
|
||||
grid-column: span 3;
|
||||
line-height: 1em;
|
||||
padding: .5em .5em .25em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
@ -166,134 +171,61 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
.row-border {
|
||||
border-bottom: $border-tertiary;
|
||||
height: 32px;
|
||||
line-height: 24px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
flex-grow: 1;
|
||||
font-size: $font-size-base;
|
||||
.task-name {
|
||||
font-size: $font-size-base;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
padding: 0 .5em;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-due-date {
|
||||
padding: 0 2em 0 1em;
|
||||
|
||||
.fas {
|
||||
padding: .25em;
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
&.day-prior {
|
||||
color: $brand-warning;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
color: $brand-success;
|
||||
}
|
||||
}
|
||||
|
||||
.task-status-container {
|
||||
grid-column: 3;
|
||||
padding: 0 .5em;
|
||||
text-align: right;
|
||||
|
||||
.task-status {
|
||||
@include font-small;
|
||||
border-radius: $border-radius-tag;
|
||||
color: $color-white;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
padding-right: 10px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-due-date {
|
||||
flex-basis: 280px;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
|
||||
.fas {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
&.day-prior {
|
||||
color: $brand-warning;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
color: $brand-success;
|
||||
}
|
||||
padding: .25em .5em;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover > * {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-progress-container {
|
||||
height: 20px;
|
||||
max-width: 250px;
|
||||
min-width: 150px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::after {
|
||||
@include font-small;
|
||||
@include font-awesome;
|
||||
content: "";
|
||||
line-height: 18px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.task-progress {
|
||||
background: $brand-focus-light;
|
||||
border: $border-tertiary;
|
||||
border-radius: $border-radius-tag;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
background: $color-white;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.task-progress-label {
|
||||
@include font-small;
|
||||
font-weight: bold;
|
||||
height: 20px;
|
||||
left: 0;
|
||||
line-height: 20px;
|
||||
padding-left: 8px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
.task-progress {
|
||||
background: $brand-danger-light;
|
||||
}
|
||||
|
||||
.task-progress-label {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
&::after {
|
||||
color: $brand-danger;
|
||||
content: $font-fas-exclamation-triangle;
|
||||
}
|
||||
}
|
||||
|
||||
&.day-prior {
|
||||
.task-progress-label {
|
||||
color: $brand-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&.completed {
|
||||
.task-progress {
|
||||
outline: $border-success;
|
||||
}
|
||||
|
||||
.task-progress,
|
||||
.task-progress::after {
|
||||
background: $brand-success-light;
|
||||
}
|
||||
|
||||
.task-progress-label {
|
||||
color: $brand-success;
|
||||
}
|
||||
|
||||
&::after {
|
||||
color: $brand-success;
|
||||
content: $font-fas-check;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1500px) {
|
||||
|
@ -370,22 +302,37 @@
|
|||
}
|
||||
|
||||
.current-tasks-list {
|
||||
grid-template-columns: auto;
|
||||
|
||||
.current-task-item {
|
||||
.item-row {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.task-due-date {
|
||||
@include font-small;
|
||||
.current-task-breadcrumbs {
|
||||
grid-column: 1;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.fas {
|
||||
display: none;
|
||||
}
|
||||
.task-name {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.task-due-date {
|
||||
@include font-small;
|
||||
border: 0;
|
||||
height: 24px;
|
||||
padding-left: 0;
|
||||
|
||||
|
||||
.fas {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.task-progress-container {
|
||||
flex-basis: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
.task-status-container {
|
||||
grid-column: 1;
|
||||
text-align: left;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,9 @@
|
|||
word-break: initial;
|
||||
|
||||
thead {
|
||||
background-color: $color-concrete;
|
||||
|
||||
tr {
|
||||
th {
|
||||
background-color: $color-concrete;
|
||||
border-bottom: 2px solid $color-white;
|
||||
border-left: 2px solid $color-white;
|
||||
font-weight: bold;
|
||||
|
@ -31,6 +30,11 @@
|
|||
|
||||
&:first-child {
|
||||
border-left: 0;
|
||||
border-top-left-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: $border-radius-default;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -169,20 +169,6 @@
|
|||
.date-container {
|
||||
flex-shrink: 0;
|
||||
padding-right: 20px;
|
||||
|
||||
.activities-group-expand-button {
|
||||
user-select: none;
|
||||
|
||||
&:hover,
|
||||
&:visited,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.fas {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date-activities {
|
||||
|
@ -197,11 +183,24 @@
|
|||
|
||||
.activities-group-expand-button {
|
||||
color: $color-emperor;
|
||||
user-select: none;
|
||||
|
||||
&:hover,
|
||||
&:visited,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.fas {
|
||||
display: inline-block;
|
||||
margin-right: 3px;
|
||||
text-align: center;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
&:not(.collapsed) .fas {
|
||||
@include rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
|
|
|
@ -166,10 +166,6 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
|
||||
.complete-step-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -503,6 +503,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
.task-information {
|
||||
column-gap: 1em;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(max-content, 18em);
|
||||
|
||||
.task-section-header {
|
||||
grid-column: 1 / span 1;
|
||||
}
|
||||
|
||||
.task-details {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
|
||||
.task-flows {
|
||||
grid-column: 2 / span 1;
|
||||
grid-row: 1 / span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.my-module-protocol-status {
|
||||
.status-info-dropdown {
|
||||
|
@ -518,4 +538,18 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-information {
|
||||
grid-template-columns: auto;
|
||||
row-gap: .5em;
|
||||
|
||||
.task-details {
|
||||
grid-row: 3 / span 1;
|
||||
}
|
||||
|
||||
.task-flows {
|
||||
grid-column: unset;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,21 +5,9 @@
|
|||
@include font-h3;
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
padding-right: 55px;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&::after {
|
||||
color: $color-alto;
|
||||
content: '[' attr(data-rows-count) ']';
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
padding-left: 5px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.my-module-inventories {
|
||||
|
@ -131,6 +119,16 @@
|
|||
|
||||
.assigned-repository-title {
|
||||
@include my-module-repository-title;
|
||||
padding-right: 2.2em;
|
||||
|
||||
&::after {
|
||||
color: $color-alto;
|
||||
content: '[' attr(data-rows-count) ']';
|
||||
display: inline-block;
|
||||
padding-right: .7em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
|
@ -218,11 +216,26 @@
|
|||
flex-grow: 1;
|
||||
max-width: calc(100% - 20px);
|
||||
|
||||
.repository-name {
|
||||
.repository-name-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.repository-title {
|
||||
@include my-module-repository-title;
|
||||
@include font-h2;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.repository-version {
|
||||
@include font-h2;
|
||||
flex-shrink: 0;
|
||||
padding-right: .7em;
|
||||
|
||||
&::after {
|
||||
color: $color-alto;
|
||||
content: '[' attr(data-rows-count) ']';
|
||||
display: inline-block;
|
||||
padding-left: .3em;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
|
|
167
app/assets/stylesheets/my_modules/status_flow.scss
Normal file
167
app/assets/stylesheets/my_modules/status_flow.scss
Normal file
|
@ -0,0 +1,167 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
// scss-lint:disable SelectorFormat
|
||||
// scss-lint:disable ImportantRule
|
||||
|
||||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
.content-pane.my-modules-protocols-index {
|
||||
.status-flow-dropdown {
|
||||
.dropdown-toggle {
|
||||
color: $color-white;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
.caret {
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.open .dropdown-menu{
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, auto) 12px minmax(0, auto);
|
||||
padding: .5em 0 0;
|
||||
|
||||
li {
|
||||
display: contents;
|
||||
|
||||
> * {
|
||||
cursor: pointer;
|
||||
line-height: 2em;
|
||||
padding: .5em 1em;
|
||||
}
|
||||
|
||||
&:hover > *{
|
||||
background: $color-concrete;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
|
||||
.status-name {
|
||||
background: $color-alto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fa-long-arrow-alt-right {
|
||||
color: $color-silver-chalice;
|
||||
padding: .5em 0;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.status-name {
|
||||
border-radius: $border-radius-tag;
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
line-height: 1em;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
padding: .5em;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
grid-column: span 3;
|
||||
line-height: 1em;
|
||||
padding: 0em 1em .5em;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.permission-error {
|
||||
padding: .5em 1em;
|
||||
}
|
||||
}
|
||||
|
||||
#viewTaskFlow {
|
||||
border-top: $border-default;
|
||||
cursor: pointer;
|
||||
display: list-item;
|
||||
grid-column: span 3;
|
||||
line-height: 2em;
|
||||
margin-top: .5em;
|
||||
padding: .5em 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#statusFlowModal {
|
||||
.status-flow {
|
||||
padding: 2em;
|
||||
|
||||
.status-container {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content 1fr;
|
||||
grid-template-rows: 28px;
|
||||
justify-content: space-around;
|
||||
position: relative;
|
||||
|
||||
.current-status {
|
||||
@include font-small;
|
||||
justify-self: end;
|
||||
|
||||
.fas {
|
||||
margin: 0 .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.status-block {
|
||||
@include font-button;
|
||||
border-radius: $border-radius-tag;
|
||||
color: $color-white;
|
||||
font-weight: bold;
|
||||
line-height: 1em;
|
||||
padding: .5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-comment {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
padding-left: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.connector {
|
||||
background: $color-black;
|
||||
height: 2em;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
width: 2px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
border-left: .2em solid transparent;
|
||||
border-right: .2em solid transparent;
|
||||
content: '';
|
||||
display: block;
|
||||
margin-left: -.1em;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-top: .2em solid $color-black;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-bottom: .2em solid $color-black;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -237,6 +237,7 @@ path, ._jsPlumb_endpoint {
|
|||
|
||||
.panel-body .due-date-link {
|
||||
color: $color-emperor;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.panel-body .due-date-label {
|
||||
|
@ -316,10 +317,10 @@ path, ._jsPlumb_endpoint {
|
|||
|
||||
.module-large .tags-container,
|
||||
.module-medium .tags-container {
|
||||
padding-top: 2px;
|
||||
padding-top: 4px;
|
||||
|
||||
div {
|
||||
font-size: 22pt;
|
||||
font-size: 20px;
|
||||
width: 4px;
|
||||
height: 0px;
|
||||
display: inline-block;
|
||||
|
@ -335,9 +336,9 @@ path, ._jsPlumb_endpoint {
|
|||
}
|
||||
|
||||
& span.badge {
|
||||
margin-left: -8px;
|
||||
margin-top: -10px;
|
||||
margin-left: -12px;
|
||||
margin-right: 4px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -280,10 +280,17 @@ label {
|
|||
|
||||
.module-start-date,
|
||||
.module-due-date {
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.module-status {
|
||||
.status-block {
|
||||
border-radius: $border-radius-tag;
|
||||
color: $color-white;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-tags {
|
||||
margin-left: 0;
|
||||
margin-top: 10px;
|
||||
|
@ -389,6 +396,16 @@ label {
|
|||
&:hover > .report-element-body .step-name {
|
||||
color: $brand-primary;
|
||||
}
|
||||
|
||||
.step-label-default {
|
||||
@include font-h3;
|
||||
color: $color-alto;
|
||||
}
|
||||
|
||||
.step-label-success {
|
||||
@include font-h3;
|
||||
color: $brand-success;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step attachment style (table, asset or checklist) */
|
||||
|
|
|
@ -87,4 +87,8 @@
|
|||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
table > tbody > tr:first-child > td {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@
|
|||
}
|
||||
|
||||
.task-link {
|
||||
color: $brand-primary;
|
||||
cursor: pointer;
|
||||
line-height: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
180
app/assets/stylesheets/shared/smart_annotation.scss
Normal file
180
app/assets/stylesheets/shared/smart_annotation.scss
Normal file
|
@ -0,0 +1,180 @@
|
|||
.atwho-view {
|
||||
background: $color_white;
|
||||
border-radius: $border-radius-default;
|
||||
box-shadow: $modal-shadow;
|
||||
display: none;
|
||||
left: 0;
|
||||
margin-top: 18px;
|
||||
max-width: 700px;
|
||||
min-width: 600px;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 11110 !important;
|
||||
|
||||
.atwho-header-res {
|
||||
.nav-tabs {
|
||||
align-items: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rep-tab.active:not(:empty) {
|
||||
border-bottom: $border-default;
|
||||
display: flex;
|
||||
padding: .25em;
|
||||
}
|
||||
|
||||
.dismiss {
|
||||
@include font-button;
|
||||
color: $color-silver-chalice;
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
padding: .5em .75em;
|
||||
}
|
||||
|
||||
.repository-object {
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.atwho-view-ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.atwho-no-results {
|
||||
color: $color-silver-chalice;
|
||||
padding: 1.5em 4em;
|
||||
text-align: center;
|
||||
|
||||
.description {
|
||||
@include font-main;
|
||||
padding: 0 4em 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.atwho-header {
|
||||
@include font-small;
|
||||
border-bottom: $border-default;
|
||||
color: $color-silver-chalice;
|
||||
padding: .5em;
|
||||
|
||||
.dismiss {
|
||||
@include font-button;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
padding: 0 .25em;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.atwho-footer {
|
||||
@include font-small;
|
||||
border-top: $border-default;
|
||||
color: $color-silver-chalice;
|
||||
padding: .5em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.atwho-scroll-container {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: .5em;
|
||||
position: relative;
|
||||
|
||||
.atwho-breadcrumbs {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
display: flex;
|
||||
|
||||
.atwho-breadcrumb {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.slash {
|
||||
margin: 0 .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.sa-type {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
margin-left: -.5em;
|
||||
overflow: hidden;
|
||||
padding: .25em .5em;
|
||||
text-overflow: ellipsis;
|
||||
width: calc(100% + 1em);
|
||||
white-space: nowrap;
|
||||
|
||||
&.cur {
|
||||
background: $color-concrete;
|
||||
}
|
||||
|
||||
.atwho-highlight {
|
||||
background: $brand-warning-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.atwho-user {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: .5em 0;
|
||||
|
||||
&.cur {
|
||||
background: $color-concrete;
|
||||
}
|
||||
|
||||
.atwho-highlight {
|
||||
background: $brand-warning-light;
|
||||
}
|
||||
|
||||
&:not:first-child {
|
||||
border-top: $border-default;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.user-email {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
line-height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.more-results {
|
||||
color: $color-silver-chalice;
|
||||
padding: .5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sa-type {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding-left: 2px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
vertical-align: super;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
28
app/assets/stylesheets/shared_styles/elements/bootstrap_tabs.scss
vendored
Normal file
28
app/assets/stylesheets/shared_styles/elements/bootstrap_tabs.scss
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
.sci-nav-tabs {
|
||||
border-bottom: $border-default;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
color: $color-volcano;
|
||||
padding: .5em;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: initial;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background: $brand-primary;
|
||||
bottom: 0;
|
||||
height: .25em;
|
||||
left: 0;
|
||||
position:absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
border-color: $brand-focus;
|
||||
|
||||
.caret {
|
||||
transform: rotateX(180deg)
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-name-container,
|
||||
.table-name-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.remove-container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#steps {
|
||||
|
@ -40,8 +50,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.complete-step-btn {
|
||||
display: inline-block;
|
||||
.step-panel-collapse-link {
|
||||
padding-left: 5px;
|
||||
|
||||
&:not(.collapsed) .fas {
|
||||
@include rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.step-heading {
|
||||
|
|
|
@ -133,6 +133,14 @@ body {
|
|||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
font-size: $font-size-base;
|
||||
|
||||
label {
|
||||
@include font-small;
|
||||
}
|
||||
}
|
||||
|
||||
.jumbotron {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
@ -742,6 +750,47 @@ ul.double-line > li {
|
|||
}
|
||||
}
|
||||
|
||||
#canvas-container {
|
||||
.panel-heading {
|
||||
padding: 10px 15px 4px;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 6px 15px;
|
||||
|
||||
.status-label {
|
||||
background-color: var(--state-color);
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
margin: 3px 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 2px 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
.nav > li > a {
|
||||
padding: 6px 15px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.badge-indicator {
|
||||
background: transparent;
|
||||
color: $color-silver-chalice;
|
||||
font-size: 12px;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-options {
|
||||
position: relative;
|
||||
bottom: 8px;
|
||||
|
@ -1112,6 +1161,15 @@ ul.content-activities {
|
|||
|
||||
.result-panel-collapse-link {
|
||||
text-decoration: none;
|
||||
|
||||
&:not(.collapsed) .fas {
|
||||
@include rotate(90deg);
|
||||
}
|
||||
|
||||
.fas {
|
||||
margin-right: 7px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
|
@ -1647,199 +1705,6 @@ th.custom-field .modal-tooltiptext {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
// AtWho (smart annotations)
|
||||
|
||||
// <Custom atwho style>
|
||||
.atwho-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
margin-top: 18px;
|
||||
background: $color-white;
|
||||
color: $color-black;
|
||||
border: 1px solid $color-emperor;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 5px $color-gainsboro;
|
||||
max-width: 800px;
|
||||
min-width: 700px;
|
||||
overflow: auto;
|
||||
z-index: 11110 !important;
|
||||
|
||||
small {
|
||||
font-size: smaller;
|
||||
color: $color-emperor;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $brand-primary;
|
||||
}
|
||||
|
||||
.cur {
|
||||
background: $brand-primary;
|
||||
color: $color-white;
|
||||
|
||||
small {
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $color-white;
|
||||
font: bold;
|
||||
}
|
||||
|
||||
.res-description {
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
.res-type {
|
||||
border-color: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
|
||||
li {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $color-emperor;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
|
||||
.global-avatar-container {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// <End of overrides>
|
||||
|
||||
.atwho-header-res {
|
||||
background-color: $color-concrete;
|
||||
border-bottom: 1px solid $color-emperor;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 3px 5px;
|
||||
|
||||
.btn {
|
||||
border-radius: 4px;
|
||||
margin: 5px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.title-user {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.help {
|
||||
margin-left: auto;
|
||||
margin-right: 15px;
|
||||
order: 99;
|
||||
padding: 4px;
|
||||
white-space: nowrap;
|
||||
|
||||
div {
|
||||
display: inline;
|
||||
font-size: smaller;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
.dismiss {
|
||||
color: $color-emperor;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.dismiss:hover {
|
||||
color: $color-black;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.atwho-li-res {
|
||||
|
||||
.fa-tint {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.res-type {
|
||||
border: 1px solid $color-black;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
padding: 0 2px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.res-name {
|
||||
font-weight: 600;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.res-description {
|
||||
color: $color-emperor;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.sa-type {
|
||||
border: 1px solid $color-emperor;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
padding: 0 2px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.atwho-user-container {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
|
||||
.global-avatar-container {
|
||||
line-height: 30px;
|
||||
margin: 0 2px 0 4px;
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.atwho-user-popover {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.atwho-user-img-popover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.atwho-no-results {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.popover {
|
||||
border-radius: 3px;
|
||||
min-width: 450px;
|
||||
|
|
|
@ -31,7 +31,9 @@ module ActiveStorage
|
|||
|
||||
unless processing
|
||||
ActiveStorage::PreviewJob.perform_later(@blob.id)
|
||||
@blob.attachments.take.record.update(file_processing: true)
|
||||
ActiveRecord::Base.no_touching do
|
||||
@blob.attachments.take.record.update(file_processing: true)
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
|
|
|
@ -23,14 +23,14 @@ module Api
|
|||
raise PermissionError.new(Asset, :create) unless can_manage_protocol_in_module?(@protocol)
|
||||
|
||||
if @form_multipart_upload
|
||||
asset = @step.assets.new(asset_params)
|
||||
asset = @step.assets.new(asset_params.merge({ team_id: @team.id }))
|
||||
else
|
||||
blob = ActiveStorage::Blob.create_after_upload!(
|
||||
io: StringIO.new(Base64.decode64(asset_params[:file_data])),
|
||||
filename: asset_params[:file_name],
|
||||
content_type: asset_params[:file_type]
|
||||
)
|
||||
asset = @step.assets.new(file: blob)
|
||||
asset = @step.assets.new(file: blob, team: @team)
|
||||
end
|
||||
|
||||
asset.save!(context: :on_api_upload)
|
||||
|
|
|
@ -201,6 +201,10 @@ module Api
|
|||
@checklist_item = @checklist.checklist_items.find(params.require(key))
|
||||
raise PermissionError.new(Protocol, :read) unless can_read_protocol_in_module?(@step.protocol)
|
||||
end
|
||||
|
||||
def load_workflow(key = :workflow_id)
|
||||
@workflow = MyModuleStatusFlow.find(params.require(key))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ module Api
|
|||
before_action only: :show do
|
||||
load_experiment(:id)
|
||||
end
|
||||
before_action :load_experiment_for_managing, only: %i(update)
|
||||
|
||||
def index
|
||||
experiments = @project.experiments
|
||||
|
@ -19,6 +20,47 @@ module Api
|
|||
def show
|
||||
render jsonapi: @experiment, serializer: ExperimentSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
raise PermissionError.new(Experiment, :create) unless can_create_experiments?(@project)
|
||||
|
||||
experiment = @project.experiments.create!(experiment_params.merge!(created_by: current_user,
|
||||
last_modified_by: current_user))
|
||||
|
||||
render jsonapi: experiment, serializer: ExperimentSerializer, status: :created
|
||||
end
|
||||
|
||||
def update
|
||||
@experiment.assign_attributes(experiment_params)
|
||||
|
||||
return render body: nil, status: :no_content unless @experiment.changed?
|
||||
|
||||
if @experiment.archived_changed?
|
||||
if @experiment.archived?
|
||||
@experiment.archived_by = current_user
|
||||
@experiment.archived_on = DateTime.now
|
||||
else
|
||||
@experiment.restored_by = current_user
|
||||
@experiment.restored_on = DateTime.now
|
||||
end
|
||||
end
|
||||
@experiment.last_modified_by = current_user
|
||||
@experiment.save!
|
||||
render jsonapi: @experiment, serializer: ExperimentSerializer, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def experiment_params
|
||||
raise TypeError unless params.require(:data).require(:type) == 'experiments'
|
||||
|
||||
params.require(:data).require(:attributes).permit(:name, :description, :archived)
|
||||
end
|
||||
|
||||
def load_experiment_for_managing
|
||||
@experiment = @project.experiments.find(params.require(:id))
|
||||
raise PermissionError.new(Experiment, :manage) unless can_manage_experiment?(@experiment)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ module Api
|
|||
load_project(:id)
|
||||
end
|
||||
before_action :load_project, only: :activities
|
||||
before_action :load_project_for_managing, only: %i(update)
|
||||
|
||||
def index
|
||||
projects = @team.projects
|
||||
|
@ -22,6 +23,31 @@ module Api
|
|||
render jsonapi: @project, serializer: ProjectSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
raise PermissionError.new(Project, :create) unless can_create_projects?(@team)
|
||||
|
||||
project = @team.projects.create!(project_params.merge!(created_by: current_user))
|
||||
|
||||
render jsonapi: project, serializer: ProjectSerializer, status: :created
|
||||
end
|
||||
|
||||
def update
|
||||
@project.assign_attributes(project_params)
|
||||
|
||||
return render body: nil, status: :no_content unless @project.changed?
|
||||
|
||||
if @project.archived_changed?
|
||||
if @project.archived?
|
||||
@project.archived_by = current_user
|
||||
else
|
||||
@project.restored_by = current_user
|
||||
end
|
||||
end
|
||||
@project.last_modified_by = current_user
|
||||
@project.save!
|
||||
render jsonapi: @project, serializer: ProjectSerializer, status: :ok
|
||||
end
|
||||
|
||||
def activities
|
||||
activities = @project.activities
|
||||
.page(params.dig(:page, :number))
|
||||
|
@ -29,6 +55,19 @@ module Api
|
|||
render jsonapi: activities,
|
||||
each_serializer: ActivitySerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_params
|
||||
raise TypeError unless params.require(:data).require(:type) == 'projects'
|
||||
|
||||
params.require(:data).require(:attributes).permit(:name, :visibility, :archived)
|
||||
end
|
||||
|
||||
def load_project_for_managing
|
||||
@project = @team.projects.find(params.require(:id))
|
||||
raise PermissionError.new(Project, :manage) unless can_manage_project?(@project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -104,10 +104,10 @@ module Api
|
|||
Result.transaction do
|
||||
@result = @task.results.create!(result_params.merge(user_id: current_user.id))
|
||||
if @form_multipart_upload
|
||||
asset = Asset.create!(result_file_params)
|
||||
asset = Asset.create!(result_file_params.merge({ team_id: @team.id }))
|
||||
else
|
||||
blob = create_blob_from_params
|
||||
asset = Asset.create!(file: blob)
|
||||
asset = Asset.create!(file: blob, team: @team)
|
||||
end
|
||||
ResultAsset.create!(asset: asset, result: @result)
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ module Api
|
|||
|
||||
def index
|
||||
tasks = @experiment.my_modules
|
||||
.includes(:my_module_status, :my_modules, :my_module_antecessors)
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
|
||||
|
@ -29,7 +30,7 @@ module Api
|
|||
def create
|
||||
raise PermissionError.new(MyModule, :create) unless can_manage_experiment?(@experiment)
|
||||
|
||||
my_module = @experiment.my_modules.create!(task_params)
|
||||
my_module = @experiment.my_modules.create!(task_params_create)
|
||||
|
||||
render jsonapi: my_module, serializer: TaskSerializer,
|
||||
rte_rendering: render_rte?,
|
||||
|
@ -37,7 +38,7 @@ module Api
|
|||
end
|
||||
|
||||
def update
|
||||
@task.assign_attributes(task_params)
|
||||
@task.assign_attributes(task_params_update)
|
||||
|
||||
if @task.changed? && @task.save!
|
||||
render jsonapi: @task, serializer: TaskSerializer, status: :ok
|
||||
|
@ -56,10 +57,16 @@ module Api
|
|||
|
||||
private
|
||||
|
||||
def task_params
|
||||
def task_params_create
|
||||
raise TypeError unless params.require(:data).require(:type) == 'tasks'
|
||||
|
||||
params.require(:data).require(:attributes).permit(%i(name x y description state))
|
||||
params.require(:data).require(:attributes).permit(%i(name x y description))
|
||||
end
|
||||
|
||||
def task_params_update
|
||||
raise TypeError unless params.require(:data).require(:type) == 'tasks'
|
||||
|
||||
params.require(:data).require(:attributes).permit(%i(name x y description my_module_status_id))
|
||||
end
|
||||
|
||||
def load_task_for_managing
|
||||
|
|
|
@ -6,6 +6,7 @@ module Api
|
|||
before_action :load_team
|
||||
before_action :load_project
|
||||
before_action :load_user_project, only: :show
|
||||
before_action :load_user_project_for_managing, only: %i(update destroy)
|
||||
|
||||
def index
|
||||
user_projects = @project.user_projects
|
||||
|
@ -23,11 +24,44 @@ module Api
|
|||
include: :user
|
||||
end
|
||||
|
||||
def create
|
||||
raise PermissionError.new(Project, :manage) unless can_manage_project?(@project)
|
||||
|
||||
user_project = @project.user_projects.create!(user_project_params.merge!(assigned_by: current_user))
|
||||
|
||||
render jsonapi: user_project, serializer: UserProjectSerializer, status: :created
|
||||
end
|
||||
|
||||
def update
|
||||
@user_project.role = user_project_params[:role]
|
||||
return render body: nil, status: :no_content unless @user_project.changed?
|
||||
|
||||
@user_project.assigned_by = current_user
|
||||
@user_project.save!
|
||||
render jsonapi: @user_project, serializer: UserProjectSerializer, status: :ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
@user_project.destroy!
|
||||
render body: nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_user_project
|
||||
@user_project = @project.user_projects.find(params.require(:id))
|
||||
end
|
||||
|
||||
def load_user_project_for_managing
|
||||
@user_project = @project.user_projects.find(params.require(:id))
|
||||
raise PermissionError.new(Project, :manage) unless can_manage_project?(@project)
|
||||
end
|
||||
|
||||
def user_project_params
|
||||
raise TypeError unless params.require(:data).require(:type) == 'user_projects'
|
||||
|
||||
params.require(:data).require(:attributes).permit(:user_id, :role)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
16
app/controllers/api/v1/workflow_statuses_controller.rb
Normal file
16
app/controllers/api/v1/workflow_statuses_controller.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class WorkflowStatusesController < BaseController
|
||||
before_action only: :index do
|
||||
load_workflow(:workflow_id)
|
||||
end
|
||||
|
||||
def index
|
||||
statuses = @workflow.my_module_statuses
|
||||
render jsonapi: statuses, each_serializer: WorkflowStatusSerializer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
app/controllers/api/v1/workflows_controller.rb
Normal file
20
app/controllers/api/v1/workflows_controller.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class WorkflowsController < BaseController
|
||||
before_action only: :show do
|
||||
load_workflow(:id)
|
||||
end
|
||||
|
||||
def index
|
||||
workflows = MyModuleStatusFlow.all
|
||||
render jsonapi: workflows, each_serializer: WorkflowSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
render jsonapi: @workflow, serializer: WorkflowSerializer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -37,7 +37,7 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
# Sets current team for all controllers
|
||||
def current_team
|
||||
Team.find_by_id(current_user.current_team_id)
|
||||
@current_team ||= current_user.teams.find_by(id: current_user.current_team_id)
|
||||
end
|
||||
|
||||
def to_user_date_format
|
||||
|
@ -83,13 +83,12 @@ class ApplicationController < ActionController::Base
|
|||
private
|
||||
|
||||
def update_current_team
|
||||
current_team = Team.find_by_id(current_user.current_team_id)
|
||||
if (current_team.nil? || !current_user.is_member_of_team?(current_team)) &&
|
||||
current_user.teams.count.positive?
|
||||
return if current_team.present? && current_team.id == current_user.current_team_id
|
||||
|
||||
current_user.update(
|
||||
current_team_id: current_user.teams.first.id
|
||||
)
|
||||
if current_user.current_team_id
|
||||
@current_team = current_user.teams.find_by(id: current_user.current_team_id)
|
||||
elsif current_user.teams.any?
|
||||
current_user.update(current_team_id: current_user.teams.first.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@ class AtWhoController < ApplicationController
|
|||
before_action :check_users_permissions
|
||||
|
||||
def users
|
||||
users = @team.search_users(@query).limit(Constants::ATWHO_SEARCH_LIMIT + 1)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
users: generate_users_data,
|
||||
users: [render_to_string(partial: 'shared/smart_annotation/users.html.erb', locals: {users: users})],
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
|
@ -31,7 +32,7 @@ class AtWhoController < ApplicationController
|
|||
end
|
||||
|
||||
def rep_items
|
||||
repository = Repository.find_by_id(params[:repository_id])
|
||||
repository = Repository.find_by_id(params[:repository_id]) || Repository.active.accessible_by_teams(@team).first
|
||||
items =
|
||||
if repository && can_read_repository?(repository)
|
||||
SmartAnnotation.new(current_user, current_team, @query)
|
||||
|
@ -42,25 +43,21 @@ class AtWhoController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
res: items,
|
||||
res: [render_to_string(partial: 'shared/smart_annotation/repository_items.html.erb', locals: {
|
||||
repository_rows: items
|
||||
})],
|
||||
repository: repository.id,
|
||||
team: current_team.id,
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def repositories
|
||||
def menu
|
||||
repositories = Repository.active.accessible_by_teams(@team)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
repositories: repositories.map do |r|
|
||||
[r.id, escape_input(r.name.truncate(Constants::ATWHO_REP_NAME_LIMIT))]
|
||||
end.to_h,
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
end
|
||||
render json: { html: render_to_string({ partial: "shared/smart_annotation/menu.html.erb",
|
||||
locals: { repositories: repositories } }) }
|
||||
end
|
||||
|
||||
def projects
|
||||
|
@ -68,7 +65,10 @@ class AtWhoController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
res: res.projects,
|
||||
res: [render_to_string(partial: 'shared/smart_annotation/project_items.html.erb', locals: {
|
||||
projects: res.projects
|
||||
})],
|
||||
team: current_team.id,
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
|
@ -80,7 +80,10 @@ class AtWhoController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
res: res.experiments,
|
||||
res: [render_to_string(partial: 'shared/smart_annotation/experiment_items.html.erb', locals: {
|
||||
experiments: res.experiments
|
||||
})],
|
||||
team: current_team.id,
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
|
@ -92,7 +95,10 @@ class AtWhoController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
res: res.my_modules,
|
||||
res: [render_to_string(partial: 'shared/smart_annotation/my_module_items.html.erb', locals: {
|
||||
my_modules: res.my_modules
|
||||
})],
|
||||
team: current_team.id,
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
|
@ -110,23 +116,4 @@ class AtWhoController < ApplicationController
|
|||
def check_users_permissions
|
||||
render_403 unless can_read_team?(@team)
|
||||
end
|
||||
|
||||
def generate_users_data
|
||||
# Search users
|
||||
res = @team.search_users(@query)
|
||||
.limit(Constants::ATWHO_SEARCH_LIMIT)
|
||||
.pluck(:id, :full_name, :email)
|
||||
|
||||
# Add avatars, Base62, convert to JSON
|
||||
data = []
|
||||
res.each do |obj|
|
||||
tmp = {}
|
||||
tmp['id'] = obj[0].base62_encode
|
||||
tmp['full_name'] = escape_input(obj[1].truncate(Constants::NAME_TRUNCATION_LENGTH_DROPDOWN))
|
||||
tmp['email'] = escape_input(obj[2])
|
||||
tmp['img_url'] = avatar_path(obj[0], :icon_small)
|
||||
data << tmp
|
||||
end
|
||||
data
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ module Dashboard
|
|||
tasks = tasks.left_outer_joins(:user_my_modules).where(user_my_modules: { user_id: current_user.id })
|
||||
end
|
||||
|
||||
tasks = filter_by_state(tasks)
|
||||
tasks = tasks.where(my_module_status_id: task_filters[:statuses])
|
||||
|
||||
case task_filters[:sort]
|
||||
when 'start_date'
|
||||
|
@ -41,7 +41,9 @@ module Dashboard
|
|||
end
|
||||
|
||||
page = (params[:page] || 1).to_i
|
||||
tasks = tasks.with_step_statistics.search_by_name(current_user, current_team, task_filters[:query])
|
||||
tasks = tasks.search_by_name(current_user, current_team, task_filters[:query])
|
||||
.joins(:my_module_status)
|
||||
.select('my_modules.*', 'my_module_statuses.name as status_name', 'my_module_statuses.color as status_color')
|
||||
.preload(experiment: :project).page(page).per(Constants::INFINITE_SCROLL_LIMIT)
|
||||
|
||||
tasks_list = tasks.map do |task|
|
||||
|
@ -50,9 +52,9 @@ module Dashboard
|
|||
experiment: escape_input(task.experiment.name),
|
||||
project: escape_input(task.experiment.project.name),
|
||||
name: escape_input(task.name),
|
||||
due_date: task.due_date.present? ? I18n.l(task.due_date, format: :full_date) : nil,
|
||||
state: task_state(task),
|
||||
steps_precentage: task.steps_completed_percentage }
|
||||
due_date: prepare_due_date(task),
|
||||
status_color: task.status_color,
|
||||
status_name: task.status_name }
|
||||
end
|
||||
|
||||
render json: { data: tasks_list, next_page: tasks.next_page }
|
||||
|
@ -90,31 +92,28 @@ module Dashboard
|
|||
|
||||
private
|
||||
|
||||
def task_state(task)
|
||||
if task.state == 'completed'
|
||||
task_state_class = task.state
|
||||
task_state_text = t('dashboard.current_tasks.progress_bar.completed')
|
||||
else
|
||||
task_state_text = t('dashboard.current_tasks.progress_bar.in_progress')
|
||||
task_state_class = 'day-prior' if task.is_one_day_prior?
|
||||
if task.is_overdue?
|
||||
task_state_text = t('dashboard.current_tasks.progress_bar.overdue')
|
||||
task_state_class = 'overdue'
|
||||
end
|
||||
if task.steps_total.positive?
|
||||
task_state_text += t('dashboard.current_tasks.progress_bar.completed_steps',
|
||||
steps: task.steps_completed, total_steps: task.steps_total)
|
||||
end
|
||||
def prepare_due_date(task)
|
||||
if task.completed?
|
||||
return { state: '', text: I18n.t('dashboard.current_tasks.completed_on_html',
|
||||
date: I18n.l(task.completed_on, format: :full_date)) }
|
||||
end
|
||||
{ text: task_state_text, class: task_state_class }
|
||||
end
|
||||
if task.due_date.present?
|
||||
due_date_formatted = I18n.l(task.due_date, format: :full_date)
|
||||
if task.is_overdue?
|
||||
return { state: 'overdue', text: I18n.t('dashboard.current_tasks.due_date_overdue_html',
|
||||
date: due_date_formatted) }
|
||||
elsif task.is_one_day_prior?
|
||||
return { state: 'day-prior', text: I18n.t('dashboard.current_tasks.due_date_html',
|
||||
date: due_date_formatted) }
|
||||
end
|
||||
|
||||
def filter_by_state(tasks)
|
||||
tasks.where(my_modules: { state: task_filters[:view] })
|
||||
return { state: '', text: I18n.t('dashboard.current_tasks.due_date_html', date: due_date_formatted) }
|
||||
end
|
||||
{ state: nil, text: nil }
|
||||
end
|
||||
|
||||
def task_filters
|
||||
params.permit(:project_id, :experiment_id, :mode, :view, :sort, :query, :page)
|
||||
params.permit(:project_id, :experiment_id, :mode, :sort, :query, :page, statuses: [])
|
||||
end
|
||||
|
||||
def load_project
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DashboardsController < ApplicationController
|
||||
def show; end
|
||||
def show
|
||||
@my_module_status_flows = MyModuleStatusFlow.all.preload(my_module_statuses: :my_module_status_consequences)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -123,7 +123,7 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
Activities::CreateActivityService.call(
|
||||
activity_type: :export_inventory_items_assigned_to_task,
|
||||
owner: current_user,
|
||||
subject: @repository,
|
||||
subject: @my_module,
|
||||
team: current_team,
|
||||
message_items: {
|
||||
my_module: @my_module.id,
|
||||
|
|
|
@ -31,7 +31,8 @@ class MyModuleRepositorySnapshotsController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
repository_snapshot = @repository.provision_snapshot(@my_module, current_user)
|
||||
repository_snapshot = RepositorySnapshot.create_preliminary(@repository, @my_module, current_user)
|
||||
RepositorySnapshotProvisioningJob.perform_later(repository_snapshot)
|
||||
|
||||
render json: {
|
||||
html: render_to_string(partial: 'my_modules/repositories/full_view_version',
|
||||
|
@ -108,7 +109,7 @@ class MyModuleRepositorySnapshotsController < ApplicationController
|
|||
Activities::CreateActivityService.call(
|
||||
activity_type: :export_inventory_snapshot_items_assigned_to_task,
|
||||
owner: current_user,
|
||||
subject: @repository_snapshot,
|
||||
subject: @my_module,
|
||||
team: current_team,
|
||||
message_items: {
|
||||
my_module: @my_module.id,
|
||||
|
|
26
app/controllers/my_module_status_flow_controller.rb
Normal file
26
app/controllers/my_module_status_flow_controller.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusFlowController < ApplicationController
|
||||
before_action :load_my_module
|
||||
before_action :check_view_permissions
|
||||
|
||||
def show
|
||||
my_module_statuses = @my_module.my_module_status_flow
|
||||
.my_module_statuses
|
||||
.preload(:my_module_status_implications, next_status: :my_module_status_conditions)
|
||||
.sort_by_position
|
||||
render json: { html: render_to_string(partial: 'my_modules/modals/status_flow_modal_body.html.erb',
|
||||
locals: { my_module_statuses: my_module_statuses }) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_my_module
|
||||
@my_module = MyModule.find_by(id: params[:my_module_id])
|
||||
render_404 unless @my_module
|
||||
end
|
||||
|
||||
def check_view_permissions
|
||||
render_403 unless can_read_experiment?(@my_module.experiment)
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@ class MyModuleTagsController < ApplicationController
|
|||
|
||||
before_action :load_vars, except: :canvas_index
|
||||
before_action :check_view_permissions, only: :index
|
||||
before_action :check_manage_permissions, only: %i(create index_edit destroy)
|
||||
before_action :check_manage_permissions, only: %i(create index_edit destroy destroy_by_tag_id)
|
||||
|
||||
def index_edit
|
||||
@my_module_tags = @my_module.my_module_tags.order(:id)
|
||||
|
@ -155,11 +155,7 @@ class MyModuleTagsController < ApplicationController
|
|||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 unless can_manage_tags?(@my_module.experiment.project)
|
||||
end
|
||||
|
||||
def init_gui
|
||||
@tags = @my_module.unassigned_tags
|
||||
render_403 unless can_manage_module?(@my_module)
|
||||
end
|
||||
|
||||
def mt_params
|
||||
|
|
|
@ -10,9 +10,8 @@ class MyModulesController < ApplicationController
|
|||
before_action :load_projects_tree, only: %i(protocols results activities archive)
|
||||
before_action :check_archive_and_restore_permissions, only: %i(update)
|
||||
before_action :check_manage_permissions, only: %i(description due_date update_description update_protocol_description)
|
||||
before_action :check_view_permissions, except: %i(update update_description update_protocol_description
|
||||
toggle_task_state)
|
||||
before_action :check_complete_module_permission, only: %i(complete_my_module toggle_task_state)
|
||||
before_action :check_view_permissions, except: %i(update update_description update_protocol_description)
|
||||
before_action :check_update_state_permissions, only: :update_state
|
||||
before_action :set_inline_name_editing, only: %i(protocols results activities archive)
|
||||
|
||||
layout 'fluid'.freeze
|
||||
|
@ -45,6 +44,14 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def status_state
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { status_changing: @my_module.status_changing? }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def activities
|
||||
params[:subjects] = {
|
||||
MyModule: [@my_module.id]
|
||||
|
@ -126,6 +133,8 @@ class MyModulesController < ApplicationController
|
|||
log_activity(:restore_module)
|
||||
end
|
||||
else
|
||||
render_403 && return unless can_manage_module?(@my_module)
|
||||
|
||||
saved = @my_module.save
|
||||
if saved
|
||||
if description_changed
|
||||
|
@ -258,100 +267,23 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def archive
|
||||
@archived_results = @my_module.archived_results
|
||||
current_team_switch(@my_module
|
||||
.experiment
|
||||
.project
|
||||
.team)
|
||||
current_team_switch(@my_module.experiment.project.team)
|
||||
end
|
||||
|
||||
def update_state
|
||||
old_status_id = @my_module.my_module_status_id
|
||||
if @my_module.update(my_module_status_id: update_status_params[:status_id])
|
||||
log_activity(:change_status_on_task_flow, @my_module, my_module_status_old: old_status_id,
|
||||
my_module_status_new: @my_module.my_module_status.id)
|
||||
|
||||
# Complete/uncomplete task
|
||||
def toggle_task_state
|
||||
respond_to do |format|
|
||||
@my_module.completed? ? @my_module.uncompleted! : @my_module.completed!
|
||||
task_completion_activity
|
||||
|
||||
# Render new button HTML
|
||||
new_btn_partial = if @my_module.completed?
|
||||
'my_modules/state_button_uncomplete.html.erb'
|
||||
else
|
||||
'my_modules/state_button_complete.html.erb'
|
||||
end
|
||||
|
||||
format.json do
|
||||
render json: {
|
||||
new_btn: render_to_string(partial: new_btn_partial),
|
||||
completed: @my_module.completed?,
|
||||
module_header_due_date: render_to_string(
|
||||
partial: 'my_modules/module_header_due_date.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
),
|
||||
module_state_label: render_to_string(
|
||||
partial: 'my_modules/module_state_label.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def complete_my_module
|
||||
respond_to do |format|
|
||||
if @my_module.uncompleted? && @my_module.check_completness_status
|
||||
@my_module.completed!
|
||||
task_completion_activity
|
||||
format.json do
|
||||
render json: {
|
||||
task_button_title: t('my_modules.buttons.uncomplete'),
|
||||
module_header_due_date: render_to_string(
|
||||
partial: 'my_modules/module_header_due_date.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
),
|
||||
module_state_label: render_to_string(
|
||||
partial: 'my_modules/module_state_label.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
)
|
||||
}, status: :ok
|
||||
end
|
||||
else
|
||||
format.json { render json: {}, status: :unprocessable_entity }
|
||||
end
|
||||
return redirect_to protocols_my_module_path(@my_module)
|
||||
else
|
||||
render json: { errors: @my_module.errors.messages.values.flatten.join('\n') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def task_completion_activity
|
||||
completed = @my_module.completed?
|
||||
log_activity(completed ? :complete_task : :uncomplete_task)
|
||||
start_work_on_next_task_notification
|
||||
end
|
||||
|
||||
def start_work_on_next_task_notification
|
||||
if @my_module.completed?
|
||||
title = t('notifications.start_work_on_next_task',
|
||||
user: current_user.full_name,
|
||||
module: @my_module.name)
|
||||
message = t('notifications.start_work_on_next_task_message',
|
||||
project: link_to(@project.name, project_url(@project)),
|
||||
experiment: link_to(@experiment.name,
|
||||
canvas_experiment_url(@experiment)),
|
||||
my_module: link_to(@my_module.name,
|
||||
protocols_my_module_url(@my_module)))
|
||||
notification = Notification.create(
|
||||
type_of: :recent_changes,
|
||||
title: sanitize_input(title, %w(strong a)),
|
||||
message: sanitize_input(message, %w(strong a)),
|
||||
generator_user_id: current_user.id
|
||||
)
|
||||
# create notification for all users on the next modules in the workflow
|
||||
@my_module.my_modules.map(&:users).flatten.uniq.each do |target_user|
|
||||
next if target_user == current_user || !target_user.recent_notification
|
||||
UserNotification.create(notification: notification, user: target_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@my_module = MyModule.find_by_id(params[:id])
|
||||
if @my_module
|
||||
|
@ -384,8 +316,9 @@ class MyModulesController < ApplicationController
|
|||
render_403 unless can_read_experiment?(@my_module.experiment)
|
||||
end
|
||||
|
||||
def check_complete_module_permission
|
||||
render_403 unless can_complete_module?(@my_module)
|
||||
def check_update_state_permissions
|
||||
return render_403 unless can_change_my_module_flow_status?(@my_module)
|
||||
render_404 unless @my_module.my_module_status
|
||||
end
|
||||
|
||||
def set_inline_name_editing
|
||||
|
@ -414,6 +347,10 @@ class MyModulesController < ApplicationController
|
|||
update_params
|
||||
end
|
||||
|
||||
def update_status_params
|
||||
params.require(:my_module).permit(:status_id)
|
||||
end
|
||||
|
||||
def log_start_date_change_activity(start_date_changes)
|
||||
type_of = if start_date_changes[0].nil? # set started_on
|
||||
message_items = { my_module_started_on: @my_module.started_on }
|
||||
|
|
|
@ -3,6 +3,7 @@ class ProjectsController < ApplicationController
|
|||
include TeamsHelper
|
||||
include InputSanitizeHelper
|
||||
|
||||
before_action :switch_team_with_param, only: :index
|
||||
before_action :load_vars, only: %i(show edit update
|
||||
notifications reports
|
||||
experiment_archive)
|
||||
|
@ -34,7 +35,6 @@ class ProjectsController < ApplicationController
|
|||
}
|
||||
end
|
||||
format.html do
|
||||
current_team_switch(Team.find_by_id(params[:team])) if params[:team]
|
||||
@teams = current_user.teams
|
||||
# New project for create new project modal
|
||||
@project = Project.new
|
||||
|
|
|
@ -325,7 +325,7 @@ class ProtocolsController < ApplicationController
|
|||
@protocol.unlink
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -353,13 +353,11 @@ class ProtocolsController < ApplicationController
|
|||
if @protocol.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
# Revert is basically update from parent
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
# Revert is basically update from parent
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
|
@ -397,12 +395,10 @@ class ProtocolsController < ApplicationController
|
|||
if @protocol.parent.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.update_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
@protocol.update_parent(current_user)
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
|
@ -440,12 +436,10 @@ class ProtocolsController < ApplicationController
|
|||
if @protocol.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
|
@ -483,12 +477,10 @@ class ProtocolsController < ApplicationController
|
|||
if @protocol.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.load_from_repository(@source, current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
@protocol.load_from_repository(@source, current_user)
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
|
@ -1140,7 +1132,7 @@ class ProtocolsController < ApplicationController
|
|||
@source = Protocol.find_by_id(params[:source_id])
|
||||
|
||||
render_403 unless @protocol.present? && @source.present? &&
|
||||
(can_manage_protocol_in_module?(@protocol) ||
|
||||
(can_manage_protocol_in_module?(@protocol) &&
|
||||
can_read_protocol_in_repository?(@source))
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class StepsController < ApplicationController
|
|||
before_action :convert_table_contents_to_utf8, only: %i(create update)
|
||||
|
||||
before_action :check_view_permissions, only: %i(show update_view_state)
|
||||
before_action :check_manage_permissions, only: %i(new create edit update destroy move_up move_down)
|
||||
before_action :check_manage_permissions, only: %i(new create edit update destroy move_up move_down toggle_step_state)
|
||||
before_action :check_complete_and_checkbox_permissions, only: %i(toggle_step_state checklistitem_state)
|
||||
|
||||
def new
|
||||
|
@ -307,10 +307,6 @@ class StepsController < ApplicationController
|
|||
@step.completed = completed
|
||||
|
||||
if @step.save
|
||||
if @protocol.in_module?
|
||||
ready_to_complete = @protocol.my_module.check_completness_status
|
||||
end
|
||||
|
||||
# Create activity
|
||||
if changed
|
||||
completed_steps = @protocol.steps.where(completed: true).count
|
||||
|
@ -336,14 +332,7 @@ class StepsController < ApplicationController
|
|||
t('protocols.steps.options.uncomplete_title')
|
||||
end
|
||||
format.json do
|
||||
if ready_to_complete && @protocol.my_module.uncompleted?
|
||||
render json: {
|
||||
task_ready_to_complete: true,
|
||||
new_title: localized_title
|
||||
}, status: :ok
|
||||
else
|
||||
render json: { new_title: localized_title }, status: :ok
|
||||
end
|
||||
render json: { new_title: localized_title }, status: :ok
|
||||
end
|
||||
else
|
||||
format.json { render json: {}, status: :unprocessable_entity }
|
||||
|
@ -354,16 +343,11 @@ class StepsController < ApplicationController
|
|||
def move_up
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @step.protocol.steps.minimum(:position) != @step.position
|
||||
@step.update!(position: @step.position - 1)
|
||||
@step.move_up
|
||||
|
||||
render json: {
|
||||
step_up_position: @step.position,
|
||||
step_down_position: @step.position + 1
|
||||
}
|
||||
else
|
||||
render json: {}
|
||||
end
|
||||
render json: {
|
||||
steps_order: @protocol.steps.order(:position).select(:id, :position)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -371,16 +355,11 @@ class StepsController < ApplicationController
|
|||
def move_down
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @step.protocol.steps.maximum(:position) != @step.position
|
||||
@step.update!(position: @step.position + 1)
|
||||
@step.move_down
|
||||
|
||||
render json: {
|
||||
step_up_position: @step.position - 1,
|
||||
step_down_position: @step.position
|
||||
}
|
||||
else
|
||||
render json: {}
|
||||
end
|
||||
render json: {
|
||||
steps_order: @protocol.steps.order(:position).select(:id, :position)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -490,6 +469,7 @@ class StepsController < ApplicationController
|
|||
item_record = ck.checklist_items.find_by(id: item[1][:id])
|
||||
|
||||
next unless item_record
|
||||
|
||||
item_record.update_attribute('position', item[1][:position])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ class TagsController < ApplicationController
|
|||
before_action :load_vars, only: [:create, :update, :destroy]
|
||||
before_action :load_vars_nested, only: [:update, :destroy]
|
||||
before_action :check_manage_permissions, only: %i(create update destroy)
|
||||
before_action :check_manage_my_module_permissions, only: %i(create)
|
||||
|
||||
def create
|
||||
@tag = Tag.new(tag_params)
|
||||
|
@ -153,6 +154,12 @@ class TagsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def check_manage_my_module_permissions
|
||||
my_module = MyModule.find_by id: params[:my_module_id]
|
||||
|
||||
render_403 if my_module && !can_manage_module?(my_module)
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 unless can_manage_tags?(@project)
|
||||
end
|
||||
|
|
|
@ -121,10 +121,6 @@ class UserMyModulesController < ApplicationController
|
|||
render_403 unless can_manage_users_in_module?(@my_module)
|
||||
end
|
||||
|
||||
def init_gui
|
||||
@users = @my_module.unassigned_users
|
||||
end
|
||||
|
||||
def um_params
|
||||
params.require(:user_my_module).permit(:user_id, :my_module_id)
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ class UserProjectsController < ApplicationController
|
|||
include InputSanitizeHelper
|
||||
|
||||
before_action :load_vars
|
||||
before_action :load_up_var, only: %i(update destroy)
|
||||
before_action :load_user_project, only: %i(update destroy)
|
||||
before_action :check_view_permissions, only: :index
|
||||
before_action :check_manage_users_permissions, only: :index_edit
|
||||
before_action :check_create_permissions, only: :create
|
||||
|
@ -26,9 +26,9 @@ class UserProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def index_edit
|
||||
@users = @project.user_projects
|
||||
@user_projects = @project.user_projects
|
||||
@unassigned_users = @project.unassigned_users
|
||||
@up = UserProject.new(project: @project)
|
||||
@new_user_project = UserProject.new(project: @project)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
@ -48,10 +48,10 @@ class UserProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
@up = UserProject.new(up_params.merge(project: @project))
|
||||
@up.assigned_by = current_user
|
||||
@user_project = @project.user_projects.new(user_project_params)
|
||||
@user_project.assigned_by = current_user
|
||||
|
||||
if @up.save
|
||||
if @user_project.save
|
||||
log_activity(:assign_user_to_project)
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -61,23 +61,23 @@ class UserProjectsController < ApplicationController
|
|||
end
|
||||
else
|
||||
error = t('user_projects.create.can_add_user_to_project')
|
||||
error = t('user_projects.create.select_user_role') unless @up.role
|
||||
error = t('user_projects.create.select_user_role') unless @user_project.role
|
||||
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
render :json => {
|
||||
format.json do
|
||||
render json: {
|
||||
status: 'error',
|
||||
error: error
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@up.role = up_params[:role]
|
||||
@user_project.role = user_project_params[:role]
|
||||
|
||||
if @up.save
|
||||
if @user_project.save
|
||||
log_activity(:change_user_role_on_project)
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -90,7 +90,7 @@ class UserProjectsController < ApplicationController
|
|||
format.json do
|
||||
render json: {
|
||||
status: 'error',
|
||||
errors: @up.errors
|
||||
errors: @user_project.errors
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -98,20 +98,20 @@ class UserProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
if @up.destroy
|
||||
if @user_project.destroy
|
||||
log_activity(:unassign_user_from_project)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
redirect_to project_users_edit_path(format: :json),
|
||||
turbolinks: false,
|
||||
status: 303
|
||||
status: :see_other
|
||||
end
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
errors: @up.errors
|
||||
errors: @user_project.errors
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -121,13 +121,13 @@ class UserProjectsController < ApplicationController
|
|||
private
|
||||
|
||||
def load_vars
|
||||
@project = Project.find_by_id(params[:project_id])
|
||||
@project = Project.find_by(id: params[:project_id])
|
||||
render_404 unless @project
|
||||
end
|
||||
|
||||
def load_up_var
|
||||
@up = UserProject.find(params[:id])
|
||||
render_404 unless @up
|
||||
def load_user_project
|
||||
@user_project = @project.user_projects.find(params[:id])
|
||||
render_404 unless @user_project
|
||||
end
|
||||
|
||||
def check_view_permissions
|
||||
|
@ -139,19 +139,14 @@ class UserProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def check_create_permissions
|
||||
render_403 unless can_create_projects?(current_team)
|
||||
render_403 unless can_manage_project?(@project)
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 unless can_manage_project?(@project) &&
|
||||
@up.user_id != current_user.id
|
||||
render_403 unless can_manage_project?(@project) && @user_project.user_id != current_user.id
|
||||
end
|
||||
|
||||
def init_gui
|
||||
@users = @project.unassigned_users
|
||||
end
|
||||
|
||||
def up_params
|
||||
def user_project_params
|
||||
params.require(:user_project).permit(:user_id, :project_id, :role)
|
||||
end
|
||||
|
||||
|
@ -163,7 +158,7 @@ class UserProjectsController < ApplicationController
|
|||
team: @project.team,
|
||||
project: @project,
|
||||
message_items: { project: @project.id,
|
||||
user_target: @up.user.id,
|
||||
role: @up.role_str })
|
||||
user_target: @user_project.user.id,
|
||||
role: @user_project.role_str })
|
||||
end
|
||||
end
|
||||
|
|
17
app/helpers/experiments_helper.rb
Normal file
17
app/helpers/experiments_helper.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ExperimentsHelper
|
||||
def grouped_by_prj(experiments)
|
||||
ungrouped_experiments = experiments.joins(:project)
|
||||
.select('projects.name as project_name,
|
||||
projects.archived as project_archived,
|
||||
experiments.*')
|
||||
ungrouped_experiments.group_by { |i| [i[:project_name]] }.map do |group, exps|
|
||||
{
|
||||
project_name: group[0],
|
||||
project_archived: exps[0]&.project_archived,
|
||||
experiments: exps
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,11 +31,9 @@ module MyModulesHelper
|
|||
|
||||
def get_task_alert_color(my_module)
|
||||
alert = ''
|
||||
if !my_module.completed?
|
||||
unless my_module.completed?
|
||||
alert = ' alert-yellow' if my_module.is_one_day_prior?
|
||||
alert = ' alert-red' if my_module.is_overdue?
|
||||
elsif my_module.completed?
|
||||
alert = ' alert-green'
|
||||
end
|
||||
alert
|
||||
end
|
||||
|
|
|
@ -154,7 +154,7 @@ module ReportsHelper
|
|||
style = 'default'
|
||||
text = t('protocols.steps.uncompleted')
|
||||
end
|
||||
"<span class=\"label label-#{style}\">#{text}</span>".html_safe
|
||||
"<span class=\"label step-label-#{style}\">[#{text}]</span>".html_safe
|
||||
end
|
||||
|
||||
# Fixes issues with avatar images in reports
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
module TeamsHelper
|
||||
# resets the current team if needed
|
||||
def current_team_switch(team)
|
||||
if team != current_team
|
||||
if team != current_team && current_user.is_member_of_team?(team)
|
||||
current_user.current_team_id = team.id
|
||||
current_user.save
|
||||
update_current_team
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -17,11 +18,7 @@ module TeamsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def team_created_by(team)
|
||||
User.find_by_id(team.created_by_id)
|
||||
end
|
||||
|
||||
def switch_team_with_param
|
||||
current_team_switch(Team.find_by(id: params[:team])) if params[:team]
|
||||
current_team_switch(current_user.teams.find_by(id: params[:team])) if params[:team]
|
||||
end
|
||||
end
|
||||
|
|
1
app/javascript/packs/fonts.js
Normal file
1
app/javascript/packs/fonts.js
Normal file
|
@ -0,0 +1 @@
|
|||
require('typeface-lato');
|
|
@ -6,7 +6,9 @@ class ActiveStorage::PreviewJob < ActiveStorage::BaseJob
|
|||
|
||||
discard_on StandardError do |job, error|
|
||||
blob = ActiveStorage::Blob.find_by(id: job.arguments.first)
|
||||
blob&.attachments&.take&.record&.update(file_processing: false)
|
||||
ActiveRecord::Base.no_touching do
|
||||
blob&.attachments&.take&.record&.update(file_processing: false)
|
||||
end
|
||||
Rails.logger.error "Couldn't generate preview for Blob with id: #{job.arguments.first}. Error:\n #{error}"
|
||||
end
|
||||
|
||||
|
@ -24,6 +26,8 @@ class ActiveStorage::PreviewJob < ActiveStorage::BaseJob
|
|||
Rails.logger.info "Preview for the Blod with id: #{blob.id} - successfully generated.\n" \
|
||||
"Transformations applied: #{preview.variation.transformations}"
|
||||
|
||||
blob.attachments.take.record.update(file_processing: false)
|
||||
ActiveRecord::Base.no_touching do
|
||||
blob.attachments.take.record.update(file_processing: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
24
app/jobs/my_module_status_consequences_job.rb
Normal file
24
app/jobs/my_module_status_consequences_job.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusConsequencesJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(my_module, my_module_status_consequences)
|
||||
error_raised = false
|
||||
my_module.transaction do
|
||||
my_module_status_consequences.each do |consequence|
|
||||
consequence.call(my_module)
|
||||
end
|
||||
my_module.update!(status_changing: false)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
error_raised = true
|
||||
end
|
||||
if error_raised
|
||||
my_module.my_module_status = my_module.changing_from_my_module_status
|
||||
my_module.status_changing = false
|
||||
my_module.save!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,17 +32,15 @@ class Asset < ApplicationRecord
|
|||
optional: true
|
||||
belongs_to :team, optional: true
|
||||
has_one :step_asset, inverse_of: :asset, dependent: :destroy
|
||||
has_one :step, through: :step_asset, dependent: :nullify
|
||||
has_one :step, through: :step_asset, touch: true, dependent: :nullify
|
||||
has_one :result_asset, inverse_of: :asset, dependent: :destroy
|
||||
has_one :result, through: :result_asset, dependent: :nullify
|
||||
has_one :result, through: :result_asset, touch: true, dependent: :nullify
|
||||
has_one :repository_asset_value, inverse_of: :asset, dependent: :destroy
|
||||
has_one :repository_cell, through: :repository_asset_value,
|
||||
dependent: :nullify
|
||||
has_many :report_elements, inverse_of: :asset, dependent: :destroy
|
||||
has_one :asset_text_datum, inverse_of: :asset, dependent: :destroy
|
||||
|
||||
after_save { result&.touch; step&.touch }
|
||||
|
||||
attr_accessor :file_content, :file_info, :in_template
|
||||
|
||||
def self.search(
|
||||
|
@ -222,8 +220,7 @@ class Asset < ApplicationRecord
|
|||
Rails.logger.info "Asset #{id}: Creating extract text job"
|
||||
# The extract_asset_text also includes
|
||||
# estimated size calculation
|
||||
Asset.delay(queue: :assets, run_at: 20.minutes.from_now)
|
||||
.extract_asset_text_delayed(id, in_template)
|
||||
Asset.delay(queue: :assets).extract_asset_text_delayed(id, in_template)
|
||||
elsif marvinjs?
|
||||
extract_asset_text
|
||||
else
|
||||
|
|
|
@ -15,7 +15,7 @@ module ArchivableModel
|
|||
# Helper for archiving project. Timestamp of archiving is handler by
|
||||
# before_save callback.
|
||||
# Sets the archived_by value to the current user.
|
||||
def archive (current_user)
|
||||
def archive(current_user)
|
||||
self.archived = true
|
||||
self.archived_by = current_user
|
||||
save
|
||||
|
@ -29,7 +29,7 @@ module ArchivableModel
|
|||
|
||||
# Helper for restoring project from archive.
|
||||
# Sets the restored_by value to the current user.
|
||||
def restore (current_user)
|
||||
def restore(current_user)
|
||||
self.archived = false
|
||||
self.restored_by = current_user
|
||||
save
|
||||
|
|
|
@ -2,20 +2,30 @@
|
|||
|
||||
module SearchableByNameModel
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# rubocop:disable Metrics/BlockLength
|
||||
included do
|
||||
def self.search_by_name(user, teams = [], query = nil, options = {})
|
||||
return if user.blank? || teams.blank?
|
||||
|
||||
viewable_by_user(user, teams)
|
||||
.where_attributes_like("#{table_name}.name", query, options)
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
sql_q = viewable_by_user(user, teams)
|
||||
|
||||
if options[:intersect]
|
||||
query_array = query.gsub(/[[:space:]]+/, ' ').split(' ')
|
||||
query_array.each do |string|
|
||||
sql_q = sql_q.where("trim_html_tags(#{table_name}.name) ILIKE ?", "%#{string}%")
|
||||
end
|
||||
else
|
||||
sql_q = sql_q.where_attributes_like("#{table_name}.name", query, options)
|
||||
end
|
||||
|
||||
sql_q.limit(Constants::SEARCH_LIMIT)
|
||||
end
|
||||
|
||||
def self.filter_by_teams(teams = [])
|
||||
return self if teams.empty?
|
||||
|
||||
if column_names.include? 'team_id'
|
||||
return where(team_id: teams)
|
||||
where(team_id: teams)
|
||||
else
|
||||
valid_subjects = Extends::ACTIVITY_SUBJECT_CHILDREN
|
||||
parent_array = [to_s.underscore]
|
||||
|
@ -38,8 +48,9 @@ module SearchableByNameModel
|
|||
query = child.to_s.camelize.constantize.where("#{last_parent}_id" => query)
|
||||
last_parent = child
|
||||
end
|
||||
return where("#{last_parent}_id" => query)
|
||||
where("#{last_parent}_id" => query)
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
end
|
||||
|
|
|
@ -248,12 +248,6 @@ class Experiment < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
# Archive all modules. Receives an array of module integer IDs.
|
||||
def archive_modules(module_ids)
|
||||
my_modules.where(id: module_ids).each(&:archive!)
|
||||
my_modules.reload
|
||||
end
|
||||
|
||||
# Archive all modules. Receives an array of module integer IDs
|
||||
# and current user.
|
||||
def archive_modules(module_ids, current_user)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModule < ApplicationRecord
|
||||
include ArchivableModel
|
||||
include SearchableModel
|
||||
|
@ -7,9 +9,11 @@ class MyModule < ApplicationRecord
|
|||
enum state: Extends::TASKS_STATES
|
||||
|
||||
before_create :create_blank_protocol
|
||||
before_validation :set_completed_on, if: :state_changed?
|
||||
before_create :assign_default_status_flow
|
||||
|
||||
auto_strip_attributes :name, :description, nullify: false
|
||||
around_save :exec_status_consequences, if: :my_module_status_id_changed?
|
||||
|
||||
auto_strip_attributes :name, :description, nullify: false, if: proc { |mm| mm.name_changed? || mm.description_changed? }
|
||||
validates :name,
|
||||
length: { minimum: Constants::NAME_MIN_LENGTH,
|
||||
maximum: Constants::NAME_MAX_LENGTH }
|
||||
|
@ -20,12 +24,19 @@ class MyModule < ApplicationRecord
|
|||
validate :coordinates_uniqueness_check, if: :active?
|
||||
validates :completed_on, presence: true, if: proc { |mm| mm.completed? }
|
||||
|
||||
validate :check_status, if: :my_module_status_id_changed?
|
||||
validate :check_status_conditions, if: :my_module_status_id_changed?
|
||||
validate :check_status_implications
|
||||
|
||||
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User', optional: true
|
||||
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User', optional: true
|
||||
belongs_to :archived_by, foreign_key: 'archived_by_id', class_name: 'User', optional: true
|
||||
belongs_to :restored_by, foreign_key: 'restored_by_id', class_name: 'User', optional: true
|
||||
belongs_to :experiment, inverse_of: :my_modules, touch: true
|
||||
belongs_to :my_module_group, inverse_of: :my_modules, optional: true
|
||||
belongs_to :my_module_status, optional: true
|
||||
belongs_to :changing_from_my_module_status, optional: true, class_name: 'MyModuleStatus'
|
||||
delegate :my_module_status_flow, to: :my_module_status, allow_nil: true
|
||||
has_many :results, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :my_module_tags, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :tags, through: :my_module_tags
|
||||
|
@ -55,16 +66,6 @@ class MyModule < ApplicationRecord
|
|||
end)
|
||||
scope :workflow_ordered, -> { order(workflow_order: :asc) }
|
||||
scope :uncomplete, -> { where(state: 'uncompleted') }
|
||||
scope :with_step_statistics, (lambda do
|
||||
left_outer_joins(protocols: :steps)
|
||||
.group(:id)
|
||||
.select('my_modules.*')
|
||||
.select('COUNT(steps.id) AS steps_total')
|
||||
.select('COUNT(steps.id) FILTER (where steps.completed = true) AS steps_completed')
|
||||
.select('CASE COUNT(steps.id) WHEN 0 THEN 0 ELSE'\
|
||||
'((COUNT(steps.id) FILTER (where steps.completed = true)) * 100 / COUNT(steps.id)) '\
|
||||
'END AS steps_completed_percentage')
|
||||
end)
|
||||
|
||||
# A module takes this much space in canvas (x, y) in database
|
||||
WIDTH = 30
|
||||
|
@ -139,14 +140,16 @@ class MyModule < ApplicationRecord
|
|||
# Remove association with module group.
|
||||
self.my_module_group = nil
|
||||
|
||||
was_archived = false
|
||||
|
||||
MyModule.transaction do
|
||||
archived = super
|
||||
was_archived = super
|
||||
# Remove all connection between modules.
|
||||
archived = Connection.where(input_id: id).delete_all if archived
|
||||
archived = Connection.where(output_id: id).delete_all if archived
|
||||
raise ActiveRecord::Rollback unless archived
|
||||
was_archived = Connection.where(input_id: id).destroy_all if was_archived
|
||||
was_archived = Connection.where(output_id: id).destroy_all if was_archived
|
||||
raise ActiveRecord::Rollback unless was_archived
|
||||
end
|
||||
archived
|
||||
was_archived
|
||||
end
|
||||
|
||||
# Similar as super restore, but also calculate new module position
|
||||
|
@ -393,6 +396,8 @@ class MyModule < ApplicationRecord
|
|||
|
||||
clone.save!
|
||||
|
||||
clone.assign_user(current_user)
|
||||
|
||||
# Remove the automatically generated protocol,
|
||||
# & clone the protocol instead
|
||||
clone.protocol.destroy
|
||||
|
@ -436,18 +441,6 @@ class MyModule < ApplicationRecord
|
|||
{ x: 0, y: positions.last[1] + HEIGHT }
|
||||
end
|
||||
|
||||
# Check if my_module is ready to become completed
|
||||
def check_completness_status
|
||||
if protocol && protocol.steps.count > 0
|
||||
completed = true
|
||||
protocol.steps.find_each do |step|
|
||||
completed = false unless step.completed
|
||||
end
|
||||
return true if completed
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def assign_user(user, assigned_by = nil)
|
||||
user_my_modules.create(
|
||||
assigned_by: assigned_by || user,
|
||||
|
@ -465,12 +458,6 @@ class MyModule < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def set_completed_on
|
||||
return if completed? && completed_on.present?
|
||||
|
||||
self.completed_on = completed? ? DateTime.now : nil
|
||||
end
|
||||
|
||||
def create_blank_protocol
|
||||
protocols << Protocol.new_blank_for_module(self)
|
||||
end
|
||||
|
@ -480,4 +467,54 @@ class MyModule < ApplicationRecord
|
|||
errors.add(:position, I18n.t('activerecord.errors.models.my_module.attributes.position.not_unique'))
|
||||
end
|
||||
end
|
||||
|
||||
def assign_default_status_flow
|
||||
return if my_module_status.present? || MyModuleStatusFlow.global.blank?
|
||||
|
||||
self.my_module_status = MyModuleStatusFlow.global.first.initial_status
|
||||
end
|
||||
|
||||
def check_status_conditions
|
||||
return if my_module_status.blank?
|
||||
|
||||
my_module_status.my_module_status_conditions.each do |condition|
|
||||
condition.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
def check_status_implications
|
||||
return if my_module_status.blank?
|
||||
|
||||
my_module_status.my_module_status_implications.each do |implication|
|
||||
implication.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
def check_status
|
||||
return unless my_module_status_id_was
|
||||
|
||||
original_status = MyModuleStatus.find_by(id: my_module_status_id_was)
|
||||
unless my_module_status && [original_status.next_status, original_status.previous_status].include?(my_module_status)
|
||||
errors.add(:my_module_status_id,
|
||||
I18n.t('activerecord.errors.models.my_module.attributes.my_module_status_id.not_correct_order'))
|
||||
end
|
||||
end
|
||||
|
||||
def exec_status_consequences
|
||||
return if my_module_status.blank? || status_changing
|
||||
|
||||
self.changing_from_my_module_status_id = my_module_status_id_was if my_module_status_id_was.present?
|
||||
self.status_changing = true
|
||||
|
||||
yield
|
||||
|
||||
if my_module_status.my_module_status_consequences.any?(&:runs_in_background?)
|
||||
MyModuleStatusConsequencesJob.perform_later(self, my_module_status.my_module_status_consequences.to_a)
|
||||
else
|
||||
my_module_status.my_module_status_consequences.each do |consequence|
|
||||
consequence.call(self)
|
||||
end
|
||||
update!(status_changing: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
70
app/models/my_module_status.rb
Normal file
70
app/models/my_module_status.rb
Normal file
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatus < ApplicationRecord
|
||||
has_many :my_modules, dependent: :nullify
|
||||
has_many :my_module_status_conditions, dependent: :destroy
|
||||
has_many :my_module_status_consequences, dependent: :destroy
|
||||
has_many :my_module_status_implications, dependent: :destroy
|
||||
belongs_to :my_module_status_flow
|
||||
belongs_to :created_by, class_name: 'User', optional: true
|
||||
belongs_to :last_modified_by, class_name: 'User', optional: true
|
||||
has_one :next_status, class_name: 'MyModuleStatus',
|
||||
foreign_key: 'previous_status_id',
|
||||
inverse_of: :previous_status,
|
||||
dependent: :nullify
|
||||
belongs_to :previous_status, class_name: 'MyModuleStatus', inverse_of: :next_status, optional: true
|
||||
|
||||
validates :name, presence: true, length: { minimum: Constants::NAME_MIN_LENGTH, maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :color, presence: true
|
||||
validates :description, length: { maximum: Constants::TEXT_MAX_LENGTH }
|
||||
validates :next_status, uniqueness: true, if: -> { next_status.present? }
|
||||
validates :previous_status, uniqueness: true, if: -> { previous_status.present? }
|
||||
validate :next_in_same_flow, if: -> { next_status.present? }
|
||||
validate :previous_in_same_flow, if: -> { previous_status.present? }
|
||||
|
||||
def initial_status?
|
||||
my_module_status_flow.initial_status == self
|
||||
end
|
||||
|
||||
def final_status?
|
||||
my_module_status_flow.final_status == self
|
||||
end
|
||||
|
||||
def self.sort_by_position(order = :asc)
|
||||
ordered_statuses, statuses = all.to_a.partition { |i| i.previous_status_id.nil? }
|
||||
|
||||
return [] if ordered_statuses.empty?
|
||||
|
||||
until statuses.empty?
|
||||
next_element, statuses = statuses.partition { |i| ordered_statuses.last.id == i.previous_status_id }
|
||||
if next_element.empty?
|
||||
break
|
||||
else
|
||||
ordered_statuses.concat(next_element)
|
||||
end
|
||||
end
|
||||
ordered_statuses = ordered_statuses.reverse if order == :desc
|
||||
ordered_statuses
|
||||
end
|
||||
|
||||
def conditions_errors(my_module)
|
||||
mm_copy = my_module.clone
|
||||
mm_copy.errors.clear
|
||||
|
||||
my_module_status_conditions.each do |condition|
|
||||
condition.call(mm_copy)
|
||||
end
|
||||
|
||||
mm_copy.errors.messages&.values&.flatten
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def next_in_same_flow
|
||||
errors.add(:next_status, :different_flow) unless next_status.my_module_status_flow == my_module_status_flow
|
||||
end
|
||||
|
||||
def previous_in_same_flow
|
||||
errors.add(:previous_status, :different_flow) unless previous_status.my_module_status_flow == my_module_status_flow
|
||||
end
|
||||
end
|
9
app/models/my_module_status_condition.rb
Normal file
9
app/models/my_module_status_condition.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusCondition < ApplicationRecord
|
||||
belongs_to :my_module_status
|
||||
|
||||
def description
|
||||
''
|
||||
end
|
||||
end
|
14
app/models/my_module_status_conditions/active.rb
Normal file
14
app/models/my_module_status_conditions/active.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusConditions
|
||||
class Active < MyModuleStatusCondition
|
||||
def call(my_module)
|
||||
my_module.errors.add(:status_conditions, I18n.t('my_module_statuses.conditions.error.my_module_not_active')) unless my_module.active?
|
||||
end
|
||||
|
||||
def description
|
||||
I18n.t('my_module_statuses.conditions.error.my_module_not_active')
|
||||
end
|
||||
end
|
||||
end
|
9
app/models/my_module_status_consequence.rb
Normal file
9
app/models/my_module_status_consequence.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusConsequence < ApplicationRecord
|
||||
belongs_to :my_module_status
|
||||
|
||||
def runs_in_background?
|
||||
false
|
||||
end
|
||||
end
|
11
app/models/my_module_status_consequences/change_activity.rb
Normal file
11
app/models/my_module_status_consequences/change_activity.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusConsequences
|
||||
class ChangeActivity < MyModuleStatusConsequence
|
||||
def call(my_module)
|
||||
# Create new activity here
|
||||
puts "State changed to #{my_module_status.name}} for #{my_module.name}"
|
||||
end
|
||||
end
|
||||
end
|
12
app/models/my_module_status_consequences/completion.rb
Normal file
12
app/models/my_module_status_consequences/completion.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusConsequences
|
||||
class Completion < MyModuleStatusConsequence
|
||||
def call(my_module)
|
||||
my_module.state = 'completed'
|
||||
my_module.completed_on = DateTime.now
|
||||
my_module.save!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MyModuleStatusConsequences
|
||||
class RepositorySnapshot < MyModuleStatusConsequence
|
||||
def runs_in_background?
|
||||
true
|
||||
end
|
||||
|
||||
def call(my_module)
|
||||
my_module.assigned_repositories.each do |repository|
|
||||
repository_snapshot = ::RepositorySnapshot.create_preliminary(repository, my_module)
|
||||
service = Repositories::SnapshotProvisioningService.call(repository_snapshot: repository_snapshot)
|
||||
unless service.succeed?
|
||||
repository_snapshot.failed!
|
||||
raise StandardError, service.errors
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
app/models/my_module_status_consequences/uncompletion.rb
Normal file
14
app/models/my_module_status_consequences/uncompletion.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusConsequences
|
||||
class Uncompletion < MyModuleStatusConsequence
|
||||
def call(my_module)
|
||||
return unless my_module.state == 'completed'
|
||||
|
||||
my_module.state = 'uncompleted'
|
||||
my_module.completed_on = nil
|
||||
my_module.save!
|
||||
end
|
||||
end
|
||||
end
|
42
app/models/my_module_status_flow.rb
Normal file
42
app/models/my_module_status_flow.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusFlow < ApplicationRecord
|
||||
enum visibility: { global: 0, in_team: 1 }
|
||||
|
||||
has_many :my_module_statuses, dependent: :destroy
|
||||
belongs_to :team, optional: true
|
||||
belongs_to :created_by, class_name: 'User', optional: true
|
||||
belongs_to :last_modified_by, class_name: 'User', optional: true
|
||||
|
||||
validates :visibility, presence: true
|
||||
validates :team, presence: true, if: :in_team?
|
||||
validates :name, uniqueness: { scope: :team_id, case_sensitive: false }, if: :in_team?
|
||||
validates :name, presence: true, length: { minimum: Constants::NAME_MIN_LENGTH, maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :description, length: { maximum: Constants::TEXT_MAX_LENGTH }
|
||||
|
||||
def initial_status
|
||||
my_module_statuses.find_by(previous_status: nil)
|
||||
end
|
||||
|
||||
def final_status
|
||||
my_module_statuses.left_outer_joins(:next_status).find_by('next_statuses_my_module_statuses.id': nil)
|
||||
end
|
||||
|
||||
def self.ensure_default
|
||||
return if MyModuleStatusFlow.global.any?
|
||||
|
||||
status_flow = MyModuleStatusFlow.create!(name: Extends::DEFAULT_FLOW_NAME, visibility: :global)
|
||||
prev_id = nil
|
||||
Extends::DEFAULT_FLOW_STATUSES.each do |status|
|
||||
new_status = MyModuleStatus.create!(my_module_status_flow: status_flow,
|
||||
name: status[:name],
|
||||
color: status[:color],
|
||||
previous_status_id: prev_id)
|
||||
prev_id = new_status.id
|
||||
|
||||
status[:conditions]&.each { |condition| condition.constantize.create!(my_module_status: new_status) }
|
||||
status[:implications]&.each { |implication| implication.constantize.create!(my_module_status: new_status) }
|
||||
status[:consequences]&.each { |consequence| consequence.constantize.create!(my_module_status: new_status) }
|
||||
end
|
||||
end
|
||||
end
|
9
app/models/my_module_status_implication.rb
Normal file
9
app/models/my_module_status_implication.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusImplication < ApplicationRecord
|
||||
belongs_to :my_module_status
|
||||
|
||||
def description
|
||||
''
|
||||
end
|
||||
end
|
11
app/models/my_module_status_implications/read_only.rb
Normal file
11
app/models/my_module_status_implications/read_only.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusImplications
|
||||
class ReadOnly < MyModuleStatusImplication
|
||||
def call(my_module)
|
||||
my_module.errors.add(:status_implication, 'Is read only')
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -229,14 +229,16 @@ class Protocol < ApplicationRecord
|
|||
|
||||
# Deep-clone given array of assets
|
||||
def self.deep_clone_assets(assets_to_clone)
|
||||
assets_to_clone.each do |src_id, dest_id|
|
||||
src = Asset.find_by(id: src_id)
|
||||
dest = Asset.find_by(id: dest_id)
|
||||
dest.destroy! if src.blank? && dest.present?
|
||||
next unless src.present? && dest.present?
|
||||
ActiveRecord::Base.no_touching do
|
||||
assets_to_clone.each do |src_id, dest_id|
|
||||
src = Asset.find_by(id: src_id)
|
||||
dest = Asset.find_by(id: dest_id)
|
||||
dest.destroy! if src.blank? && dest.present?
|
||||
next unless src.present? && dest.present?
|
||||
|
||||
# Clone file
|
||||
src.duplicate_file(dest)
|
||||
# Clone file
|
||||
src.duplicate_file(dest)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -524,12 +526,14 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
|
||||
def update_parent(current_user)
|
||||
# First, destroy parent's step contents
|
||||
parent.destroy_contents
|
||||
parent.reload
|
||||
ActiveRecord::Base.no_touching do
|
||||
# First, destroy parent's step contents
|
||||
parent.destroy_contents
|
||||
parent.reload
|
||||
|
||||
# Now, clone step contents
|
||||
Protocol.clone_contents(self, parent, current_user, false)
|
||||
# Now, clone step contents
|
||||
Protocol.clone_contents(self, parent, current_user, false)
|
||||
end
|
||||
|
||||
# Lastly, update the metadata
|
||||
parent.reload
|
||||
|
@ -542,11 +546,13 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
|
||||
def update_from_parent(current_user)
|
||||
# First, destroy step contents
|
||||
destroy_contents
|
||||
ActiveRecord::Base.no_touching do
|
||||
# First, destroy step contents
|
||||
destroy_contents
|
||||
|
||||
# Now, clone parent's step contents
|
||||
Protocol.clone_contents(parent, self, current_user, false)
|
||||
# Now, clone parent's step contents
|
||||
Protocol.clone_contents(parent, self, current_user, false)
|
||||
end
|
||||
|
||||
# Lastly, update the metadata
|
||||
reload
|
||||
|
@ -558,11 +564,13 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
|
||||
def load_from_repository(source, current_user)
|
||||
# First, destroy step contents
|
||||
destroy_contents
|
||||
ActiveRecord::Base.no_touching do
|
||||
# First, destroy step contents
|
||||
destroy_contents
|
||||
|
||||
# Now, clone source's step contents
|
||||
Protocol.clone_contents(source, self, current_user, false)
|
||||
# Now, clone source's step contents
|
||||
Protocol.clone_contents(source, self, current_user, false)
|
||||
end
|
||||
|
||||
# Lastly, update the metadata
|
||||
reload
|
||||
|
@ -588,12 +596,14 @@ class Protocol < ApplicationRecord
|
|||
# Don't proceed further if clone is invalid
|
||||
return clone if clone.invalid?
|
||||
|
||||
# Okay, clone seems to be valid: let's clone it
|
||||
clone = deep_clone(clone, current_user)
|
||||
ActiveRecord::Base.no_touching do
|
||||
# Okay, clone seems to be valid: let's clone it
|
||||
clone = deep_clone(clone, current_user)
|
||||
|
||||
# If the above operation went well, update published_on
|
||||
# timestamp
|
||||
clone.update(published_on: Time.now) if clone.in_repository_public?
|
||||
# If the above operation went well, update published_on
|
||||
# timestamp
|
||||
clone.update(published_on: Time.zone.now) if clone.in_repository_public?
|
||||
end
|
||||
|
||||
# Link protocols if neccesary
|
||||
if link_protocols
|
||||
|
@ -659,7 +669,7 @@ class Protocol < ApplicationRecord
|
|||
def destroy_contents
|
||||
# Calculate total space taken by the protocol
|
||||
st = space_taken
|
||||
steps.destroy_all
|
||||
steps.order(position: :desc).destroy_all
|
||||
|
||||
# Release space taken by the step
|
||||
team.release_space(st)
|
||||
|
|
|
@ -142,7 +142,7 @@ class Repository < RepositoryBase
|
|||
end
|
||||
|
||||
def self.viewable_by_user(_user, teams)
|
||||
where(team: teams)
|
||||
accessible_by_teams(teams)
|
||||
end
|
||||
|
||||
def self.name_like(query)
|
||||
|
@ -209,21 +209,6 @@ class Repository < RepositoryBase
|
|||
importer.run
|
||||
end
|
||||
|
||||
def provision_snapshot(my_module, created_by = nil)
|
||||
created_by ||= self.created_by
|
||||
repository_snapshot = dup.becomes(RepositorySnapshot)
|
||||
repository_snapshot.assign_attributes(type: RepositorySnapshot.name,
|
||||
original_repository: self,
|
||||
my_module: my_module,
|
||||
created_by: created_by,
|
||||
team: my_module.experiment.project.team,
|
||||
permission_level: Extends::SHARED_INVENTORIES_PERMISSION_LEVELS[:not_shared])
|
||||
repository_snapshot.provisioning!
|
||||
repository_snapshot.reload
|
||||
RepositorySnapshotProvisioningJob.perform_later(repository_snapshot)
|
||||
repository_snapshot
|
||||
end
|
||||
|
||||
def assigned_rows(my_module)
|
||||
repository_rows.joins(:my_module_repository_rows).where(my_module_repository_rows: { my_module_id: my_module.id })
|
||||
end
|
||||
|
|
|
@ -75,6 +75,8 @@ class RepositoryChecklistValue < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.import_from_text(text, attributes, _options = {})
|
||||
return nil if text.blank?
|
||||
|
||||
value = new(attributes)
|
||||
column = attributes.dig(:repository_cell_attributes, :repository_column)
|
||||
RepositoryImportParser::Util.split_by_delimiter(text: text, delimiter: column.delimiter_char).each do |item_text|
|
||||
|
|
|
@ -66,6 +66,8 @@ class RepositoryListValue < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.import_from_text(text, attributes, _options = {})
|
||||
return nil if text.blank?
|
||||
|
||||
value = new(attributes)
|
||||
column = attributes.dig(:repository_cell_attributes, :repository_column)
|
||||
list_item = column.repository_list_items.find { |item| item.data == text }
|
||||
|
|
|
@ -26,6 +26,19 @@ class RepositorySnapshot < RepositoryBase
|
|||
.order(:parent_id, updated_at: :desc)
|
||||
}
|
||||
|
||||
def self.create_preliminary(repository, my_module, created_by = nil)
|
||||
created_by ||= repository.created_by
|
||||
repository_snapshot = repository.dup.becomes(RepositorySnapshot)
|
||||
repository_snapshot.assign_attributes(type: RepositorySnapshot.name,
|
||||
original_repository: repository,
|
||||
my_module: my_module,
|
||||
created_by: created_by,
|
||||
team: my_module.experiment.project.team,
|
||||
permission_level: Extends::SHARED_INVENTORIES_PERMISSION_LEVELS[:not_shared])
|
||||
repository_snapshot.provisioning!
|
||||
repository_snapshot.reload
|
||||
end
|
||||
|
||||
def default_columns_count
|
||||
Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['length']
|
||||
end
|
||||
|
|
|
@ -13,10 +13,10 @@ class Step < ApplicationRecord
|
|||
validates :completed, inclusion: { in: [true, false] }
|
||||
validates :user, :protocol, presence: true
|
||||
validates :completed_on, presence: true, if: proc { |s| s.completed? }
|
||||
validates :position, uniqueness: { scope: :protocol }, if: :position_changed?
|
||||
|
||||
before_validation :set_completed_on, if: :completed_changed?
|
||||
before_save :set_last_modified_by
|
||||
around_save :adjust_positions_on_save, if: :position_changed?
|
||||
before_destroy :cascade_before_destroy
|
||||
after_destroy :adjust_positions_after_destroy
|
||||
|
||||
|
@ -124,25 +124,66 @@ class Step < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def move_up
|
||||
return if position.zero?
|
||||
|
||||
move_in_protocol(:up)
|
||||
end
|
||||
|
||||
def move_down
|
||||
return if position == protocol.steps.count - 1
|
||||
|
||||
move_in_protocol(:down)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def adjust_positions_on_save
|
||||
step_to_swap = protocol.steps.find_by(position: position)
|
||||
def move_in_protocol(direction)
|
||||
transaction do
|
||||
re_index_following_steps
|
||||
|
||||
return yield unless step_to_swap
|
||||
case direction
|
||||
when :up
|
||||
new_position = position - 1
|
||||
when :down
|
||||
new_position = position + 1
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
position_to_swap = position_was
|
||||
step_to_swap.position = -1
|
||||
yield
|
||||
step_to_swap.update!(position: position_to_swap)
|
||||
step_to_swap = protocol.steps.find_by(position: new_position)
|
||||
position_to_swap = position
|
||||
|
||||
if step_to_swap
|
||||
step_to_swap.update!(position: -1)
|
||||
update!(position: new_position)
|
||||
step_to_swap.update!(position: position_to_swap)
|
||||
else
|
||||
update!(position: new_position)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def adjust_positions_after_destroy
|
||||
protocol.steps.where('position > ?', position).find_each do |step|
|
||||
re_index_following_steps
|
||||
protocol.steps.where('position > ?', position).order(:position).each do |step|
|
||||
step.update!(position: step.position - 1)
|
||||
end
|
||||
end
|
||||
|
||||
def re_index_following_steps
|
||||
steps = protocol.steps.where(position: position..).order(:position).where.not(id: id)
|
||||
i = position
|
||||
steps.each do |step|
|
||||
i += 1
|
||||
step.position = i
|
||||
end
|
||||
|
||||
steps.reverse_each do |step|
|
||||
step.save! if step.position_changed?
|
||||
end
|
||||
end
|
||||
|
||||
def cascade_before_destroy
|
||||
assets.each(&:destroy)
|
||||
tables.each(&:destroy)
|
||||
|
|
|
@ -25,7 +25,17 @@ Canaid::Permissions.register_for(Experiment) do
|
|||
# module: create, copy, reposition, create/update/delete connection,
|
||||
# assign/reassign/unassign tags
|
||||
can :manage_experiment do |user, experiment|
|
||||
user.is_user_or_higher_of_project?(experiment.project)
|
||||
user.is_user_or_higher_of_project?(experiment.project) &&
|
||||
MyModule.joins(:experiment)
|
||||
.where(experiment: experiment)
|
||||
.preload(my_module_status: :my_module_status_implications)
|
||||
.all? do |my_module|
|
||||
if my_module.my_module_status
|
||||
my_module.my_module_status.my_module_status_implications.all? { |implication| implication.call(my_module) }
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# experiment: archive
|
||||
|
@ -53,82 +63,6 @@ Canaid::Permissions.register_for(Experiment) do
|
|||
end
|
||||
end
|
||||
|
||||
Canaid::Permissions.register_for(MyModule) do
|
||||
# Module, its experiment and its project must be active for all the specified
|
||||
# permissions
|
||||
%i(manage_module
|
||||
manage_users_in_module
|
||||
assign_repository_rows_to_module
|
||||
complete_module
|
||||
create_comments_in_module
|
||||
create_my_module_repository_snapshot
|
||||
manage_my_module_repository_snapshots)
|
||||
.each do |perm|
|
||||
can perm do |_, my_module|
|
||||
my_module.active? &&
|
||||
my_module.experiment.active? &&
|
||||
my_module.experiment.project.active?
|
||||
end
|
||||
end
|
||||
|
||||
# module: update
|
||||
# result: create, update
|
||||
can :manage_module do |user, my_module|
|
||||
can_manage_experiment?(user, my_module.experiment)
|
||||
end
|
||||
|
||||
# module: archive
|
||||
can :archive_module do |user, my_module|
|
||||
can_manage_experiment?(user, my_module.experiment)
|
||||
end
|
||||
|
||||
# NOTE: Must not be dependent on canaid parmision for which we check if it's
|
||||
# active
|
||||
# module: restore
|
||||
can :restore_module do |user, my_module|
|
||||
user.is_user_or_higher_of_project?(my_module.experiment.project) &&
|
||||
my_module.archived?
|
||||
end
|
||||
|
||||
# module: move
|
||||
can :move_module do |user, my_module|
|
||||
can_manage_experiment?(user, my_module.experiment)
|
||||
end
|
||||
|
||||
# module: assign/reassign/unassign users
|
||||
can :manage_users_in_module do |user, my_module|
|
||||
user.is_owner_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: assign/unassign repository record
|
||||
# NOTE: Use 'module_page? &&' before calling this permission!
|
||||
can :assign_repository_rows_to_module do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: complete/uncomplete
|
||||
can :complete_module do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: create comment
|
||||
# result: create comment
|
||||
# step: create comment
|
||||
can :create_comments_in_module do |user, my_module|
|
||||
can_create_comments_in_project?(user, my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: create a snapshot of repository item
|
||||
can :create_my_module_repository_snapshot do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: make a repository snapshot selected
|
||||
can :manage_my_module_repository_snapshots do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
end
|
||||
|
||||
Canaid::Permissions.register_for(Protocol) do
|
||||
# Protocol needs to be in a module for all Protocol permissions below
|
||||
# experiment level
|
||||
|
@ -170,7 +104,7 @@ Canaid::Permissions.register_for(Protocol) do
|
|||
|
||||
# step: complete/uncomplete
|
||||
can :complete_or_checkbox_step do |user, protocol|
|
||||
can_complete_module?(user, protocol.my_module)
|
||||
can_change_my_module_flow_status?(user, protocol.my_module)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue