Merge branch 'develop' into features/sso-improvements

This commit is contained in:
Andrej 2024-03-26 10:36:04 +01:00
commit ac9c16d459
425 changed files with 13176 additions and 7953 deletions

View file

@ -12,7 +12,7 @@ gem 'figaro'
gem 'pg', '~> 1.5'
gem 'pg_search' # PostgreSQL full text search
gem 'psych', '< 4.0'
gem 'rails', '~> 7.0.5'
gem 'rails', '~> 7.0.8'
gem 'recaptcha', require: 'recaptcha/rails'
gem 'sanitize'
gem 'sprockets-rails'

View file

@ -60,47 +60,47 @@ GIT
GEM
remote: http://rubygems.org/
specs:
actioncable (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
actioncable (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.5.1)
actionpack (= 7.0.5.1)
activejob (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
actionmailbox (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.5.1)
actionpack (= 7.0.5.1)
actionview (= 7.0.5.1)
activejob (= 7.0.5.1)
activesupport (= 7.0.5.1)
actionmailer (7.0.8.1)
actionpack (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activesupport (= 7.0.8.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.5.1)
actionview (= 7.0.5.1)
activesupport (= 7.0.5.1)
actionpack (7.0.8.1)
actionview (= 7.0.8.1)
activesupport (= 7.0.8.1)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.5.1)
actionpack (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
actiontext (7.0.8.1)
actionpack (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.5.1)
activesupport (= 7.0.5.1)
actionview (7.0.8.1)
activesupport (= 7.0.8.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -110,14 +110,14 @@ GEM
activemodel (>= 4.1, < 7.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.0.5.1)
activesupport (= 7.0.5.1)
activejob (7.0.8.1)
activesupport (= 7.0.8.1)
globalid (>= 0.3.6)
activemodel (7.0.5.1)
activesupport (= 7.0.5.1)
activerecord (7.0.5.1)
activemodel (= 7.0.5.1)
activesupport (= 7.0.5.1)
activemodel (7.0.8.1)
activesupport (= 7.0.8.1)
activerecord (7.0.8.1)
activemodel (= 7.0.8.1)
activesupport (= 7.0.8.1)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activerecord-session_store (2.1.0)
@ -127,14 +127,14 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 2.0.8, < 4)
railties (>= 6.1)
activestorage (7.0.5.1)
actionpack (= 7.0.5.1)
activejob (= 7.0.5.1)
activerecord (= 7.0.5.1)
activesupport (= 7.0.5.1)
activestorage (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activesupport (= 7.0.8.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.5.1)
activesupport (7.0.8.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -197,12 +197,13 @@ GEM
aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2)
base62 (1.0.0)
base64 (0.2.0)
bcrypt (3.1.18)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
bindata (2.4.15)
bindata (2.5.0)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
bootsnap (1.16.0)
@ -240,7 +241,7 @@ GEM
combine_pdf (1.0.23)
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.2.2)
concurrent-ruby (1.2.3)
crack (0.4.5)
rexml
crass (1.0.6)
@ -284,7 +285,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.3)
date (3.3.4)
debug_inspector (1.1.0)
deface (1.9.0)
actionview (>= 5.2)
@ -303,7 +304,7 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise_invitable (2.0.8)
devise_invitable (2.0.9)
actionmailer (>= 5.0)
devise (>= 4.6)
diff-lcs (1.5.0)
@ -327,12 +328,12 @@ GEM
railties (>= 5.0.0)
faker (3.2.0)
i18n (>= 1.8.11, < 2)
faraday (2.7.6)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday (2.9.0)
faraday-net_http (>= 2.0, < 3.2)
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-net_http (3.0.2)
faraday-net_http (3.1.0)
net-http
fastimage (2.2.7)
ffi (1.15.5)
ffi-compiler (1.0.1)
@ -343,8 +344,8 @@ GEM
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
globalid (1.1.0)
activesupport (>= 5.0)
globalid (1.2.1)
activesupport (>= 6.1)
graphviz (1.2.1)
process-pipeline
grover (1.1.5)
@ -382,9 +383,10 @@ GEM
jsbundling-rails (1.1.1)
railties (>= 6.0.0)
json (2.6.3)
json-jwt (1.16.3)
json-jwt (1.16.6)
activesupport (>= 4.2)
aes_key_wrap
base64
bindata
faraday (~> 2.0)
faraday-follow_redirects
@ -415,7 +417,7 @@ GEM
logging (2.0.0)
little-plugger (~> 1.1)
multi_json (~> 1.10)
loofah (2.21.3)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@ -430,9 +432,9 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0218.1)
mini_magick (4.12.0)
mini_mime (1.1.2)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.20.0)
minitest (5.22.2)
msgpack (1.7.1)
multi_json (1.15.0)
multi_test (1.1.0)
@ -441,14 +443,16 @@ GEM
coffee-rails (>= 3.2.1)
jquery-rails
rails (>= 3.2.0)
net-imap (0.3.6)
net-http (0.4.1)
uri
net-imap (0.4.10)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
net-protocol (0.2.2)
timeout
net-smtp (0.3.3)
net-smtp (0.4.0.1)
net-protocol
newrelic_rpm (9.2.2)
nio4r (2.7.0)
@ -475,7 +479,7 @@ GEM
rack-protection
omniauth-azure-activedirectory-v2 (2.0.1)
omniauth-oauth2 (~> 1.8)
omniauth-linkedin-oauth2 (1.0.0)
omniauth-linkedin-oauth2 (1.0.1)
omniauth-oauth2
omniauth-oauth2 (1.8.0)
oauth2 (>= 1.4, < 3)
@ -537,10 +541,10 @@ GEM
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.7.3)
rack (2.2.7)
rack (2.2.8.1)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack-cors (2.0.1)
rack-cors (2.0.2)
rack (>= 2.0.0)
rack-oauth2 (2.2.0)
activesupport
@ -553,25 +557,25 @@ GEM
rack
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.5.1)
actioncable (= 7.0.5.1)
actionmailbox (= 7.0.5.1)
actionmailer (= 7.0.5.1)
actionpack (= 7.0.5.1)
actiontext (= 7.0.5.1)
actionview (= 7.0.5.1)
activejob (= 7.0.5.1)
activemodel (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
rails (7.0.8.1)
actioncable (= 7.0.8.1)
actionmailbox (= 7.0.8.1)
actionmailer (= 7.0.8.1)
actionpack (= 7.0.8.1)
actiontext (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activemodel (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
bundler (>= 1.15.0)
railties (= 7.0.5.1)
railties (= 7.0.8.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.1.1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@ -587,22 +591,22 @@ GEM
railties (> 3.1)
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
railties (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.0.6)
rake (13.1.0)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rdoc (6.3.3)
recaptcha (5.14.0)
regexp_parser (2.8.1)
responders (3.1.0)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.5)
@ -613,7 +617,7 @@ GEM
roo (2.10.0)
nokogiri (~> 1)
rubyzip (>= 1.3.0, < 3.0.0)
rotp (6.2.2)
rotp (6.3.0)
rouge (4.1.2)
rqrcode (2.2.0)
chunky_png (~> 1.0)
@ -662,7 +666,6 @@ GEM
rexml
ruby-vips (2.1.4)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
rufus-scheduler (3.9.1)
fugit (~> 1.1, >= 1.1.6)
@ -711,10 +714,10 @@ GEM
railties (>= 6.0.0)
tailwindcss-rails (2.0.29-x86_64-linux)
railties (>= 6.0.0)
thor (1.2.2)
thor (1.3.1)
tilt (2.2.0)
timecop (0.9.6)
timeout (0.4.0)
timeout (0.4.1)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
@ -734,6 +737,7 @@ GEM
validate_url (1.0.15)
activemodel (>= 3.0.0)
public_suffix
uri (0.13.0)
version_gem (1.1.3)
view_component (3.9.0)
activesupport (>= 5.2.0, < 8.0)
@ -751,7 +755,7 @@ GEM
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1)
websocket (1.2.9)
websocket-driver (0.7.5)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
whacamole (1.2.0)
@ -759,7 +763,7 @@ GEM
activesupport
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.8)
zeitwerk (2.6.13)
zip-zip (0.3)
rubyzip (>= 1.0.0)
@ -842,7 +846,7 @@ DEPENDENCIES
puma
rack-attack
rack-cors
rails (~> 7.0.5)
rails (~> 7.0.8)
rails-controller-testing
rails_12factor
rails_autolink (~> 1.1, >= 1.1.6)

View file

@ -96,9 +96,7 @@ tests-ci:
-e CORE_API_RATE_LIMIT=1000000 \
-e PROTOCOLS_IO_ACCESS_TOKEN=PROTOCOLS_IO_ACCESS_TOKEN \
-e ENABLE_WEBHOOKS=true \
--rm web bash -c "rake db:create && rake db:migrate && \
yarn install && yarn build && yarn build:css && rails tailwindcss:build && \
bundle exec rspec ./spec/"
--rm web bash -c "rake db:create && rake db:migrate && bundle exec rspec ./spec/"
console:
@$(MAKE) rails cmd="rails console"

View file

@ -1 +1 @@
1.30.0
1.31.0.1

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1C0 0.447715 0.447715 0 1 0H15C15.5523 0 16 0.447715 16 1V15C16 15.5523 15.5523 16 15 16H1C0.447715 16 0 15.5523 0 15V1Z" fill="#3B99FD"/>
<path d="M6.56358 11.8292L3 8.32244L3.70142 7.60969L6.55139 10.4143L12.8641 4L13.5775 4.70204L6.56358 11.8292Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 381 B

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H1V15H15V1ZM1 0C0.447715 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V1C16 0.447715 15.5523 0 15 0H1Z" fill="#1D2939"/>
<path d="M1 1H15V15H1V1Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 351 B

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H1V15H15V1ZM1 0C0.447715 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V1C16 0.447715 15.5523 0 15 0H1Z" fill="#EAECF0"/>
<path d="M1 1H15V15H1V1Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 351 B

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1C0 0.447715 0.447716 0 1 0H15C15.5523 0 16 0.447715 16 1V15C16 15.5523 15.5523 16 15 16H1C0.447716 16 0 15.5523 0 15V1Z" fill="#3B99FD"/>
<path d="M13 8H3V9H13V8Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 295 B

View file

@ -27,7 +27,6 @@
//= require shared/barcode_search
//= require activestorage
//= require global_activities/side_pane
//= require protocols/header
//= require protocols/print
//= require marvinjslauncher
//= require jstree.min

View file

@ -1,125 +0,0 @@
/* globals renderFormError animateSpinner */
(function() {
function clearModal(id) {
// Completely remove modal when it gets closed
$(id).on('hidden.bs.modal', function() {
$(id).remove();
});
}
// Reload after successfully updated experiment
function validateExperimentForm(element) {
if ($(element)) {
let form = $(element).find('form');
form.on('ajax:success', function(e, data) {
animateSpinner(form, true);
if (element.match(/#new-experiment-modal/)) {
window.location.replace(data.path);
} else {
location.reload();
}
}).on('ajax:error', function(e, error) {
let msg = JSON.parse(error.responseText);
if ('name' in msg) {
renderFormError(e, $(element).find('#experiment-name'), msg.name.toString(), true);
} else if ('description' in msg) {
renderFormError(e, $(element).find('#experiment-description'), msg.description.toString(), true);
} else {
renderFormError(e, $(element).find('#experiment-name'), error.statusText, true);
}
});
}
}
// Validates move action
function validateMoveModal(modal) {
if (modal.match(/#move-experiment-modal-[0-9]*/)) {
let form = $(modal).find('form');
form.on('ajax:success', function(e, data) {
animateSpinner(form, true);
window.location.replace(data.path);
}).on('ajax:error', function(e, error) {
form.clearFormErrors();
let msg = JSON.parse(error.responseText);
renderFormError(e, form.find('#experiment_project_id'), msg.message.toString());
});
}
}
// Create ajax hook on given 'element', which should return modal with 'id' =>
// show that modal
function initializeModal(element, id) {
// Initialize new experiment modal listener
$(element).on('ajax:beforeSend', function() {
animateSpinner();
}).on('ajax:success', function(e, data) {
$('body').append($.parseHTML(data.html));
$(id).modal('show', {
backdrop: true,
keyboard: false
});
validateMoveModal(id);
clearModal($(id));
validateExperimentForm(id);
}).on('ajax:error', function() {
animateSpinner(null, false);
// TODO
}).on('ajax:complete', function() {
animateSpinner(null, false);
$(id).find('.selectpicker').selectpicker();
});
}
// Initialize dropdown actions on experiment:
// - edit
// - clone
function initializeDropdownActions() {
// { buttonClass: modalName } mappings
// click on buttonClass summons modalName dialog
let modals = {
'.edit-experiment': '#edit-experiment-modal-',
'.clone-experiment': '#clone-experiment-modal-',
'.move-experiment': '#move-experiment-modal-'
};
$.each($('.dropdown-experiment-actions'), function() {
var $dropdown = $(this);
$.each(modals, function(buttonClass, modalName) {
var id = modalName + $dropdown.data('id');
initializeModal($dropdown.find(buttonClass), id);
});
});
}
// Initialize no description edit link
function initEditNoDescription() {
var modal = '#edit-experiment-modal-';
$.each($('.experiment-no-description'), function() {
var id = modal + $(this).data('id');
initializeModal($(this), id);
});
}
// Bind modal to new-experiment action
initializeModal($('#new-experiment'), '#new-experiment-modal');
// Bind modal to big-plus new experiment actions
initializeModal('.big-plus', '#new-experiment-modal');
// Bind modal to new-exp-title action
initializeModal('.new-exp-title', '#new-experiment-modal');
// Bind modals to all clone-experiment actions
$.each($('.clone-experiment'), function() {
var id = $(this).closest('.experiment-panel').data('id');
initializeModal($(this), '#clone-experiment-modal-' + id);
});
// Bind modal to all actions listed on dropdown accesible from experiment
// panel
initializeDropdownActions();
// init
initEditNoDescription();
}());

View file

@ -7,7 +7,22 @@
let myModuleUserSelector = '#my_module_user_ids';
var myModuleTagsSelector = '#module-tags-selector';
$(document).on('submit', '#new-my-module-modal form', (event) => {
event.preventDefault();
$.post({
url: $('#new-my-module-modal form').attr('action'),
data: {
my_module: {
name: $('#new-my-module-modal input[name="my_module[name]"]').val(),
view_mode: $('#new-my-module-modal input[name="my_module[view_mode]"]').val(),
due_date: $('#new-my-module-modal input[name="my_module[due-date]"]').val(),
tag_ids: dropdownSelector.getValues(myModuleTagsSelector),
user_ids: dropdownSelector.getValues(myModuleUserSelector)
}
}
});
});
// Modal's submit handler function
$(experimentWrapper)
.on('ajax:success', newMyModuleModal, function() {
@ -126,5 +141,28 @@
initBSTooltips();
}
function initAccessModal() {
$('#experiment-canvas').on('click', '.openAccessModal', (e) => {
e.preventDefault();
const { target } = e;
$.get(target.dataset.url, (data) => {
const object = {
...data.data.attributes,
id: data.data.id,
type: data.data.type
};
const { rolesUrl } = target.dataset;
const params = {
object: object,
roles_path: rolesUrl
};
const modal = $('#accessModalComponent').data('accessModal');
modal.params = params;
modal.open();
});
});
}
initNewMyModuleModal();
initAccessModal();
}());

View file

@ -1,836 +0,0 @@
/* global I18n GLOBAL_CONSTANTS InfiniteScroll
initBSTooltips filterDropdown dropdownSelector Sidebar HelperModule notTurbolinksPreview _ */
var ExperimnetTable = {
selectedId: [],
table: '.experiment-table',
tableContainer: '.experiment-table-container',
selectedMyModules: [],
activeFilters: {},
filters: [], // Filter {name: '', init(), closeFilter(), apply(), active(), clearFilter()}
myModulesCurrentSort: '',
pageSize: GLOBAL_CONSTANTS.DEFAULT_ELEMENTS_PER_PAGE,
provisioningStatusTimeout: '',
render: {
task_name: function(data) {
let tooltip = ` title="${_.escape(data.name)}" data-toggle="tooltip" data-placement="bottom"`;
if (data.provisioning_status === 'in_progress') {
return `<span data-full-name="${_.escape(data.name)}">${data.name}</span>`;
}
return `<a
href="${data.url}"
${tooltip}
title="${_.escape(data.name)}"
id="taskName${data.id}"
data-full-name="${_.escape(data.name)}">${data.name}</a>`;
},
id: function(data) {
return `
<div>${data.id}</div>
`;
},
due_date: function(data) {
return data.data;
},
archived: function(data) {
return data;
},
age: function(data) {
return data;
},
results: function(data) {
return `<a href="${data.url}">${data.count}</a>`;
},
status: function(data) {
return `<div class="my-module-status ${data.light_color ? 'status-light' : ''}"
style="background-color: ${data.color}">${data.name}</div>`;
},
assigned: function(data) {
return data.html;
},
tags: function(data) {
const value = parseInt(data.tags, 10) === 0 ? I18n.t('experiments.table.add_tag') : data.tags;
if (data.tags === 0 && !data.can_create) {
return `<span class="disabled">${I18n.t('experiments.table.not_set')}</span>`;
}
return `<a href="${data.edit_url}"
id="myModuleTags${data.my_module_id}"
data-remote="true"
class="edit-tags-link">${value}</a>`;
},
comments: function(data) {
if (data.count === 0 && !data.can_create) return '<span class="disabled">0</span>';
return `<a href="#"
class="open-comments-sidebar" tabindex=0 id="comment-count-${data.id}"
data-object-type="MyModule" data-object-id="${data.id}">
${data.count > 0 ? data.count : '+'}
${data.count_unseen > 0 ? `<span class="unseen-comments"> ${data.count_unseen} </span>` : ''}
</a>`;
}
},
getUrls: function(id) {
return $(`.table-row[data-id="${id}"]`).data('urls');
},
loadPlaceholder: function() {
let placeholder = '';
$.each(Array(this.pageSize), function() {
placeholder += $('#experimentTablePlaceholder').html();
});
$(placeholder).insertAfter($(this.table).find('.table-body'));
},
appendRows: function(result) {
$.each(result, (_j, data) => {
let row;
const isProvisioning = data.provisioning_status === 'in_progress';
const provisioningTooltipAttrs = `title="${I18n.t('experiments.duplicate_tasks.duplicating')}"
data-toggle="tooltip"`;
// Checkbox selector
row = `
<div class="table-body-cell">
<div class="sci-checkbox-container">
<div title="${I18n.t('experiments.duplicate_tasks.duplicating')}"
class="loading-overlay" data-toggle="tooltip" data-placement="right"></div>
<input type="checkbox" class="sci-checkbox my-module-selector" data-my-module="${data.id}">
<span class="sci-checkbox-label"></span>
</div>
</div>`;
// Task columns
$.each(data.columns, (_i, cell) => {
let hidden = '';
if ($(`.table-display-modal .sn-icon-visibility-hide[data-column="${cell.column_type}"]`).length === 1) {
hidden = 'hidden';
}
row += `
<div class="table-body-cell ${cell.column_type}-column ${hidden}"
${cell.column_type === 'task_name' && isProvisioning ? provisioningTooltipAttrs : ''}>
${ExperimnetTable.render[cell.column_type](cell.data)}
</div>
`;
});
// Menu
row += `
<div class="table-body-cell">
<div ref="dropdown" class="dropdown my-module-menu" data-url="${data.urls.actions_dropdown}">
<div class="btn btn-light btn-xs icon-btn open-my-module-menu" tabindex="0"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" >
<i class="sn-icon sn-icon-more-hori"></i>
</div>
<div class="dropdown-menu dropdown-menu-right">
<a class="open-access-modal hidden" data-action="remote-modal" href="${data.urls.access}"></a>
</div>
</div>
</div>`;
let tableRowClass = `table-row ${isProvisioning ? 'table-row-provisioning' : ''}`;
$(`<div class="${tableRowClass}" data-urls='${JSON.stringify(data.urls)}' data-id="${data.id}">${row}</div>`)
.appendTo(`${this.table} .table-body`);
});
},
initDueDatePicker: function(data) {
// eslint-disable-next-line no-unused-vars
$.each(data, (_, row) => {
let element = `#calendarDueDate${row.id}`;
let dueDateContainer = $(element).closest('#dueDateContainer');
let dateText = $(element).closest('.date-text');
let clearDate = $(element).closest('.datetime-container').find('.clear-date');
$(`#calendarDueDateContainer${row.id}`).parent().on('dp:ready', () => {
$(element).data('dateTimePicker').onChange = () => {
$.ajax({
url: dueDateContainer.data('update-url'),
type: 'PATCH',
dataType: 'json',
data: { my_module: { due_date: $(element).val() } },
success: function(result) {
dueDateContainer.find('#dueDateLabelContainer').html(result.table_due_date_label.html);
dateText.data('due-status', result.table_due_date_label.due_status);
if ($(result.table_due_date_label.html).data('due-date')) {
clearDate.removeClass('tw-hidden');
} else {
clearDate.addClass('tw-hidden');
}
}
});
}
clearDate.on('click', () => {
$(element).data('dateTimePicker').clearDate();
});
});
if ($(`#calendarDueDateContainer${row.id}`).length > 0) {
window.initDateTimePickerComponent(`#calendarDueDateContainer${row.id}`);
}
});
},
initMyModuleActions: function() {
$(this.table).on('show.bs.dropdown', '.my-module-menu', (e) => {
let menu = $(e.target).find('.dropdown-menu');
$.get(e.currentTarget.dataset.url, (result) => {
$(menu).find('li').remove();
$(result.html).appendTo(menu);
});
});
$(this.table).on('click', '.archive-my-module', (e) => {
e.preventDefault();
e.stopPropagation();
this.archiveMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
});
$(this.table).on('click', '.restore-my-module', (e) => {
e.preventDefault();
e.stopPropagation();
this.restoreMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
});
$(this.table).on('click', '.duplicate-my-module', (e) => {
e.preventDefault();
e.stopPropagation();
this.duplicateMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
});
$(this.table).on('click', '.move-my-module', (e) => {
e.preventDefault();
e.stopPropagation();
this.openMoveModulesModal([e.currentTarget.dataset.id]);
});
$(this.table).on('click', '.edit-my-module', (e) => {
e.preventDefault();
$('#modal-edit-module').modal('show');
$('#modal-edit-module').data('id', e.currentTarget.dataset.id);
$('#edit-module-name-input').val($(`#taskName${$('#modal-edit-module').data('id')}`).data('full-name'));
});
},
initDuplicateMyModules: function() {
$(this.tableContainer).on('click', '#duplicateTasks', (e) => {
e.stopPropagation();
this.duplicateMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
});
},
duplicateMyModules: function(url, ids) {
$.post(url, { my_module_ids: ids }, () => {
this.loadTable();
window.navigatorContainer.reloadChildrenLevel = true;
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
},
initArchiveMyModules: function() {
$(this.tableContainer).on('click', '#archiveTask', (e) => {
e.stopPropagation();
this.archiveMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
});
},
archiveMyModules: function(url, ids) {
$.post(url, { my_modules: ids }, (data) => {
HelperModule.flashAlertMsg(data.message, 'success');
this.loadTable();
window.navigatorContainer.reloadChildrenLevel = true;
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
},
initRestoreMyModules: function() {
$(this.tableContainer).on('click', '#restoreTask', (e) => {
e.stopPropagation();
this.restoreMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
});
},
restoreMyModules: function(url, ids) {
$.post(url, { my_modules_ids: ids, view: 'table' });
},
initRenameModal: function() {
$(this.tableContainer).on('click', '#editTask', (e) => {
e.preventDefault();
$('#modal-edit-module').modal('show');
$('#modal-edit-module').data('id', this.selectedMyModules[0]);
$('#edit-module-name-input').val($(`#taskName${$('#modal-edit-module').data('id')}`).data('full-name'));
});
const handleRenameModal = () => {
let id = $('#modal-edit-module').data('id');
let newValue = $('#edit-module-name-input').val();
$(`.my-module-selector[data-my-module="${id}"]`).trigger('click');
if (newValue === $(`#taskName${id}`).data('full-name')) {
$('#modal-edit-module').modal('hide');
return false;
}
$.ajax({
url: this.getUrls(id).name_update,
type: 'PATCH',
dataType: 'json',
data: { my_module: { name: $('#edit-module-name-input').val() } },
success: () => {
$(`#taskName${id}`).text(newValue);
$(`#taskName${id}`).data('full-name', newValue);
$('#edit-module-name-input').closest('.sci-input-container').removeClass('error');
$('#modal-edit-module').modal('hide');
},
error: function(response) {
let error = response.responseJSON.name.join(', ');
$('#edit-module-name-input')
.closest('.sci-input-container')
.addClass('error')
.attr('data-error-text', error);
}
});
if ($(`.my-module-selector[data-my-module="${id}"]`).prop('checked')) {
$(`.my-module-selector[data-my-module="${id}"]`).trigger('click');
}
this.clearRowTaskSelection();
return true;
};
$('#modal-edit-module')
.on('click', 'button[data-action="confirm"]', handleRenameModal)
.on('submit', 'form', (e) => {
e.preventDefault();
handleRenameModal();
});
},
initManageUsersDropdown: function() {
$(this.table).on('show.bs.dropdown', '.assign-users-dropdown', (e) => {
setTimeout(() => {
$('.sci-input-field.user-search').each(function() {
this.focus();
});
}, 200);
let usersList = $(e.target).find('.users-list');
let isArchivedView = $('#experimentTable').hasClass('archived');
let viewOnly = $(e.target).data('view-only');
let checkbox = '';
usersList.find('.user-container').remove();
$.get(usersList.data('list-url'), (result) => {
$.each(result, (_i, user) => {
if (!isArchivedView && !viewOnly) {
checkbox = `<div class="sci-checkbox-container">
<input type="checkbox"
class="sci-checkbox user-selector"
${user.params.designated ? 'checked' : ''}
value="${user.value}"
data-assign-url="${user.params.assign_url}"
data-unassign-url="${user.params.unassign_url}"
>
<span class="sci-checkbox-label"></span>
</div>`;
}
$(`
<div class="user-container">
${checkbox}
<div class="user-avatar ${isArchivedView ? 'archived' : ''}">
<img src="${user.params.avatar_url}"></img>
</div>
<div class="user-name">
${user.label}
</div>
</div>
`).appendTo(usersList);
});
});
});
$(this.table).on('click', '.assign-users-dropdown .dropdown-menu', (e) => {
if (e.target.tagName === 'INPUT') return;
e.preventDefault();
e.stopPropagation();
});
$(this.table).on('keyup', '.assigned-users-container, .open-my-module-menu, .calendar-input', (e) => {
if (e.keyCode === 13) { // Enter
e.currentTarget.click();
}
});
$(this.table).on('change keyup', '.assign-users-dropdown .user-search', (e) => {
let query = e.currentTarget.value;
let usersList = $(e.target).closest('.dropdown-menu').find('.user-container');
$.each(usersList, (_i, user) => {
$(user).removeClass('hidden');
if (query.length && !$(user).find('.user-name').text().toLowerCase()
.includes(query.toLowerCase())) {
$(user).addClass('hidden');
}
});
});
$(this.table).on('change', '.assign-users-dropdown .user-selector', (e) => {
let checkbox = e.target;
if (checkbox.checked) {
$.post(checkbox.dataset.assignUrl, {
table: true,
user_my_module: {
my_module_id: $(checkbox).closest('.table-row').data('id'),
user_id: checkbox.value
}
}, (result) => {
checkbox.dataset.unassignUrl = result.unassign_url;
$(checkbox).closest('.table-row').find('.assigned-users-container')
.replaceWith($(result.html).find('.assigned-users-container'));
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.errors, 'danger');
});
} else {
$.ajax({
url: checkbox.dataset.unassignUrl,
method: 'DELETE',
data: { table: true },
success: (result) => {
$(checkbox).closest('.table-row').find('.assigned-users-container')
.replaceWith($(result.html).find('.assigned-users-container'));
},
error: (data) => {
HelperModule.flashAlertMsg(data.responseJSON.errors, 'danger');
}
});
}
});
},
initModalInputFocus: function() {
$(document).on('shown.bs.modal', function() {
var inputField = $('#edit-module-name-input');
var value = inputField.val();
inputField.focus().val('').val(value);
});
},
initMoveModulesModal: function() {
$(this.tableContainer).on('click', '#moveTask', (e) => {
e.stopPropagation();
this.openMoveModulesModal(this.selectedMyModules);
});
},
openMoveModulesModal: function(ids) {
let table = $(this.table);
$.get(table.data('move-modules-modal-url'), (modalData) => {
if ($('#modal-move-modules').length > 0) {
$('#modal-move-modules').replaceWith(modalData.html);
} else {
$('#experimentTable').append(modalData.html);
}
$('#modal-move-modules').on('shown.bs.modal', function() {
$(this).find('.selectpicker').selectpicker().focus();
});
$('#modal-move-modules').on('click', 'button[data-action="confirm"]', () => {
let moveParams = {
to_experiment_id: $('#modal-move-modules').find('.selectpicker').val(),
my_module_ids: ids
};
$.post(table.data('move-modules-url'), moveParams, (data) => {
HelperModule.flashAlertMsg(data.message, 'success');
this.loadTable();
window.navigatorContainer.reloadChildrenLevel = true;
}).fail((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
$('#modal-move-modules').modal('hide');
});
$('#modal-move-modules').modal('show');
});
},
checkActionPermission: function(permission) {
let allMyModules;
allMyModules = this.selectedMyModules.every((id) => {
return $(`.table-row[data-id="${id}"]`).data(permission);
});
return allMyModules;
},
updateSelectAllCheckbox: function() {
const tableWrapper = $(this.table);
const checkboxesCount = $('.sci-checkbox.my-module-selector', tableWrapper).length;
const selectedCheckboxesCount = this.selectedMyModules.length;
const selectAllCheckbox = $('.select-all-checkboxes .sci-checkbox', tableWrapper);
selectAllCheckbox.prop('indeterminate', false);
if (selectedCheckboxesCount === 0) {
selectAllCheckbox.prop('checked', false);
} else if (selectedCheckboxesCount === checkboxesCount) {
selectAllCheckbox.prop('checked', true);
} else {
selectAllCheckbox.prop('indeterminate', true);
}
},
initSelectAllCheckbox: function() {
$(this.table).on('click', '.select-all-checkboxes .sci-checkbox', (e1) => {
$.each($('.my-module-selector'), (_i, e2) => {
if (e1.target.checked !== e2.checked) e2.click();
});
});
},
initSelector: function() {
$(this.table).on('click', '.my-module-selector', (e) => {
let checkbox = e.target;
let myModuleId = checkbox.dataset.myModule;
let index = $.inArray(myModuleId, this.selectedMyModules);
// If checkbox is checked and row ID is not in list of selected project IDs
if (checkbox.checked && index === -1) {
$(checkbox).closest('.table-row').addClass('selected');
this.selectedMyModules.push(myModuleId);
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
} else if (!this.checked && index !== -1) {
$(checkbox).closest('.table-row').removeClass('selected');
this.selectedMyModules.splice(index, 1);
}
this.updateSelectAllCheckbox();
this.updateExperimentToolbar();
});
},
updateExperimentToolbar: function() {
if (window.actionToolbarComponent) {
window.actionToolbarComponent.fetchActions({ my_module_ids: this.selectedMyModules });
}
},
selectDate: function($field) {
var datePicker = $field.data('dateTimePicker');
if (datePicker && datePicker.date) {
return datePicker.date.toString();
}
return null;
},
initManageColumnsModal: function() {
$.each($('.table-display-modal .sn-icon-visibility-hide'), (_i, column) => {
$(column).parent().removeClass('visible');
});
$('.experiment-table')[0].style
.setProperty('--columns-count', $('.table-display-modal .sn-icon-visibility-show:not(.disabled)').length + 1);
$('.table-display-modal').on('click', '.column-container .sn-icon', (e) => {
let icon = $(e.target);
if (icon.hasClass('sn-icon-visibility-show')) {
$(`.experiment-table .${icon.data('column')}-column`).addClass('hidden');
icon.removeClass('sn-icon-visibility-show').addClass('sn-icon-visibility-hide');
icon.parent().removeClass('visible');
} else {
if (icon.hasClass('disabled')) return;
$(`.experiment-table .${icon.data('column')}-column`).removeClass('hidden');
icon.addClass('sn-icon-visibility-show').removeClass('sn-icon-visibility-hide');
icon.parent().addClass('visible');
}
let visibleColumns = $('.table-display-modal .sn-icon-visibility-show').map((_i, col) => col.dataset.column).toArray();
// Update columns on backend - $.post('', { columns: visibleColumns }, () => {});
$.post($('.table-display-modal').data('column-state-url'), { columns: visibleColumns }, () => {});
$('.experiment-table')[0].style
.setProperty('--columns-count', $('.table-display-modal .sn-icon-visibility-show:not(.disabled)').length + 1);
});
},
clearRowTaskSelection: function() {
this.selectedMyModules = [];
this.updateSelectAllCheckbox();
this.updateExperimentToolbar();
},
initNewTaskModal: function(table) {
$('.experiment-new-my_module').on('ajax:success', '#new-my-module-modal', function() {
table.loadTable();
window.navigatorContainer.reloadChildrenLevel = true;
});
},
initSorting: function(table) {
$('#sortMenuDropdown a').click(function() {
if (table.myModulesCurrentSort !== $(this).data('sort')) {
$('#sortMenuDropdown a').removeClass('selected');
// eslint-disable-next-line no-param-reassign
table.myModulesCurrentSort = $(this).data('sort');
table.loadTable();
$(this).addClass('selected');
$('#sortMenu').dropdown('toggle');
}
});
},
getFilterValues: function() {
let $experimentFilter = $('#experimentTable .my-modules-filters');
$.each(this.filters, (_i, filter) => {
this.activeFilters[filter.name] = filter.apply($experimentFilter);
});
// filters are active if they have any non-empty value
let filtersEmpty = Object.values(this.activeFilters).every(value => /^\s+$/.test(value)
|| value === null
|| value === undefined
|| (value && value.length === 0));
this.filtersActive = !filtersEmpty;
},
filtersEnabled: function() {
this.getFilterValues();
return this.filters.some((filter) => {
return filter.active(this.activeFilters[filter.name]);
});
},
initFilters: function() {
let $experimentFilter = $('#experimentTable .my-modules-filters');
this.filterDropdown = filterDropdown.init(() => this.filtersEnabled());
$.each(this.filters, (_i, filter) => {
filter.init($experimentFilter);
});
this.filterDropdown.on('filter:apply', () => {
filterDropdown.toggleFilterMark(this.filterDropdown, this.filtersEnabled());
this.loadTable();
});
this.filterDropdown.on('filter:clickBody', () => {
$.each(this.filters, (_i, filter) => {
filter.closeFilter($experimentFilter);
});
});
this.filterDropdown.on('filter:clear', () => {
$.each(this.filters, (_i, filter) => {
filter.clearFilter($experimentFilter);
});
});
},
loadTable: function() {
var tableParams = {
filters: this.activeFilters,
sort: this.myModulesCurrentSort
};
var dataUrl = $(this.table).data('my-modules-url');
$(this.table).find('.table-row').remove();
this.loadPlaceholder();
$.get(dataUrl, tableParams, (result) => {
$(this.table).find('.table-row-placeholder, .table-row-placeholder-divider').remove();
setTimeout(() => {
this.appendRows(result.data);
this.initDueDatePicker(result.data);
this.handleNoResults();
this.initProvisioningStatusPolling();
}, 100);
InfiniteScroll.init(this.table, {
url: dataUrl,
eventTarget: window,
placeholderTemplate: '#experimentTablePlaceholder',
endOfListTemplate: '#experimentTableEndOfList',
pageSize: this.pageSize,
lastPage: !result.next_page,
customResponse: (response) => {
setTimeout(() => {
this.appendRows(response.data);
this.initDueDatePicker(response.data);
this.initProvisioningStatusPolling();
}, 100);
},
customParams: (params) => {
return { ...params, ...tableParams };
}
});
initBSTooltips();
this.clearRowTaskSelection();
this.initProvisioningStatusPolling();
});
},
initProvisioningStatusPolling: function() {
let provisioningStatusUrls = $('.table-row-provisioning').toArray()
.map((u) => $(u).data('urls').provisioning_status)
.filter((u) => !!u);
this.provisioningMyModulesCount = provisioningStatusUrls.length;
if (this.provisioningMyModulesCount > 0) this.pollProvisioningStatuses(provisioningStatusUrls);
},
handleNoResults: function() {
let tableRowLength = document.getElementsByClassName('table-row').length;
let noResultsContainer = document.getElementById('tasksNoResultsContainer');
$('.no-data-container').hide();
if (this.filtersActive && tableRowLength === 0) {
noResultsContainer.style.display = 'block';
} else if (tableRowLength === 0) {
$(this.table).find('.table-header').hide();
$(this.table).addClass('no-data');
$('.no-data-container').show();
} else {
noResultsContainer.style.display = 'none';
$(this.table).find('.table-header').show();
}
},
pollProvisioningStatuses: function(provisioningStatusUrls) {
let remainingUrls = [];
provisioningStatusUrls.forEach((url) => {
jQuery.ajax({
url: url,
success: (data) => {
if (data.provisioning_status === 'in_progress') remainingUrls.push(url);
},
async: false
});
});
if (remainingUrls.length > 0) {
clearTimeout(this.provisioningStatusTimeout);
this.provisioningStatusTimeout = setTimeout(() => {
this.pollProvisioningStatuses(remainingUrls);
}, 10000);
} else {
HelperModule.flashAlertMsg(
I18n.t('experiments.duplicate_tasks.success', { count: this.provisioningMyModulesCount }),
'success'
);
this.loadTable();
}
},
init: function() {
window.initActionToolbar();
this.initSelector();
this.initSelectAllCheckbox();
this.initFilters();
this.initSorting(this);
this.loadTable();
this.initRenameModal();
this.initDuplicateMyModules();
this.initMoveModulesModal();
this.initArchiveMyModules();
this.initManageColumnsModal();
this.initNewTaskModal(this);
this.initMyModuleActions();
this.initRestoreMyModules();
this.initManageUsersDropdown();
this.initModalInputFocus();
}
};
// Filters
ExperimnetTable.filters.push({
name: 'name',
init: () => {},
closeFilter: ($container) => {
$('#textSearchFilterHistory').hide();
$('#textSearchFilterInput', $container).closest('.dropdown').removeClass('open');
},
apply: ($container) => {
return $('#textSearchFilterInput', $container).val();
},
active: (value) => { return value; },
clearFilter: ($container) => {
$('#textSearchFilterInput', $container).val('');
}
});
ExperimnetTable.filters.push({
name: 'due_date_from',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.due-date-filter .from-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
$('.due-date-filter .from-date', $container).data('dateTimePicker')?.clearDate();
}
});
ExperimnetTable.filters.push({
name: 'due_date_to',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.due-date-filter .to-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
$('.due-date-filter .to-date', $container).data('dateTimePicker')?.clearDate();
}
});
ExperimnetTable.filters.push({
name: 'archived_on_from',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.archived-on-filter .from-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
$('.archived-on-filter .from-date', $container).data('dateTimePicker')?.clearDate();
}
});
ExperimnetTable.filters.push({
name: 'archived_on_to',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.archived-on-filter .to-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
$('.archived-on-filter .to-date', $container).data('dateTimePicker')?.clearDate();
}
});
ExperimnetTable.filters.push({
name: 'assigned_users',
init: ($container) => {
dropdownSelector.init($('.assigned-filter', $container), {
optionClass: 'checkbox-icon users-dropdown-list',
optionLabel: (data) => {
return `<img class="item-avatar" src="${data.params.avatar_url}"/> ${data.label}`;
},
tagLabel: (data) => {
return `<img class="item-avatar" src="${data.params.avatar_url}"/> ${data.label}`;
},
labelHTML: true,
tagClass: 'users-dropdown-list'
});
},
closeFilter: ($container) => {
dropdownSelector.closeDropdown($('.assigned-filter', $container));
},
apply: ($container) => {
return dropdownSelector.getValues($('.assigned-filter', $container));
},
active: (value) => { return value && value.length !== 0; },
clearFilter: ($container) => {
dropdownSelector.clearData($('.assigned-filter', $container));
}
});
ExperimnetTable.filters.push({
name: 'statuses',
init: ($container) => {
dropdownSelector.init($('.status-filter', $container), {
singleSelect: true,
closeOnSelect: true,
selectAppearance: 'simple'
});
},
closeFilter: ($container) => {
dropdownSelector.closeDropdown($('.status-filter', $container));
},
apply: ($container) => {
return dropdownSelector.getValues($('.status-filter', $container));
},
active: (value) => { return value && value.length !== 0; },
clearFilter: ($container) => {
dropdownSelector.clearData($('.status-filter', $container));
}
});
if (notTurbolinksPreview()) {
ExperimnetTable.init();
}

View file

@ -1,306 +0,0 @@
/* global I18n DataTableHelpers HelperModule */
/* eslint-disable no-use-before-define no-param-reassign */
(function() {
'use strict';
var LABEL_TEMPLATE_TABLE;
var rowsSelected = [];
function rowsSelectedIDs() {
return rowsSelected.map(i => i.id);
}
function renderCheckboxHTML(data) {
return `<div class="sci-checkbox-container">
<input type="checkbox" class="sci-checkbox label-row-checkbox" data-action='toggle'
data-label-template-id="${data}">
<span class="sci-checkbox-label"></span>
</div>`;
}
function renderDefaultTemplateHTML(data) {
return data ? '<i class="fas fa-thumbtack"></i>' : '';
}
function renderNameHTML(data, type, row) {
return `<div class="flex gap-2">${data.icon_image_tag}<a
href='${row.DT_RowAttr['data-edit-url']}'
class='label-info-link'
>${data.name}</a></div>`;
}
function addAttributesToRow(row, data) {
$(row).addClass('label-template-row')
.attr('data-id', data['0']);
}
function initNameClick() {
$('.label-info-link', '.dataTables_scrollBody').on('click', function() {
window.location.href = this.href;
return false;
});
}
function initToggleAllCheckboxes() {
$('input[name="select_all"]').change(function() {
if ($(this).is(':checked')) {
$("[data-action='toggle']").prop('checked', true);
$('.label-template-row').addClass('selected');
$('.label-template-row [data-action="toggle"]').change();
} else {
$("[data-action='toggle']").prop('checked', false);
$('.label-template-row').removeClass('selected');
$('.label-template-row [data-action="toggle"]').change();
}
});
}
function initCreateButton() {
$('#newLabelTemplate').on('click', function() {
$.post(this.dataset.url);
});
}
function initSetDefaultButton() {
$(document).on('click', '#setZplDefaultLabelTemplate, #setFluicsDefaultLabelTemplate', function(e) {
e.preventDefault();
e.stopPropagation();
if (rowsSelected.length === 1) {
$.post(rowsSelected[0].setDefaultUrl, function(response) {
reloadTable();
HelperModule.flashAlertMsg(response.message, 'success');
}).fail((response) => {
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
});
}
});
}
function initEditButton() {
$('#editTemplate').on('click', function() {
if (rowsSelected.length === 1) {
window.location.href = rowsSelected[0].editUrl;
}
});
}
function initDuplicateButton() {
$(document).on('click', '#duplicateLabelTemplate', function(e) {
e.preventDefault();
e.stopPropagation();
if (rowsSelected.length > 0) {
$.post(this.dataset.url, { selected_ids: rowsSelectedIDs() }, function(response) {
reloadTable();
HelperModule.flashAlertMsg(response.message, 'success');
}).fail((response) => {
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
});
}
});
}
function initDeleteModal() {
$(document).on('click', '#deleteLabelTemplate', function(e) {
e.preventDefault();
e.stopPropagation();
$('#deleteLabelTemplatesModal').modal('show');
});
}
function initDeleteButton() {
$('#confirmLabeleDeletion').on('click', function() {
if (rowsSelected.length > 0) {
$.post(this.dataset.url, { selected_ids: rowsSelectedIDs() }, function(response) {
reloadTable();
HelperModule.flashAlertMsg(response.message, 'success');
$('#deleteLabelTemplatesModal').modal('hide');
}).fail((response) => {
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
$('#deleteLabelTemplatesModal').modal('hide');
});
}
});
}
function initRefreshFluicsButton() {
$('#syncFluicsTemplates').on('click', function() {
$.post(this.dataset.url, function(response) {
reloadTable();
HelperModule.flashAlertMsg(response.message, 'success');
}).fail((response) => {
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
});
});
}
function tableDrawCallback() {
initToggleAllCheckboxes();
initRowSelection();
initNameClick();
}
function updateButtons() {
if (window.actionToolbarComponent) {
window.actionToolbarComponent.fetchActions({ label_template_ids: rowsSelectedIDs() });
$('.dataTables_scrollBody').css('margin-bottom', `${rowsSelectedIDs().length > 0 ? 54 : 0}px`);
}
}
function reloadTable() {
LABEL_TEMPLATE_TABLE.ajax.reload(null, false);
rowsSelected = [];
updateButtons();
}
function updateDataTableSelectAllCtrl() {
var $table = LABEL_TEMPLATE_TABLE.table().node();
var $header = LABEL_TEMPLATE_TABLE.table().header();
var $chkboxAll = $('.label-row-checkbox', $table);
var $chkboxChecked = $('.label-row-checkbox:checked', $table);
var chkboxSelectAll = $('input[name="select_all"]', $header).get(0);
// If none of the checkboxes are checked
if ($chkboxChecked.length === 0) {
chkboxSelectAll.checked = false;
if ('indeterminate' in chkboxSelectAll) {
chkboxSelectAll.indeterminate = false;
}
// If all of the checkboxes are checked
} else if ($chkboxChecked.length === $chkboxAll.length) {
chkboxSelectAll.checked = true;
if ('indeterminate' in chkboxSelectAll) {
chkboxSelectAll.indeterminate = false;
}
// If some of the checkboxes are checked
} else {
chkboxSelectAll.checked = true;
if ('indeterminate' in chkboxSelectAll) {
chkboxSelectAll.indeterminate = true;
}
}
}
function initRowSelection() {
// Handle clicks on checkbox
$('#label-templates-table').on('change', '.label-row-checkbox', function(ev) {
var rowId;
var index;
var row;
rowId = this.dataset.labelTemplateId;
row = $(this).closest('tr')[0];
// Determine whether row ID is in the list of selected row IDs
index = rowsSelected.findIndex(v => v.id === rowId);
// If checkbox is checked and row ID is not in list of selected row IDs
if (this.checked && index === -1) {
rowsSelected.push({
id: rowId,
default: row.dataset.default,
editUrl: row.dataset.editUrl,
setDefaultUrl: row.dataset.setDefaultUrl,
format: row.dataset.format
});
// Otherwise, if checkbox is not checked and row ID is in list of selected row IDs
} else if (!this.checked && index !== -1) {
rowsSelected.splice(index, 1);
}
if (this.checked) {
$(this).closest('.label-template-row').addClass('selected');
} else {
$(this).closest('.label-template-row').removeClass('selected');
}
updateDataTableSelectAllCtrl();
ev.stopPropagation();
updateButtons();
});
}
// INIT
function initDatatable() {
var $table = $('#label-templates-table');
LABEL_TEMPLATE_TABLE = $table.DataTable({
dom: "R<'label-toolbar'<'label-buttons-container'><'label-search-container'f>>t<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
order: [[2, 'desc']],
stateSave: true,
sScrollX: '100%',
sScrollXInner: '100%',
processing: true,
serverSide: true,
ajax: $table.data('source'),
pagingType: 'simple_numbers',
colReorder: {
fixedColumnsLeft: 1000000 // Disable reordering
},
columnDefs: [{
targets: 0,
searchable: false,
orderable: false,
className: 'dt-body-center',
sWidth: '1%',
render: renderCheckboxHTML
}, {
targets: 1,
searchable: false,
orderable: true,
width: '1.5rem',
render: renderDefaultTemplateHTML
}, {
targets: 2,
className: 'label-template-name',
render: renderNameHTML
}, {
targets: 4,
className: 'whitespace-break-spaces',
render: data => `<span class='whitespace-break-spaces'>${data}</span>`
}],
oLanguage: {
sSearch: I18n.t('general.filter')
},
fnDrawCallback: tableDrawCallback,
createdRow: addAttributesToRow,
fnInitComplete: function() {
DataTableHelpers.initLengthAppearance($table.closest('.dataTables_wrapper'));
DataTableHelpers.initSearchField(
$table.closest('.dataTables_wrapper'),
I18n.t('label_templates.index.search_templates')
);
$('.pagination-row').removeClass('hidden');
let toolBar = $($('#labelTemplatesToolbar').html());
$('.label-buttons-container').html(toolBar);
initCreateButton();
initEditButton();
initSetDefaultButton();
initDuplicateButton();
initDeleteModal();
initRefreshFluicsButton();
window.initActionToolbar();
window.actionToolbarComponent.setBottomOffset(68);
},
stateLoadParams: function(_, state) {
state.search.search = '';
}
});
}
$('#wrapper').on('sideBar::shown sideBar::hidden', function() {
if (LABEL_TEMPLATE_TABLE) {
LABEL_TEMPLATE_TABLE.columns.adjust();
}
});
initDatatable();
initDeleteButton();
}());

View file

@ -61,7 +61,9 @@
updateStartDate();
};
});
window.initDateTimePickerComponent('#calendarStartDateContainer');
if ($('#calendarStartDateContainer').length) {
window.initDateTimePickerComponent('#calendarStartDateContainer');
}
}
function updateDueDate() {
@ -90,7 +92,10 @@
updateDueDate();
};
});
window.initDateTimePickerComponent('#calendarDueDateContainer');
if ($('#calendarDueDateContainer').length) {
window.initDateTimePickerComponent('#calendarDueDateContainer');
}
}
function initTagsSelector() {
@ -135,7 +140,7 @@
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
}
});
} else {
} else if (lastTag.length > 0) {
newTag = {
tag: {
name: lastTag.find('.tag-label').html(),
@ -161,7 +166,7 @@
},
onUnSelect: (id) => {
$.post(`${$(myModuleTagsSelector).data('update-module-tags-url')}/${id}/destroy_by_tag_id`)
.success(function() {
.done(() => {
dropdownSelector.closeDropdown(myModuleTagsSelector);
})
.fail(function(r) {

View file

@ -8,7 +8,6 @@
// Currently selected row in "load from protocol" modal
var selectedRow = null;
function initEditMyModuleDescription() {
var viewObject = $('#my_module_description_view');
viewObject.on('click', function(e) {
@ -305,6 +304,39 @@ function initProtocolSectionOpenEvent() {
});
});
}
function initAccessModal() {
$('#openAccessModal').on('click', (e) => {
e.preventDefault();
const container = document.getElementById('accessModalContainer');
$.get(container.dataset.url, (data) => {
const object = {
...data.data.attributes,
id: data.data.id,
type: data.data.type
};
const { rolesUrl } = container.dataset;
const params = {
object: object,
roles_path: rolesUrl
};
const modal = $('#accessModalComponent').data('accessModal');
modal.params = params;
modal.open();
});
});
}
function initWrapTables() {
const viewMode = new URLSearchParams(window.location.search).get('view_mode');
if (['archived', 'locked', 'active'].includes(viewMode)) {
setTimeout(() => {
const notesContainerEl = document.getElementById('notes-container');
window.wrapTables(notesContainerEl);
}, 100);
}
}
/**
* Initializes page
*/
@ -315,6 +347,8 @@ function init() {
initLoadFromRepository();
initProtocolSectionOpenEvent();
initDetailsDropdown();
initAccessModal();
initWrapTables();
}
init();

View file

@ -146,13 +146,11 @@
});
// initialize my_module tab remote loading
$('#experimentTable, .my-modules-protocols-index, #experiment-canvas')
.on('ajax:before', '.edit-tags-link', function() {
manageTagsModal.modal('show');
.on('click', '.edit-tags-link', function() {
if($('#tagsModalComponent').length) {
$('#tagsModalComponent').data('tagsModal').open()
}
})
.on('ajax:success', '.edit-tags-link', function(e, data) {
$('#manage-module-tags-modal-module').text(data.my_module.name);
initTagsModalBody(data);
});
}
bindEditTagsAjax();

View file

@ -545,6 +545,40 @@ function bindTouchDropdowns(selector) {
});
}
function handleAnchorClick(event) {
event.preventDefault();
// check if the clicked element is an anchor tag with an href
if (event.target.tagName === 'A' && event.target.href) {
const targetUrl = event.target.href;
const alertText = $("#update-canvas").attr("data-unsaved-work-text");
if (!alertText) {
window.location.href = targetUrl;
return;
}
const exit = confirm(alertText);
if (exit) {
// remove unload listeners and navigate to location
$(window).off("beforeunload");
$(document).off("page:before-change");
window.location.href = targetUrl;
}
}
};
// listen to clicks on links in navigator and leftMenuContainer
$(document).ready(function() {
const navigatorEl = $('.sci--layout-navigation-navigator');
const leftMenuContainerEl = $('.sci--layout--left-menu-container');
navigatorEl.on('click', 'a', handleAnchorClick);
leftMenuContainerEl.on('click', 'a', handleAnchorClick);
});
function bindEditModeCloseWindow() {
var alertText = $("#update-canvas").attr("data-unsaved-work-text");
@ -933,17 +967,9 @@ function bindEditTagsAjax(elements) {
// initialize my_module tab remote loading
$(elements).find("a.edit-tags-link")
.on("ajax:before", function () {
var moduleId = $(this).closest(".panel-default").attr("data-module-id");
manageTagsModal.attr("data-module-id", moduleId);
manageTagsModal.modal('show');
})
.on("ajax:success", function (e, data) {
$("#manage-module-tags-modal-module").text(data.my_module.name);
initTagsModalBody(data);
})
.on('click', function(){
$(this).addClass('updated-module-tags');
var modal = $(this).closest(".panel-default").find('.tags-modal-component').data('tagsModal').open();
});
}

View file

@ -1,723 +0,0 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
// TODO
// - error handling of assigning user to project, check XHR data.errors
// - error handling of removing user from project, check XHR data.errors
// - refresh project users tab after manage user modal is closed
// - refactor view handling using library, ex. backbone.js
/* global HelperModule dropdownSelector Turbolinks filterDropdown InfiniteScroll
AsyncDropdown GLOBAL_CONSTANTS loadPlaceHolder */
/* eslint-disable no-use-before-define */
var ProjectsIndex = (function() {
var projectsWrapper = '#projectsWrapper';
var cardsWrapper = '#cardsWrapper';
var editProjectModal = '#edit-modal';
var moveToModal = '#move-to-modal';
var pageSize = GLOBAL_CONSTANTS.DEFAULT_ELEMENTS_PER_PAGE;
var exportProjectsModal = null;
var exportProjectsModalHeader = null;
var exportProjectsModalBody = null;
var exportProjectsBtn = '.export-projects-btn';
var exportProjectsSubmit = '#export-projects-modal-submit';
let projectsCurrentSort;
let projectsViewSearch;
let createdOnFromFilter;
let createdOnToFilter;
let membersFilter;
let lookInsideFolders;
let archivedOnFromFilter;
let archivedOnToFilter;
let currentFilters;
// Arrays with selected project and folder IDs shared between both views
var selectedProjects = [];
var selectedProjectFolders = [];
var singleSelectedProject;
var destinationFolder;
// Init new project folder modal function
function initNewProjectFolderModal() {
var newProjectFolderModal = '#new-project-folder-modal';
// Modal's submit handler function
$(projectsWrapper)
.on('ajax:success', newProjectFolderModal, function(ev, data) {
$(newProjectFolderModal).modal('hide');
HelperModule.flashAlertMsg(data.message, 'success');
refreshCurrentView();
})
.on('ajax:error', newProjectFolderModal, function(e, data) {
let form = $(this).find('.new_project_folder');
form.renderFormErrors('project_folder', data.responseJSON);
});
$(projectsWrapper)
.on('ajax:success', '.new-project-folder-btn', function(e, data) {
// Add and show modal
$(projectsWrapper).append($.parseHTML(data.html));
$(newProjectFolderModal).modal('show');
$(newProjectFolderModal).find("input[type='text']").focus();
// Remove modal when it gets closed
$(newProjectFolderModal).on('hidden.bs.modal', function() {
$(newProjectFolderModal).remove();
});
});
}
/**
* Initialize the JS for new project modal to work.
*/
function initNewProjectModal() {
var newProjectModal = '#new-project-modal';
// Modal's submit handler function
$(projectsWrapper)
.on('submit', '#new_project', function() {
$('#new-project-modal button[type="submit"]').prop('disabled', true);
})
.on('ajax:complete', '#new_project', function() {
$('#new-project-modal button[type="submit"]').prop('disabled', false);
})
.on('ajax:success', newProjectModal, function(_ev, data) {
$(newProjectModal).modal('hide');
HelperModule.flashAlertMsg(data.message, 'success');
refreshCurrentView();
})
.on('ajax:error', newProjectModal, function(_ev, data) {
$(this).renderFormErrors('project', data.responseJSON);
});
$(projectsWrapper)
.on('ajax:success', '.new-project-btn', function(_ev, data) {
// Add and show modal
$(projectsWrapper).append($.parseHTML(data.html));
$(newProjectModal).modal('show');
$(newProjectModal).find("input[type='text']").focus();
// init select picker
$(newProjectModal).find('.selectpicker').selectpicker();
// Remove modal when it gets closed
$(newProjectModal).on('hidden.bs.modal', function() {
$(newProjectModal).remove();
});
});
}
// init delete project folders
function initDeleteFoldersToolbarButton() {
$(document)
.on('ajax:success', '.delete-folders-form', function(ev, data) {
$('.modal-project-folder-delete').modal('hide');
HelperModule.flashAlertMsg(data.message, 'success');
refreshCurrentView();
})
.on('ajax:error', '.delete-folders-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
}
// init project toolbar archive/restore functions
function initArchiveRestoreToolbarButtons() {
$(projectsWrapper)
.on('ajax:before', '.archive-projects-form, .restore-projects-form', function() {
let buttonForm = $(this);
buttonForm.find('input[name="projects_ids[]"]').remove();
selectedProjects.forEach(function(id) {
$('<input>').attr({
type: 'hidden',
name: 'projects_ids[]',
value: id
}).appendTo(buttonForm);
});
})
.on('ajax:success', '.archive-projects-form, .restore-projects-form', function(ev, data) {
HelperModule.flashAlertMsg(data.message, 'success');
// Project saved, reload view
refreshCurrentView();
})
.on('ajax:error', '.archive-projects-form, .restore-projects-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
}
// init project card archive/restore function
function initArchiveRestoreButton() {
$(projectsWrapper)
.on('ajax:success', '.project-archive-restore-form', function(ev, data) {
HelperModule.flashAlertMsg(data.message, 'success');
refreshCurrentView();
})
.on('ajax:error', '.project-archive-restore-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
}
/**
* Initialize the JS for export projects modal to work.
*/
function initExportProjectsModal() {
$(projectsWrapper).on('click', exportProjectsBtn, function(ev) {
ev.stopPropagation();
ev.preventDefault();
const projectId = $(this).data('projectId');
singleSelectedProject = projectId;
// Load HTML to refresh users list
$.ajax({
url: $(exportProjectsBtn).data('url'),
type: 'GET',
dataType: 'json',
data: {
project_ids: projectId ? [projectId] : selectedProjects,
project_folder_ids: projectId ? [] : selectedProjectFolders
},
success: function(data) {
// Update modal title
exportProjectsModalHeader.text(data.title);
// Set modal body
exportProjectsModalBody.html(data.html);
// Update modal footer (show/hide buttons)
if (data.status === 'error') {
$('#export-projects-modal-close').show();
$('#export-projects-modal-cancel').hide();
$(exportProjectsSubmit).hide();
} else {
$('#export-projects-modal-close').hide();
$('#export-projects-modal-cancel').show();
$(exportProjectsSubmit).show();
}
// Show the modal
exportProjectsModal.modal('show');
},
error: function(data) {
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
}
});
});
// Remove modal content when modal window is closed.
exportProjectsModal.on('hidden.bs.modal', function() {
exportProjectsModalHeader.html('');
exportProjectsModalBody.html('');
});
}
function initSelectAllCheckbox() {
$(projectsWrapper).on('click', '.sci-checkbox.select-all', function() {
var selectAll = this.checked;
$.each($('.folder-card-selector, .project-card-selector'), function() {
if (this.checked !== selectAll) this.click();
});
});
}
function initExportProjects() {
// Submit the export projects
$(exportProjectsSubmit).click(function() {
$.ajax({
url: $(exportProjectsSubmit).data('export-projects-submit-url'),
type: 'POST',
dataType: 'json',
data: {
project_ids: selectedProjects.length > 0 ? selectedProjects : [singleSelectedProject],
project_folder_ids: selectedProjectFolders
},
success: function(data) {
// Hide modal and show success flash
exportProjectsModal.modal('hide');
HelperModule.flashAlertMsg(data.flash, 'success');
},
error: function() {
HelperModule.flashAlertMsg(I18n.t('projects.export_projects.error_flash'), 'danger');
}
});
});
}
function updateSelectedCards() {
$('.project-card').removeClass('selected');
$.each(selectedProjects, function(index, value) {
let selectedCard = $('.project-card[data-id="' + value + '"]');
selectedCard.addClass('selected');
});
}
function updateProjectsToolbar() {
if (window.actionToolbarComponent) {
window.actionToolbarComponent.fetchActions({
project_ids: selectedProjects,
project_folder_ids: selectedProjectFolders
});
window.actionToolbarComponent.setReloadCallback(refreshCurrentView);
}
}
function refreshCurrentView() {
loadCardsView();
window.navigatorContainer.reloadCurrentLevel = true;
}
function initEditButton() {
function loadEditModal(url) {
$.get(url, function(result) {
$(editProjectModal).find('.modal-content').html(result.html);
$(editProjectModal).modal('show');
['project_name', 'project_folder_name'].forEach(function(inputId) {
var inputField = $('#' + inputId);
var value = inputField.val();
inputField.focus().val('').val(value);
})
$(editProjectModal).find('.selectpicker').selectpicker();
$(editProjectModal).find('form')
.on('ajax:success', function(ev, data) {
$(editProjectModal).modal('hide');
HelperModule.flashAlertMsg(data.message, 'success');
refreshCurrentView();
}).on('ajax:error', function(ev, data) {
if ($(this).hasClass('edit_project')) {
$(this).renderFormErrors('project', data.responseJSON.errors);
} else {
$(this).renderFormErrors('project_folder', data.responseJSON.errors);
}
});
});
}
$(projectsWrapper).on('click', '.edit-btn', function(ev) {
var editUrl = $(`.project-card[data-id=${selectedProjects[0]}]`).data('edit-url') ||
$(`.folder-card[data-id=${selectedProjectFolders[0]}]`).data('edit-url');
ev.stopPropagation();
ev.preventDefault();
loadEditModal(editUrl);
});
$('.projects-container').on('click', '.edit-project-btn', function(ev) {
var editUrl = $(this).attr('href');
ev.stopPropagation();
ev.preventDefault();
loadEditModal(editUrl);
});
}
function initMoveButton() {
function initializeJSTree(foldersTree) {
var timeOut = false;
foldersTree.jstree({
core: {
multiple: false,
themes: {
dots: false,
variant: 'large'
}
},
search: {
show_only_matches: true,
show_only_matches_children: true
},
plugins: ['wholerow', 'search']
});
foldersTree.on('changed.jstree', function(e, data) {
destinationFolder = data.instance.get_node(data.selected[0]).id.replace('folder_', '');
});
// Search timeout
$('#searchFolderTree').keyup(function() {
if (timeOut) { clearTimeout(timeOut); }
timeOut = setTimeout(function() {
foldersTree.jstree(true).search($('#searchFolderTree').val());
}, 250);
});
}
function loadMoveToModal(url, projectId) {
let items;
let projects;
let folders;
if (projectId) {
items = 'projects';
} else if ((selectedProjects.length) && (selectedProjectFolders.length)) {
items = 'project_and_folders';
} else if (selectedProjectFolders.length) {
items = 'folders';
} else {
items = 'projects';
}
let movables;
if (projectId) {
movables = [{
id: projectId,
type: 'project'
}];
} else {
projects = selectedProjects.map(e => ({ id: e, type: 'project' }));
folders = selectedProjectFolders.map(e => ({ id: e, type: 'project_folder' }));
movables = projects.concat(folders);
}
$.get(url, { items: items, sort: projectsCurrentSort, view_mode: $('.projects-index').data('view-mode') }, function(result) {
$(moveToModal).find('.modal-content').html(result.html);
$(moveToModal).modal('show');
initializeJSTree($(moveToModal).find('#moveToFolders'));
$('#searchFolderTree').focus();
$(moveToModal).find('form')
.on('ajax:before', function() {
$('<input>').attr({
type: 'hidden',
name: 'destination_folder_id',
value: destinationFolder
}).appendTo($(this));
$('<input>').attr({
type: 'hidden',
name: 'movables',
value: JSON.stringify(movables)
}).appendTo($(this));
})
.on('ajax:success', function(ev, data) {
$(moveToModal).modal('hide');
HelperModule.flashAlertMsg(data.flash, 'success');
refreshCurrentView();
})
.on('ajax:error', function(ev, data) {
$(moveToModal).modal('hide');
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
});
});
}
$(projectsWrapper).on('click', '.move-projects-btn', function(e) {
e.preventDefault();
loadMoveToModal($(this).data('url'), $(this).data('projectId'));
});
}
function initCardData(viewContainer, data) {
viewContainer.data('projects-cards-url', data.projects_cards_url);
viewContainer.removeClass('no-results no-data');
viewContainer.find('.card, .projects-group, .no-results-container, .no-data-container').remove();
viewContainer.append(data.cards_html);
if (viewContainer.hasClass('list')) {
viewContainer.find('.table-header').show();
}
if (viewContainer.find('.no-results-container').length) {
viewContainer.addClass('no-results');
}
if (viewContainer.find('.no-data-container').length) {
viewContainer.addClass('no-data');
viewContainer.find('.table-header').hide();
}
}
function loadCardsView() {
var requestParams = {
view_mode: $('.projects-index').data('view-mode'),
sort: projectsCurrentSort,
search: projectsViewSearch,
members: membersFilter && membersFilter.map(m => m.value),
created_on_from: createdOnFromFilter,
created_on_to: createdOnToFilter,
folders_search: lookInsideFolders,
archived_on_from: archivedOnFromFilter,
archived_on_to: archivedOnToFilter
};
var viewContainer = $(cardsWrapper);
var cardsUrl = viewContainer.data('projects-cards-url');
loadPlaceHolder($(cardsWrapper), $('#projectPlaceholder'), '.project-placeholder');
$.ajax({
url: cardsUrl,
type: 'GET',
dataType: 'json',
data: { ...requestParams, ...{ page: 1 } },
success: function(data) {
$(projectsWrapper).find('.projects-title').html(data.title_html);
initCardData(viewContainer, data);
selectedProjects.length = 0;
selectedProjectFolders.length = 0;
updateProjectsToolbar();
if (data.filtered) {
$(projectsWrapper).find('.project-list-end-placeholder').remove();
if (window.innerWidth > document.body.clientWidth) {
$($($(cardsWrapper).data('config').endOfListTemplate).html()).appendTo($(cardsWrapper));
}
} else {
InfiniteScroll.init(cardsWrapper, {
url: cardsUrl,
eventTarget: window,
placeholderTemplate: '#projectPlaceholder',
endOfListTemplate: '#projectEndOfList',
pageSize: pageSize,
lastPage: !data.next_page,
customResponse: (response) => {
$(response.cards_html).appendTo(cardsWrapper);
},
customParams: (params) => {
return { ...params, ...requestParams };
}
});
}
},
error: function() {
viewContainer.html('Error loading project list');
},
complete: function() {
updateSelectAllCheckbox();
}
});
}
function initProjectsViewModeSwitch() {
let projectsPageSelector = '.projects-index';
$('.button-to.selected').removeClass('btn-light');
$('.button-to.selected').addClass('form-dropdown-state-item');
$('.button-to.selected .view-switch-list-span').css('color', 'white');
$('.button-to.selected').removeClass('selected');
$(projectsPageSelector)
.on('ajax:success', '.change-projects-view-type-form', function(ev, data) {
$('.view-switch-btn-name').text($(ev.target).find('.button-to').text());
$(cardsWrapper).removeClass('list cards').addClass(data.cards_view_type_class);
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('form-dropdown-state-item');
$(projectsPageSelector).find('.cards-switch .button-to').addClass('btn-light');
$(projectsPageSelector).find('.cards-switch .button-to .view-switch-list-span').css('color', 'black');
$(ev.target).find('.button-to').removeClass('btn-light');
$(ev.target).find('.button-to').addClass('form-dropdown-state-item');
$(ev.target).find('.button-to .view-switch-list-span').css('color', 'white');
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
InfiniteScroll.loadMore(cardsWrapper);
})
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
});
// Active/Archived switch
// We have different sorting, filters for active/archived views.
// With turbolinks visit all those elements are updated.
$(projectsPageSelector).on('click', '.archive-switch', function() {
$(projectsPageSelector).find('.projects-container').remove();
Turbolinks.visit($(this).data('url'));
});
}
function initSorting() {
$('#sortMenuDropdown a').on('click', function() {
if (projectsCurrentSort !== $(this).data('sort')) {
$('#sortMenuDropdown a').removeClass('selected');
projectsCurrentSort = $(this).data('sort');
loadCardsView();
$(this).addClass('selected');
$('#sortMenu').dropdown('toggle');
}
});
}
function selectDate($field) {
let datePicker = $field.data('dateTimePicker');
if (datePicker && datePicker.date) {
return datePicker.date.toString();
}
return null;
}
function initProjectsFilters() {
var $filterDropdown = filterDropdown.init(filtersEnabled);
let $projectsFilter = $('.projects-index .projects-filters');
let $membersFilter = $('.members-filter', $projectsFilter);
let $foldersCB = $('#folder_search', $projectsFilter);
let $createdOnFromFilter = $('.created-on-filter .from-date', $projectsFilter);
let $createdOnToFilter = $('.created-on-filter .to-date', $projectsFilter);
let $archivedOnFromFilter = $('.archived-on-filter .from-date', $projectsFilter);
let $archivedOnToFilter = $('.archived-on-filter .to-date', $projectsFilter);
let $textFilter = $('#textSearchFilterInput', $projectsFilter);
function getFilterValues() {
createdOnFromFilter = selectDate($createdOnFromFilter);
createdOnToFilter = selectDate($createdOnToFilter);
membersFilter = dropdownSelector.getData($('.members-filter'));
lookInsideFolders = $foldersCB.prop('checked') || '';
archivedOnFromFilter = selectDate($archivedOnFromFilter) || $archivedOnFromFilter.val();
archivedOnToFilter = selectDate($archivedOnToFilter) || $archivedOnToFilter.val();
projectsViewSearch = $textFilter.val();
}
function filtersEnabled() {
getFilterValues();
return projectsViewSearch
|| createdOnFromFilter
|| createdOnToFilter
|| (membersFilter && membersFilter.length !== 0)
|| lookInsideFolders
|| archivedOnFromFilter
|| archivedOnToFilter;
}
function appliedFiltersMark() {
filterDropdown.toggleFilterMark($filterDropdown, filtersEnabled());
}
dropdownSelector.init($membersFilter, {
optionClass: 'checkbox-icon users-dropdown-list',
optionLabel: (data) => {
return `<img class="item-avatar" src="${data.params.avatar_url}"/> ${data.label}`;
},
tagLabel: (data) => {
return `<img class="item-avatar" src="${data.params.avatar_url}"/> ${data.label}`;
},
labelHTML: true,
tagClass: 'users-dropdown-list'
});
$projectsFilter.on('click', '#folderSearchInfoBtn', function(e) {
e.stopPropagation();
$('#folderSearchInfo').toggle();
});
$projectsFilter.on('click', '#folder_search', function(e) {
e.stopPropagation();
});
$filterDropdown.on('filter:apply', function() {
appliedFiltersMark();
loadCardsView();
});
// Clear filters
$filterDropdown.on('filter:clear', function() {
currentFilters = null;
dropdownSelector.clearData($membersFilter);
$createdOnFromFilter.data('dateTimePicker').clearDate();
$createdOnToFilter.data('dateTimePicker').clearDate();
$archivedOnFromFilter.data('dateTimePicker').clearDate();
$archivedOnToFilter.data('dateTimePicker').clearDate();
$foldersCB.prop('checked', false);
$textFilter.val('');
});
// Prevent filter window close
$filterDropdown.on('filter:clickBody', function() {
$('#textSearchFilterHistory').hide();
$textFilter.closest('.dropdown').removeClass('open');
dropdownSelector.closeDropdown($membersFilter);
$('#folderSearchInfo').hide();
});
appliedFiltersMark();
}
function updateSelectAllCheckbox() {
const tableWrapper = $(cardsWrapper);
const checkboxesCount = $(
'.sci-checkbox.folder-card-selector, .sci-checkbox.project-card-selector',
tableWrapper
).length;
const selectedCheckboxesCount = selectedProjects.length + selectedProjectFolders.length;
const selectAllCheckbox = $('.sci-checkbox.select-all', tableWrapper);
selectAllCheckbox.prop('indeterminate', false);
if (selectedCheckboxesCount === 0) {
selectAllCheckbox.prop('checked', false);
} else if (selectedCheckboxesCount === checkboxesCount) {
selectAllCheckbox.prop('checked', true);
} else {
selectAllCheckbox.prop('indeterminate', true);
}
}
/**
* Initializes cards view
*/
function init() {
exportProjectsModal = $('#export-projects-modal');
exportProjectsModalHeader = exportProjectsModal.find('.modal-title');
exportProjectsModalBody = exportProjectsModal.find('.modal-body');
window.initActionToolbar();
updateSelectedCards();
initNewProjectFolderModal();
initNewProjectModal();
initExportProjectsModal();
initExportProjects();
initDeleteFoldersToolbarButton();
initArchiveRestoreToolbarButtons();
initEditButton();
initMoveButton();
initSelectAllCheckbox();
initArchiveRestoreButton();
loadCardsView();
initProjectsViewModeSwitch();
initProjectsFilters();
initSorting();
AsyncDropdown.init($(projectsWrapper));
$(projectsWrapper).on('click', '.folder-card-selector', function() {
let folderCard = $(this).closest('.folder-card');
let folderId = folderCard.data('id');
let index = $.inArray(folderId, selectedProjectFolders);
// If checkbox is checked and row ID is not in list of selected folder IDs
if (this.checked && index === -1) {
selectedProjectFolders.push(folderId);
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
} else if (!this.checked && index !== -1) {
selectedProjectFolders.splice(index, 1);
}
updateSelectAllCheckbox();
updateProjectsToolbar();
});
$(projectsWrapper).on('click', '.project-card-selector', function() {
let projectCard = $(this).closest('.project-card');
let projectId = projectCard.data('id');
// Determine whether ID is in the list of selected project IDs
let index = $.inArray(projectId, selectedProjects);
// If checkbox is checked and row ID is not in list of selected project IDs
if (this.checked && index === -1) {
$(this).closest('.project-card').addClass('selected');
selectedProjects.push(projectId);
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
} else if (!this.checked && index !== -1) {
$(this).closest('.project-card').removeClass('selected');
selectedProjects.splice(index, 1);
}
updateSelectAllCheckbox();
updateProjectsToolbar();
});
}
init();
return {
loadCardsView: loadCardsView
};
}());

View file

@ -1,20 +0,0 @@
$("#content-wrapper")
.on(
"ajax:success",
function(e, data) {
if ($(e.target).is("[data-role='edit-step-form'], [data-role='new-step-form']")) {
// Get the updated_at URL
var url = $("[data-role='updated-at-label-url']").attr("data-url");
// Fetch new updated at label
$.ajax({
url: url,
type: "GET",
dataType: "json",
success: function (data2) {
$("[data-role='updated-at-refresh']").html(data2.html);
}
});
}
}
);

View file

@ -1,46 +0,0 @@
/* global dropdownSelector TinyMCE */
var ProtocolRepositoryHeader = (function() {
function initEditKeywords() {
dropdownSelector.init('#keyword-input-field', {
inputTagMode: true,
onChange: function() {
$.ajax({
url: $('#keyword-input-field').data('update-url'),
type: 'PATCH',
dataType: 'json',
data: { keywords: dropdownSelector.getValues('#keyword-input-field') },
success: function() {
dropdownSelector.highlightSuccess('#keyword-input-field');
},
error: function() {
dropdownSelector.highlightError('#keyword-input-field');
}
});
}
});
}
function initEditDescription() {
var viewObject = $('#protocol_description_view');
viewObject.on('click', function(e) {
if ($(e.target).hasClass('record-info-link') || $(e.target).parent().hasClass('record-info-link')) return;
TinyMCE.init('#protocol_description_textarea');
}).on('click', 'a', function(e) {
if ($(this).hasClass('record-info-link')) return;
e.stopPropagation();
});
}
return {
init: () => {
if ($('.protocol-repository-header').length > 0) {
initEditKeywords();
initEditDescription();
}
}
};
}());
$(document).on('turbolinks:load', function() {
ProtocolRepositoryHeader.init();
});

View file

@ -1,7 +1,8 @@
//= require protocols/import_export/import
/* eslint-disable no-use-before-define, no-underscore-dangle, max-len, no-param-reassign */
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile _ PerfectSb protocolsIO
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile
protocolFileImportModal PerfectSb protocolsIO
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
// Global variables
var ProtocolsIndex = (function() {
@ -27,10 +28,10 @@ var ProtocolsIndex = (function() {
* Initializes page
*/
function init() {
window.initActionToolbar();
window.actionToolbarComponent.setReloadCallback(reloadTable);
// window.initActionToolbar();
// window.actionToolbarComponent.setReloadCallback(reloadTable);
// make room for pagination
window.actionToolbarComponent.setBottomOffset(68);
// window.actionToolbarComponent.setBottomOffset(68);
updateButtons();
initProtocolsTable();
initKeywordFiltering();
@ -38,6 +39,7 @@ var ProtocolsIndex = (function() {
initLinkedChildrenModal();
initModals();
initVersionsModal();
initLocalFileImport();
}
function reloadTable() {
@ -267,7 +269,6 @@ var ProtocolsIndex = (function() {
let protocolFilters = $($('#protocolFilters').html());
$(protocolFilters).appendTo('.protocols-container .protocol-filters');
initLocalFileImport();
initProtocolsFilters();
initRowSelection();
},
@ -649,8 +650,6 @@ var ProtocolsIndex = (function() {
}
function updateButtons() {
window.actionToolbarComponent.fetchActions({ protocol_ids: rowsSelected });
$('.dataTables_scrollBody').css('margin-bottom', `${rowsSelected.length > 0 ? 46 : 0}px`);
}
function initLocalFileImport() {
@ -673,7 +672,6 @@ var ProtocolsIndex = (function() {
var importUrl = fileInput.attr('data-import-url');
var teamId = fileInput.attr('data-team-id');
var type = fileInput.attr('data-type');
if(ev.target.files[0].name.split('.').pop() === 'eln') {
importProtocolFromFile(
ev.target.files[0],
@ -691,14 +689,14 @@ var ProtocolsIndex = (function() {
if (nrSuccessful) {
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success');
reloadTable();
window.protocolsTable.$refs.table.updateTable();
} else {
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger');
}
}
);
} else {
protocolFileImportModal.init(ev.target.files, reloadTable);
protocolFileImportModal.init(ev.target.files, window.protocolsTable.$refs.table.updateTable());
}
// $(this).val('');
});

View file

@ -215,7 +215,7 @@ var protocolsIO = function() {
animateSpinner(modal, false);
modal.modal('hide');
HelperModule.flashAlertMsg(data.message, 'success');
ProtocolsIndex.reloadTable();
window.protocolsTable.$refs.table.updateTable();
},
error: function(data) {
showFormErrors(modal, data.responseJSON.validation_errors);

View file

@ -224,7 +224,7 @@
},
createdRow: addAttributesToRow,
initComplete: function(settings) {
initActionToolbar();
window.initActionToolbar();
actionToolbarComponent.setBottomOffset(68);
const { nTableWrapper: dataTableWrapper } = settings;

View file

@ -1,279 +0,0 @@
/* globals I18n animateSpinner HelperModule */
(function() {
'use strict';
var INVENTORY_PICKER;
var COLUMN_PICKER;
var ITEM_PICKER;
var SELECTED_IDS = {
repository_id: null,
respository_column_id: null,
repository_item_id: null
};
function clearErrors() {
var $columnsAlertSection = $('#save-PDF-to-inventory-column-warnings');
var $itemsAlertSection = $('#save-PDF-to-inventory-warnings');
$itemsAlertSection.empty();
$columnsAlertSection.empty();
}
function toggleHasFileErrorMessage(value) {
var element = $('#selectInventoryItem [value="' + value + '"]');
var $alertSection = $('#save-PDF-to-inventory-warnings');
$alertSection.empty();
if (element.data('hasfile')) {
$alertSection.append(
`<div class="alert alert-danger">
${I18n.t('projects.reports.new.save_PDF_to_inventory_modal.asset_present_warning_html')}
</div>`
);
}
}
function appendSearchResults(data) {
var items = [];
if (data.hasOwnProperty('results')) {
$.each(data.results, function(index, el) {
items.push(
{
value: el.id,
text: el.name,
disabled: false
}
);
});
}
return items;
}
function appendSearchResultsForItems(data) {
var items = [];
if (data.hasOwnProperty('results')) {
$('#selectInventoryItem').parent().find('button').attr('disabled', false);
$.each(data.results, function(index, el) {
items.push(
{
value: el.id,
text: el.name,
disabled: false,
data: {
hasFile: el.hasOwnProperty('has_file_attached') ? el.has_file_attached : null
}
}
);
});
} else {
$('#selectInventoryItem').parent().find('button').attr('disabled', true);
clearErrors();
$('#save-PDF-to-inventory-warnings').append(
`<strong class="danger">${data.no_items}</strong>`
);
}
return items;
}
function appendSearchResultsForColumns(data) {
var items = [];
if (data.hasOwnProperty('results')) {
$('#selectInventoryColumn').parent().find('button').attr('disabled', false);
$.each(data.results, function(index, el) {
items.push(
{
value: el.id,
text: el.name,
disabled: false
}
);
});
} else {
$('#selectInventoryColumn').parent().find('button').attr('disabled', true);
clearErrors();
$('#save-PDF-to-inventory-column-warnings').append(
`<div class="save-PDF-to-inventory-alerts"><strong class="danger">${data.no_items}</strong></div>`
);
}
return items;
}
function submitButtonEnableToggle(status) {
var button = $('#savePDFtoInventorySubmit');
if (status) {
button.attr('disabled', false);
} else {
button.attr('disabled', true);
}
}
function deselect(element) {
if (element) {
element.selectpicker('val', null);
element.selectpicker('render');
element.attr('disabled', true);
}
}
// triggers first request and cleans the dropdown selectpicker
function clearDropdownResultsCallback(element) {
element.parent().find('button').on('click', function() {
$(this).parent().find('input').trigger('keyup');
});
}
function initInventoryItemSelectPicker() {
ITEM_PICKER = $('#selectInventoryItem')
.attr('disabled', false)
.selectpicker({ liveSearch: true })
.ajaxSelectPicker({
ajax: {
url: $('#selectInventoryItem').data('target-url'),
type: 'POST',
dataType: 'json',
data: function() {
return {
q: '{{{q}}}',
repository_id: SELECTED_IDS.repository_id,
repository_column_id: SELECTED_IDS.respository_column_id
};
}
},
locale: {
emptyTitle: I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected')
},
preprocessData: appendSearchResultsForItems,
emptyRequest: true,
clearOnEmpty: true,
cache: false,
preserveSelected: false
})
.on('change.bs.select', function(el) {
var value = el.target.value;
toggleHasFileErrorMessage(value);
submitButtonEnableToggle(true);
SELECTED_IDS.repository_item_id = value;
});
clearDropdownResultsCallback(ITEM_PICKER);
}
function initInventoryColumnSelectPicker() {
COLUMN_PICKER = $('#selectInventoryColumn')
.attr('disabled', false)
.selectpicker({ liveSearch: true })
.ajaxSelectPicker({
ajax: {
url: $('#selectInventoryColumn').data('target-url'),
type: 'POST',
dataType: 'json',
data: function() {
return {
q: '{{{q}}}',
repository_id: SELECTED_IDS.repository_id
};
}
},
locale: {
emptyTitle: I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected')
},
preprocessData: appendSearchResultsForColumns,
emptyRequest: true,
clearOnEmpty: true,
cache: false,
preserveSelected: false
})
.on('change.bs.select', function(el) {
SELECTED_IDS.respository_column_id = el.target.value;
deselect(ITEM_PICKER);
submitButtonEnableToggle(false);
initInventoryItemSelectPicker();
// refresh the dropdown state
$('#selectInventoryItem').parent().find('input').trigger('keyup');
clearErrors();
});
clearDropdownResultsCallback(COLUMN_PICKER);
}
function initInventoriesSelectPicker() {
INVENTORY_PICKER = $('#selectInventory')
.selectpicker({ liveSearch: true })
.ajaxSelectPicker({
ajax: {
url: $('#selectInventory').data('target-url'),
type: 'POST',
dataType: 'json',
data: function() {
return { q: '{{{q}}}' };
}
},
locale: {
emptyTitle: I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected')
},
preprocessData: appendSearchResults,
emptyRequest: true,
clearOnEmpty: false,
cache: false,
preserveSelected: false
}).on('change.bs.select', function(el) {
SELECTED_IDS.repository_id = el.target.value;
deselect(COLUMN_PICKER);
deselect(ITEM_PICKER);
submitButtonEnableToggle(false);
initInventoryColumnSelectPicker();
clearErrors();
// refresh the dropdown state
$('#selectInventoryColumn').parent().find('input').trigger('keyup');
});
clearDropdownResultsCallback(INVENTORY_PICKER);
}
$('#content-reports-index').on('click', '#savePDFtoInventorySubmit', function() {
animateSpinner();
$.ajax({
url: $('#savePDFtoInventorySubmit').data('target-url'),
data: SELECTED_IDS,
type: 'POST',
success: function(data) {
animateSpinner(null, false);
HelperModule.flashAlertMsg(data.message, 'success');
$('#savePDFtoInventory').modal('hide');
},
error: function(xhr) {
animateSpinner(null, false);
HelperModule.flashAlertMsg(xhr.responseJSON.message, 'danger');
$('#savePDFtoInventory').modal('hide');
}
});
});
/*
* INITIALIZERS
*/
function initializeSavePDFtoInventoryModal() {
$('#content-reports-index').on('shown.bs.modal', '#savePDFtoInventory', function() {
initInventoriesSelectPicker();
clearErrors();
// refresh the dropdown state
$('#selectInventory').parent().find('input').trigger('keyup');
}).on('hidden.bs.modal', function() {
// clear ids
SELECTED_IDS = {
repository_id: null,
respository_column_id: null,
repository_item_id: null
};
// clear select picker objects
if (COLUMN_PICKER) {
deselect(COLUMN_PICKER);
}
if (ITEM_PICKER) {
deselect(ITEM_PICKER);
}
submitButtonEnableToggle(false);
});
}
initializeSavePDFtoInventoryModal();
}());

View file

@ -1,190 +0,0 @@
/* global I18n animateSpinner HelperModule
DataTableHelpers DataTableCheckboxes notTurbolinksPreview */
/* eslint-disable no-param-reassign */
(function() {
'use strict';
var REPOSITORIES_TABLE;
var CHECKBOX_SELECTOR;
function updateActionButtons() {
if (window.actionToolbarComponent) {
window.actionToolbarComponent.fetchActions({ repository_ids: CHECKBOX_SELECTOR.selectedRows });
$('.dataTables_scrollBody').css('padding-bottom', `${CHECKBOX_SELECTOR.selectedRows.length > 0 ? 68 : 0}px`);
}
var rowsCount = CHECKBOX_SELECTOR.selectedRows.length;
var row;
$('#renameRepoBtn').attr('href', '#');
$('#deleteRepoBtn').attr('href', '#');
$('#copyRepoBtn').attr('href', '#');
switch (rowsCount) {
case 0:
$('.main-actions [data-action-mode="single"]').addClass('disabled hidden');
$('.main-actions [data-action-mode="multiple"]').addClass('disabled hidden');
break;
case 1:
row = $('#repositoriesList').find('tr#' + CHECKBOX_SELECTOR.selectedRows[0]);
$('.main-actions [data-action-mode="single"]').removeClass('disabled hidden');
$('.main-actions [data-action-mode="multiple"]').removeClass('disabled hidden');
$('#renameRepoBtn').attr('href', row.data('rename-modal-url'));
$('#deleteRepoBtn').attr('href', row.data('delete-modal-url'));
$('#copyRepoBtn').attr('href', row.data('copy-modal-url'));
break;
default:
$('.main-actions [data-action-mode="single"]').removeClass('hidden').addClass('disabled');
$('.main-actions [data-action-mode="multiple"]').removeClass('disabled hidden');
}
}
function initRepositoriesDataTable(tableContainer, archived = false) {
var tableTemplate = $('#RepositoriesListTable').html();
$.get($(tableTemplate).data('source'), { archived: archived }, function(data) {
if (REPOSITORIES_TABLE) REPOSITORIES_TABLE.destroy();
CHECKBOX_SELECTOR = null;
$('.content-body').html(tableTemplate);
REPOSITORIES_TABLE = $(tableContainer).DataTable({
aaData: data,
dom: "R<'main-actions hidden'<'toolbar'><'filter-container'f>>t<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
processing: true,
stateSave: true,
pageLength: 25,
colReorder: {
enable: false
},
sScrollX: '100%',
sScrollXInner: '100%',
order: [[1, 'asc']],
destroy: true,
language: {
emptyTable: archived ? I18n.t('repositories.index.no_archived_inventories') : I18n.t('repositories.index.no_inventories'),
zeroRecords: archived ? I18n.t('repositories.index.no_archived_inventories_matched') : I18n.t('repositories.index.no_inventories_matched')
},
columnDefs: [{
targets: 0,
visible: !$('.repositories-index').data('readonly'),
searchable: false,
orderable: false,
render: function() {
return `<div class="sci-checkbox-container">
<input class='repository-row-selector sci-checkbox' type='checkbox' data-e2e="e2e-CB-inventories-all">
<span class='sci-checkbox-label'></span>
</div>`;
}
}, {
targets: 1,
className: 'item-name',
render: function(value, type, row) {
return `<a href="${row.repositoryUrl}" data-e2e="e2e-TL-inventories-Inventory-${row.DT_RowId}">${value}</a>`;
}
}, {
targets: 5,
render: {
_: 'display',
sort: 'sort'
}
}, {
targets: 7,
visible: archived,
render: {
_: 'display',
sort: 'sort'
}
}, {
targets: 8,
visible: archived
},
{
visible: true,
searchable: false,
data: 'stock',
render: {
_: 'display',
sort: 'sort'
}
}],
fnInitComplete: function(e) {
initActionToolbar();
window.actionToolbarComponent.setReloadCallback(() =>
initRepositoriesDataTable('#repositoriesList', archived));
window.actionToolbarComponent.setBottomOffset(68);
var dataTableWrapper = $(e.nTableWrapper);
CHECKBOX_SELECTOR = new DataTableCheckboxes(dataTableWrapper, {
checkboxSelector: '.repository-row-selector',
selectAllSelector: '.select-all-checkbox',
onChanged: function() {
updateActionButtons();
}
});
updateActionButtons();
DataTableHelpers.initLengthAppearance(dataTableWrapper);
DataTableHelpers.initSearchField(dataTableWrapper, I18n.t('repositories.index.filter_inventory'));
$('.content-body .toolbar').html($('#repositoriesListButtons').html());
dataTableWrapper.find('.main-actions, .pagination-row').removeClass('hidden');
$('#createRepoBtn').initSubmitModal('#create-repo-modal', 'repository');
$('#deleteRepoBtn').initSubmitModal('#delete-repo-modal', 'repository');
$('#renameRepoBtn').initSubmitModal('#rename-repo-modal', 'repository');
$('#copyRepoBtn').initSubmitModal('#copy-repo-modal', 'repository');
},
drawCallback: function() {
if (CHECKBOX_SELECTOR) CHECKBOX_SELECTOR.checkSelectAllStatus();
},
rowCallback: function(row) {
let $row = $(row);
let checkbox = $row.find('.repository-row-selector');
if ($row.attr('data-shared') === 'true') {
checkbox.attr('disabled', 'disabled');
}
if (CHECKBOX_SELECTOR) CHECKBOX_SELECTOR.checkRowStatus(row);
},
stateLoadParams: function(_, state) {
state.search.search = '';
}
});
});
}
$('#wrapper').on('sideBar::hidden sideBar::shown', function() {
if (REPOSITORIES_TABLE) {
REPOSITORIES_TABLE.columns.adjust();
}
});
$(document).on('shown.bs.modal', function() {
var inputField = $('#repository_name');
var value = inputField.val();
inputField.focus().val('').val(value);
}).on('shown.bs.modal', '#export-repositories-modal', function() {
if (!CHECKBOX_SELECTOR) return;
const exportButton = $(this).find('#export-repositories-modal-submit');
const exportURL = exportButton.data('export-url');
exportButton.on('click', function() {
$.ajax({
url: exportURL,
method: 'POST',
data: { repository_ids: CHECKBOX_SELECTOR.selectedRows },
success: function(data) {
HelperModule.flashAlertMsg(data.message, 'success');
CHECKBOX_SELECTOR.clearSelection();
$('#export-repositories-modal').modal('hide');
},
error: function(data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
}
});
});
});
$('.create-new-repository').initSubmitModal('#create-repo-modal', 'repository');
if (notTurbolinksPreview()) {
initRepositoriesDataTable('#repositoriesList', $('.repositories-index').hasClass('archived'));
}
}());

View file

@ -162,9 +162,9 @@ $.fn.dataTable.render.defaultRepositoryNumberValue = function() {
return '';
};
$.fn.dataTable.render.RepositoryNumberValue = function(data) {
$.fn.dataTable.render.RepositoryNumberValue = function (data) {
return `<span class="number-value" data-value="${data.value}">
${data.value.toLocaleString('en-US', { timeZone: 'UTC' })}
${data.value}
</span>`;
};

View file

@ -406,35 +406,6 @@ var RepositoryDatatable = (function(global) {
});
}
function initRepositoryViewSwitcher() {
const viewSwitch = $('.view-switch');
const repositoryShow = $('.repository-show');
const stateViewSwitchBtnName = $('.state-view-switch-btn-name');
const selectedSwitchOptionClass = 'form-dropdown-state-item prevent-shrink';
function switchView(event, activeClass, inactiveClass) {
event.preventDefault();
event.stopPropagation();
repositoryShow.removeClass(inactiveClass).addClass(activeClass);
$(`.view-switch-${inactiveClass} a`).removeClass(selectedSwitchOptionClass);
$(`.view-switch-${activeClass} a`).addClass(selectedSwitchOptionClass);
stateViewSwitchBtnName.text($(`.view-switch-${activeClass}`).text());
viewSwitch.removeClass('open');
RepositoryDatatable.reload();
}
viewSwitch.on('click', '.view-switch-archived', function(event) {
switchView(event, 'archived', 'active');
});
viewSwitch.on('click', '.view-switch-active', function(event) {
switchView(event, 'active', 'archived');
});
}
function initExportActions() {
const exportModal = $('#exportRepositoryRowsModal');
$(document).on('click', '#exportRepositoryRowsButton', (e) => {
@ -825,7 +796,7 @@ var RepositoryDatatable = (function(global) {
initSaveButton();
initCancelButton();
initBSTooltips();
initRepositoryViewSwitcher();
window.initRepositoryStateMenu();
DataTableHelpers.initLengthAppearance($(TABLE_ID).closest('.dataTables_wrapper'));
$('.dataTables_wrapper').on('click', '.pagination', () => {

View file

@ -1,7 +1,7 @@
/* global HelperModule PerfectScrollbar */
// eslint-disable-next-line no-unused-vars
const ShareModal = (function() {
// eslint-disable-next-line func-names
window.ShareModal = (function() {
function init() {
var form = $('.share-repo-modal').find('form');
var sharedCBs = form.find("input[name='share_team_ids[]']");

View file

@ -17,6 +17,8 @@
var formGroup = $('#form-records-file').find('.form-group');
formGroup.addClass('has-error');
formGroup.find('.help-block').remove();
$('#form-records-file input[type="submit"]').removeAttr('disabled');
$('#parse-sheet-loader').addClass('hidden');
formGroup.append(
'<span class="help-block">' + XHR.responseJSON.message + '</span>'
);
@ -24,6 +26,8 @@
}
function handleSuccessfulSubmit(data) {
$('#form-records-file input[type="submit"]').removeAttr('disabled');
$('#parse-sheet-loader').addClass('hidden');
$('#modal-import-records').modal('hide');
$(data.html).appendTo('body').promise().done(function() {
$('#parse-records-modal').modal('show');
@ -54,6 +58,8 @@
submitBtn.on('click', function(event) {
var data = new FormData();
submitBtn.attr('disabled', true);
$('#parse-sheet-loader').removeClass('hidden');
event.preventDefault();
event.stopPropagation();
data.append('file', document.getElementById('file').files[0]);

View file

@ -31,6 +31,7 @@ var CommentsSidebar = (function() {
// Replace the number in comment element
commentsCounter.text(commentsCounter.text().replace(/[\d\\+]+/g, commentsAmount));
commentsCounter.removeClass('hidden');
commentsCounter.css('display', 'flex');
}
}

View file

@ -3,6 +3,8 @@ const GLOBAL_CONSTANTS = {
NAME_MAX_LENGTH: <%= Constants::NAME_MAX_LENGTH %>,
NAME_MIN_LENGTH: <%= Constants::NAME_MIN_LENGTH %>,
TEXT_MAX_LENGTH: <%= Constants::TEXT_MAX_LENGTH %>,
TABLE_CARD_MIN_WIDTH: 340, <%# pixels %>
TABLE_CARD_GAP: 16, <%# pixels %>
FILENAME_TRUNCATION_LENGTH: <%= Constants::FILENAME_TRUNCATION_LENGTH %>,
FILE_MAX_SIZE_MB: parseInt($('meta[name="max-file-size"]').attr('content'), 10),
IS_SAFARI: /^((?!chrome|android).)*safari/i.test(navigator.userAgent),

View file

@ -716,15 +716,16 @@ var dropdownSelector = (function() {
// Add timeout for deleting animation
setTimeout(() => {
if (selector.data('combine-tags')) {
const $selector = $(selector);
if ($selector.data('combine-tags')) {
// if we use combine-tags options we simply clear all values
container.find('.data-field').val('[]');
updateTags(selector, container);
updateTags($selector, container);
} else {
// Or delete specific one
deleteValue(selector, container, tagLabel.data('ds-tag-id'), tagLabel.data('ds-tag-group'));
if (selector.data('config').tagClass) {
removeOptionFromSelector(selector, tagLabel.data('ds-tag-id'));
deleteValue($selector, container, tagLabel.data('ds-tag-id'), tagLabel.data('ds-tag-group'));
if ($selector.data('config').tagClass) {
removeOptionFromSelector($selector, tagLabel.data('ds-tag-id'));
}
}
}, 350);

View file

@ -320,13 +320,14 @@ $(document).on('click', '.gene-sequence-edit-button', function() {
});
function initMarvinJs() {
if (typeof (ChemicalizeMarvinJs) === 'undefined') {
setTimeout(initMarvinJs, 100);
return;
}
MarvinJsEditor = MarvinJsEditorApi();
if (MarvinJsEditor.enabled()) {
if (typeof (ChemicalizeMarvinJs) === 'undefined') {
setTimeout(initMarvinJs, 100);
return;
}
if ($('#marvinjs-editor')[0].dataset.marvinjsMode === 'remote') {
ChemicalizeMarvinJs.createEditor('#marvinjs-sketch').then(function(marvin) {
marvin.setDisplaySettings({ toolbars: 'reporting' });

View file

@ -135,3 +135,30 @@ $.fn.initSubmitModal = function(modalID, modelName) {
})
.animateSpinner();
};
/**
* Wraps tables in HTML with a specified wrapper.
* @param {string || Element} htmlStringOrDomEl - HTML containing tables to be wrapped.
* @returns {string} - HTML with tables wrapped.
*/
function wrapTables(htmlStringOrDomEl) {
if (typeof htmlStringOrDomEl === 'string') {
const container = $(`<span class="text-base">${htmlStringOrDomEl}</span>`);
container.find('table').toArray().forEach((table) => {
if ($(table).parent().hasClass('table-wrapper')) return;
$(table).css('float', 'none').wrapAll(`
<div class="table-wrapper" style="overflow: auto; width: 100%"></div>
`);
});
return container.prop('outerHTML');
}
// Check if the value is a DOM element
if (htmlStringOrDomEl instanceof Element) {
const tableElement = $(htmlStringOrDomEl).find('table');
if (tableElement.length > 0) {
tableElement.wrap('<div class="table-wrapper" style="overflow: auto; width: 100%"></div>');
const updatedHtml = $(htmlStringOrDomEl).html();
$(htmlStringOrDomEl).replaceWith(updatedHtml);
}
}
}

View file

@ -1,4 +0,0 @@
(function() {
const formErrors = $('#form-error-data').data('form-errors');
$('form').renderFormErrors('team', formErrors, false);
}());

View file

@ -81,6 +81,7 @@
@import "shared/action_toolbar";
@import "shared/assets";
@import "shared/avatar";
@import "shared/ag_table";
@import "shared/cards";
@import "shared/comments_sidebar";
@import "shared/comments";
@ -111,6 +112,7 @@
@import "themes/repositories";
@import "themes/scinote";
@import "navigation/general";
@import "navigation/breadcrumbs";
@import "navigation/left_menu";

View file

@ -51,3 +51,12 @@ html {
opacity: 1;
bottom: 5px;
}
/* Hide caret in Safari */
#relationships-section summary::-webkit-details-marker {
display: none;
}
.ag-theme-alpine {
--ag-font-family: "SN Inter", "Open Sans", Arial, Helvetica, sans-serif !important;
}

View file

@ -2,14 +2,6 @@
#canvas-container,
#module-archive {
.experimnet-name {
max-width: calc(100% - 2rem);
.fas {
margin-right: .5em;
}
}
.toolbar {
.left {
align-items: center;

View file

@ -3,14 +3,6 @@
#experimentTable,
#experiment-canvas,
#module-archive {
.experimnet-name {
max-width: calc(100% - 2rem);
.fas {
margin-right: .5em;
}
}
.toolbar {
align-items: center;
column-gap: .5rem;

View file

@ -20,567 +20,4 @@
}
}
}
.experiment-table-container {
height: calc(100vh - var(--content-header-size) - var(--navbar-height) - var(--toolbar-height));
overflow: auto;
}
.toolbar-row {
align-items: center;
display: flex;
height: var(--toolbar-height);
justify-content: space-between;
.toolbar-left-block {
align-items: center;
display: flex;
.btn {
margin-right: .25em;
}
}
.header-actions {
&.experiment-header {
column-gap: .25em;
}
.sort-task-menu {
&:not(.archived) {
[data-view-mode="archived"] {
display: none;
}
}
}
}
.view-switch-button {
outline: 1px solid $color-alto;
}
.view-switch,
.filter-container {
display: inline-block;
}
.view-switch {
margin-left: auto;
.caret {
margin: 8px 0 8px 8px;
}
&.open {
.caret {
transform: rotateX(180deg);
}
.sn-icon-down {
transform: rotateX(180deg);
}
}
.dropdown-menu {
@include font-button;
min-width: 100%;
padding: 0;
.divider-label {
@include font-small;
color: $color-silver-chalice;
padding: .25em 1em;
}
.divider {
margin: 0;
}
li {
cursor: pointer;
padding: .5em 1em;
white-space: nowrap;
.button-icon {
margin-right: .5em;
}
&:hover:not(.divider-label) {
background: $color-concrete;
}
.btn {
height: 36px;
}
a {
display: inline-block;
margin: -1em;
padding: .5em 1em;
width: calc(100% + 2em);
&.selected::after {
@include font-awesome;
content: $font-fas-check;
margin-left: auto;
position: absolute;
right: 1em;
}
}
}
}
.cards-switch {
&.active::after {
@include font-awesome;
content: "\f00c";
position: absolute;
right: 1em;
}
}
}
.toolbar-right-block {
margin-left: auto;
}
}
.experiment-table {
display: grid;
grid-auto-rows: 3em 1px;
grid-template-columns: max-content repeat(calc(var(--columns-count)), minmax(100px, auto)) max-content;
min-width: 100%;
.table-header-cell {
align-items: center;
background-color: $color-concrete;
border: 1px solid $color-white;
display: flex;
height: 3em;
padding: 0 .5em;
position: sticky;
position: -webkit-sticky;
top: 0;
z-index: 7;
&.select-all-checkboxes {
justify-content: center;
}
.fa-comment {
color: $color-silver-chalice;
}
}
.table-header {
display: contents;
height: 10px;
&::after {
content: "";
grid-column: 1/-1;
}
}
.table-body {
display: contents;
.table-body-cell {
display: flex;
}
.table-body-cell:nth-child(1) {
justify-content: center;
}
}
.loading-overlay {
display: none;
}
.table-row-provisioning {
.loading-overlay {
display: block;
}
.sci-checkbox-container {
height: 1.5em;
width: 1.5em;
.loading-overlay::after {
background-size: 1.5em;
cursor: default;
}
.sci-checkbox,
.sci-checkbox-label {
display: none;
}
}
}
.table-body-cell {
align-items: center;
display: flex;
padding: 0 .5em;
.my-module-users-link {
color: $color-silver-chalice;
&:hover {
text-decoration: none;
}
}
.global-avatar-container {
color: $color-silver-chalice;
height: 2em;
line-height: 2em;
margin-right: .25em;
width: 2em;
}
.more-users {
background: $color-volcano;
border-radius: 50%;
color: $color-white;
height: 2em;
line-height: 2em;
margin-right: .25em;
text-align: center;
text-decoration: none;
width: 2em;
}
.new-user {
background: $color-concrete;
text-align: center;
}
}
.archived-column {
display: none;
}
.comments-column .disabled {
color: $color-silver-chalice;
}
.table-row {
display: contents;
&::after {
background: $color-concrete;
content: "";
display: inline-block;
grid-column: 1/-1;
height: 1px;
}
}
.open-my-module-menu:focus {
box-shadow: 0 0 0 1px $brand-focus;
}
.assign-users-dropdown {
.dropdown-menu {
padding: .5em;
width: 280px;
}
.users-list {
max-height: 300px;
overflow: auto;
}
.user-container {
align-items: center;
display: flex;
padding: .5em;
.user-avatar {
padding: 0 .75em;
&.archived {
padding-left: 0;
}
img {
border-radius: 50%;
width: 24px;
}
}
}
.assigned-users-container {
cursor: pointer;
display: flex;
}
.avatar-container {
align-items: center;
border: 1px solid $color-white;
border-radius: 50%;
display: inline-flex;
height: 26px;
justify-content: center;
margin-right: -5px;
width: 26px;
img {
border-radius: 50%;
max-height: 100%;
max-width: 100%;
}
}
.more-users {
font-size: 10px;
line-height: 24px;
}
.new-user {
color: var(--sn-black);
line-height: 24px;
&:not(:first-child) {
margin-left: 5px;
}
}
}
.my-module-status {
color: $color-white;
display: inline-block;
margin: 3px 0;
max-width: 100%;
overflow: hidden;
padding: 2px 8px;
text-overflow: ellipsis;
white-space: nowrap;
&.status-light {
@include not-started;
}
}
.table-row-placeholder-divider {
background: $color-concrete;
display: inline-block;
grid-column: 1/-1;
height: 1px;
}
.table-row-placeholder {
align-items: center;
background-color: $color-white;
border-radius: $border-radius-default;
box-shadow: $flyout-shadow;
display: grid;
grid-column: 1 / -1;
grid-template-columns: 32px repeat(9, minmax(max-content, auto));
.placeholder-cell {
animation-duration: 2s;
animation-iteration-count: infinite;
animation-name: placeholder-pulsing;
background-color: $color-alto;
border-radius: $border-radius-default;
display: block;
height: 18px;
margin: auto;
width: 90%;
&.circle-0 {
border-radius: 100%;
height: 24px;
width: 24px;
}
@keyframes placeholder-pulsing {
0% {
opacity: 1;
}
50% {
opacity: .5;
}
100% {
opacity: 1;
}
}
}
}
&.last-page {
padding-bottom: 5em;
position: relative;
&.no-data {
padding-bottom: 0;
}
}
.experiment-table-list-end-placeholder {
align-items: center;
background-color: $color-concrete;
bottom: 1em;
display: flex;
height: 3em;
left: calc(50% - 150px);
margin: 0 auto;
padding: 1em;
position: absolute;
width: 300px;
> * {
flex-grow: 1;
text-align: center;
}
}
}
.unseen-comments {
align-items: center;
background-color: var(--sn-science-blue);
border: 1px solid $color-white;
border-radius: 50%;
color: var(--sn-white);
display: flex;
font-weight: bold;
font-size: 11px;
height: 16px;
justify-content: center;
margin: -4px -14px 0 0;
min-width: 16px;
right: 0;
position: absolute;
top: 0;
}
.datetime-container {
width: 100%;
.clear-date {
cursor: pointer;
left: calc(100% - 16px);
margin-left: auto;
opacity: 0;
text-align: center;
width: 24px;
}
&:hover .clear-date {
opacity: 1;
}
.date-text {
.alert-yellow {
color: $brand-warning;
margin-left: 4px;
}
.alert-red {
color: $brand-danger;
margin-left: 4px;
}
}
.datetime-picker-container {
left: 0;
position: absolute;
top: 0;
width: calc(100% - 24px);
.calendar-due-date {
opacity: 0;
}
}
}
.open-comments-sidebar {
margin-bottom: 0;
position: relative;
}
&.archived {
.table-body-cell {
background-color: $color-concrete;
}
.archived-column {
display: flex;
}
}
.task_name-column {
a {
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
display: -webkit-box;
-webkit-line-clamp: 2;
}
span {
color: $color-silver-chalice;
}
}
.no-data-container {
display: none;
}
}
.table-display-modal {
.modal-description,
.column-container {
font-size: 14px;
}
.column-container {
align-items: center;
border-bottom: $border-default;
display: flex;
padding: .5em 1.4em;
&:not(.visible) {
color: $color-alto;
}
&:last-child {
border: 0;
}
.fas {
cursor: pointer;
margin-right: 1em;
&.disabled {
color: $color-alto;
pointer-events: none;
}
}
.sn-icon {
cursor: pointer;
margin-right: .6em;
&.disabled {
cursor: not-allowed;
}
}
}
}
@media (max-width: 1000px) {
.toolbar-row {
.button-text {
display: none;
}
}
}

View file

@ -102,4 +102,10 @@
.shareable-link-disclaimer {
color: var(--sn-grey);
}
.sci-input-container-v2 {
textarea::placeholder {
color: var(--sn-grey);
}
}
}

View file

@ -197,6 +197,7 @@
.repository-name-container {
display: flex;
align-items: center;
}
.repository-title {

View file

@ -40,6 +40,10 @@ body.navigator-collapsed {
.sci--layout--navigator-open {
display: inline-block;
+ h1 {
max-width: calc(100% - 2.5rem);
}
}
}

View file

@ -1,6 +1,6 @@
.sci--layout-navigation-navigator {
.handle-mr {
cursor: url("/images/icon_small/resize_cursor.svg") 0 0, auto !important;
cursor: col-resize;
display: block !important;
height: 100%;
opacity: 0;

View file

@ -4,14 +4,6 @@
.sci--navigation--notificaitons-flyout-container {
position: relative;
.sci--navigation--notificaitons-flyout-backdrop {
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
}
.has-unseen {
&::after {
align-items: center;
@ -31,99 +23,94 @@
white-space: nowrap;
}
}
}
.sci--navigation--notificaitons-flyout {
background-color: $color-white;
border-radius: 0 0 $border-radius-default $border-radius-default;
box-shadow: $flyout-shadow;
display: flex;
flex-direction: column;
height: calc(100vh - var(--top-navigation-height) - 2em);
height: calc(100vh - 8rem);
padding: 1.5rem;
position: absolute;
right: 0;
top: calc(var(--top-navigation-height) - 1.5rem);
width: 400px;
.sci--navigation--notificaitons-flyout-title {
@include font-h2;
.sci--navigation--notificaitons-flyout-title {
@include font-h2;
align-items: center;
display: flex;
margin-bottom: .625rem;
.sn-icon {
@include font-button;
cursor: pointer;
margin-left: auto;
}
}
hr {
margin: .625rem 0;
}
.sci-navigation--notificaitons-flyout-subtitle {
@include font-main;
line-height: 2.25rem;
margin-bottom: .625rem;
}
.sci--navigation--notificaitons-flyout-notifications {
margin-left: -1.5rem;
overscroll-behavior: contain;
padding: 0 1.5rem;
position: relative;
width: calc(100% + 3rem);
.next-page-loader {
align-items: center;
display: flex;
margin-bottom: .625rem;
justify-content: center;
margin: 1rem 0;
}
}
.sn-icon {
@include font-button;
.sci-navigation--notificaitons-flyout-notification {
border-bottom: $border-tertiary;
padding: 1rem 0;
.sci-navigation--notificaitons-flyout-notification-icon {
align-items: center;
background-color: $brand-primary;
border-radius: 50%;
color: $color-white;
display: flex;
grid-row: 1 / 5;
height: 2rem;
justify-content: center;
margin-right: .75rem;
width: 2rem;
&.deliver {
background-color: $brand-warning;
}
&.system {
background-color: $brand-success;
}
}
.sci-navigation--notificaitons-flyout-notification-date {
@include font-small;
color: $color-silver-chalice;
}
.sci-navigation--notificaitons-flyout-notification-title {
margin: .25rem 0;
&:not([data-seen="true"]) {
font-weight: bold;
}
}
.sci-navigation--notificaitons-flyout-notification-message {
&[data-notification="system"] {
cursor: pointer;
margin-left: auto;
}
}
hr {
margin: .625rem 0;
}
.sci-navigation--notificaitons-flyout-subtitle {
@include font-main;
line-height: 2.25rem;
margin-bottom: .625rem;
}
.sci--navigation--notificaitons-flyout-notifications {
margin-left: -1.5rem;
padding: 0 1.5rem;
position: relative;
width: calc(100% + 3rem);
.next-page-loader {
align-items: center;
display: flex;
justify-content: center;
margin: 1rem 0;
}
}
.sci-navigation--notificaitons-flyout-notification {
border-bottom: $border-tertiary;
padding: 1rem 0;
.sci-navigation--notificaitons-flyout-notification-icon {
align-items: center;
background-color: $brand-primary;
border-radius: 50%;
color: $color-white;
display: flex;
grid-row: 1 / 5;
height: 2rem;
justify-content: center;
margin-right: .75rem;
width: 2rem;
&.deliver {
background-color: $brand-warning;
}
&.system {
background-color: $brand-success;
}
}
.sci-navigation--notificaitons-flyout-notification-date {
@include font-small;
color: $color-silver-chalice;
}
.sci-navigation--notificaitons-flyout-notification-title {
margin: .25rem 0;
&:not([data-seen="true"]) {
font-weight: bold;
}
}
.sci-navigation--notificaitons-flyout-notification-message {
&[data-notification="system"] {
cursor: pointer;
}
}
}
}

View file

@ -54,7 +54,7 @@
.view-mode {
border: 1px solid transparent;
cursor: url("/images/icon_small/edit.svg") 0 16, auto;
cursor: text;
height: 2rem;
min-height: 1.5rem;
overflow: hidden;

View file

@ -568,10 +568,6 @@ li.module-hover {
// New projects page
.projects-index {
.content-header {
height: var(--content-header-size);
}
.project-users-list {
hr {
margin: .5em 0;

View file

@ -52,9 +52,15 @@
min-height: 300px;
padding: 0;
.dropdown-selector,
.users-filter-dropdown {
overflow: hidden;
.dropdown-selector {
.ps__rail-x,
.ps__rail-x::after,
.ps__rail-x::before,
.ps__rail-y,
.ps__rail-y::after,
.ps__rail-y::before {
background-color: transparent !important;
}
}
.header {

View file

@ -256,6 +256,12 @@
.dropdown-selector-container {
width: 150px;
.dropdown-container {
left: 0 !important;
margin: 0 !important;
position: absolute !important;
}
.emoji-status {
.emoji {
height: 24px;

View file

@ -0,0 +1,41 @@
.ag-root-wrapper {
--agg-row-border-color: var(--sn-light-grey);
--ag-odd-row-background-color: var(--sn-white);
--ag-header-background-color: var(--sn-light-grey);
--ag-selected-row-background-color: var(--sn-super-light-blue);
--ag-range-selection-border: var(--sn-science-blue);
--ag-grid-size: .5rem;
--ag-row-hover-color: var(--sn-super-light-grey);
--ag-header-column-resize-handle-height: 1rem;
--ag-header-column-resize-handle-color: var(--sn-grey);
--ag-header-column-resize-handle-width: 1px;
--ag-row-border-width: 1px;
--ag-icon-font-code-checkbox-unchecked: asset-url("checkbox/default.svg");
--ag-icon-font-code-checkbox-checked: asset-url("checkbox/checked.svg");
--ag-icon-font-code-checkbox-indeterminate: asset-url("checkbox/indeterminate.svg");
--ag-input-focus-box-shadow: none;
--ag-cell-horizontal-padding: .75rem;
border: 0;
.ag-cell {
border: 0;
}
.ag-header {
border-bottom: 0;
border-radius: .25rem .25rem 0 0;
}
.ag-input-field-input {
cursor: pointer;
}
.ag-header-cell-resize {
width: 1rem;
}
.ag-input-field-input:focus {
outline: none !important;
outline-offset: 0 !important;
}
}

View file

@ -15,6 +15,10 @@
width: var(--comments-sidebar-width);
}
.atwho-user-container {
white-space: normal;
}
.sidebar-content {
background: $color-concrete;
border-left: 1px solid $color-white;
@ -168,6 +172,10 @@
color: $color-volcano;
padding: .5em 1em;
}
.atwho-user-container {
white-space: wrap;
}
}
.comment-footer {

View file

@ -2,7 +2,7 @@
// scss-lint:disable NestingDepth QualifyingElement
.content-pane {
--content-header-size: 9.5em;
--content-header-size: 4em;
background-color: var(--sn-white);
margin: 20px 0;
@ -14,6 +14,11 @@
margin: 0;
}
.fixed-content-body {
height: calc(100vh - var(--content-header-size) - var(--navbar-height));
width: 100%;
}
.content-header {
&.sticky-header {
background-color: inherit;
@ -38,7 +43,7 @@
display: flex;
flex-grow: 1;
margin: 0;
max-width: calc(100% - 2.5rem);
max-width: 100%;
}
.name-readonly-placeholder {

View file

@ -105,16 +105,16 @@
.dp__input_wrap {
.dp__input_icon {
display: flex;
height: 1.5rem;
left: .75rem;
}
left: .5rem;
.dp__clear_icon {
width: 1.375rem;
img {
width: 1rem;
}
}
.dp__input {
height: 2.25rem;
line-height: unset;
}
}
@ -129,6 +129,26 @@
}
}
.date-time-picker {
&.size-mb {
.dp__input {
height: 44px;
}
}
&.size-sm {
.dp__input {
height: 40px;
}
}
&.size-xs {
.dp__input {
height: 36px;
}
}
}
.dp__theme_light {
--dp-background-color: var(--sn-white);
--dp-text-color: var(--sn-black);

View file

@ -2,7 +2,7 @@
// scss-lint:disable NestingDepth
.sci-cursor-edit {
cursor: url("/images/icon_small/edit.svg") 0 16, auto;
cursor: text;
}
.inline-edit-placeholder:empty::before {

View file

@ -78,7 +78,6 @@
left: 0;
max-height: 300px;
overflow: hidden;
top: 2.5rem;
width: 100%;
z-index: 9999;
}

View file

@ -55,6 +55,32 @@ $sn-icon-check: "\e95f";
}
}
.sn-checkbox-icon {
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
height: 1rem;
width: 1rem;
min-height: 1rem;
min-width: 1rem;
&.unchecked {
background-image: asset-url("checkbox/default.svg");
}
&.checked {
background-image: asset-url("checkbox/checked.svg");
}
&.disabled {
background-image: asset-url("checkbox/disabled.svg");
}
&.indeterminate {
background-image: asset-url("checkbox/indeterminate.svg");
}
}
@mixin font-h1 {
font-size: 24px;
font-weight: bold;

View file

@ -1,6 +1,9 @@
$flyout-shadow: 0px 1px 4px rgba(35, 31, 32, 0.15);
$modal-shadow: 0px 4px 16px rgba(35, 31, 32, 0.15);
.sn-shadow-flyout {
box-shadow: $flyout-shadow;
}
.sn-shadow-menu-sm {
box-shadow: 0px 16px 32px 0px rgba(16, 24, 40, 0.07);

View file

@ -90,5 +90,13 @@ input[type="checkbox"].sci-checkbox {
border: $border-tertiary;
}
}
&:checked + .sci-checkbox-label {
&::before {
background-color: var(--sn-sleepy-grey);
border: 1px solid var(--sn-sleepy-grey);
}
}
}
}

View file

@ -4,7 +4,7 @@
}
.btn {
@apply relative inline-flex items-center text-sm shrink-0 gap-2 justify-center leading-[22px] py-2 px-4 rounded border border-solid appearance-none whitespace-nowrap cursor-pointer max-h-[40px] focus:outline-none;
@apply relative inline-flex items-center text-sm shrink-0 gap-2 justify-center px-4 rounded border border-solid appearance-none whitespace-nowrap cursor-pointer h-[40px] focus:outline-none;
border-color: transparent;
}
@ -13,7 +13,7 @@
}
.btn.btn-lg {
@apply py-2.5 px-[1.125rem] text-base leading-5 h-[44px];
@apply px-[1.125rem] text-base h-[44px];
}
.btn.btn-lg.icon-btn {
@ -21,7 +21,7 @@
}
.btn.btn-sm {
@apply py-1.5 px-2.5 text-xs leading-5 h-[36px];
@apply px-2.5 text-xs h-[36px];
}
.btn.btn-sm.icon-btn {
@ -29,7 +29,7 @@
}
.btn.btn-xs {
@apply py-0.5 px-2.5 text-xs leading-5 h-[30px];
@apply px-2.5 text-xs h-[30px];
}
.btn.btn-xs.icon-btn {
@ -59,7 +59,9 @@
}
.btn.btn-primary:hover,
.btn.btn-success:hover{
.btn.btn-success:hover,
.btn.btn-primary:focus,
.btn.btn-success:focus{
@apply bg-sn-blue-hover text-sn-white;
}
@ -67,7 +69,7 @@
.btn.btn-primary.disabled,
.btn.btn-success:disabled,
.btn.btn-success.disabled {
@apply bg-sn-light-grey text-sn-sleepy-grey border-sn-light-grey;
@apply bg-sn-super-light-grey text-sn-grey border-sn-grey;
}
.btn.btn-secondary,
@ -80,7 +82,8 @@
}
.btn.btn-secondary:hover,
.btn.btn-default:hover {
.btn.btn-default:hover,
.btn.btn-secondary:focus {
@apply border-sn-blue-hover;
}
@ -129,7 +132,8 @@
@apply bg-sn-delete-red text-sn-white;
}
.btn.btn-danger:hover {
.btn.btn-danger:hover,
.btn.btn-danger:focus {
@apply bg-sn-delete-red-hover;
}

View file

@ -4,8 +4,12 @@
@apply text-sm font-medium text-sn-dark-grey;
}
.sci-label.error {
@apply text-sn-coral;
}
.sci-input-container-v2 {
@apply relative h-[2.75rem] flex items-center;
@apply relative h-[2.75rem] flex items-center transition-all;
}
.sci-input-container-v2.input-sm {
@ -24,6 +28,18 @@
@apply !border-sn-coral;
}
.sci-input-container-v2.error::before {
@apply !text-sn-coral;
@apply !text-xs;
bottom: -1rem;
content: attr(data-error-text);
left: 0;
position: absolute;
white-space: nowrap;
width: 100%;
}
.sci-input-container-v2 input:focus {
@apply border-sn-science-blue shadow-none;
}
@ -77,4 +93,13 @@
.sci-input-container-v2 .history-flyout li:hover {
@apply bg-sn-super-light-grey;
}
.sci-input-container-v2.error input {
@apply border-sn-alert-passion;
}
.sci-input-container-v2.error::after {
@apply absolute -bottom-5 text-sn-alert-passion text-xs;
content: attr(data-error);
}
}

View file

@ -7,7 +7,15 @@ module AccessPermissions
before_action :check_read_permissions, only: %i(show)
before_action :check_manage_permissions, only: %i(edit update)
def show; end
def show
render json: @experiment.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
each_serializer: UserAssignmentSerializer, user: current_user
end
def new
render json: @available_users, each_serializer: UserSerializer, user: current_user
end
def edit; end
@ -36,7 +44,7 @@ module AccessPermissions
log_change_activity
render :experiment_member
render json: {}, status: :ok
end
private

View file

@ -8,8 +8,14 @@ module AccessPermissions
before_action :check_read_permissions, only: %i(show)
before_action :check_manage_permissions, only: %i(edit update)
def show; end
def show
render json: @my_module.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
each_serializer: UserAssignmentSerializer, user: current_user
end
def new
render json: @available_users, each_serializer: UserSerializer, user: current_user
end
def edit; end
def update

View file

@ -9,17 +9,59 @@ module AccessPermissions
before_action :check_manage_permissions, except: %i(show)
before_action :available_users, only: %i(new create)
def new
@user_assignment = @project.user_assignments.new(
assigned_by: current_user,
team: current_team
)
def show
render json: @project.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
each_serializer: UserAssignmentSerializer, user: current_user
end
def show; end
def new
render json: @available_users, each_serializer: UserSerializer, user: current_user
end
def edit; end
def create
ActiveRecord::Base.transaction do
created_count = 0
if permitted_create_params[:user_id] == 'all'
@project.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id])
log_activity(:project_grant_access_to_all_team_members,
{ visibility: t('projects.activity.visibility_visible'),
role: @project.default_public_user_role.name,
team: @project.team.id })
else
user_assignment = UserAssignment.find_or_initialize_by(
assignable: @project,
user_id: permitted_create_params[:user_id],
team: current_team
)
user_assignment.update!(
user_role_id: permitted_create_params[:user_role_id],
assigned_by: current_user,
assigned: :manually
)
log_activity(:assign_user_to_project, { user_target: user_assignment.user.id,
role: user_assignment.user_role.name })
created_count += 1
propagate_job(user_assignment)
end
@message = if created_count.zero?
t('access_permissions.create.success', member_name: t('access_permissions.all_team'))
else
t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name))
end
render json: { message: @message }
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error e.message
errors = @project.errors.present? ? @project.errors&.map(&:message)&.join(',') : e.message
render json: { flash: errors }, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
end
def update
@user_assignment = @project.user_assignments.find_by(
user_id: permitted_update_params[:user_id],
@ -44,53 +86,6 @@ module AccessPermissions
render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity
end
def create
ActiveRecord::Base.transaction do
created_count = 0
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
next unless user_assignment_params[:assign] == '1'
if user_assignment_params[:user_id] == 'all'
@project.update!(visibility: :visible, default_public_user_role_id: user_assignment_params[:user_role_id])
log_activity(:project_grant_access_to_all_team_members,
{ visibility: t('projects.activity.visibility_visible'),
role: @project.default_public_user_role.name,
team: @project.team.id })
else
user_assignment = UserAssignment.find_or_initialize_by(
assignable: @project,
user_id: user_assignment_params[:user_id],
team: current_team
)
user_assignment.update!(
user_role_id: user_assignment_params[:user_role_id],
assigned_by: current_user,
assigned: :manually
)
log_activity(:assign_user_to_project, { user_target: user_assignment.user.id,
role: user_assignment.user_role.name })
created_count += 1
propagate_job(user_assignment)
end
end
@message = if created_count.zero?
t('access_permissions.create.success', count: t('access_permissions.all_team'))
else
t('access_permissions.create.success', count: created_count)
end
render :edit
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error e.message
errors = @project.errors.present? ? @project.errors&.map(&:message)&.join(',') : e.message
render json: { flash: errors }, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
end
def destroy
user = @project.assigned_users.find(params[:user_id])
user_assignment = @project.user_assignments.find_by(user: user, team: current_team)
@ -108,12 +103,14 @@ module AccessPermissions
destroy: true
)
user_assignment.destroy!
log_activity(:unassign_user_from_project, { user_target: user_assignment.user.id,
role: user_assignment.user_role.name })
render json: { flash: t('access_permissions.destroy.success', member_name: escape_input(user.full_name)) }
render json: { message: t('access_permissions.destroy.success', member_name: escape_input(user.full_name)) }
rescue ActiveRecord::RecordInvalid
render json: { flash: t('access_permissions.destroy.failure') },
render json: { message: t('access_permissions.destroy.failure') },
status: :unprocessable_entity
end
@ -131,7 +128,7 @@ module AccessPermissions
{ visibility: t('projects.activity.visibility_hidden'),
role: previous_user_role_name,
team: @project.team.id })
render json: { flash: t('access_permissions.update.revoke_all_team_members') }
render json: { message: t('access_permissions.update.revoke_all_team_members') }
else
# update all team members access
@project.visibility = :visible
@ -150,7 +147,7 @@ module AccessPermissions
private
def permitted_default_public_user_role_params
params.require(:project).permit(:default_public_user_role_id)
params.require(:object).permit(:default_public_user_role_id)
end
def permitted_update_params
@ -159,8 +156,8 @@ module AccessPermissions
end
def permitted_create_params
params.require(:access_permissions_new_user_form)
.permit(resource_members: %i(assign user_id user_role_id))
params.require(:user_assignment)
.permit(%i(user_id user_role_id))
end
def set_project
@ -184,7 +181,7 @@ module AccessPermissions
end
def check_read_permissions
render_403 unless can_read_project?(@project)
render_403 unless can_read_project_users?(@project)
end
def available_users
@ -193,7 +190,7 @@ module AccessPermissions
id: @project.user_assignments.automatically_assigned.select(:user_id)
).or(
current_team.users.where.not(id: @project.users.select(:id))
)
).order('users.full_name ASC')
end
def log_activity(type_of, message_items = {})

View file

@ -2,22 +2,63 @@
module AccessPermissions
class ProtocolsController < ApplicationController
include InputSanitizeHelper
before_action :set_protocol
before_action :check_read_permissions, only: %i(show)
before_action :check_manage_permissions, except: %i(show)
before_action :available_users, only: %i(new create)
def new
@user_assignment = UserAssignment.new(
assignable: @protocol,
assigned_by: current_user,
team: current_team
)
def show
render json: @protocol.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
each_serializer: UserAssignmentSerializer, user: current_user
end
def show; end
def new
render json: @available_users, each_serializer: UserSerializer, user: current_user
end
def edit; end
def create
ActiveRecord::Base.transaction do
created_count = 0
if permitted_create_params[:user_id] == 'all'
@protocol.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id])
log_activity(:protocol_template_access_granted_all_team_members,
{ team: @protocol.team.id, role: @protocol.default_public_user_role&.name })
else
user_assignment = UserAssignment.find_or_initialize_by(
assignable: @protocol,
user_id: permitted_create_params[:user_id],
team: current_team
)
user_assignment.update!(
user_role_id: permitted_create_params[:user_role_id],
assigned_by: current_user,
assigned: :manually
)
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
role: user_assignment.user_role.name })
created_count += 1
end
@message = if created_count.zero?
t('access_permissions.create.success', member_name: t('access_permissions.all_team'))
else
t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name))
end
render json: { message: @message }
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error e.message
errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message
render json: { flash: errors }, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
end
def update
@user_assignment = @protocol.user_assignments.find_by(
user_id: permitted_update_params[:user_id],
@ -40,49 +81,6 @@ module AccessPermissions
render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity
end
def create
ActiveRecord::Base.transaction do
created_count = 0
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
next unless user_assignment_params[:assign] == '1'
if user_assignment_params[:user_id] == 'all'
@protocol.update!(default_public_user_role_id: user_assignment_params[:user_role_id])
log_activity(:protocol_template_access_granted_all_team_members,
{ team: @protocol.team.id, role: @protocol.default_public_user_role&.name })
else
user_assignment = UserAssignment.find_or_initialize_by(
assignable: @protocol,
user_id: user_assignment_params[:user_id],
team: current_team
)
user_assignment.update!(
user_role_id: user_assignment_params[:user_role_id],
assigned_by: current_user,
assigned: :manually
)
created_count += 1
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
role: user_assignment.user_role.name })
end
end
@message = if created_count.zero?
t('access_permissions.create.success', count: t('access_permissions.all_team'))
else
t('access_permissions.create.success', count: created_count)
end
render :edit
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error e.message
errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message
render json: { flash: errors }, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
end
def destroy
user = @protocol.assigned_users.find(params[:user_id])
user_assignment = @protocol.user_assignments.find_by(user: user, team: current_team)
@ -105,10 +103,10 @@ module AccessPermissions
role: user_assignment.user_role.name })
end
render json: { flash: t('access_permissions.destroy.success', member_name: user.full_name) }
render json: { message: t('access_permissions.destroy.success', member_name: user.full_name) }
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error e.message
render json: { flash: t('access_permissions.destroy.failure') }, status: :unprocessable_entity
render json: { message: t('access_permissions.destroy.failure') }, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
@ -137,7 +135,7 @@ module AccessPermissions
private
def permitted_default_public_user_role_params
params.require(:protocol).permit(:default_public_user_role_id)
params.require(:object).permit(:default_public_user_role_id)
end
def permitted_update_params
@ -146,8 +144,17 @@ module AccessPermissions
end
def permitted_create_params
params.require(:access_permissions_new_user_form)
.permit(resource_members: %i(assign user_id user_role_id))
params.require(:user_assignment)
.permit(%i(user_id user_role_id))
end
def available_users
# automatically assigned or not assigned to project
@available_users = current_team.users.where(
id: @protocol.user_assignments.automatically_assigned.select(:user_id)
).or(
current_team.users.where.not(id: @protocol.users.select(:id))
).order('users.full_name ASC')
end
def set_protocol

View file

@ -136,10 +136,7 @@ module Api
end
def check_read_permissions
# team owners can always manage users, so they should also be able to read them
unless can_read_project_users?(@project) || can_manage_project_users?(@project)
raise PermissionError.new(Project, :read_users)
end
raise PermissionError.new(Project, :read_users) unless can_read_project_users?(@project)
end
def load_user_assignment

View file

@ -113,4 +113,14 @@ class ApplicationController < ActionController::Base
ensure
I18n.backend.date_format = nil
end
def pagination_dict(object)
{
current_page: object.current_page,
next_page: object.next_page,
prev_page: object.prev_page,
total_pages: object.total_pages,
total_count: object.total_count
}
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class AssetSyncController < ApplicationController
include FileIconsHelper
skip_before_action :authenticate_user!, only: %i(update download)
skip_before_action :verify_authenticity_token, only: %i(update download)
before_action :authenticate_asset_sync_token!, only: %i(update download)
@ -36,9 +38,19 @@ class AssetSyncController < ApplicationController
return
end
orig_file_size = @asset.file_size
ActiveRecord::Base.transaction do
@asset.update(last_modified_by: current_user)
@asset.file.attach(io: request.body, filename: @asset.file.filename)
if wopi_file?(@asset)
@asset.update_contents(request.body)
else
@asset.file.attach(io: request.body, filename: @asset.file.filename)
@asset.touch
end
@asset.team.release_space(orig_file_size)
@asset.post_process_file
log_activity(:edit)
end
@ -75,11 +87,15 @@ class AssetSyncController < ApplicationController
Asset.transaction do
new_asset = @asset.dup
new_asset.save
new_asset.file.attach(
blob = ActiveStorage::Blob.create_and_upload!(
io: request.body,
filename: "#{@asset.file.filename.base} (#{t('general.copy')}).#{@asset.file.filename.extension}"
filename: "#{@asset.file.filename.base} (#{t('general.copy')}).#{@asset.file.filename.extension}",
metadata: @asset.blob.metadata
)
new_asset.file.attach(blob)
case @asset.parent
when Step
StepAsset.create!(step: @asset.step, asset: new_asset)
@ -89,6 +105,8 @@ class AssetSyncController < ApplicationController
@asset = new_asset.reload
new_asset.post_process_file
current_user.asset_sync_tokens.create!(asset_id: new_asset.id)
end
end

View file

@ -52,7 +52,7 @@ module Breadcrumbs
@breadcrumbs_items.push(
{
label: project.name,
url: project_path(project, view_mode: archived_branch ? :archived : :active),
url: experiments_path(project_id: project, view_mode: archived_branch ? :archived : :active),
archived: archived_branch
}
)

View file

@ -1,4 +1,19 @@
class DesignElementsController < ApplicationController
def index
end
def test_select
render json: { data: [
['1', 'One'],
['2', 'Two'],
['3', 'Three'],
['4', 'Four'],
['5', 'Five'],
['6', 'Six'],
['7', 'Seven'],
['8', 'Eight'],
['9', 'Nine'],
['10', 'Ten']
].select { |item| item[1].downcase.include?(params[:query].downcase) } }
end
end

View file

@ -8,34 +8,44 @@ class ExperimentsController < ApplicationController
include Rails.application.routes.url_helpers
include Breadcrumbs
before_action :load_project, only: %i(new create archive_group restore_group move)
before_action :load_experiment, except: %i(new create archive_group restore_group
inventory_assigning_experiment_filter actions_toolbar
move move_modal)
before_action :load_experiments, only: %i(move_modal move)
before_action :check_move_permissions, only: %i(move_modal move)
before_action :check_read_permissions, except: %i(edit archive clone move move_modal new
before_action :load_project, only: %i(index create archive_group restore_group move)
before_action :load_experiment, except: %i(create archive_group restore_group
inventory_assigning_experiment_filter actions_toolbar index move)
before_action :load_experiments, only: :move
before_action :check_read_permissions, except: %i(index edit archive clone move new
create archive_group restore_group
inventory_assigning_experiment_filter actions_toolbar)
before_action :check_canvas_read_permissions, only: %i(canvas)
before_action :check_create_permissions, only: %i(new create move)
before_action :check_manage_permissions, only: %i(edit batch_clone_my_modules)
before_action :check_update_permissions, only: %i(update)
before_action :check_create_permissions, only: %i(create move)
before_action :check_manage_permissions, only: :batch_clone_my_modules
before_action :check_update_permissions, only: :update
before_action :check_archive_permissions, only: :archive
before_action :check_clone_permissions, only: %i(clone_modal clone)
before_action :set_inline_name_editing, only: %i(canvas table module_archive)
before_action :set_breadcrumbs_items, only: %i(canvas table module_archive)
before_action :set_navigator, only: %i(canvas module_archive table)
before_action :set_inline_name_editing, only: %i(index canvas module_archive)
before_action :set_breadcrumbs_items, only: %i(index canvas module_archive)
before_action :set_navigator, only: %i(index canvas module_archive)
layout 'fluid'
def new
@experiment = Experiment.new
render json: {
html: render_to_string(
partial: 'new_modal', formats: :html
)
}
def index
respond_to do |format|
format.json do
experiments = Lists::ExperimentsService.new(@project.experiments,
params.merge(project: @project),
user: current_user).call
render json: experiments, each_serializer: Lists::ExperimentSerializer, user: current_user,
meta: pagination_dict(experiments)
end
format.html do
render 'experiments/index'
end
end
end
def assigned_users
render json: User.where(id: @experiment.user_assignments.select(:user_id)),
each_serializer: UserSerializer,
user: current_user
end
def create
@ -46,7 +56,7 @@ class ExperimentsController < ApplicationController
if @experiment.save
experiment_annotation_notification
log_activity(:create_experiment, @experiment)
flash[:success] = t('experiments.create.success_flash',
flash[:success] = t('.success_flash',
experiment: @experiment.name)
render json: { path: my_modules_experiment_url(@experiment) }, status: :ok
@ -55,24 +65,6 @@ class ExperimentsController < ApplicationController
end
end
def show
render json: {
html: render_to_string(partial: 'experiments/details_modal', formats: :html)
}
end
def permissions
if stale?([@experiment, @experiment.project])
render json: {
editable: can_manage_experiment?(@experiment),
moveable: can_move_experiment?(@experiment),
archivable: can_archive_experiment?(@experiment),
restorable: can_restore_experiment?(@experiment),
duplicable: can_clone_experiment?(@experiment)
}
end
end
def canvas
@project = @experiment.project
@active_modules = unless @experiment.archived_branch?
@ -84,40 +76,8 @@ class ExperimentsController < ApplicationController
.select('COUNT(DISTINCT comments.id) as task_comments_count')
.select('my_modules.*').group(:id)
end
end
def table
@project = @experiment.project
@experiment.current_view_state(current_user)
@my_module_visible_table_columns = current_user.my_module_visible_table_columns
view_state = @experiment.current_view_state(current_user)
@current_sort = view_state.state.dig('my_modules', my_modules_view_mode(@project), 'sort') || 'atoz'
end
def my_modules_view_mode(my_module)
return 'archived' if my_module.archived?
params[:view_mode] == 'archived' ? 'archived' : 'active'
end
def load_table
active_view_mode = params[:view_mode] != 'archived'
my_modules = nil
unless @experiment.archived_branch? && active_view_mode
my_modules = @experiment.my_modules.readable_by_user(current_user)
unless @experiment.archived_branch?
my_modules = if active_view_mode
my_modules.active
else
my_modules.archived
end
end
end
render json: Experiments::TableViewService.new(@experiment, my_modules, current_user, params).call
save_view_type('canvas')
end
def my_modules
@ -135,12 +95,6 @@ class ExperimentsController < ApplicationController
redirect_to view_mode_redirect_url(view_type_params)
end
def edit
render json: {
html: render_to_string(partial: 'edit_modal', formats: :html)
}
end
def update
old_text = @experiment.description
@experiment.assign_attributes(experiment_params)
@ -160,25 +114,9 @@ class ExperimentsController < ApplicationController
end
log_activity(activity_type, @experiment)
respond_to do |format|
format.json do
render json: {}, status: :ok
end
format.html do
flash[:success] = t('experiments.update.success_flash', experiment: @experiment.name)
redirect_to project_path(@experiment.project)
end
end
render json: { message: t('experiments.update.success_flash', experiment: @experiment.name) }, status: :ok
else
respond_to do |format|
format.json do
render json: @experiment.errors, status: :unprocessable_entity
end
format.html do
flash[:alert] = t('experiments.update.error_flash')
redirect_back(fallback_location: root_path)
end
end
render json: { message: @experiment.errors.full_messages }, status: :unprocessable_entity
end
end
@ -241,47 +179,50 @@ class ExperimentsController < ApplicationController
end
end
# GET: clone_modal_experiment_path(id)
def clone_modal
@projects = @experiment.project.team.projects.active
.with_user_permission(current_user, ProjectPermissions::EXPERIMENTS_CREATE)
render json: {
html: render_to_string(
partial: 'clone_modal',
locals: { view_mode: params[:view_mode] },
formats: :html
)
}
def projects_to_clone
projects = @experiment.project.team.projects.active
.with_user_permission(current_user, ProjectPermissions::EXPERIMENTS_CREATE)
.where('trim_html_tags(projects.name) ILIKE ?',
"%#{ActiveRecord::Base.sanitize_sql_like(params['query'])}%")
.map { |p| [p.id, p.name] }
render json: { data: projects }, status: :ok
end
def projects_to_move
projects = @experiment.movable_projects(current_user)
.where('trim_html_tags(projects.name) ILIKE ?',
"%#{ActiveRecord::Base.sanitize_sql_like(params['query'])}%")
.map { |p| [p.id, p.name] }
render json: { data: projects }, status: :ok
end
def experiments_to_move
experiments = @experiment.project.experiments.active.where.not(id: @experiment)
.managable_by_user(current_user).order(name: :asc).map { |e| [e.id, e.name] }
render json: { data: experiments }, status: :ok
end
# POST: clone_experiment(id)
def clone
@project = current_team.projects.find(move_experiment_param)
return render_403 unless can_create_project_experiments?(project)
return render_403 unless can_create_project_experiments?(@project)
service = Experiments::CopyExperimentAsTemplateService.call(experiment: @experiment,
project: project,
project: @project,
user: current_user)
if service.succeed?
flash[:success] = t('experiments.clone.success_flash',
experiment: @experiment.name)
redirect_to canvas_experiment_path(service.cloned_experiment)
render json: { url: canvas_experiment_path(service.cloned_experiment) }
else
flash[:error] = t('experiments.clone.error_flash',
experiment: @experiment.name)
redirect_to project_path(@experiment.project)
render json: {
message: t('experiments.clone.error_flash',
experiment: @experiment.name)
}, status: :unprocessable_entity
end
end
# GET: move_modal_experiment_path(id)
def move_modal
@projects = @experiments.first.movable_projects(current_user)
render json: {
html: render_to_string(partial: 'move_modal', formats: :html)
}
end
def search_tags
tags = @experiment.project.tags.where.not(id: JSON.parse(params[:selected_tags]))
.where_attributes_like(:name, params[:query])
@ -297,8 +238,10 @@ class ExperimentsController < ApplicationController
render json: tags
end
# POST: move_experiment(id)
# POST: move_experiments(ids)
def move
return render_403 unless @experiments.all? { |e| can_move_experiment?(e) }
@project.transaction do
@experiments.each do |experiment|
service = Experiments::MoveToProjectService
@ -386,35 +329,7 @@ class ExperimentsController < ApplicationController
end
render json: {
workflowimg: render_to_string(
partial: 'projects/show/workflow_img',
locals: { experiment: @experiment },
formats: :html
)
}
end
def sidebar
view_state = @experiment.current_view_state(current_user)
view_mode = params[:view_mode].presence || 'active'
default_sort = view_state.state.dig('my_modules', view_mode, 'sort') || 'atoz'
my_modules = if @experiment.archived_branch?
@experiment.my_modules
elsif params[:view_mode] == 'archived'
@experiment.my_modules.archived
else
@experiment.my_modules.active
end
my_modules = sort_my_modules(my_modules, params[:sort].presence || default_sort)
render json: {
html: render_to_string(
partial: if params[:view_mode] == 'archived'
'shared/sidebar/archived_my_modules'
else
'shared/sidebar/my_modules'
end, locals: { experiment: @experiment, my_modules: my_modules }
)
workflowimg_url: rails_blob_path(@experiment.workflowimg, only_path: true),
}
end
@ -435,6 +350,7 @@ class ExperimentsController < ApplicationController
.joins(:my_modules)
.where(experiments: { id: viewable_experiments })
.where(my_modules: { id: assignable_my_modules })
.order(:name)
.distinct
.pluck(:id, :name)
@ -443,17 +359,6 @@ class ExperimentsController < ApplicationController
render json: experiments
end
def actions_dropdown
if stale?([@experiment, @experiment.project])
render json: {
html: render_to_string(
partial: 'projects/show/experiment_actions_dropdown',
locals: { experiment: @experiment }
)
}
end
end
def assigned_users_to_tasks
users = current_team.users.where(id: @experiment.my_modules.joins(:user_my_modules).select(:user_id))
.search(false, params[:query]).map do |u|
@ -526,7 +431,7 @@ class ExperimentsController < ApplicationController
actions:
Toolbars::ExperimentsService.new(
current_user,
experiment_ids: params[:experiment_ids].split(',')
experiment_ids: JSON.parse(params[:items]).map { |i| i['id'] }
).actions
}
end
@ -545,7 +450,9 @@ class ExperimentsController < ApplicationController
def load_project
@project = Project.find_by(id: params[:project_id])
render_404 unless @project
render_403 unless can_read_project?(@project)
end
def experiment_params
@ -560,6 +467,12 @@ class ExperimentsController < ApplicationController
params.require(:experiment).require(:view_type)
end
def save_view_type(view_type)
view_state = @experiment.current_view_state(current_user)
view_state.state['my_modules']['view_type'] = view_type
view_state.save!
end
def check_read_permissions
current_team_switch(@experiment.project.team) if current_team != @experiment.project.team
render_403 unless can_read_experiment?(@experiment) ||
@ -594,20 +507,28 @@ class ExperimentsController < ApplicationController
render_403 unless can_clone_experiment?(@experiment)
end
def check_move_permissions
render_403 unless @experiments.all? { |e| can_move_experiment?(e) }
end
def set_inline_name_editing
return unless can_manage_experiment?(@experiment)
if @experiment
return unless can_manage_experiment?(@experiment)
@inline_editable_title_config = {
name: 'title',
params_group: 'experiment',
item_id: @experiment.id,
field_to_udpate: 'name',
path_to_update: experiment_path(@experiment)
}
@inline_editable_title_config = {
name: 'title',
params_group: 'experiment',
item_id: @experiment.id,
field_to_udpate: 'name',
path_to_update: experiment_path(@experiment)
}
else
return unless can_manage_project?(@project)
@inline_editable_title_config = {
name: 'title',
params_group: 'project',
item_id: @project.id,
field_to_udpate: 'name',
path_to_update: project_path(@project)
}
end
end
def experiment_annotation_notification(old_text = nil)
@ -652,10 +573,10 @@ class ExperimentsController < ApplicationController
when 'canvas'
module_archive_experiment_path(@experiment)
else
table_experiment_path(@experiment, view_mode: :archived)
my_modules_path(experiment_id: @experiment, view_mode: :archived)
end
else
view_type == 'canvas' ? canvas_experiment_path(@experiment) : table_experiment_path(@experiment)
view_type == 'canvas' ? canvas_experiment_path(@experiment) : my_modules_path(experiment_id: @experiment)
end
end
@ -689,10 +610,18 @@ class ExperimentsController < ApplicationController
end
def set_navigator
@navigator = {
url: tree_navigator_experiment_path(@experiment),
archived: (action_name == 'module_archive' || params[:view_mode] == 'archived'),
id: @experiment.code
}
@navigator = if @experiment
{
url: tree_navigator_experiment_path(@experiment),
archived: (action_name == 'module_archive' || params[:view_mode] == 'archived'),
id: @experiment.code
}
else
{
url: tree_navigator_project_path(@project),
archived: (action_name == 'index' && params[:view_mode] == 'archived'),
id: @project.code
}
end
end
end

View file

@ -16,7 +16,8 @@ class LabelTemplatesController < ApplicationController
def index
respond_to do |format|
format.json do
render json: @label_templates, each_serializer: LabelTemplateSerializer, user: current_user
label_templates = Lists::LabelTemplatesService.new(@label_templates, params).call
render json: label_templates, each_serializer: Lists::LabelTemplateSerializer, user: current_user, meta: pagination_dict(label_templates)
end
format.html do
unless LabelTemplate.enabled?
@ -28,13 +29,6 @@ class LabelTemplatesController < ApplicationController
end
end
def datatable
render json: ::LabelTemplateDatatable.new(
view_context,
@label_templates
)
end
def show
respond_to do |format|
format.json { render json: @label_template, serializer: LabelTemplateSerializer, user: current_user }
@ -50,13 +44,11 @@ class LabelTemplatesController < ApplicationController
label_template.last_modified_by = current_user
label_template.save!
log_activity(:label_template_created, label_template)
redirect_to label_template_path(label_template, new_label: true)
render json: { redirect_url: label_template_path(label_template, new_label: true) }
end
rescue StandardError => e
Rails.logger.error(e.message)
Rails.logger.error(e.backtrace.join("\n"))
flash[:error] = I18n.t('errors.general')
redirect_to label_templates_path
end
def update
@ -154,7 +146,7 @@ class LabelTemplatesController < ApplicationController
actions:
Toolbars::LabelTemplatesService.new(
current_user,
label_template_ids: params[:label_template_ids].split(',')
label_template_ids: JSON.parse(params[:items]).map { |i| i['id'] }
).actions
}
end

View file

@ -27,6 +27,10 @@ class MyModuleTagsController < ApplicationController
}
end
def assigned_tags
render json: @my_module.my_module_tags, each_serializer: MyModuleTagSerializer
end
def canvas_index
experiment = Experiment.find(params[:id])
return render_403 unless can_read_experiment?(experiment)

View file

@ -8,9 +8,9 @@ class MyModulesController < ApplicationController
include MyModulesHelper
include Breadcrumbs
before_action :load_vars, except: %i(restore_group create new save_table_state
before_action :load_vars, except: %i(index restore_group create new save_table_state
inventory_assigning_my_module_filter actions_toolbar)
before_action :load_experiment, only: %i(create new)
before_action :load_experiment, only: %i(create new index)
before_action :check_create_permissions, only: %i(new create)
before_action :check_archive_permissions, only: %i(update)
before_action :check_manage_permissions, only: %i(
@ -19,15 +19,31 @@ class MyModulesController < ApplicationController
before_action :check_read_permissions, except: %i(create new update update_description
inventory_assigning_my_module_filter
update_protocol_description restore_group
save_table_state actions_toolbar)
save_table_state actions_toolbar index)
before_action :check_update_state_permissions, only: :update_state
before_action :set_inline_name_editing, only: %i(protocols activities archive)
before_action :set_inline_name_editing, only: %i(protocols activities archive index)
before_action :load_experiment_my_modules, only: %i(protocols activities archive)
before_action :set_breadcrumbs_items, only: %i(protocols activities archive)
before_action :set_navigator, only: %i(protocols activities archive)
before_action :set_breadcrumbs_items, only: %i(protocols activities archive index)
before_action :set_navigator, only: %i(protocols activities archive index)
layout 'fluid'.freeze
def index
respond_to do |format|
format.json do
my_modules = Lists::MyModulesService.new(@experiment.my_modules.readable_by_user(current_user),
params.merge(experiment: @experiment),
user: current_user).call
render json: my_modules, each_serializer: Lists::MyModuleSerializer, user: current_user,
meta: pagination_dict(my_modules)
end
format.html do
save_view_type('table')
render 'my_modules/index'
end
end
end
def new
@my_module = @experiment.my_modules.new
assigned_users = User.where(id: @experiment.user_assignments.select(:user_id))
@ -51,7 +67,7 @@ class MyModulesController < ApplicationController
)
@my_module.transaction do
if my_module_tags_params[:tag_ids].present?
@my_module.tags << @experiment.project.tags.where(id: JSON.parse(my_module_tags_params[:tag_ids]))
@my_module.tags << @experiment.project.tags.where(id: my_module_tags_params[:tag_ids])
end
if my_module_designated_users_params[:user_ids].present? && can_designate_users_to_new_task?(@experiment)
@my_module.designated_users << @experiment.users.where(id: my_module_designated_users_params[:user_ids])
@ -76,9 +92,16 @@ class MyModulesController < ApplicationController
end
def show
render json: {
html: render_to_string(partial: 'show')
}
respond_to do |format|
format.html do
render json: {
html: render_to_string(partial: 'show')
}
end
format.json do
render json: @my_module, serializer: Lists::MyModuleSerializer, user: current_user
end
end
end
# Description modal window in full-zoom canvas
@ -328,16 +351,22 @@ class MyModulesController < ApplicationController
end
end
if counter == my_modules.size
flash[:success] = t('my_modules.restore_group.success_flash_html', number: counter)
message = t('my_modules.restore_group.success_flash_html', number: counter)
elsif counter.positive?
flash[:warning] = t('my_modules.restore_group.partial_success_flash_html', number: counter)
message = t('my_modules.restore_group.partial_success_flash_html', number: counter)
else
flash[:error] = t('my_modules.restore_group.error_flash')
error = t('my_modules.restore_group.error_flash')
end
if params[:view] == 'table'
redirect_to table_experiment_path(experiment, view_mode: :archived)
if message
render json: { message: message }
else
render json: { error: error }, status: :unprocessable_entity
end
else
flash[:notice] = message if message
flash[:error] = error if error
redirect_to module_archive_experiment_path(experiment)
end
end
@ -381,7 +410,7 @@ class MyModulesController < ApplicationController
actions:
Toolbars::MyModulesService.new(
current_user,
my_module_ids: params[:my_module_ids].split(',')
my_module_ids: JSON.parse(params[:items]).map { |i| i['id'] }
).actions
}
end
@ -404,6 +433,7 @@ class MyModulesController < ApplicationController
my_modules = experiment.my_modules
.where(my_modules: { id: assignable_my_modules })
.order(:name)
.pluck(:id, :name)
return render plain: [].to_json if my_modules.blank?
@ -424,8 +454,11 @@ class MyModulesController < ApplicationController
end
def load_experiment
@experiment = Experiment.preload(user_assignments: %i(user user_role)).find_by(id: params[:id])
@experiment = Experiment.preload(user_assignments: %i(user user_role))
.find_by(id: params[:id] || params[:experiment_id])
render_404 unless @experiment
render_403 unless can_read_experiment?(@experiment)
end
def load_experiment_my_modules
@ -460,6 +493,19 @@ class MyModulesController < ApplicationController
end
def set_inline_name_editing
if action_name == 'index'
return unless can_manage_experiment?(@experiment)
@inline_editable_title_config = {
name: 'title',
params_group: 'experiment',
item_id: @experiment.id,
field_to_udpate: 'name',
path_to_update: experiment_path(@experiment)
}
return
end
return unless can_manage_my_module?(@my_module)
@inline_editable_title_config = {
@ -487,7 +533,7 @@ class MyModulesController < ApplicationController
end
def my_module_tags_params
params.require(:my_module).permit(:tag_ids)
params.require(:my_module).permit(tag_ids: [])
end
def my_module_designated_users_params
@ -538,6 +584,12 @@ class MyModulesController < ApplicationController
end
end
def save_view_type(view_type)
view_state = @experiment.current_view_state(current_user)
view_state.state['my_modules']['view_type'] = view_type
view_state.save!
end
def log_activity(type_of, my_module = nil, message_items = {})
my_module ||= @my_module
message_items = { my_module: my_module.id }.merge(message_items)
@ -588,6 +640,15 @@ class MyModulesController < ApplicationController
end
def set_navigator
if action_name == 'index'
@navigator = {
url: tree_navigator_experiment_path(@experiment),
archived: params[:view_mode] == 'archived',
id: @experiment.code
}
return
end
@navigator = {
url: tree_navigator_my_module_path(@my_module),
archived: params[:view_mode] == 'archived',

View file

@ -40,7 +40,8 @@ class NavigationsController < ApplicationController
{
name: current_user.full_name,
avatar_url: avatar_path(current_user, :icon_small),
sign_out_url: destroy_user_session_path
sign_out_url: destroy_user_session_path,
preferences_url: preferences_url
}
end

View file

@ -8,7 +8,7 @@ module Navigator
{
id: project.code,
name: project.name,
url: project_path(project, view_mode: archived ? 'archived' : 'active'),
url: experiments_path(project_id: project, view_mode: archived ? 'archived' : 'active'),
archived: project.archived,
type: :project,
has_children: project.has_children,
@ -24,7 +24,7 @@ module Navigator
url: project_folder_path(folder, view_mode: archived ? 'archived' : 'active'),
archived: folder.archived,
type: :folder,
has_children: folder.has_children,
has_children: folder.try(:has_children),
children_url: navigator_project_folder_path(folder)
}
end
@ -123,7 +123,7 @@ module Navigator
'project_folders.archived',
"#{has_children_sql} AS has_children"
).group('project_folders.id')
.having("project_folders.archived = :archived OR #{has_children_sql}", archived: archived)
.having('project_folders.archived = :archived', archived: archived)
end
def fetch_experiments(object, archived = false)
@ -180,6 +180,8 @@ module Navigator
archived = params[:archived] == 'true'
tree = fetch_projects(folder.parent_folder, archived).map { |i| project_serializer(i, archived) } +
fetch_project_folders(folder.parent_folder, archived).map { |i| folder_serializer(i, archived) }
# Tree will not contain folder when folder archived state and params archived values are contradictory
tree.find { |i| i[:id] == folder.code } || (tree << folder_serializer(folder, archived))
tree.find { |i| i[:id] == folder.code }[:children] = children
tree = build_folder_tree(folder.parent_folder, tree) if folder.parent_folder.present?
tree

View file

@ -2,7 +2,7 @@
module Navigator
class ProjectsController < BaseController
before_action :load_project
before_action :load_project, except: :index
before_action :check_read_permissions, except: :index
def index
@ -27,6 +27,8 @@ module Navigator
def load_project
@project = current_team.projects.find_by(id: params[:id])
render_404 unless @project
end
def check_read_permissions

View file

@ -13,6 +13,10 @@ class ProjectFoldersController < ApplicationController
before_action :check_create_permissions, only: %i(new create)
before_action :check_manage_permissions, only: %i(archive move_to)
def tree
render json: folders_tree(current_team, current_user)
end
def new
@project_folder =
current_team.project_folders.new(parent_folder: current_folder, archived: projects_view_mode_archived?)
@ -48,11 +52,11 @@ class ProjectFoldersController < ApplicationController
move_projects(destination_folder)
move_folders(destination_folder)
end
render json: { flash: I18n.t('projects.move.success_flash') }
render json: { message: I18n.t('projects.move.success_flash') }
rescue StandardError => e
Rails.logger.error e.message
Rails.logger.error e.backtrace.join("\n")
render json: { flash: I18n.t('projects.move.error_flash') }, status: :bad_request
render json: { error: I18n.t('projects.move.error_flash') }, status: :bad_request
end
def move_to_modal
@ -109,7 +113,7 @@ class ProjectFoldersController < ApplicationController
if counter.positive?
render json: { message: t('projects.delete_folders.success_flash', number: counter) }
else
render json: { message: t('projects.delete_folders.error_flash') }, status: :unprocessable_entity
render json: { error: t('projects.delete_folders.error_flash') }, status: :unprocessable_entity
end
end
@ -132,13 +136,9 @@ class ProjectFoldersController < ApplicationController
end
def move_params
parsed_params = ActionController::Parameters.new(
movables: JSON.parse(params[:movables]),
destination_folder_id: params[:destination_folder_id]
)
parsed_params.require(:destination_folder_id)
parsed_params.require(:movables)
parsed_params.permit(:destination_folder_id, movables: %i(id type))
params.require(:destination_folder_id)
params.require(:movables)
params.permit(:destination_folder_id, movables: %i(id type))
end
def check_create_permissions
@ -150,7 +150,7 @@ class ProjectFoldersController < ApplicationController
end
def move_projects(destination_folder)
project_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project' }.compact
project_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'projects' }.compact
return if project_ids.blank?
current_team.projects.where(id: project_ids).each do |project|
@ -167,7 +167,7 @@ class ProjectFoldersController < ApplicationController
end
def move_folders(destination_folder)
folder_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project_folder' }.compact
folder_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project_folders' }.compact
return if folder_ids.blank?
current_team.project_folders.where(id: folder_ids).each do |folder|

View file

@ -8,98 +8,38 @@ class ProjectsController < ApplicationController
include CardsViewHelper
include ExperimentsHelper
include Breadcrumbs
include UserRolesHelper
attr_reader :current_folder
helper_method :current_folder
before_action :switch_team_with_param, only: :index
before_action :load_vars, only: %i(show permissions edit update notifications
sidebar experiments_cards view_type actions_dropdown create_tag)
before_action :load_current_folder, only: %i(index cards new show)
before_action :check_view_permissions, except: %i(index cards new create edit update archive_group restore_group
users_filter actions_dropdown inventory_assigning_project_filter
actions_toolbar)
before_action :check_create_permissions, only: %i(new create)
before_action :check_manage_permissions, only: :edit
before_action :load_exp_sort_var, only: :show
before_action :reset_invalid_view_state, only: %i(index cards show)
before_action :load_vars, only: %i(update notifications create_tag)
before_action :load_current_folder, only: :index
before_action :check_view_permissions, except: %i(index create update archive_group restore_group
inventory_assigning_project_filter
actions_toolbar user_roles users_filter)
before_action :check_create_permissions, only: :create
before_action :check_manage_permissions, only: :update
before_action :set_folder_inline_name_editing, only: %i(index cards)
before_action :set_breadcrumbs_items, only: %i(index show)
before_action :set_navigator, only: %i(index show)
before_action :set_current_projects_view_type, only: %i(index cards)
before_action :set_breadcrumbs_items, only: :index
before_action :set_navigator, only: :index
layout 'fluid'
def index; end
def cards
overview_service = ProjectsOverviewService.new(current_team, current_user, current_folder, params)
title = params[:view_mode] == 'archived' ? t('projects.index.head_title_archived') : t('projects.index.head_title')
if filters_included?
render json: {
toolbar_html: render_to_string(partial: 'projects/index/toolbar'),
filtered: true,
cards_html: render_to_string(
partial: 'projects/index/team_projects_grouped_by_folder',
locals: { projects_by_folder: overview_service.grouped_by_folder_project_cards }
)
}
else
if current_folder
projects_cards_url = project_folder_cards_url(current_folder)
title_html = if @inline_editable_title_config.present?
render_to_string(partial: 'shared/inline_editing',
locals: {
initial_value: current_folder&.name,
config: @inline_editable_title_config
})
else
escape_input(current_folder.name)
end
else
projects_cards_url = cards_projects_url
title_html = title
def index
respond_to do |format|
format.json do
projects = Lists::ProjectsService.new(current_team, current_user, current_folder, params).call
render json: projects, each_serializer: Lists::ProjectAndFolderSerializer, user: current_user,
meta: pagination_dict(projects)
end
format.html do
render 'projects/index'
end
cards = Kaminari.paginate_array(overview_service.project_and_folder_cards)
.page(params[:page] || 1).per(Constants::DEFAULT_ELEMENTS_PER_PAGE)
render json: {
projects_cards_url: projects_cards_url,
title_html: title_html,
next_page: cards.next_page,
cards_html: render_to_string(
partial: 'projects/index/team_projects',
locals: { cards: cards, view_mode: params[:view_mode] }
)
}
end
end
def permissions
if stale?([@product, current_team])
render json: {
editable: can_manage_project?(@project),
moveable: can_manage_team?(current_team),
archivable: can_archive_project?(@project),
restorable: can_restore_project?(@project)
}
end
end
def sidebar
@current_sort = params[:sort] || @project.current_view_state(current_user)
.state.dig('experiments', params[:view_mode], 'sort')
render json: {
html: render_to_string(
partial: 'shared/sidebar/experiments', locals: {
project: @project,
view_mode: experiments_view_mode(@project)
}
)
}
end
def inventory_assigning_project_filter
viewable_experiments = Experiment.viewable_by_user(current_user, current_team)
@ -110,6 +50,7 @@ class ProjectsController < ApplicationController
.joins(experiments: :my_modules)
.where(experiments: { id: viewable_experiments })
.where(my_modules: { id: assignable_my_modules })
.order(:name)
.distinct
.pluck(:id, :name)
@ -118,13 +59,6 @@ class ProjectsController < ApplicationController
render json: projects
end
def new
@project = current_team.projects.new(project_folder: current_folder)
render json: {
html: render_to_string(partial: 'projects/index/modals/new_project')
}
end
def create
@project = current_team.projects.new(project_params)
@project.created_by = current_user
@ -139,14 +73,6 @@ class ProjectsController < ApplicationController
end
end
def edit
render json: {
html: render_to_string(partial: 'projects/index/modals/edit_project_contents',
formats: :html,
locals: { project: @project })
}
end
def update
@project.assign_attributes(project_update_params)
return_error = false
@ -321,72 +247,25 @@ class ProjectsController < ApplicationController
end
end
def show
view_state = @project.current_view_state(current_user)
@current_sort = view_state.state.dig('experiments', experiments_view_mode(@project), 'sort') || 'atoz'
@current_view_type = view_state.state.dig('experiments', 'view_type')
@project_is_managable = can_manage_project?(@project)
set_inline_name_editing if @project_is_managable
end
def experiments_cards
overview_service = ExperimentsOverviewService.new(@project, current_user, params)
cards = overview_service.experiments
.preload(my_modules: { my_module_status: :my_module_status_implications })
.page(params[:page] || 1)
.per(Constants::DEFAULT_ELEMENTS_PER_PAGE)
render json: {
next_page: cards.next_page,
cards_html: render_to_string(
partial: 'projects/show/experiments_list',
locals: { cards: cards,
view_mode: params[:view_mode],
filters_included: filters_included? }
)
}
end
def notifications
@modules = @project.assigned_modules(current_user).order(due_date: :desc)
render json: {
html: render_to_string(partial: 'notifications', formats: :html)
}
end
def users_filter
users = current_team.users.search(false, params[:query]).map do |u|
{ value: u.id, label: escape_input(u.name), params: { avatar_url: avatar_path(u, :icon_small) } }
[u.id, u.name, { avatar_url: avatar_path(u, :icon_small) }]
end
render json: users, status: :ok
render json: { data: users }, status: :ok
end
def view_type
view_state = @project.current_view_state(current_user)
view_state.state['experiments']['view_type'] = view_type_params
view_state.save!
render json: { cards_view_type_class: cards_view_type_class(view_type_params) }, status: :ok
def user_roles
render json: { data: user_roles_collection(Project.new).map(&:reverse) }
end
def actions_dropdown
if stale?(@project)
render json: {
html: render_to_string(
partial: 'projects/index/project_actions_dropdown',
locals: { project: @project }
)
}
end
end
def actions_toolbar
render json: {
actions:
Toolbars::ProjectsService.new(
current_user,
project_ids: params[:project_ids].split(','),
project_folder_ids: params[:project_folder_ids].split(',')
items: JSON.parse(params[:items])
).actions
}
end
@ -464,30 +343,6 @@ class ProjectsController < ApplicationController
}
end
def load_exp_sort_var
if params[:sort]
@project.experiments_order = params[:sort].to_s
@project.save
end
@current_sort = @project.experiments_order || 'new'
end
def filters_included?
%i(search created_on_from created_on_to updated_on_from updated_on_to members
archived_on_from archived_on_to folders_search)
.any? { |param_name| params.dig(param_name).present? }
end
def reset_invalid_view_state
view_state = if action_name == 'show'
@project.current_view_state(current_user)
else
current_team.current_view_state(current_user)
end
view_state.destroy unless view_state.valid?
end
def log_activity(type_of, project = nil, message_items = {})
project ||= @project
message_items = { project: project.id }.merge(message_items)
@ -521,12 +376,4 @@ class ProjectsController < ApplicationController
}
end
end
def set_current_projects_view_type
if current_team
view_state = current_team.current_view_state(current_user)
@current_sort = view_state.state.dig('projects', projects_view_mode, 'sort') || 'atoz'
@current_view_type = view_state.state.dig('projects', 'view_type')
end
end
end

View file

@ -7,6 +7,7 @@ class ProtocolsController < ApplicationController
include ProtocolsIoHelper
include TeamsHelper
include ProtocolsExporterV2
include UserRolesHelper
before_action :check_create_permissions, only: %i(
create
@ -19,6 +20,7 @@ class ProtocolsController < ApplicationController
protocol_status_bar
linked_children
linked_children_datatable
versions_list
permissions
)
before_action :switch_team_with_param, only: %i(index protocolsio_index)
@ -72,23 +74,36 @@ class ProtocolsController < ApplicationController
layout 'fluid'
def index; end
def datatable
render json: ::ProtocolsDatatable.new(
view_context,
@current_team,
@type,
current_user
)
def index
respond_to do |format|
format.json do
protocols = Lists::ProtocolsService.new(Protocol.viewable_by_user(current_user, @current_team), params).call
render json: protocols,
each_serializer: Lists::ProtocolSerializer,
user: current_user,
meta: pagination_dict(protocols)
end
format.html do
render 'index'
end
end
end
def versions_modal
return render_403 unless @protocol.in_repository_published_original? || @protocol.initial_draft?
@published_versions = @protocol.published_versions_with_original.order(version_number: :desc)
if @protocol.draft.present? || @protocol.initial_draft?
draft = @protocol.initial_draft? ? @protocol : @protocol.draft
draft_hash = ProtocolDraftSerializer.new(draft, scope: current_user).as_json
end
render json: {
html: render_to_string(partial: 'protocols/index/protocol_versions_modal')
draft: draft_hash,
versions: @published_versions.map do |version|
ProtocolVersionSerializer.new(version, scope: current_user).as_json
end
}
end
@ -97,14 +112,44 @@ class ProtocolsController < ApplicationController
end
def linked_children
if params[:version].present?
records = @protocol.published_versions_with_original
.find_by!(version_number: params[:version])
.linked_children
else
records = Protocol.where(protocol_type: Protocol.protocol_types[:linked])
records = records.where(parent_id: @protocol.published_versions)
.or(records.where(parent_id: @protocol.id))
end
records = records.preload(my_module: { experiment: { project: :project_folder } })
.distinct.order(updated_at: :desc).page(params[:page]).per(10)
render json: {
title: I18n.t('protocols.index.linked_children.title',
protocol: escape_input(@protocol.name)),
html: render_to_string(partial: 'protocols/index/linked_children_modal_body',
locals: { protocol: @protocol })
data: records.map { |record|
project_folder = record.my_module.experiment.project.project_folder
{
my_module_name: record.my_module.name,
experiment_name: record.my_module.experiment.name,
project_name: record.my_module.experiment.project.name,
my_module_url: protocols_my_module_path(record.my_module),
experiment_url: my_modules_path(experiment_id: record.my_module.experiment.id),
project_url: experiments_path(project_id: record.my_module.experiment.project.id),
project_folder_name: project_folder.present? ? project_folder.name : nil,
project_folder_url: project_folder.present? ? project_folder_projects_url(project_folder) : nil
}
},
next_page: records.next_page,
total_pages: records.total_pages
}
end
def versions_list
render json: { versions: (@protocol.parent || @protocol).published_versions_with_original
.order(version_number: :desc)
.map(&:version_number) }
end
def linked_children_datatable
render json: ::ProtocolLinkedChildrenDatatable.new(
view_context,
@ -152,8 +197,12 @@ class ProtocolsController < ApplicationController
nil,
protocol: @protocol.id)
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
redirect_to protocols_path
if params[:version_modal]
render json: { message: I18n.t('protocols.delete_draft_modal.success') }
else
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
redirect_to protocols_path
end
rescue ActiveRecord::RecordNotDestroyed => e
Rails.logger.error e.message
render json: { message: e.message }, status: :unprocessable_entity
@ -325,17 +374,15 @@ class ProtocolsController < ApplicationController
draft = @protocol.save_as_draft(current_user)
if draft.invalid?
flash[:error] = draft.errors.full_messages.join(', ')
redirect_to protocols_path
render json: { error: draft.errors.messages.map { |_, value| value }.join(' ') }, status: :unprocessable_entity
else
log_activity(:protocol_template_draft_created, nil, protocol: @protocol.id)
redirect_to protocol_path(draft)
render json: { url: protocol_path(draft) }
end
rescue StandardError => e
Rails.logger.error(e.message)
Rails.logger.error(e.backtrace.join("\n"))
flash[:error] = I18n.t('errors.general')
redirect_to protocols_path
render json: { error: I18n.t('errors.general') }, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
end
@ -439,19 +486,16 @@ class ProtocolsController < ApplicationController
transaction_error = false
Protocol.transaction do
@protocol.load_from_repository(@source, current_user)
rescue StandardError
rescue StandardError => e
Rails.logger.error(e.message)
Rails.logger.error(e.backtrace.join("\n"))
transaction_error = true
raise ActiveRecord::Rollback
end
if transaction_error
# Bad request error
format.json do
render json: {
message: t('my_modules.protocols.load_from_repository_error')
},
status: :bad_request
end
render json: { message: t('my_modules.protocols.load_from_repository_error') }, status: :bad_request
else
# Everything good, record activity, display flash & render 200
log_activity(:load_protocol_to_task_from_repository,
@ -460,7 +504,7 @@ class ProtocolsController < ApplicationController
protocol_repository: @protocol.parent.id)
flash[:success] = t('my_modules.protocols.load_from_repository_flash')
flash.keep(:success)
render json: {}, status: :ok
render json: {}
end
else
render json: {
@ -785,7 +829,7 @@ class ProtocolsController < ApplicationController
actions:
Toolbars::ProtocolsService.new(
current_user,
protocol_ids: params[:protocol_ids].split(',')
protocol_ids: JSON.parse(params[:items]).map { |i| i['id'] }
).actions
}
end
@ -806,6 +850,10 @@ class ProtocolsController < ApplicationController
render json: { job_id: @job.job_id }
end
def user_roles
render json: { data: user_roles_collection(Protocol.new).map(&:reverse) }
end
private
def set_importer

View file

@ -12,7 +12,7 @@ class ReportsController < ApplicationController
before_action :load_available_repositories, only: %i(index save_pdf_to_inventory_modal available_repositories)
before_action :check_project_read_permissions, only: %i(create edit update generate_pdf
generate_docx new_template_values project_contents)
before_action :check_read_permissions, except: %i(index datatable new create edit update destroy actions_toolbar generate_pdf
before_action :check_read_permissions, except: %i(index new create edit update destroy actions_toolbar generate_pdf
generate_docx new_template_values project_contents
available_repositories)
before_action :check_create_permissions, only: %i(new create)
@ -21,14 +21,17 @@ class ReportsController < ApplicationController
after_action :generate_pdf_report, only: %i(create update generate_pdf)
# Index showing all reports of a single project
def index; end
def datatable
render json: ::ReportDatatable.new(
view_context,
current_user,
Report.viewable_by_user(current_user, current_team)
)
def index
respond_to do |format|
format.json do
reports = Lists::ReportsService.new(Report.viewable_by_user(current_user, current_team), params).call
render json: reports, each_serializer: Lists::ReportSerializer,
user: current_user, meta: pagination_dict(reports)
end
format.html do
render 'index'
end
end
end
# Report grouped by modules
@ -137,7 +140,7 @@ class ReportsController < ApplicationController
# Destroy multiple entries action
def destroy
begin
report_ids = JSON.parse(params[:report_ids])
report_ids = params[:report_ids]
rescue
render_404
end
@ -151,25 +154,11 @@ class ReportsController < ApplicationController
report.destroy
end
redirect_to reports_path
render json: { message: I18n.t('projects.reports.index.modal_delete.success') }
end
def status
docx = @report.docx_preview_file.attached? ? document_preview_report_path(@report, report_type: :docx) : nil
pdf = @report.pdf_file.attached? ? document_preview_report_path(@report, report_type: :pdf) : nil
render json: {
docx: {
processing: @report.docx_processing?,
preview_url: docx,
error: @report.docx_error?
},
pdf: {
processing: @report.pdf_processing?,
preview_url: pdf,
error: @report.pdf_error?
}
}
render json: @report, serializer: Lists::ReportSerializer
end
# Generation actions
@ -283,7 +272,7 @@ class ReportsController < ApplicationController
end
def available_repositories
render json: { results: @available_repositories }, status: :ok
render json: { data: @available_repositories.map { |r| [r.id, r.name] } }
end
def document_preview
@ -302,7 +291,7 @@ class ReportsController < ApplicationController
actions:
Toolbars::ReportsService.new(
current_user,
report_ids: params[:report_ids].split(',')
report_ids: JSON.parse(params[:items]).map { |i| i['id'] }
).actions
}
end
@ -360,7 +349,7 @@ class ReportsController < ApplicationController
@available_repositories = []
repositories = Repository.active
.accessible_by_teams(current_team)
.name_like(search_params[:q])
.name_like(search_params[:query])
.limit(Constants::SEARCH_LIMIT)
repositories.each do |repository|
next unless can_manage_repository_rows?(current_user, repository)
@ -376,11 +365,11 @@ class ReportsController < ApplicationController
end
def search_params
params.permit(:q)
params.permit(:query)
end
def save_pdf_params
params.permit(:repository_id, :respository_column_id, :repository_item_id)
params.permit(:repository_id, :repository_column_id, :repository_item_id)
end
def log_activity(type_of, report = @report)
@ -401,6 +390,8 @@ class ReportsController < ApplicationController
ensure_report_template!
Reports::PdfJob.perform_later(@report.id, user_id: current_user.id)
rescue ActiveRecord::ActiveRecordError => e
Rails.logger.error e.message
end
def ensure_report_template!

View file

@ -11,7 +11,7 @@ class RepositoriesController < ApplicationController
before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar
export_modal export_repositories)
before_action :load_repositories, only: %i(index show sidebar)
before_action :load_repositories, only: :index
before_action :load_repositories_for_archiving, only: :archive
before_action :load_repositories_for_restoring, only: :restore
before_action :check_view_all_permissions, only: %i(index sidebar)
@ -35,7 +35,9 @@ class RepositoriesController < ApplicationController
render 'index'
end
format.json do
render json: prepare_repositories_datatable(@repositories, current_team, params)
repositories = Lists::RepositoriesService.new(@repositories, params).call
render json: repositories, each_serializer: Lists::RepositorySerializer, user: current_user,
meta: pagination_dict(repositories)
end
end
end
@ -93,6 +95,11 @@ class RepositoriesController < ApplicationController
render json: { html: render_to_string(partial: 'share_repository_modal', formats: :html) }
end
def shareable_teams
teams = current_user.teams.order(:name) - [@repository.team]
render json: teams, each_serializer: ShareableTeamSerializer, repository: @repository
end
def hide_reminders
# synchronously hide currently visible reminders
if params[:visible_reminder_repository_row_ids].present?
@ -122,12 +129,9 @@ class RepositoriesController < ApplicationController
if @repository.save
log_activity(:create_inventory)
flash[:success] = t('repositories.index.modal_create.success_flash_html', name: @repository.name)
render json: { url: repository_path(@repository) }
render json: { message: t('repositories.index.modal_create.success_flash_html', name: @repository.name) }
else
render json: @repository.errors,
status: :unprocessable_entity
render json: @repository.errors, status: :unprocessable_entity
end
end
@ -163,14 +167,14 @@ class RepositoriesController < ApplicationController
end
def destroy
flash[:success] = t('repositories.index.delete_flash',
name: @repository.name)
log_activity(:delete_inventory) # Log before delete id
@repository.discard
@repository.destroy_discarded(current_user.id)
redirect_to team_repositories_path(archived: true)
render json: {
message: t('repositories.index.delete_flash', name: @repository.name)
}
end
def rename_modal
@ -236,17 +240,22 @@ class RepositoriesController < ApplicationController
render json: @tmp_repository.errors, status: :unprocessable_entity
else
copied_repository = @repository.copy(current_user, @tmp_repository.name)
old_repo_stock_column = @repository.repository_columns.find_by(data_type: 'RepositoryStockValue')
copied_repo_stock_column = copied_repository.repository_columns.find_by(data_type: 'RepositoryStockValue')
if old_repo_stock_column && copied_repo_stock_column
old_repo_stock_column.repository_stock_unit_items.each do |item|
copied_item = item.dup
copied_repo_stock_column.repository_stock_unit_items << copied_item
end
copied_repository.save!
end
if !copied_repository
render json: { name: ['Server error'] }, status: :unprocessable_entity
else
flash[:success] = t(
'repositories.index.copy_flash',
old: @repository.name,
new: copied_repository.name
)
render json: {
url: repository_path(copied_repository)
message: t('repositories.index.copy_flash', old: @repository.name, new: copied_repository.name)
}
end
end
@ -426,7 +435,7 @@ class RepositoriesController < ApplicationController
Toolbars::RepositoriesService.new(
current_user,
current_team,
repository_ids: params[:repository_ids].split(',')
repository_ids: JSON.parse(params[:items]).map { |i| i['id'] }
).actions
}
end
@ -450,12 +459,7 @@ class RepositoriesController < ApplicationController
end
def load_repositories
@repositories = Repository.accessible_by_teams(current_team).order('repositories.created_at ASC')
@repositories = if params[:archived] == 'true' || @repository&.archived?
@repositories.archived
else
@repositories.active
end
@repositories = Repository.accessible_by_teams(current_team)
end
def load_repositories_for_archiving

View file

@ -83,15 +83,7 @@ class RepositoryColumnsController < ApplicationController
end
def available_asset_type_columns
if @asset_columns.blank?
render json: {
no_items: t(
'projects.reports.new.save_PDF_to_inventory_modal.no_columns'
)
}
else
render json: { results: @asset_columns }, status: :ok
end
render json: { data: @asset_columns.map { |c| [c.id, c.name] } }, status: :ok
end
def available_columns

View file

@ -148,7 +148,7 @@ class RepositoryRowConnectionsController < ApplicationController
name: repository_row.name_with_label,
code: repository_row.code,
path: repository_repository_row_path(repository_row.repository, repository_row),
repository_name: repository_row.repository.name,
repository_name: repository_row.repository.name_with_label,
repository_path: repository_path(repository_row.repository),
unlink_path: repository_repository_row_repository_row_connection_path(
repository_row.repository,

View file

@ -272,11 +272,9 @@ class RepositoryRowsController < ApplicationController
"#{link_to(t('projects.reports.new.save_PDF_to_inventory_modal.here'),
repository_path(@repository),
data: { 'no-turbolink' => true })}"
render json: { no_items: no_items_string },
status: :ok
render json: { no_items: no_items_string }, status: :unprocessable_entity
else
render json: { results: load_available_rows },
status: :ok
render json: { results: load_available_rows }, status: :ok
end
end

View file

@ -2,6 +2,7 @@
class ResultsController < ApplicationController
include Breadcrumbs
include TeamsHelper
skip_before_action :verify_authenticity_token, only: %i(create update destroy duplicate)
before_action :load_my_module
before_action :load_vars, only: %i(destroy elements assets upload_attachment archive restore destroy
@ -148,17 +149,17 @@ class ResultsController < ApplicationController
def apply_sort!
case params[:sort]
when 'updated_at_asc'
@results = @results.order(updated_at: :asc)
@results = @results.order('results.updated_at' => :asc)
when 'updated_at_desc'
@results = @results.order(updated_at: :desc)
@results = @results.order('results.updated_at' => :desc)
when 'created_at_asc'
@results = @results.order(created_at: :asc)
@results = @results.order('results.created_at' => :asc)
when 'created_at_desc'
@results = @results.order(created_at: :desc)
@results = @results.order('results.created_at' => :desc)
when 'name_asc'
@results = @results.order(name: :asc)
@results = @results.order('results.name' => :asc)
when 'name_desc'
@results = @results.order(name: :desc)
@results = @results.order('results.name' => :desc)
end
end
@ -167,14 +168,15 @@ class ResultsController < ApplicationController
@results = @results.search(current_user, params[:view_mode] == 'archived', params[:query], params[:page] || 1)
end
@results = @results.where('created_at >= ?', params[:created_at_from]) if params[:created_at_from]
@results = @results.where('created_at <= ?', params[:created_at_to]) if params[:created_at_to]
@results = @results.where('updated_at >= ?', params[:updated_at_from]) if params[:updated_at_from]
@results = @results.where('updated_at <= ?', params[:updated_at_to]) if params[:updated_at_to]
@results = @results.where('results.created_at >= ?', params[:created_at_from]) if params[:created_at_from]
@results = @results.where('results.created_at <= ?', params[:created_at_to]) if params[:created_at_to]
@results = @results.where('results.updated_at >= ?', params[:updated_at_from]) if params[:updated_at_from]
@results = @results.where('results.updated_at <= ?', params[:updated_at_to]) if params[:updated_at_to]
end
def load_my_module
@my_module = MyModule.readable_by_user(current_user).find(params[:my_module_id])
current_team_switch(@my_module.team) if current_team != @my_module.team
end
def load_vars

View file

@ -1,12 +1,17 @@
class TagsController < ApplicationController
before_action :load_vars, only: [:create, :update, :destroy]
before_action :load_vars, only: %i(index create update destroy)
before_action :load_vars_nested, only: [:update, :destroy]
before_action :check_manage_permissions, only: %i(create update destroy)
def index
render json: @project.tags, each_serializer: TagSerializer
end
def create
@tag = Tag.new(tag_params)
@tag.created_by = current_user
@tag.last_modified_by = current_user
@tag.project ||= @project
if @tag.name.blank?
@tag.name = t("tags.create.new_name")

View file

@ -55,7 +55,7 @@ class TeamRepositoriesController < ApplicationController
end
def update_params
params.permit(:permission_changes, share_team_ids: [], write_permissions: [])
params.permit(permission_changes: {}, share_team_ids: [], write_permissions: [])
end
def check_sharing_permissions
@ -79,7 +79,7 @@ class TeamRepositoriesController < ApplicationController
def teams_to_update
return [] if update_params[:permission_changes].blank?
teams_to_update = JSON.parse(update_params[:permission_changes]).keys.map(&:to_i).to_a &
teams_to_update = update_params[:permission_changes].keys.map(&:to_i).to_a &
update_params[:share_team_ids]&.map(&:to_i).to_a
wp = update_params[:write_permissions]&.map(&:to_i)

View file

@ -91,12 +91,11 @@ class UserMyModulesController < ApplicationController
users = @my_module.users
.joins("LEFT OUTER JOIN user_my_modules ON user_my_modules.user_id = users.id "\
"AND user_my_modules.my_module_id = #{@my_module.id}")
.search(false, params[:query])
.order(:full_name)
.limit(Constants::SEARCH_LIMIT)
.where('trim_html_tags(users.full_name) ILIKE ?',
"%#{ActiveRecord::Base.sanitize_sql_like(params[:query])}%")
.select('users.*', 'user_my_modules.id as user_my_module_id')
.select('CASE WHEN user_my_modules.id IS NOT NULL '\
'THEN true ELSE false END as designated')
.select('CASE WHEN user_my_modules.id IS NOT NULL THEN true ELSE false END as designated')
.order('designated DESC', :full_name)
users = users.map do |user|
next if params[:skip_assigned] && user.designated

View file

@ -34,7 +34,7 @@ module Users
user = User.from_omniauth(auth)
# User found in database so just signing in
return sign_in_and_redirect(user) if user.present?
return sign_in_and_redirect(user, event: :authentication) if user.present?
if email.blank?
# No email in the token so can not link or create user
@ -47,12 +47,12 @@ module Users
if user.blank?
# Create new user and identity
user = create_user_from_auth(email, auth)
sign_in_and_redirect(user)
sign_in_and_redirect(user, event: :authentication)
elsif provider_conf['auto_link_on_sign_in']
# Link to existing local account
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
user.update!(confirmed_at: user.created_at) if user.confirmed_at.blank?
sign_in_and_redirect(user)
sign_in_and_redirect(user, event: :authentication)
else
# Cannot do anything with it, so just return an error
error_message = I18n.t('devise.azure.errors.no_local_user_map')
@ -74,14 +74,13 @@ module Users
def linkedin
auth_hash = request.env['omniauth.auth']
@user = User.from_omniauth(auth_hash)
if @user && @user.current_team_id?
# User already exists and has been signed up with LinkedIn; just sign in
set_flash_message(:notice,
:success,
kind: I18n.t('devise.linkedin.provider_name'))
sign_in_and_redirect @user
sign_in_and_redirect(@user, event: :authentication)
elsif @user
# User already exists and has started sign up with LinkedIn;
# but doesn't have team (needs to complete sign up - agrees to TOS)
@ -89,7 +88,8 @@ module Users
:failure,
kind: I18n.t('devise.linkedin.provider_name'),
reason: I18n.t('devise.linkedin.complete_sign_up'))
redirect_to users_sign_up_provider_path(user: @user)
sign_in(@user, event: :authentication)
redirect_to users_sign_up_provider_path
elsif User.find_by_email(auth_hash['info']['email'])
# email is already taken, so sign up with Linked in is not allowed
set_flash_message(:alert,
@ -123,7 +123,8 @@ module Users
end
# Confirm user
@user.update!(confirmed_at: @user.created_at)
redirect_to users_sign_up_provider_path(user: @user)
sign_in(@user, event: :authentication)
redirect_to users_sign_up_provider_path
end
end
@ -131,7 +132,7 @@ module Users
auth = request.env['omniauth.auth']
user = User.from_omniauth(auth)
# User found in database so just signing in
return sign_in_and_redirect(user) if user.present?
return sign_in_and_redirect(user, event: :authentication) if user.present?
user = User.find_by(email: auth.info.email.downcase)
@ -142,7 +143,7 @@ module Users
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
user.update!(confirmed_at: user.created_at) if user.confirmed_at.blank?
end
sign_in_and_redirect(user)
sign_in_and_redirect(user, event: :authentication)
rescue StandardError => e
Rails.logger.error e.message
Rails.logger.error e.backtrace.join("\n")

View file

@ -152,29 +152,28 @@ class Users::RegistrationsController < Devise::RegistrationsController
end
end
def new_with_provider; end
def new_with_provider
return render_403 unless current_user
return render_403 unless Rails.configuration.x.new_team_on_signup
@team = Team.new
render layout: 'sign_in_halt'
end
def create_with_provider
@user = User.find_by_id(user_provider_params['user'])
return render_403 unless current_user
return render_403 unless Rails.configuration.x.new_team_on_signup
# Create new team for the new user
@team = Team.new(team_provider_params)
@team.created_by = current_user # set created_by for the team
if @team.valid? && @user && Rails.configuration.x.new_team_on_signup
# Set the confirmed_at == created_at IF not using email confirmations
unless Rails.configuration.x.enable_email_confirmations
@user.update!(confirmed_at: @user.created_at)
end
@team.created_by = @user # set created_by for team
@team.save!
if @team.save
# set current team to new user
@user.current_team_id = @team.id
@user.save!
sign_in_and_redirect @user
current_user.update(current_team_id: @team.id)
redirect_to root_path
else
render :new_with_provider
render :new_with_provider, layout: 'sign_in_halt'
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Users
module Settings
class UserSettingsController < ApplicationController
def show
render json: { data: current_user.settings[params[:key]] }
end
def update
settings_params = params.require(:settings)
settings_params.each do |setting|
key = setting[:key]
data = setting[:data]
current_user.settings[key] = data if Extends::WHITELISTED_USER_SETTINGS.include?(key.to_s)
end
if current_user.save
head :ok
else
render json: { error: 'Failed to update settings', details: current_user.errors.full_messages },
status: :unprocessable_entity
end
end
end
end
end

View file

@ -1,91 +0,0 @@
# frozen_string_literal: true
class LabelTemplateDatatable < CustomDatatable
include InputSanitizeHelper
include Rails.application.routes.url_helpers
TABLE_COLUMNS = %w(
label_templates.default
label_templates.name
label_templates.type
label_templates.description
label_templates.modified_by
label_templates.updated_at
label_templates.created_by_user
label_templates.created_at
).freeze
def initialize(view, label_templates)
super(view)
@label_templates = label_templates
end
def sortable_columns
@sortable_columns ||= TABLE_COLUMNS
end
def searchable_columns
@searchable_columns ||= TABLE_COLUMNS
end
private
def data
records.map do |record|
{
'0' => record.id,
'1' => record.default,
'2' => append_format_icon(record),
'3' => escape_input(record.label_format),
'4' => escape_input(record.description),
'5' => escape_input(record.modified_by),
'6' => I18n.l(record.updated_at, format: :full),
'7' => escape_input(record.created_by_user),
'8' => I18n.l(record.created_at, format: :full),
'recordInfoUrl' => '',
'DT_RowAttr': {
'data-edit-url': label_template_path(record),
'data-set-default-url': set_default_label_template_path(record),
'data-default': record.default,
'data-format': record.label_format
}
}
end
end
def append_format_icon(record)
{
icon_image_tag:
ActionController::Base.helpers.image_tag(
"label_template_icons/#{record.icon}.svg",
class: 'label-template-icon'
),
name: escape_input(record.name)
}
end
def get_raw_records
res = @label_templates.joins(
'LEFT OUTER JOIN users AS creators ' \
'ON label_templates.created_by_id = creators.id'
).joins(
'LEFT OUTER JOIN users AS modifiers '\
'ON label_templates.last_modified_by_id = modifiers.id'
).select('label_templates.* AS label_templates')
.select('creators.full_name AS created_by_user')
.select('modifiers.full_name AS modified_by')
.select(
"('#{Extends::LABEL_TEMPLATE_FORMAT_MAP.to_json}'::jsonb -> label_templates.type)::text "\
"AS label_format"
)
LabelTemplate.from(res, :label_templates)
end
def filter_records(records)
records.where_attributes_like(
['label_templates.name', 'label_templates.label_format', 'label_templates.description',
'label_templates.modified_by', 'label_templates.created_by_user'],
dt_params.dig(:search, :value)
)
end
end

View file

@ -1,282 +0,0 @@
# frozen_string_literal: true
class ProtocolsDatatable < CustomDatatable
# Needed for sanitize_sql_like method
include ActiveRecord::Sanitization::ClassMethods
include InputSanitizeHelper
include Rails.application.routes.url_helpers
include Canaid::Helpers::PermissionsHelper
PREFIXED_ID_SQL = "('#{Protocol::ID_PREFIX}' || COALESCE(\"protocols\".\"parent_id\", \"protocols\".\"id\"))"
def_delegator :@view, :linked_children_protocol_path
def_delegator :@view, :protocol_path
def initialize(view, team, type, user)
super(view)
@team = team
@type = type # :active or :archived
@user = user
end
def sortable_columns
@sortable_columns ||= [
'name',
'adjusted_parent_id',
'nr_of_versions',
'protocol_keywords_str',
'nr_of_linked_tasks',
'nr_of_assigned_users',
'full_username_str',
'published_on',
'updated_at',
'archived_full_username_str',
'archived_on'
]
end
def searchable_columns
@searchable_columns ||= [
'Protocol.name',
'Protocol.archived_on',
'Protocol.published_on',
"Protocol.#{PREFIXED_ID_SQL}",
'Protocol.updated_at',
'ProtocolKeyword.name'
]
end
def as_json(_options = {})
{
draw: dt_params[:draw].to_i,
recordsTotal: get_raw_records_base.distinct.count,
recordsFiltered: records.present? ? records.first.filtered_count : 0,
data: data
}
end
# A hack that overrides the new_search_contition method default behavior of the ajax-datatables-rails gem
# now the method checks if the column is the created_at or updated_at and generate a custom SQL to parse
# it back to the caller method
def new_search_condition(column, value)
model, column = column.split('.', 2)
model = model.constantize
case column
when PREFIXED_ID_SQL
casted_column = ::Arel::Nodes::SqlLiteral.new(PREFIXED_ID_SQL)
when 'published_on'
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[ Arel.sql("to_char( protocols.created_at, '#{ formated_date }' ) AS VARCHAR") ] )
when 'updated_at'
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[ Arel.sql("to_char( protocols.updated_at, '#{ formated_date }' ) AS VARCHAR") ] )
else
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[model.arel_table[column.to_sym].as(typecast)])
end
casted_column.matches("%#{ActiveRecord::Base.sanitize_sql_like(value)}%")
end
private
# Returns json of current protocols (already paginated)
def data
records.map do |record|
parent = record.parent || record
{
DT_RowId: record.id,
DT_RowAttr: {
'data-permissions-url': permissions_protocol_path(parent),
'data-versions-url': versions_modal_protocol_path(parent)
},
'1': name_html(parent),
'2': parent.code,
'3': versions_html(record),
'4': keywords_html(record),
'5': modules_html(record),
'6': access_html(parent),
'7': published_by(record),
'8': published_timestamp(record),
'9': modified_timestamp(record),
'10': escape_input(record.archived_full_username_str),
'11': (I18n.l(record.archived_on, format: :full) if record.archived_on)
}
end
end
def fetch_records
super.select('COUNT("protocols"."id") OVER() AS filtered_count')
end
def filter_protocols_records(records)
if params[:name_and_keywords].present?
records = records.where_attributes_like(['protocols.name', 'protocol_keywords.name'], params[:name_and_keywords])
end
if params[:published_on_from].present?
records = records.where('protocols.published_on > ?', params[:published_on_from])
end
records = records.where('protocols.published_on < ?', params[:published_on_to]) if params[:published_on_to].present?
records = records.where('protocols.updated_at > ?', params[:modified_on_from]) if params[:modified_on_from].present?
records = records.where('protocols.updated_at < ?', params[:modified_on_to]) if params[:modified_on_to].present?
records = records.where(protocols: { published_by_id: params[:published_by] }) if params[:published_by].present?
if params[:members].present?
records = records.where(all_user_assignments: { user_id: params[:members] })
end
if params[:archived_on_from].present?
records = records.where('protocols.archived_on > ?', params[:archived_on_from])
end
records = records.where('protocols.archived_on < ?', params[:archived_on_to]) if params[:archived_on_to].present?
records = records.where(protocols: { archived_by_id: params[:archived_by] }) if params[:archived_by].present?
if params[:has_draft].present?
records =
records
.joins("LEFT OUTER JOIN protocols protocol_drafts " \
"ON protocol_drafts.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
"AND (protocol_drafts.parent_id = protocols.id OR protocol_drafts.parent_id = protocols.parent_id)")
.where('protocols.protocol_type = ? OR protocol_drafts.id IS NOT NULL',
Protocol.protocol_types[:in_repository_draft])
end
records
end
def get_raw_records_base
team_protocols = Protocol.where(team: @team)
original_without_versions = team_protocols
.left_outer_joins(:published_versions)
.in_repository_published_original
.where(published_versions: { id: nil })
.select(:id)
published_versions = team_protocols
.in_repository_published_version
.order(:parent_id, version_number: :desc)
.select('DISTINCT ON (parent_id) id')
new_drafts = team_protocols
.where(protocol_type: Protocol.protocol_types[:in_repository_draft], parent_id: nil)
.select(:id)
records = Protocol.where('protocols.id IN (?) OR protocols.id IN (?) OR protocols.id IN (?)',
original_without_versions, published_versions, new_drafts)
records = @type == :archived ? records.archived : records.active
records.viewable_by_user(@user, @team)
end
# Query database for records (this will be later paginated and filtered)
# after that "data" function will return json
def get_raw_records
records =
get_raw_records_base
.preload(:parent, :latest_published_version, :draft, :protocol_keywords, user_assignments: %i(user user_role))
.joins("LEFT OUTER JOIN protocols protocol_versions " \
"ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} " \
"AND protocol_versions.parent_id = protocols.parent_id")
.joins("LEFT OUTER JOIN protocols self_linked_task_protocols " \
"ON self_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
"AND self_linked_task_protocols.parent_id = protocols.id")
.joins("LEFT OUTER JOIN protocols parent_linked_task_protocols " \
"ON parent_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
"AND parent_linked_task_protocols.parent_id = protocols.parent_id")
.joins("LEFT OUTER JOIN protocols version_linked_task_protocols " \
"ON version_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
"AND version_linked_task_protocols.parent_id = protocol_versions.id " \
"AND version_linked_task_protocols.parent_id != protocols.id")
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
.joins('LEFT OUTER JOIN "users" "archived_users" ON "archived_users"."id" = "protocols"."archived_by_id"')
.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" ' \
'ON "all_user_assignments"."assignable_type" = \'Protocol\' ' \
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
.group('"protocols"."id"')
records = filter_protocols_records(records)
records.select(
'"protocols".*',
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
"THEN 0 ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
"END AS nr_of_versions",
'(COUNT(DISTINCT("self_linked_task_protocols"."id")) + ' \
'COUNT(DISTINCT("parent_linked_task_protocols"."id")) + ' \
'COUNT(DISTINCT("version_linked_task_protocols"."id"))) AS nr_of_linked_tasks',
'COUNT(DISTINCT("all_user_assignments"."id")) AS "nr_of_assigned_users"',
'MAX("users"."full_name") AS "full_username_str"', # "Hack" to get single username
'MAX("archived_users"."full_name") AS "archived_full_username_str"'
)
end
# Various helper methods
def name_html(record)
path =
if record.in_repository_published_original? && record.latest_published_version.present?
protocol_path(record.latest_published_version)
else
protocol_path(record)
end
if can_read_protocol_in_repository?(@user, record)
"<a href='#{path}'>#{escape_input(record.name)}</a>"
else
# team admin can only see recod name
"<span class='not-clickable-record'>#{escape_input(record.name)}</span>"
end
end
def keywords_html(record)
if record.protocol_keywords.blank?
I18n.t('protocols.no_keywords')
else
res = []
record.protocol_keywords.sort_by { |kw| kw.name.downcase }.each do |kw|
sanitized_kw = escape_input(kw.name)
res << "<a href='#' data-action='filter' data-param='#{sanitized_kw}'>#{sanitized_kw}</a>"
end
res.join(', ')
end
end
def modules_html(record)
"<a href='#' data-action='load-linked-children'" \
"data-url='#{linked_children_protocol_path(record.parent || record)}'>" \
"#{record.nr_of_linked_tasks}" \
"</a>"
end
def versions_html(record)
@view.controller
.render_to_string(partial: 'protocols/index/protocol_versions',
formats: :html,
locals: { protocol: record, readable: can_read_protocol_in_repository?(@user, record) })
end
def access_html(record)
@view.controller.render_to_string(partial: 'protocols/index/protocol_access', formats: :html, locals: { protocol: record })
end
def published_by(record)
return '' if record.published_by.blank?
escape_input(record.published_by.full_name)
end
def published_timestamp(record)
return '' if record.published_on.blank?
I18n.l(record.published_on, format: :full)
end
def modified_timestamp(record)
I18n.l(record.updated_at, format: :full)
end
end

View file

@ -68,11 +68,15 @@ module GlobalActivitiesHelper
path = repository_path(obj.repository, team: obj.repository.team.id)
when Project
path = obj.archived? ? projects_path : project_path(obj)
path = obj.archived? ? projects_path : experiments_path(project_id: obj)
when Experiment
return current_value unless obj.navigable?
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : my_modules_experiment_path(obj)
path = if obj.archived?
experiments_path(project_id: obj.project, view_mode: :archived)
else
my_modules_experiment_path(obj)
end
when MyModule
return current_value unless obj.navigable?

View file

@ -138,7 +138,7 @@ module MyModulesHelper
{
type: project.class.name.underscore,
value: project.name,
url: project_path(project, view_mode: archived ? 'archived' : 'active'),
url: experiments_path(project_id: project, view_mode: archived ? 'archived' : 'active'),
archived: archived
}
end

View file

@ -30,34 +30,13 @@ module ProjectsHelper
conns.to_s[1..-2]
end
def sidebar_folders_tree(team, user, sort, folders_only: false)
def folders_tree(team, user)
sort ||= team.current_view_state(user).state.dig('projects', 'active', 'sort')
if projects_view_mode_archived?
records = ProjectFolder.archived.inner_folders(team)
records += team.projects.archived.visible_to(user, team) unless folders_only
records = ProjectFolder.archived.inner_folders(team).order(:name).select(:id, :name, :parent_folder_id)
else
records = ProjectFolder.active.inner_folders(team)
records += team.projects.active.visible_to(user, team) unless folders_only
sort = 'new' if %w(archived_old archived_new).include?(sort)
records = ProjectFolder.active.inner_folders(team).order(:name).select(:id, :name, :parent_folder_id)
end
records = case sort
when 'new'
records.sort_by(&:created_at).reverse!
when 'old'
records.sort_by(&:created_at)
when 'atoz'
records.sort_by { |c| c.name.downcase }
when 'ztoa'
records.sort_by { |c| c.name.downcase }.reverse!
when 'id_asc'
records.sort_by(&:id)
when 'id_desc'
records.sort_by(&:id).reverse!
when 'archived_old'
records.sort_by(&:archived_on)
when 'archived_new'
records.sort_by(&:archived_on).reverse!
end
folders_recursive_builder(nil, records)
end

View file

@ -1 +1,3 @@
@import "bootstrap-select/sass/bootstrap-select";
@import "ag-grid-community/styles/ag-grid.css";
@import "ag-grid-community/styles/ag-theme-alpine.css";

View file

@ -0,0 +1,45 @@
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import SelectDropdown from '../../../vue/shared/select_dropdown.vue';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import { mountWithTurbolinks } from '../helpers/turbolinks.js';
const app = createApp({
data() {
return {
size: 'md',
}
},
computed: {
simpleOptions() {
return [
['1', 'One', { icon: 'sn-icon-edit' }],
['2', 'Two', { icon: 'sn-icon-drag' }],
['3', 'Three', { icon: 'sn-icon-delete' }],
['4', 'Four', { icon: 'sn-icon-visibility-show' }],
['5', 'Five', { icon: 'sn-icon-edit' }],
['6', 'Six', { icon: 'sn-icon-locked-task' }],
['7', 'Seven', { icon: 'sn-icon-drag' }],
['8', 'Eight', { icon: 'sn-icon-delete' }],
['9', 'Nine', { icon: 'sn-icon-edit' }],
['10', 'Ten', { icon: 'sn-icon-close' }],
];
},
longOptions() {
return [
['1', 'Very long long long option and label to test responsivness'],
['2', 'Two'],
['3', 'Three'],
['4', 'Four'],
]
},
renderer() {
return (option) => {
return `<span class="flex items-center gap-2"><i class="sn-icon ${option[2].icon}"></i> ${option[1]}</span>`;
}
}
},
});
app.component('SelectDropdown', SelectDropdown);
app.config.globalProperties.i18n = window.I18n;
app.use(PerfectScrollbar);
mountWithTurbolinks(app, '#selects');

View file

@ -0,0 +1,10 @@
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import ExperimentsList from '../../vue/experiments/list.vue';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
const app = createApp();
app.component('ExperimentsList', ExperimentsList);
app.config.globalProperties.i18n = window.I18n;
app.use(PerfectScrollbar);
mountWithTurbolinks(app, '#ExperimentsList');

View file

@ -0,0 +1,10 @@
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import LabelTemplatesTable from '../../vue/label_template/table.vue';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
const app = createApp();
app.component('LabelTemplatesTable', LabelTemplatesTable);
app.config.globalProperties.i18n = window.I18n;
app.use(PerfectScrollbar);
mountWithTurbolinks(app, '#labelTemplatesTable');

Some files were not shown because too many files have changed in this diff Show more