Merge branch 'develop' into features/ui-revamp

This commit is contained in:
Anton 2023-06-14 15:18:50 +02:00
commit d795f996f2
47 changed files with 516 additions and 286 deletions

View file

@ -68,40 +68,40 @@ GIT
GEM
remote: http://rubygems.org/
specs:
actioncable (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
actioncable (6.1.7.3)
actionpack (= 6.1.7.3)
activesupport (= 6.1.7.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
actionmailbox (6.1.7.3)
actionpack (= 6.1.7.3)
activejob (= 6.1.7.3)
activerecord (= 6.1.7.3)
activestorage (= 6.1.7.3)
activesupport (= 6.1.7.3)
mail (>= 2.7.1)
actionmailer (6.1.7.1)
actionpack (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activesupport (= 6.1.7.1)
actionmailer (6.1.7.3)
actionpack (= 6.1.7.3)
actionview (= 6.1.7.3)
activejob (= 6.1.7.3)
activesupport (= 6.1.7.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.7.1)
actionview (= 6.1.7.1)
activesupport (= 6.1.7.1)
actionpack (6.1.7.3)
actionview (= 6.1.7.3)
activesupport (= 6.1.7.3)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.7.1)
actionpack (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
actiontext (6.1.7.3)
actionpack (= 6.1.7.3)
activerecord (= 6.1.7.3)
activestorage (= 6.1.7.3)
activesupport (= 6.1.7.3)
nokogiri (>= 1.8.5)
actionview (6.1.7.1)
activesupport (= 6.1.7.1)
actionview (6.1.7.3)
activesupport (= 6.1.7.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -111,24 +111,24 @@ GEM
activemodel (>= 4.1, < 6.2)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (6.1.7.1)
activesupport (= 6.1.7.1)
activejob (6.1.7.3)
activesupport (= 6.1.7.3)
globalid (>= 0.3.6)
activemodel (6.1.7.1)
activesupport (= 6.1.7.1)
activerecord (6.1.7.1)
activemodel (= 6.1.7.1)
activesupport (= 6.1.7.1)
activemodel (6.1.7.3)
activesupport (= 6.1.7.3)
activerecord (6.1.7.3)
activemodel (= 6.1.7.3)
activesupport (= 6.1.7.3)
activerecord-import (1.0.7)
activerecord (>= 3.2)
activestorage (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activesupport (= 6.1.7.1)
activestorage (6.1.7.3)
actionpack (= 6.1.7.3)
activejob (= 6.1.7.3)
activerecord (= 6.1.7.3)
activesupport (= 6.1.7.3)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (6.1.7.1)
activesupport (6.1.7.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -235,7 +235,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.12.2)
concurrent-ruby (1.1.10)
concurrent-ruby (1.2.2)
crack (0.4.5)
rexml
crass (1.0.6)
@ -293,7 +293,7 @@ GEM
discard (1.2.0)
activerecord (>= 4.2, < 7)
docile (1.3.5)
doorkeeper (5.4.0)
doorkeeper (5.6.6)
railties (>= 5)
down (5.2.0)
addressable (~> 2.5)
@ -321,7 +321,7 @@ GEM
raabro (~> 1.4)
generator (0.0.1)
gherkin (5.1.0)
globalid (1.0.1)
globalid (1.1.0)
activesupport (>= 5.0)
graphviz (1.2.1)
process-pipeline
@ -331,7 +331,7 @@ GEM
httparty (0.21.0)
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
i18n (1.12.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
i18n-js (3.8.0)
i18n (>= 0.6.6)
@ -382,10 +382,10 @@ GEM
logging (2.0.0)
little-plugger (~> 1.1)
multi_json (~> 1.10)
loofah (2.19.1)
loofah (2.21.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.8.0.1)
nokogiri (>= 1.12.0)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
@ -397,7 +397,8 @@ GEM
mime-types-data (3.2022.0105)
mini_magick (4.11.0)
mini_mime (1.1.2)
minitest (5.17.0)
mini_portile2 (2.8.2)
minitest (5.18.0)
momentjs-rails (2.17.1)
railties (>= 3.1)
msgpack (1.4.2)
@ -418,8 +419,11 @@ GEM
net-smtp (0.3.3)
net-protocol
newrelic_rpm (8.16.0)
nio4r (2.5.8)
nokogiri (1.14.3-x86_64-linux)
nio4r (2.5.9)
nokogiri (1.14.5)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.14.5-x86_64-linux)
racc (~> 1.4)
oauth2 (2.0.9)
faraday (>= 0.17.3, < 3.0)
@ -473,30 +477,30 @@ GEM
puma (5.6.4)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.2)
rack (2.2.6.4)
racc (1.7.0)
rack (2.2.7)
rack-attack (6.4.0)
rack (>= 1.0, < 3)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-protection (3.0.1)
rack
rack-test (2.0.2)
rack-test (2.1.0)
rack (>= 1.3)
rails (6.1.7.1)
actioncable (= 6.1.7.1)
actionmailbox (= 6.1.7.1)
actionmailer (= 6.1.7.1)
actionpack (= 6.1.7.1)
actiontext (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activemodel (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
rails (6.1.7.3)
actioncable (= 6.1.7.3)
actionmailbox (= 6.1.7.3)
actionmailer (= 6.1.7.3)
actionpack (= 6.1.7.3)
actiontext (= 6.1.7.3)
actionview (= 6.1.7.3)
activejob (= 6.1.7.3)
activemodel (= 6.1.7.3)
activerecord (= 6.1.7.3)
activestorage (= 6.1.7.3)
activesupport (= 6.1.7.3)
bundler (>= 1.15.0)
railties (= 6.1.7.1)
railties (= 6.1.7.3)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -505,8 +509,9 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
@ -514,9 +519,9 @@ GEM
rails (> 3.1)
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
railties (6.1.7.3)
actionpack (= 6.1.7.3)
activesupport (= 6.1.7.3)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -625,14 +630,16 @@ GEM
generator
tailwindcss-rails (2.0.27)
railties (>= 6.0.0)
thor (1.2.1)
tailwindcss-rails (2.0.27-x86_64-linux)
railties (>= 6.0.0)
thor (1.2.2)
tilt (2.0.10)
timecop (0.9.2)
timeout (0.3.1)
timeout (0.3.2)
turbolinks (5.1.1)
turbolinks-source (~> 5.1)
turbolinks-source (5.2.0)
tzinfo (2.0.5)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
@ -656,11 +663,12 @@ GEM
activesupport
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.6)
zeitwerk (2.6.8)
zip-zip (0.3)
rubyzip (>= 1.0.0)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES

View file

@ -1 +1 @@
1.27.1.2
1.27.2

View file

@ -43,9 +43,6 @@
// forms with clicking on links outside form in cases when other than
// GET method is used.
// eslint-disable-next-line no-unused-vars
var formatJS = $('body').data('datetime-picker-format-date-only');
function initFormSubmitLinks(el) {
el = el || $(document.body);

View file

@ -83,6 +83,8 @@ var DasboardCalendarWidget = (function() {
};
}());
var formatJS;
$(document).on('turbolinks:load', function() {
DasboardCalendarWidget.init();
formatJS = $('body').data('datetime-picker-format');
});

View file

@ -1,10 +1,11 @@
/* global I18n formatJS */
/* global I18n */
(function() {
$('.datetime-picker-container').each(function() {
const id = $(this).data('id');
if (id) {
const dt = $(`#calendar-${id}`);
const useCurrent = $(this).data('use-current');
const formatJS = $(this).data('datetime-picker-format');
dt.datetimepicker({
useCurrent, ignoreReadonly: true, locale: I18n.locale, format: formatJS
});

View file

@ -783,10 +783,9 @@ var MyModuleRepositories = (function() {
SELECTED_ROWS = {};
$(FULL_VIEW_TABLE.table().container()).find('.dataTable')
.attr('data-assigned-items-count', data.rows_count);
FULL_VIEW_TABLE.ajax.reload(null, false);
reloadRepositoriesList(data.repository_id, true);
updateFullViewRowsCount(data.rows_count);
renderFullViewAssignButtons();
FULL_VIEW_MODAL.modal('hide');
},
error: function(response) {
if (response.status === 403) {

View file

@ -76,17 +76,25 @@ var ProjectsIndex = (function() {
// Modal's submit handler function
$(projectsWrapper)
.on('ajax:success', newProjectModal, function(ev, data) {
.on('submit', '#new_project', function() {
$('#project_name').prop('disabled', true);
$('#new-project-modal button[type="submit"]').prop('disabled', true);
})
.on('ajax:complete', '#new_project', function() {
$('#project_name').prop('disabled', false);
$('#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) {
.on('ajax:error', newProjectModal, function(_ev, data) {
$(this).renderFormErrors('project', data.responseJSON);
});
$(projectsWrapper)
.on('ajax:success', '.new-project-btn', function(ev, data) {
.on('ajax:success', '.new-project-btn', function(_ev, data) {
// Add and show modal
$(projectsWrapper).append($.parseHTML(data.html));
$(newProjectModal).modal('show');

View file

@ -1065,9 +1065,15 @@ function reportHandsonTableConverter() {
type: 'POST',
data: JSON.stringify(getReportData()),
contentType: 'application/json; charset=utf-8',
beforeSend: function() {
$('.generate-button').prop('disabled', true);
},
success: function() {},
error: function(jqxhr) {
HelperModule.flashAlertMsg(jqxhr.responseJSON.join(' '), 'danger');
},
ajaxComplete: function() {
$('.generate-button').prop('disabled', false);
}
});
});

View file

@ -115,25 +115,35 @@ $.fn.dataTable.render.editRepositoryChecklistValue = function(formId, columnId,
};
$.fn.dataTable.render.editRepositoryNumberValue = function(formId, columnId, cell, $header) {
let $cell = $(cell.node());
let decimals = $header.data('metadata-decimals');
const $cell = $(cell.node());
const decimals = $header.data('metadata-decimals');
let number = $cell.find('.number-value').data('value');
if (!number) number = '';
$cell.html(`
<div class="sci-input-container text-field error-icon">
<input class="sci-input-field"
form="${formId}"
type="text"
oninput="regexp = ${decimals} === 0 ? /[^0-9]/g : /[^0-9.]/g
this.value = this.value.replace(regexp, '');
this.value = this.value.match(/^\\d*(\\.\\d{0,${decimals}})?/)[0];"
name="repository_cells[${columnId}]"
placeholder="${I18n.t('repositories.table.number.enter_number')}"
value="${number}"
data-type="RepositoryNumberValue">
</div>`);
let $input = $('<input>', {
class: 'sci-input-field',
form: formId,
type: 'text',
name: 'repository_cells[' + columnId + ']',
placeholder: I18n.t('repositories.table.number.enter_number'),
value: number,
'data-type': 'RepositoryNumberValue'
});
$input.on('input', function() {
const regexp = decimals === 0 ? /[^0-9]/g : /[^0-9.]/g;
const decimalsRegex = new RegExp(`^\\d*(\\.\\d{0,${decimals}})?`);
let value = this.value;
value = value.replace(regexp, '');
value = value.match(decimalsRegex)[0];
this.value = value;
});
let $div = $('<div>', {
class: 'sci-input-container text-field error-icon'
}).append($input);
$cell.html($div);
};
$.fn.dataTable.render.editRepositoryStockValue = function(formId, columnId, cell) {

View file

@ -50,20 +50,31 @@ $.fn.dataTable.render.newRepositoryChecklistValue = function(formId, columnId, $
};
$.fn.dataTable.render.newRepositoryNumberValue = function(formId, columnId, $cell, $header) {
let decimals = $header.data('metadata-decimals');
const decimals = $header.data('metadata-decimals');
$cell.html(`
<div class="sci-input-container text-field error-icon">
<input class="sci-input-field"
form="${formId}"
type="text"
oninput="this.value = this.value.replace(/[^0-9.]/g, '');
this.value = this.value.match(/^\\d*(\\.\\d{0,${decimals}})?/)[0];"
name="repository_cells[${columnId}]"
value=""
placeholder="${I18n.t('repositories.table.number.enter_number')}"
data-type="RepositoryNumberValue">
</div>`);
let $input = $('<input>', {
class: 'sci-input-field',
form: formId,
type: 'text',
name: 'repository_cells[' + columnId + ']',
value: '',
placeholder: I18n.t('repositories.table.number.enter_number'),
'data-type': 'RepositoryNumberValue'
});
$input.on('input', function() {
const decimalsRegex = new RegExp(`^\\d*(\\.\\d{0,${decimals}})?`);
let value = this.value;
value = value.replace(/[^0-9.]/g, '');
value = value.match(decimalsRegex)[0];
this.value = value;
});
let $div = $('<div>', {
class: 'sci-input-container text-field error-icon'
}).append($input);
$cell.html($div);
};
$.fn.dataTable.render.newRepositoryDateTimeValue = function(formId, columnId, $cell) {

View file

@ -26,6 +26,19 @@
animateSpinner(null, false);
ShareModal.init();
}
if (['rename-repo-modal', 'copy-repo-modal'].includes($(this).attr('id'))) {
$(this).find('form')
.on('ajax:success', function(_e, data) {
if (data.url) {
window.location = data.url;
} else {
window.location.reload();
}
})
.on('ajax:error', function(_e, data) {
$(this).renderFormErrors('repository', data.responseJSON);
});
}
$(this).find('.selectpicker').selectpicker();
})
.on('hidden.bs.modal', function() {

View file

@ -716,7 +716,9 @@ var dropdownSelector = (function() {
} else {
// Or delete specific one
deleteValue(selector, container, tagLabel.data('ds-tag-id'), tagLabel.data('ds-tag-group'));
removeOptionFromSelector(selector, tagLabel.data('ds-tag-id'));
if (selector.data('config').tagClass) {
removeOptionFromSelector(selector, tagLabel.data('ds-tag-id'));
}
}
}, 350);
}
@ -1010,7 +1012,9 @@ var dropdownSelector = (function() {
currentData = getCurrentData($(selector).next());
currentData.push(value);
setData($(selector), currentData, skip_event);
appendOptionToSelector(selector, value);
if (selector.data('config').tagClass) {
appendOptionToSelector(selector, value);
}
return this;
},

View file

@ -2,22 +2,34 @@
.menu-item:not(.active):hover {
background-color: var(--sn-super-light-grey);
a:not(.no-hover) {
:not(.no-hover) {
color: var(--sn-blue);
}
a.no-hover {
.no-hover {
color: var(--sn-science-blue-hover);
}
}
.menu-item.active:hover {
a:not(.no-hover) {
:not(.no-hover) {
color: var(--sn-blue);
}
a.no-hover {
.no-hover {
color: var(--sn-science-blue-hover);
}
}
.menu-item,
.menu-item:not(.active):hover,
.menu-item.active:hover {
.disabled-link,
.disabled-link:not(.no-hover):hover,
.disabled-link.no-hover:hover {
color: var(--sn-grey);
pointer-events: none;
text-decoration: none;
}
}
}

View file

@ -747,6 +747,16 @@ li.module-hover {
a {
color: inherit;
&.disabled-link {
color: var(--sn-grey);
pointer-events: none;
text-decoration: none;
.name {
color: var(--sn-grey);
}
}
}
.name {
@ -872,7 +882,7 @@ li.module-hover {
animation-duration: 2s;
animation-iteration-count: infinite;
animation-name: placeholder-pulsing;
background-color: $color-alto;
background-color: var(--sn-sleepy-grey);
border-radius: $border-radius-default;
height: 18px;

View file

@ -447,22 +447,22 @@ class ExperimentsController < ApplicationController
end
def inventory_assigning_experiment_filter
readable_experiments = Experiment.readable_by_user(current_user)
viewable_experiments = Experiment.viewable_by_user(current_user, current_team)
assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user)
project = Project.readable_by_user(current_user)
project = Project.viewable_by_user(current_user, current_team)
.joins(experiments: :my_modules)
.where(experiments: { id: readable_experiments })
.where(experiments: { id: viewable_experiments })
.where(my_modules: { id: assignable_my_modules })
.find_by(id: params[:project_id])
return render_404 if project.blank?
experiments = project.experiments
.active
.joins(:my_modules)
.where(experiments: { id: readable_experiments })
.where(experiments: { id: viewable_experiments })
.where(my_modules: { id: assignable_my_modules })
.search(current_user, false, params[:query], 1, current_team)
.distinct
.pluck(:id, :name)

View file

@ -452,12 +452,12 @@ class MyModulesController < ApplicationController
end
def inventory_assigning_my_module_filter
readable_experiments = Experiment.readable_by_user(current_user)
viewable_experiments = Experiment.viewable_by_user(current_user, current_team)
assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user)
experiment = Experiment.readable_by_user(current_user)
experiment = Experiment.viewable_by_user(current_user, current_team)
.joins(:my_modules)
.where(experiments: { id: readable_experiments })
.where(experiments: { id: viewable_experiments })
.where(my_modules: { id: assignable_my_modules })
.find_by(id: params[:experiment_id])
@ -465,8 +465,6 @@ class MyModulesController < ApplicationController
my_modules = experiment.my_modules
.where(my_modules: { id: assignable_my_modules })
.distinct
.search(current_user, false, params[:query], 1, current_team)
.pluck(:id, :name)
return render plain: [].to_json if my_modules.blank?

View file

@ -12,7 +12,8 @@ module Navigator
archived: project.archived,
type: :project,
has_children: project.has_children,
children_url: navigator_project_path(project)
children_url: navigator_project_path(project),
disabled: project.disabled
}
end
@ -69,25 +70,35 @@ module Navigator
(projects.archived IS TRUE AND experiments.id IS NOT NULL)
THEN 1 ELSE 0 END) > 0 AS has_children'
end
disabled_sql = 'SUM(CASE WHEN project_user_roles IS NULL THEN 0 ELSE 1 END) < 1 AS disabled'
current_team.projects
.where(project_folder_id: folder)
.visible_to(current_user, current_team)
.with_children_viewable_by_user(current_user)
.where('
projects.archived = :archived OR
(
(
experiments.archived = :archived OR
my_modules.archived = :archived
) AND
:archived IS TRUE
) OR
projects.id = :project_id
', archived: archived, project_id: project&.id || -1)
.joins("LEFT OUTER JOIN user_assignments project_user_assignments
ON project_user_assignments.assignable_type = 'Project'
AND project_user_assignments.assignable_id = projects.id
AND project_user_assignments.user_id = #{current_user.id}
LEFT OUTER JOIN user_roles project_user_roles
ON project_user_roles.id = project_user_assignments.user_role_id
AND project_user_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[]")
.where('projects.archived = :archived OR
(
(
experiments.archived = :archived OR
my_modules.archived = :archived
) AND
:archived IS TRUE
) OR
projects.id = :project_id',
archived: archived,
project_id: project&.id || -1)
.select(
'projects.id',
'projects.name',
'projects.archived',
disabled_sql,
has_children_sql
).group('projects.id')
end

View file

@ -107,14 +107,14 @@ class ProjectsController < ApplicationController
end
def inventory_assigning_project_filter
readable_experiments = Experiment.readable_by_user(current_user)
viewable_experiments = Experiment.viewable_by_user(current_user, current_team)
assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user)
projects = Project.readable_by_user(current_user)
projects = Project.viewable_by_user(current_user, current_team)
.active
.joins(experiments: :my_modules)
.where(experiments: { id: readable_experiments })
.where(experiments: { id: viewable_experiments })
.where(my_modules: { id: assignable_my_modules })
.search(current_user, false, params[:query], 1, current_team)
.distinct
.pluck(:id, :name)
@ -167,64 +167,70 @@ class ProjectsController < ApplicationController
end
def update
@project.assign_attributes(project_update_params)
return_error = false
flash_error = t('projects.update.error_flash', name: escape_input(@project.name))
return render_403 unless can_manage_project?(@project) || @project.archived_changed?
# Check archive permissions if archiving/restoring
if project_params.include? :archived
if (project_params[:archived] == 'true' &&
!can_archive_project?(@project)) ||
(project_params[:archived] == 'false' &&
!can_restore_project?(@project))
if @project.archived_changed? &&
((@project.archived == 'true' && !can_archive_project?(@project)) ||
(@project.archived == 'false' && !can_restore_project?(@project)))
return_error = true
is_archive = project_params[:archived] == 'true' ? 'archive' : 'restore'
is_archive = @project.archived? ? 'archive' : 'restore'
flash_error =
t("projects.#{is_archive}.error_flash", name: escape_input(@project.name))
end
elsif !can_manage_project?(@project)
render_403 && return
end
message_renamed = nil
message_visibility = nil
if (project_params.include? :name) &&
(project_params[:name] != @project.name)
message_renamed = true
end
if (project_params.include? :visibility) &&
(project_params[:visibility] != @project.visibility)
message_visibility = if project_params[:visibility] == 'visible'
t('projects.activity.visibility_visible')
else
t('projects.activity.visibility_hidden')
end
message_renamed = @project.name_changed?
message_visibility = if @project.visibility_changed?
nil
elsif @project.visible?
t('projects.activity.visibility_visible')
else
t('projects.activity.visibility_hidden')
end
message_archived = if !@project.archived_changed?
nil
elsif @project.archived?
'archive'
else
'restore'
end
default_public_user_name = nil
if @project.visibility_changed? && @project.default_public_user_role_id_changed?
default_public_user_name = UserRole.find(project_params[:default_public_user_role_id])&.name
end
@project.last_modified_by = current_user
if !return_error && @project.update(project_params)
# Add activities if needed
if !return_error && @project.save
# Add activities if needed
log_activity(:change_project_visibility, @project, visibility: message_visibility) if message_visibility.present?
log_activity(:rename_project) if message_renamed.present?
log_activity(:archive_project) if project_params[:archived] == 'true'
log_activity(:restore_project) if project_params[:archived] == 'false'
log_activity(:archive_project) if message_archived == 'archive'
log_activity(:restore_project) if message_archived == 'restore'
if default_public_user_name.present?
log_activity(:project_access_changed_all_team_members,
@project,
{ team: @project.team.id, role: default_public_user_name })
end
flash_success = t('projects.update.success_flash', name: escape_input(@project.name))
if project_params[:archived] == 'true'
if message_archived == 'archive'
flash_success = t('projects.archive.success_flash', name: escape_input(@project.name))
elsif project_params[:archived] == 'false'
elsif message_archived == 'restore'
flash_success = t('projects.restore.success_flash', name: escape_input(@project.name))
end
respond_to do |format|
format.html do
# Redirect URL for archive view is different as for other views.
if project_params[:archived] == 'false'
# The project should be restored
@project.restore(current_user) unless @project.archived
elsif @project.archived
# The project should be archived
@project.archive(current_user)
end
@project.restore(current_user) if message_archived == 'restore'
@project.archive(current_user) if message_archived == 'archive'
redirect_to projects_path
flash[:success] = flash_success
end
@ -404,12 +410,17 @@ class ProjectsController < ApplicationController
def project_params
params.require(:project)
.permit(
:name, :team_id, :visibility,
:name, :visibility,
:archived, :project_folder_id,
:default_public_user_role_id
)
end
def project_update_params
params.require(:project)
.permit(:name, :visibility, :archived, :default_public_user_role_id)
end
def view_type_params
params.require(:project).require(:view_type)
end

View file

@ -382,6 +382,16 @@ class RepositoriesController < ApplicationController
end
end
def export_repositories
repositories = Repository.viewable_by_user(current_user, current_team).where(id: params[:repository_ids])
if repositories.present?
RepositoriesExportJob.perform_later(repositories.pluck(:id), current_user, current_team)
render json: { message: t('zip_export.export_request_success') }
else
render json: { message: t('zip_export.export_error') }, status: :unprocessable_entity
end
end
def assigned_my_modules
my_modules = MyModule.joins(:repository_rows).where(repository_rows: { repository: @repository })
.readable_by_user(current_user).distinct

View file

@ -1,8 +1,11 @@
import TurbolinksAdapter from 'vue-turbolinks';
import Vue from 'vue/dist/vue.esm';
import AssignItemsToTaskModalContainer from '../../vue/assign_items_to_tasks_modal/container.vue';
import PerfectScrollbar from 'vue2-perfect-scrollbar';
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
Vue.use(TurbolinksAdapter);
Vue.use(PerfectScrollbar);
Vue.prototype.i18n = window.I18n;
function initAssignItemsToTaskModalComponent() {

View file

@ -43,6 +43,7 @@
ref="projectsSelector"
@change="changeProject"
:options="projects"
:isLoading="projectsLoading"
:placeholder="
i18n.t(
'repositories.modal_assign_items_to_task.body.project_select.placeholder'
@ -76,6 +77,7 @@
ref="experimentsSelector"
@change="changeExperiment"
:options="experiments"
:isLoading="experimentsLoading"
:placeholder="experimentsSelectorPlaceholder"
:no-options-placeholder="
i18n.t(
@ -105,6 +107,7 @@
ref="tasksSelector"
@change="changeTask"
:options="tasks"
:isLoading="tasksLoading"
:placeholder="tasksSelectorPlaceholder"
:no-options-placeholder="
i18n.t(
@ -153,6 +156,9 @@ export default {
selectedProject: null,
selectedExperiment: null,
selectedTask: null,
projectsLoading: null,
experimentsLoading: null,
tasksLoading: null,
showCallback: null
};
},
@ -164,12 +170,16 @@ export default {
},
mounted() {
$(this.$refs.modal).on("shown.bs.modal", () => {
this.projectsLoading = true;
$.get(this.projectURL, data => {
if (Array.isArray(data)) {
this.projects = data;
return false;
}
this.projects = [];
}).always(() => {
this.projectsLoading = false;
});
});
@ -240,24 +250,30 @@ export default {
this.resetExperimentSelector();
this.resetTaskSelector();
this.experimentsLoading = true;
$.get(this.experimentURL, data => {
if (Array.isArray(data)) {
this.experiments = data;
return false;
}
this.experiments = [];
}).always(() => {
this.experimentsLoading = false;
});
},
changeExperiment(value) {
this.selectedExperiment = value;
this.resetTaskSelector();
this.tasksLoading = true;
$.get(this.taskURL, data => {
if (Array.isArray(data)) {
this.tasks = data;
return false;
}
this.tasks = [];
}).always(() => {
this.tasksLoading = false;
});
},
changeTask(value) {

View file

@ -92,7 +92,10 @@
this.buttonOverflow = false;
this.$nextTick(() => {
if (!this.$el.getBoundingClientRect) return;
if (
!(this.$el.getBoundingClientRect &&
document.querySelector('.sn-action-toolbar__action:last-child'))
) return;
let containerRect = this.$el.getBoundingClientRect();
let lastActionRect = document.querySelector('.sn-action-toolbar__action:last-child').getBoundingClientRect();

View file

@ -17,7 +17,8 @@
class="text-ellipsis overflow-hidden hover:no-underline pr-3"
:class="{
'text-sn-science-blue-hover': (!item.archived && archived),
'no-hover': (!item.archived && archived)
'no-hover': (!item.archived && archived),
'disabled-link': item.disabled
}">
<template v-if="item.archived">(A)</template>
{{ item.name }}
@ -62,7 +63,7 @@ export default {
},
computed: {
hasChildren: function() {
return this.item.has_children || this.children.length > 0;
return !this.item.disabled && (this.item.has_children || this.children.length > 0);
},
sortedMenuItems: function() {
return this.children.sort((a, b) => {

View file

@ -95,7 +95,8 @@
editingTable: false,
tableObject: null,
nameModalOpen: false,
reloadHeader: 0
reloadHeader: 0,
updatingTableData: false
}
},
computed: {
@ -104,10 +105,10 @@
}
},
updated() {
this.loadTableData();
if(!this.updatingTableData) this.loadTableData();
},
beforeUpdate() {
this.tableObject.destroy();
if(!this.updatingTableData) this.tableObject.destroy();
},
mounted() {
this.loadTableData();
@ -130,6 +131,7 @@
},
disableTableEdit() {
this.editingTable = false;
this.updatingTableData = false;
},
enableNameEdit() {
this.editingName = true;
@ -203,6 +205,7 @@
}
this.$emit('update', this.element)
this.ajax_update_url()
this.updatingTableData = false;
},
ajax_update_url() {
$.ajax({
@ -229,7 +232,10 @@
formulas: formulasEnabled,
preventOverflow: 'horizontal',
readOnly: !this.editingTable,
afterUnlisten: () => setTimeout(this.updateTable, 100) // delay makes cancel button work
afterUnlisten: () => {
this.updatingTableData = true;
setTimeout(this.updateTable, 100) // delay makes cancel button work
}
});
}
}

View file

@ -14,7 +14,7 @@
<template v-if="options.length">
<div
v-for="option in options"
:key="option[0]" @click="setValue(option[0])"
:key="option[0]" @mousedown.prevent.stop="setValue(option[0])"
class="sn-select__option"
>
{{ option[1] }}
@ -47,7 +47,8 @@
data() {
return {
isOpen: false,
optionPositionStyle: ''
optionPositionStyle: '',
blurPrevented: false
}
},
computed: {
@ -64,13 +65,27 @@
document.addEventListener("scroll", this.updateOptionPosition);
},
methods: {
preventBlur() {
this.blurPrevented = true;
},
allowBlur() {
setTimeout(() => { this.blurPrevented = false }, 200);
},
blur() {
setTimeout(() => {
this.isOpen = false;
this.$emit('blur');
}, 200);
if (this.blurPrevented) {
this.focusElement.focus();
} else {
this.isOpen = false;
this.$emit('blur');
}
}, 100);
},
toggle() {
if (this.isOpen && this.blurPrevented) {
return;
}
this.isOpen = !this.isOpen;
if (this.isOpen) {
@ -80,6 +95,7 @@
});
this.$refs.optionsContainer.scrollTop = 0;
this.updateOptionPosition();
this.setUpBlurHandlers();
} else {
this.optionPositionStyle = '';
this.$emit('close');
@ -104,6 +120,13 @@
}
this.optionPositionStyle = `position: fixed; top: ${top}px; left: ${left}px; width: ${width}px`
},
setUpBlurHandlers() {
setTimeout(() => {
this.$refs.optionsContainer.$el.querySelector('.ps__thumb-y').addEventListener('mousedown', this.preventBlur);
this.$refs.optionsContainer.$el.querySelector('.ps__thumb-y').addEventListener('mouseup', this.allowBlur);
document.addEventListener('mouseup', this.allowBlur);
}, 100);
}
}
}

View file

@ -4,7 +4,7 @@
:value="value"
:options="currentOptions"
:placeholder="placeholder"
:noOptionsPlaceholder="noOptionsPlaceholder"
:noOptionsPlaceholder="isLoading ? i18n.t('general.loading') : noOptionsPlaceholder"
v-bind:disabled="disabled"
@change="change"
@blur="blur"
@ -29,7 +29,8 @@
placeholder: { type: String },
searchPlaceholder: { type: String },
noOptionsPlaceholder: { type: String },
disabled: { type: Boolean }
disabled: { type: Boolean },
isLoading: { type: Boolean, default: false }
},
components: { Select },
data() {

View file

@ -0,0 +1,98 @@
# frozen_string_literal: true
class RepositoriesExportJob < ApplicationJob
include StringUtility
def perform(repository_ids, user, team)
@user = user
@team = team
@repositories = Repository.viewable_by_user(@user, @team).where(id: repository_ids).order(:id)
zip_input_dir = FileUtils.mkdir_p(Rails.root.join("tmp/temp_zip_#{Time.now.to_i}")).first
zip_dir = FileUtils.mkdir_p(Rails.root.join('tmp/zip-ready')).first
zip_name = "inventories_export_#{Time.now.utc.strftime('%F_%H-%M-%S_UTC')}.zip"
full_zip_name = File.join(zip_dir, zip_name)
fill_content(zip_input_dir)
ZipExport.transaction do
@zip_export = ZipExport.create!(user: @user)
@zip_export.zip!(zip_input_dir, full_zip_name)
@zip_export.zip_file.attach(io: File.open(full_zip_name), filename: zip_name)
generate_notification
end
ensure
FileUtils.rm_rf([zip_input_dir, full_zip_name], secure: true)
end
private
def fill_content(tmp_dir)
# Create team dir
team_path = "#{tmp_dir}/#{to_filesystem_name(@team.name)}"
FileUtils.mkdir_p(team_path)
@repositories.each_with_index do |repository, idx|
save_repository_to_csv(team_path, repository, idx)
end
end
def save_repository_to_csv(path, repository, idx)
repository_name = "#{to_filesystem_name(repository.name)} (#{idx})"
# Attachments dir
relative_attachments_path = "#{repository_name} attachments"
attachments_path = "#{path}/#{relative_attachments_path}"
FileUtils.mkdir_p(attachments_path)
# CSV file
csv_file = FileUtils.touch("#{path}/#{repository_name}.csv").first
# Define headers and columns IDs
col_ids = [-3, -4, -5, -6] + repository.repository_columns.map(&:id)
# Define callback function for file name
assets = {}
asset_counter = 0
handle_name_func = lambda do |asset|
file_name = append_file_suffix(asset.file_name, "_#{asset_counter}").to_s
# Save pair for downloading it later
assets[asset] = "#{attachments_path}/#{file_name}"
asset_counter += 1
relative_path = "#{relative_attachments_path}/#{file_name}"
return "=HYPERLINK(\"#{relative_path}\", \"#{relative_path}\")"
end
# Generate CSV
csv_data = RepositoryZipExport.to_csv(repository.repository_rows, col_ids, @user, repository, handle_name_func)
File.binwrite(csv_file, csv_data)
# Save all attachments (it doesn't work directly in callback function
assets.each do |asset, asset_path|
asset.file.open do |file|
FileUtils.mv(file.path, asset_path)
end
end
end
def append_file_suffix(file_name, suffix)
file_name = to_filesystem_name(file_name)
ext = File.extname(file_name)
File.basename(file_name, ext) + suffix + ext
end
def generate_notification
notification = Notification.create!(
type_of: :deliver,
title: I18n.t('zip_export.notification_title'),
message: "<a data-id='#{@zip_export.id}' " \
"data-turbolinks='false' " \
"href='#{Rails.application
.routes
.url_helpers
.zip_exports_download_export_all_path(@zip_export)}'>" \
"#{@zip_export.zip_file_name}</a>"
)
UserNotification.create!(notification: notification, user: @user)
end
end

View file

@ -113,23 +113,21 @@ class Project < ApplicationRecord
joins("
LEFT OUTER JOIN experiments ON experiments.project_id = projects.id
LEFT OUTER JOIN user_assignments experiment_user_assignments
ON experiment_user_assignments.assignable_id = experiments.id AND
experiment_user_assignments.assignable_type = 'Experiment'
ON experiment_user_assignments.assignable_id = experiments.id
AND experiment_user_assignments.assignable_type = 'Experiment'
AND experiment_user_assignments.user_id = #{user.id}
LEFT OUTER JOIN user_roles experiment_user_roles
ON experiment_user_roles.id = experiment_user_assignments.user_role_id
AND experiment_user_roles.permissions @> ARRAY['#{ExperimentPermissions::READ}']::varchar[]
LEFT OUTER JOIN my_modules ON my_modules.experiment_id = experiments.id
LEFT OUTER JOIN user_assignments my_module_user_assignments
ON my_module_user_assignments.assignable_id = my_modules.id AND
my_module_user_assignments.assignable_type = 'MyModule'
ON my_module_user_assignments.assignable_id = my_modules.id
AND my_module_user_assignments.assignable_type = 'MyModule'
AND my_module_user_assignments.user_id = #{user.id}
LEFT OUTER JOIN user_roles my_module_user_roles
ON my_module_user_roles.id = my_module_user_assignments.user_role_id
AND my_module_user_roles.permissions @> ARRAY['#{MyModulePermissions::READ}']::varchar[]
")
.where('
(experiment_user_assignments.user_id = ? AND experiment_user_roles.permissions @> ARRAY[?]::varchar[]
OR experiments.id IS NULL) AND
(my_module_user_assignments.user_id = ? AND my_module_user_roles.permissions @> ARRAY[?]::varchar[]
OR my_modules.id IS NULL)
', user.id, ExperimentPermissions::READ, user.id, MyModulePermissions::READ)
end
def self.filter_by_teams(teams = [])

View file

@ -97,7 +97,7 @@ class Step < ApplicationRecord
end
def self.viewable_by_user(user, teams)
where(protocol: Protocol.viewable_by_user(user, teams))
joins(:protocol).where(protocol: { my_module: MyModule.viewable_by_user(user, teams) })
end
def can_destroy?

View file

@ -13,16 +13,15 @@ class TeamZipExport < ZipExport
).first
zip_dir = FileUtils.mkdir_p(File.join(Rails.root, 'tmp/zip-ready')).first
zip_name = "projects_export_#{Time.now.strftime('%F_%H-%M-%S_UTC')}.zip"
zip_name = "projects_export_#{Time.now.utc.strftime('%F_%H-%M-%S_UTC')}.zip"
full_zip_name = File.join(zip_dir, zip_name)
zip_file = File.new(full_zip_name, 'w+')
fill_content(zip_input_dir, data, type, options)
zip!(zip_input_dir, zip_file)
self.zip_file.attach(io: File.open(zip_file), filename: zip_name)
zip!(zip_input_dir, full_zip_name)
zip_file.attach(io: File.open(full_zip_name), filename: zip_name)
generate_notification(user) if save
ensure
FileUtils.rm_rf([zip_input_dir, zip_file], secure: true)
FileUtils.rm_rf([zip_input_dir, full_zip_name], secure: true)
end
handle_asynchronously :generate_exportable_zip,
@ -320,38 +319,4 @@ class TeamZipExport < ZipExport
csv_file_path
end
# Recursive zipping
def zip!(input_dir, output_file)
files = Dir.entries(input_dir)
# Don't zip current/above directory
files.delete_if { |el| ['.', '..'].include?(el) }
Zip::File.open(output_file.path, Zip::File::CREATE) do |zipfile|
write_entries(input_dir, files, '', zipfile)
end
end
# A helper method to make the recursion work.
def write_entries(input_dir, entries, path, io)
entries.each do |e|
zip_file_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(input_dir, zip_file_path)
puts 'Deflating ' + disk_file_path
if File.directory?(disk_file_path)
io.mkdir(zip_file_path)
subdir = Dir.entries(disk_file_path)
# Remove current/above directory to prevent infinite recursion
subdir.delete_if { |el| ['.', '..'].include?(el) }
write_entries(input_dir, subdir, zip_file_path, io)
else
io.get_output_stream(zip_file_path) do |f|
f.write(File.open(disk_file_path, 'rb').read)
end
end
end
end
end

View file

@ -36,19 +36,27 @@ class ZipExport < ApplicationRecord
zip_file.blob&.filename&.to_s
end
def zip!(input_dir, output_file)
entries = Dir.glob('**/*', base: input_dir)
Zip::File.open(output_file, create: true) do |zipfile|
entries.each do |entry|
zipfile.add(entry, "#{input_dir}/#{entry}")
end
end
end
def generate_exportable_zip(user, data, type, options = {})
I18n.backend.date_format = user.settings[:date_format] || Constants::DEFAULT_DATE_FORMAT
zip_input_dir = FileUtils.mkdir_p(File.join(Rails.root, "tmp/temp_zip_#{Time.now.to_i}")).first
tmp_zip_dir = FileUtils.mkdir_p(File.join(Rails.root, 'tmp/zip-ready')).first
tmp_zip_name = "export_#{Time.now.strftime('%F %H-%M-%S_UTC')}.zip"
tmp_zip_file = File.new(File.join(tmp_zip_dir, tmp_zip_name), 'w+')
tmp_full_zip_name = File.join(tmp_zip_dir, "export_#{Time.now.strftime('%F %H-%M-%S_UTC')}.zip")
fill_content(zip_input_dir, data, type, options)
zip!(zip_input_dir, tmp_zip_file)
zip_file.attach(io: File.open(tmp_zip_file), filename: tmp_zip_name)
zip_file.attach(io: File.open(tmp_full_zip_name), filename: tmp_zip_name)
generate_notification(user) if save
ensure
FileUtils.rm_rf([zip_input_dir, tmp_zip_file], secure: true)
FileUtils.rm_rf([zip_input_dir, tmp_full_zip_name], secure: true)
end
handle_asynchronously :generate_exportable_zip
@ -60,14 +68,14 @@ class ZipExport < ApplicationRecord
.delete_expired_export(id)
end
def method_missing(m, *args, &block)
puts 'Method is missing! To use this zip_export you have to ' \
'define a method: generate_( type )_zip.'
object.public_send(m, *args, &block)
def method_missing(method_name, *args, &block)
return super unless method_name.to_s.start_with?('generate_')
raise StandardError, 'Method is missing! To use this zip_export you have to define a method: generate_( type )_zip.'
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?(' generate_') || super
method_name.to_s.start_with?('generate_') || super
end
def fill_content(dir, data, type, options = {})
@ -89,16 +97,6 @@ class ZipExport < ApplicationRecord
UserNotification.create(notification: notification, user: user)
end
def zip!(input_dir, output_file)
files = Dir.entries(input_dir)
files.delete_if { |el| el == '..' || el == '.' }
Zip::File.open(output_file.path, Zip::File::CREATE) do |zipfile|
files.each do |filename|
zipfile.add(filename, input_dir + '/' + filename)
end
end
end
def generate_repositories_zip(tmp_dir, data, _options = {})
file = FileUtils.touch("#{tmp_dir}/export.csv").first
File.open(file, 'wb') { |f| f.write(data) }

View file

@ -19,8 +19,7 @@ Canaid::Permissions.register_for(Project) do
export_project)
.each do |perm|
can perm do |user, project|
project.permission_granted?(user, ProjectPermissions::READ) ||
project.team.permission_granted?(user, TeamPermissions::MANAGE)
project.permission_granted?(user, ProjectPermissions::READ)
end
end

View file

@ -57,7 +57,7 @@ module RepositoryZipExport
when -8
I18n.t('repositories.table.archived_on')
else
column = RepositoryColumn.find_by_id(c_id)
column = repository.repository_columns.find_by(id: c_id)
column ? column.name : nil
end
end
@ -88,8 +88,7 @@ module RepositoryZipExport
.find_by(repository_column_id: c_id)
if cell
if cell.value_type == 'RepositoryAssetValue' &&
handle_file_name_func
if cell.value_type == 'RepositoryAssetValue' && handle_file_name_func
handle_file_name_func.call(cell.value.asset)
else
SmartAnnotations::TagToText.new(

View file

@ -2,8 +2,8 @@
<div class="title-row">
<i class="sn-icon sn-icon-navigator sci--layout--navigator-open cursor-pointer p-1.5 border rounded border-sn-light-grey mr-4"></i>
<h1 class="experimnet-name" data-toggle="tooltip" data-placement="bottom" title="<%= @experiment.name %>">
<% if @experiment.archived_branch? %>
<i class="sn-icon sn-icon-archive" data-view-mode="archived"></i>
<% if @experiment.archived? %>
<span><%= t('labels.archived')%></span>&nbsp;
<% end %>
<% if @inline_editable_title_config.present? %>
<%= render partial: "shared/inline_editing",

View file

@ -1,7 +1,8 @@
<div class="datetime-picker-container"
id="<%= id %>"
data-id="<%= id %>"
data-use-current="<%= use_current %>">
data-use-current="<%= use_current %>"
data-datetime-picker-format="<%= datetime_picker_format_date_only %>">
<% if label %>
<label class="control-label required" for="calendar-<%= id %>"><%= label %></label>
<% end %>

View file

@ -16,7 +16,7 @@
<% if ::NewRelic::Agent.instance.started? %>
<%= ::NewRelic::Agent.browser_timing_header(controller.request.content_security_policy_nonce) %>
<% end %>
<%= javascript_include_tag 'jquery_bundle' %>
<%= javascript_include_tag 'jquery_bundle', nonce: true %>
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag 'application_pack' %>
<%= javascript_include_tag 'session_end' %>
@ -59,7 +59,7 @@
data-atwho-repositories-url="<%= atwho_menu_team_path(current_team) %>"
data-atwho-rep-items-url="<%= atwho_rep_items_team_path(current_team) %>"
data-atwho-menu-items="<%= atwho_menu_items_team_path(current_team) %>"
data-datetime-picker-format-date-only="<%= datetime_picker_format_date_only %>"
data-datetime-picker-format="<%= datetime_picker_format_date_only %>"
<% end %>
>

View file

@ -15,7 +15,7 @@
</h1>
<% end %>
<h1 data-view-mode="archived">
<i class="sn-icon sn-icon-archive"></i>&nbsp;
<span><%= t('labels.archived')%></span>&nbsp;
<span class="projects-title name-readonly-placeholder"><%= current_folder&.name || t('projects.index.head_title_archived') %></span>
</h1>
</div>

View file

@ -13,14 +13,17 @@
</div>
<div class="project-name-cell table-cell">
<%
disabled_link = 'disabled-link' unless can_read_project?(project)
%>
<% if project.archived? %>
<%= link_to project_url(project, view_mode: :archived) do %>
<%= link_to project_url(project, view_mode: :archived), class: disabled_link do %>
<h3 class="name" title="<%= project.name %>">
<%= project.name %>
</h3>
<% end %>
<% else %>
<%= link_to project_url(project) do %>
<%= link_to project_url(project), class: disabled_link do %>
<h3 class="name" title="<%= project.name %>">
<%= project.name %>
</h3>

View file

@ -3,7 +3,7 @@
<i class="sn-icon sn-icon-navigator sci--layout--navigator-open cursor-pointer p-1.5 border rounded border-sn-light-grey mr-4"></i>
<h1 class="project-name">
<% if @project.archived? %>
<i class="sn-icon sn-icon-archive" data-view-mode="archived"></i>
<span><%= t('labels.archived')%></span>&nbsp;
<% end %>
<% if @inline_editable_title_config.present? %>
<%= render partial: "shared/inline_editing",

View file

@ -16,7 +16,7 @@
<div class="title-row">
<% if templates_view_mode_archived?(type: @type) %>
<h1>
<i class="sn-icon sn-icon-archive"></i>
<span><%= t('labels.archived')%></span>&nbsp;
<%= t('protocols.index.head_title_archived') %>
</h1>
<% else %>

View file

@ -15,7 +15,7 @@
} %>
<% else %>
<% if @protocol.archived %>
<i class="sn-icon sn-icon-archive"></i>&nbsp;
<span><%= t('labels.archived')%></span>&nbsp;
<% end %>
<div class="name-readonly-placeholder">
<% if @protocol.in_repository_draft? %>

View file

@ -5,7 +5,7 @@
class: "sidebar-link repository-link #{ 'selected' if current_page?(repository_path(repository)) }",
data: {type: 'repository', id: repository.id } do %>
<% if repository.archived? %>
<i class="sn-icon sn-icon-archive"></i>&nbsp;
<span><%= t('labels.archived')%></span>&nbsp;
<% end %>
<%= repository.name %>
<%= inventory_shared_status_icon(repository, current_team) %>

View file

@ -3,7 +3,7 @@
<div class="content-header">
<div class="title-row">
<h1 data-view-mode="active"><%= t('libraries.index.head_title') %></h1>
<h1 data-view-mode="archived"><i class="sn-icon sn-icon-archive"></i>&nbsp;<%= t('libraries.index.head_title_archived') %></h1>
<h1 data-view-mode="archived"><span><%= t('labels.archived')%></span>&nbsp;<%= t('libraries.index.head_title_archived') %></h1>
</div>
</div>
<div class="content-body">

View file

@ -37,7 +37,7 @@
<% end %>
<div class="repository-archived-title-name" data-view-mode="archived">
<% if @repository.archived? %>
<i class="sn-icon sn-icon-archive"></i>&nbsp;
<span><%= t('labels.archived')%></span>&nbsp;
<%= t('repositories.show.archived_inventory', repository_name: @repository.name) %>
<% else %>
<%= t('repositories.show.archived_inventory_items', repository_name: @repository.name) %>

View file

@ -430,6 +430,9 @@ en:
delete_error: "Error occurred while deleting comment."
step_url: "Go to step"
labels:
archived: "(A)"
projects:
index:
header:
@ -3444,6 +3447,7 @@ en:
download: "Download"
access: "Access"
select: "Select"
loading: "Loading..."
# In order to use the strings 'yes' and 'no' as keys, you need to wrap them with quotes
'yes': "Yes"
'no': "No"

View file

@ -87,7 +87,7 @@ en:
uncomplete_task_html: "%{user} uncompleted task %{my_module}."
assign_repository_record_html: "%{user} assigned inventory item(s) %{record_names} from inventory %{repository} to task %{my_module}."
unassign_repository_record_html: "%{user} unassigned inventory item(s) %{record_names} from inventory %{repository} to task %{my_module}."
assign_user_to_project_html: "%{user} granted with access %{user_target} with user role %{role} to project %{project}."
assign_user_to_project_html: "%{user} granted access to %{user_target} with user role %{role} to project %{project}."
unassign_user_from_project_html: "%{user} removed %{user_target} with user role %{role} from project %{project}."
change_user_role_on_project_html: "%{user} changed %{user_target}'s role on project %{project} to %{role}."
change_user_role_on_experiment_html: "%{user} changed %{user_target}'s role on experiment %{experiment} to %{role}."
@ -258,10 +258,10 @@ en:
protocol_template_revision_notes_updated_html: "%{user} edited revision notes of %{protocol}."
protocol_template_draft_deleted_html: "%{user} deleted draft of %{protocol}."
protocol_template_draft_created_html: "%{user} created draft of %{protocol}."
protocol_template_access_granted_html: "%{user} granted with access %{user_target} with user role %{role} to protocol template %{protocol}."
protocol_template_access_granted_html: "%{user} granted access to %{user_target} with user role %{role} to protocol template %{protocol}."
protocol_template_access_changed_html: "%{user} changed %{user_target}s role on protocol template %{protocol} to %{role}."
protocol_template_access_revoked_html: "%{user} removed %{user_target} with user role %{role} from protocol template %{protocol}."
protocol_template_access_granted_all_team_members_html: "%{user} granted with access all team members of %{team} team with user role %{role} to protocol template %{protocol}."
protocol_template_access_granted_all_team_members_html: "%{user} granted access to all team members of %{team} team with user role %{role} to protocol template %{protocol}."
protocol_template_access_changed_all_team_members_html: "%{user} changed %{team}s role on protocol template %{protocol} to %{role}."
protocol_template_access_revoked_all_team_members_html: "%{user} removed %{team} team members with user role %{role} from protocol template %{protocol}."
task_protocol_save_to_template_html: "%{user} created a new protocol template %{protocol} from a task."

View file

@ -219,6 +219,7 @@ Rails.application.routes.draw do
member do
post 'parse_sheet', defaults: { format: 'json' }
post 'export_repository', to: 'repositories#export_repository'
post 'export_repositories', to: 'repositories#export_repositories'
post 'export_projects'
get 'sidebar'
get 'export_projects_modal'