Merge branch 'features/august-release' into SCI-8807-notifications

This commit is contained in:
ivanscinote 2023-08-02 13:40:06 +02:00 committed by GitHub
commit 242df0632d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
135 changed files with 808 additions and 843 deletions

View file

@ -73,7 +73,7 @@ gem 'nested_form_fields'
gem 'nokogiri', '~> 1.14.3' # HTML/XML parser
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
gem 'rgl' # Graph framework for project diagram calculations
gem 'roo', '~> 2.8.2' # Spreadsheet parser
gem 'roo', '~> 2.10.0' # Spreadsheet parser
gem 'rotp'
gem 'rqrcode', '~> 2.0' # QR code generator
gem 'rubyzip', '>= 2.3.0' # will load new rubyzip version

View file

@ -60,47 +60,47 @@ GIT
GEM
remote: http://rubygems.org/
specs:
actioncable (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
actioncable (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
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)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.5)
actionpack (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activesupport (= 7.0.5)
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)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.5)
actionview (= 7.0.5)
activesupport (= 7.0.5)
actionpack (7.0.5.1)
actionview (= 7.0.5.1)
activesupport (= 7.0.5.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)
actionpack (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
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)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.5)
activesupport (= 7.0.5)
actionview (7.0.5.1)
activesupport (= 7.0.5.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -110,24 +110,24 @@ GEM
activemodel (>= 4.1, < 7.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.0.5)
activesupport (= 7.0.5)
activejob (7.0.5.1)
activesupport (= 7.0.5.1)
globalid (>= 0.3.6)
activemodel (7.0.5)
activesupport (= 7.0.5)
activerecord (7.0.5)
activemodel (= 7.0.5)
activesupport (= 7.0.5)
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)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activestorage (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activesupport (= 7.0.5)
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)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.5)
activesupport (7.0.5.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -412,8 +412,8 @@ GEM
mime-types-data (3.2023.0218.1)
mini_magick (4.12.0)
mini_mime (1.1.2)
mini_portile2 (2.8.2)
minitest (5.18.0)
mini_portile2 (2.8.4)
minitest (5.18.1)
momentjs-rails (2.17.1)
railties (>= 3.1)
msgpack (1.7.1)
@ -505,26 +505,27 @@ GEM
rack
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.5)
actioncable (= 7.0.5)
actionmailbox (= 7.0.5)
actionmailer (= 7.0.5)
actionpack (= 7.0.5)
actiontext (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activemodel (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
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)
bundler (>= 1.15.0)
railties (= 7.0.5)
railties (= 7.0.5.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.0.3)
activesupport (>= 4.2.0)
rails-dom-testing (2.1.1)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
@ -538,9 +539,9 @@ GEM
railties (> 3.1)
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
railties (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -561,7 +562,7 @@ GEM
pairing_heap (>= 0.3.0)
rexml (~> 3.2, >= 3.2.4)
stream (~> 0.5.3)
roo (2.8.3)
roo (2.10.0)
nokogiri (~> 1)
rubyzip (>= 1.3.0, < 3.0.0)
rotp (6.2.2)
@ -664,7 +665,7 @@ GEM
thor (1.2.2)
tilt (2.2.0)
timecop (0.9.6)
timeout (0.3.2)
timeout (0.4.0)
turbolinks (5.1.1)
turbolinks-source (~> 5.1)
turbolinks-source (5.2.0)
@ -772,7 +773,7 @@ DEPENDENCIES
omniauth-okta!
omniauth-rails_csrf_protection (~> 1.0)
overcommit
pg (~> 1.1)
pg (~> 1.5)
pg_search
pry
pry-byebug
@ -781,13 +782,13 @@ DEPENDENCIES
puma
rack-attack
rack-cors
rails (~> 7.0.5)
rails (~> 7.0.5.1)
rails-controller-testing
rails_12factor
rails_autolink (~> 1.1, >= 1.1.6)
recaptcha
rgl
roo (~> 2.8.2)
roo (~> 2.10.0)
rotp
rqrcode (~> 2.0)
rspec-rails

View file

@ -1,7 +0,0 @@
/* global ActiveStoragePreviews */
(function() {
$('.attachment-preview, .asset-inline-image')
.on('load', (event) => ActiveStoragePreviews.showPreview(event))
.on('error', (event) => ActiveStoragePreviews.reCheckPreview(event));
}());

View file

@ -1,7 +0,0 @@
/* global ActiveStoragePreviews */
(function() {
$('.attachment-preview .asset-thumbnail-image')
.on('load', (event) => ActiveStoragePreviews.showPreview(event))
.on('error', (event) => ActiveStoragePreviews.reCheckPreview(event));
}());

View file

@ -37,6 +37,7 @@ var ProjectsIndex = (function() {
// 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
@ -163,6 +164,7 @@ var ProjectsIndex = (function() {
ev.preventDefault();
const projectId = $(this).data('projectId');
singleSelectedProject = projectId;
// Load HTML to refresh users list
$.ajax({
@ -223,7 +225,7 @@ var ProjectsIndex = (function() {
type: 'POST',
dataType: 'json',
data: {
project_ids: selectedProjects,
project_ids: selectedProjects.length > 0 ? selectedProjects : [singleSelectedProject],
project_folder_ids: selectedProjectFolders
},
success: function(data) {
@ -232,7 +234,7 @@ var ProjectsIndex = (function() {
HelperModule.flashAlertMsg(data.flash, 'success');
},
error: function() {
// TODO
HelperModule.flashAlertMsg(I18n.t('projects.export_projects.error_flash'), 'danger');
}
});
});

View file

@ -26,6 +26,10 @@
.on('shown.bs.modal', function() {
$(`${protocolModal} #protocol_name`).parent().removeClass('error');
$(`${protocolModal} #protocol_name`).val('');
const protocolName = $(`a[data-target="${protocolModal}"]`).attr('data-protocol-name');
if (protocolName) {
$(this).find('.sci-input-field').val(protocolName);
}
$(this).find('.sci-input-field').focus();
})
.on('ajax:error', 'form', function(e, error) {

View file

@ -64,7 +64,7 @@
el.handsontable('getInstance').getPlugin('columnSorting').sort(3, order);
} else {
metadata = JSON.parse(metadataJson.val() || '{}');
metadata = metadataJson.val() || {};
el.handsontable({
disableVisualSelection: true,
rowHeaders: tableRowHeaders(metadata.plateTemplate),

View file

@ -162,20 +162,9 @@
}).on('shown.bs.modal', '#export-repositories-modal', function() {
if (!CHECKBOX_SELECTOR) return;
const selectedInventoriesCount = CHECKBOX_SELECTOR.selectedRows.length;
const firstDescription = $(this).find('.description-p1');
const teamName = firstDescription.data('team-name');
const exportButton = $(this).find('#export-repositories-modal-submit');
const exportURL = exportButton.data('export-url');
firstDescription.html(I18n.t(
'repositories.index.modal_export.description_p1_html',
{
team_name: teamName,
count: selectedInventoriesCount
}
));
exportButton.on('click', function() {
$.ajax({
url: exportURL,

View file

@ -48,7 +48,8 @@ var ChecklistColumnHelper = (function() {
optionClass: 'checkbox-icon',
selectAppearance: 'simple',
onChange: function() {
$hiddenField.val(JSON.stringify(dropdownSelector.getValues('#' + select)));
let currentValues = dropdownSelector.getValues('#' + select);
$hiddenField.val(currentValues.length ? JSON.stringify(currentValues) : null);
}
});
}

View file

@ -532,7 +532,7 @@ var RepositoryDatatable = (function(global) {
TABLE.ColSizes = state.ColSizes;
restoreColumnSizes();
setTimeout(restoreColumnSizes, 100);
}
function dataTableInit() {
@ -576,6 +576,15 @@ var RepositoryDatatable = (function(global) {
// force width of checkbox column
data[0] = 30;
// perserve widths of invisible columns or enforce min width
for (let i = 0; i < data.length; i++) {
if (data[i] === 0) {
let minWidth = parseInt($(TABLE.column(i).header()).css('min-width'), 10);
data[i] = colSizeMap[i] || minWidth;
}
}
state.ColSizes = data;
$(TABLE_WRAPPER_ID).find('.table').addClass('table--resizable-columns');
@ -773,8 +782,8 @@ var RepositoryDatatable = (function(global) {
}
});
},
stateSaveCallback: function(settings, data) {
if (Object.keys(colSizeMap).length === 0) return;
stateSaveCallback: function(_, data) {
if (Object.keys(colSizeMap).length === 0) return true;
let colSizes = [];

View file

@ -237,24 +237,22 @@ var RepositoryColumns = (function() {
}
function toggleColumnVisibility() {
var lis = $(columnsList).find('.vis');
lis.on('click', function(event) {
var self = $(this);
var li = self.closest('li');
var column = TABLE.column(li.attr('data-position'));
$(columnsList).find('.vis').on('click', function(event) {
const $this = $(this);
const li = $this.closest('li');
const column = TABLE.column(li.attr('data-position'));
event.stopPropagation();
if (column.header.id !== 'row-name') {
if (!['row-name', 'archived-by', 'archived-on'].includes(column.header().id)) {
if (column.visible()) {
self.addClass('sn-icon-visibility-hide');
self.removeClass('sn-icon-visibility-show');
$this.addClass('sn-icon-visibility-hide');
$this.removeClass('sn-icon-visibility-show');
li.addClass('col-invisible');
column.visible(false);
TABLE.setColumnSearchable(column.index(), false);
} else {
self.addClass('sn-icon-visibility-show');
self.removeClass('sn-icon-visibility-hide');
$this.addClass('sn-icon-visibility-show');
$this.removeClass('sn-icon-visibility-hide');
li.removeClass('col-invisible');
column.visible(true);
TABLE.setColumnSearchable(column.index(), true);

View file

@ -1,4 +1,4 @@
/* global TinyMCE Results Comments animateSpinner initFormSubmitLinks */
/* global TinyMCE Results Comments animateSpinner initFormSubmitLinks Prism */
(function() {
'use strict';
@ -82,6 +82,7 @@
TinyMCE.destroyAll();
Comments.init();
initNewReslutText();
Prism.highlightAllUnder(newResult.get(0));
});
$form.on('ajax:error', function(e, xhr) {
var data = xhr.responseJSON;

View file

@ -30,3 +30,16 @@ var ActiveStoragePreviews = (function() {
}
});
}());
$(document).on('turbolinks:load', function() {
$('.asset-preview-image')
.one('load', (event) => ActiveStoragePreviews.showPreview(event))
.one('error', (event) => ActiveStoragePreviews.reCheckPreview(event))
.each(function() {
if (this.complete) {
$(this).load();
} else if (this.error) {
$(this).error();
}
});
});

View file

@ -136,7 +136,9 @@ var dropdownSelector = (function() {
if (selector.data('select-by-group')) {
$.each(container.find('.dropdown-group'), function(gi, group) {
if ($(group).find('.dropdown-option').length === $(group).find('.dropdown-option.select').length) {
$(group).addClass('select');
$(group).find('.group-name').addClass('select');
} else {
$(group).find('.group-name').removeClass('select');
}
});
}
@ -619,7 +621,7 @@ var dropdownSelector = (function() {
// Disable group select to single select
if (selector.data('config').singleSelect) return;
if (groupContainer.toggleClass('select').hasClass('select')) {
if ($(this).toggleClass('select').hasClass('select')) {
groupContainer.find('.dropdown-option').addClass('select');
} else {
groupContainer.find('.dropdown-option').removeClass('select');

View file

@ -19,7 +19,7 @@
.column-grip {
color: $color-volcano;
cursor: grab;
cursor: grabbing;
display: block;
left: 0;
opacity: 0;

View file

@ -419,6 +419,7 @@
.task-notes-content {
margin-left: 10px;
overflow-x: auto;
.form-group.has-error {
border: 1px solid $brand-danger;

View file

@ -72,6 +72,10 @@
margin-left: 4.25em !important;
}
.ql-editor {
overflow-x: auto;
}
.result-icon {
@include font-main;
background: $color-concrete;

View file

@ -1,23 +1,44 @@
.sci--layout-navigation-breadcrumbs {
--max-breadcrumbs-link-width: 11.25rem;
align-items: center;
background-color: $color-white;
column-gap: .875rem;
background-color: var(--sn-white);
display: flex;
flex-wrap: wrap;
flex-wrap: nowrap;
padding-left: 1.5rem;
}
.breadcrumbs-container {
@include font-small;
align-items: center;
display: flex;
flex-wrap: nowrap;
max-width: calc(100vw - var(--left-navigation-width) - 3rem);
overflow: hidden;
width: calc(100vw - var(--left-navigation-width) - 3rem);
.delimiter {
@include font-button;
color: var(--sn-grey);
font-weight: bold;
padding: 0 .5em;
}
}
.breadcrumbs-item {
align-items: center;
display: inline-flex;
flex-wrap: nowrap;
.breadcrumbs-link {
@include font-small;
color: var(--sn-blue);
display: inline-block;
max-width: var(--max-breadcrumbs-link-width);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:last-child {
color: var(--sn-grey);
&.shortened {
max-width: var(--max-breadcrumbs-link-width);
overflow: hidden;
text-overflow: ellipsis;
}
&.plain-text {
@ -25,24 +46,15 @@
}
}
.delimiter {
padding-bottom: .25rem;
&:hover {
text-decoration: none;
}
.breadcrumbs-collapsed-container {
color: var(--sn-blue);
position: relative;
.show-breadcrumbs {
align-items: center;
cursor: pointer;
display: flex;
position: relative;
}
a {
@include font-button;
color: var(--sn-blue);
}
&:last-child .breadcrumbs-link {
color: var(--sn-grey);
}
}
.breadcrumbs-collapsed-container .breadcrumbs-item .breadcrumbs-link {
color: var(--sn-blue);
}

View file

@ -386,7 +386,6 @@ path, ._jsPlumb_endpoint {
div {
font-size: 20px;
width: 4px;
height: 0px;
display: inline-block;
& .fas {

View file

@ -273,6 +273,10 @@
}
}
#protocol-description-container {
overflow-x: auto;
}
.delete-steps-modal {
.btn {
float: initial;

View file

@ -109,7 +109,6 @@
flex-direction: column;
flex-grow: 1;
height: calc(100% - var(--datatable-pagination-row) - var(--repository-top-toolbar-height));
z-index: 1;
.dataTables_scrollHead {
z-index: 90;
@ -445,7 +444,7 @@
background: $color-white;
color: $color-silver-chalice;
display: none;
z-index: 1000;
z-index: 1;
.repository-save-changes-link {
cursor: pointer;

View file

@ -81,7 +81,11 @@
}
// Cells
td {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// Assigned

View file

@ -9,7 +9,6 @@
height: 3.5rem;
justify-content: space-between;
padding-bottom: 1rem;
z-index: 99;
.view-switch,

View file

@ -34,6 +34,8 @@
}
.control {
align-items: center;
display: flex;
margin-left: auto;
}
@ -51,7 +53,7 @@
}
}
.fas-check {
.sn-icon-check {
margin-left: .25em;
}
}
@ -62,3 +64,14 @@
}
}
}
.panel.panel-default {
.pull-right {
align-items: center;
display: flex;
.sn-icon-check {
margin-left: .25em;
}
}
}

View file

@ -127,6 +127,10 @@
padding: 5px 0;
}
.filter-table input {
width: auto !important;
}
.filter-table,
.display-limit {
flex-shrink: 0;

View file

@ -1,42 +0,0 @@
.breadcrumbs-container {
@include font-small;
align-items: center;
display: flex;
flex-wrap: wrap;
.delimiter {
@include font-button;
color: $color-silver-chalice;
font-weight: bold;
padding: 0 .5em;
}
.breadcrumbs-link {
display: inline-block;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: none;
}
}
.breadcrumbs-collapsed-container {
color: $brand-primary;
position: relative;
.show-breadcrumbs {
align-items: center;
cursor: pointer;
display: flex;
position: relative;
}
a {
@include font-button;
color: $brand-primary;
}
}
}

View file

@ -2,6 +2,7 @@
.sci-input-container {
.help-block {
color: $brand-danger;
font-size: 12px;
}
}

View file

@ -37,6 +37,11 @@
}
}
.sci-input-field[type=password] {
font-family: Arial;
letter-spacing: .075em;
}
.sn-icon {
position: absolute;
text-align: center;

View file

@ -22,6 +22,9 @@
}
.view-text-element {
overflow-x: auto;
pointer-events: initial;
a {
pointer-events: initial;
}

View file

@ -58,7 +58,7 @@ module ActiveStorage
current_user.permission_team = asset.team || current_team
return true if !asset.saved && can_read_team?(asset.team)
return true if asset.object.nil? && can_read_team?(asset.team)
case asset.object_type
when 'MyModule'

View file

@ -41,10 +41,6 @@ class GlobalActivitiesController < ApplicationController
@teams.order(name: :asc)
end
@activity_types = Activity.activity_types_list
@user_list = User.where(id: UserTeam.where(team: current_user.teams).select(:user_id))
.distinct
.order(full_name: :asc)
.pluck(:full_name, :id)
activities = ActivitiesService.load_activities(current_user, selected_teams, activity_filters)

View file

@ -21,7 +21,8 @@ class MyModuleTagsController < ApplicationController
render json: {
html_module_header: render_to_string(
partial: 'my_modules/tags',
locals: { my_module: @my_module, editable: can_manage_my_module?(@my_module) }
locals: { my_module: @my_module, editable: can_manage_my_module?(@my_module) },
formats: :html
)
}
end
@ -36,7 +37,8 @@ class MyModuleTagsController < ApplicationController
id: my_module.id,
tags_html: render_to_string(
partial: 'canvas/tags',
locals: { my_module: my_module }
locals: { my_module: my_module },
formats: :html
)
}
end

View file

@ -115,9 +115,7 @@ class MyModulesController < ApplicationController
MyModule: [@my_module.id]
}
@activity_types = Activity.activity_types_list
@user_list = User.where(id: UserTeam.where(team: current_user.teams).select(:user_id))
.distinct
.pluck(:full_name, :id)
activities = ActivitiesService.load_activities(current_user, current_team, activity_filters)
@grouped_activities = activities.group_by do |activity|

View file

@ -109,20 +109,21 @@ module Navigator
else
object&.project_folder
end
has_children_sql = 'SUM(CASE WHEN viewable_projects.id IS NOT NULL OR project_folders_project_folders.id IS NOT NULL
THEN 1 ELSE 0 END) > 0'
current_team.project_folders.where(parent_folder: folder)
.where(project_folders: { archived: archived })
.left_outer_joins(:projects, project_folders: {})
.joins(
"LEFT OUTER JOIN (#{Project.viewable_by_user(current_user, current_team).to_sql}) " \
"LEFT OUTER JOIN (#{Project.viewable_by_user(current_user, current_team).where(archived: archived).to_sql}) " \
"viewable_projects ON viewable_projects.project_folder_id = project_folders.id"
)
.select(
'project_folders.id',
'project_folders.name',
'project_folders.archived',
'SUM(CASE WHEN viewable_projects.id IS NOT NULL OR project_folders_project_folders.id IS NOT NULL
THEN 1 ELSE 0 END) > 0 AS has_children'
"#{has_children_sql} AS has_children"
).group('project_folders.id')
.having("project_folders.archived = :archived OR #{has_children_sql}", archived: archived)
end
def fetch_experiments(object, archived = false)

View file

@ -175,9 +175,7 @@ class RepositoriesController < ApplicationController
def rename_modal
render json: {
html: render_to_string(
partial: 'rename_repository_modal'
)
html: render_to_string(partial: 'rename_repository_modal', formats: :html)
}
end
@ -200,7 +198,7 @@ class RepositoriesController < ApplicationController
name: @repository.name
)
render json: {
html: render_to_string(partial: 'copy_repository_modal')
html: render_to_string(partial: 'copy_repository_modal', formats: :html)
}
end
@ -210,6 +208,7 @@ class RepositoriesController < ApplicationController
html: render_to_string(
partial: 'export_repositories_modal',
locals: { team_name: current_team.name,
counter: params[:counter].to_i,
export_limit: TeamZipExport.exports_limit,
num_of_requests_left: current_user.exports_left - 1 },
formats: :html
@ -288,7 +287,7 @@ class RepositoriesController < ApplicationController
if (@temp_file = parsed_file.generate_temp_file)
render json: {
html: render_to_string(partial: 'repositories/parse_records_modal')
html: render_to_string(partial: 'repositories/parse_records_modal', formats: :html)
}
else
repository_response(t('repositories.parse_sheet.errors.temp_file_failure'))

View file

@ -103,9 +103,9 @@ class ResultAssetsController < ApplicationController
format.json do
render json: {
html: render_to_string(
partial: 'my_modules/result', locals: { result: @result }
partial: 'my_modules/result', locals: { result: @result }, formats: :html
)
}, status: :ok
}
end
else
format.json do

View file

@ -54,7 +54,7 @@ class UserMyModulesController < ApplicationController
render json: {
user: {
id: @um.user.id,
full_name: @um.user.full_name,
full_name: escape_input(@um.user.full_name),
avatar_url: avatar_path(@um.user, :icon_small),
user_module_id: @um.id
}

View file

@ -150,8 +150,7 @@ module Users
.distinct
teams = teams.where_attributes_like('teams.name', params[:query]) if params[:query].present?
teams.select { |team| can_invite_team_users?(team) }
teams = teams.select { |team| can_invite_team_users?(team) }
render json: teams.map { |t| { value: t.id, label: escape_input(t.name) } }.to_json
end

View file

@ -131,9 +131,6 @@ class Users::RegistrationsController < Devise::RegistrationsController
@team.created_by = resource # set created_by for oraganization
@team.save
# Add this user to the team as owner
UserTeam.create(user: resource, team: @team, role: :admin)
# set current team to new user
resource.current_team_id = @team.id
resource.save
@ -171,9 +168,6 @@ class Users::RegistrationsController < Devise::RegistrationsController
@team.created_by = @user # set created_by for team
@team.save!
# Add this user to the team as owner
UserTeam.create(user: @user, team: @team, role: :admin)
# set current team to new user
@user.current_team_id = @team.id
@user.save!

View file

@ -107,7 +107,7 @@ class Users::SessionsController < Devise::SessionsController
def remove_authenticate_mesasge_if_root_path
if session[:user_return_to] == root_path && flash[:alert] == I18n.t('devise.failure.unauthenticated')
flash[:alert] = nil
flash.clear
end
end

View file

@ -57,9 +57,7 @@ module Users
end
end
def show
@user_team = UserTeam.find_by(user: @user, team: @team)
end
def show; end
def users_datatable
render json: ::TeamUsersDatatable.new(view_context, @team, @user)

View file

@ -100,15 +100,15 @@ class WopiController < ActionController::Base
if @asset.lock == lock
@asset.refresh_lock
response.headers['X-WOPI-ItemVersion'] = @asset.version
return render body: nil, status: :ok
render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
else
@asset.lock_asset(lock)
response.headers['X-WOPI-ItemVersion'] = @asset.version
return render body: nil, status: :ok
render body: nil, status: :ok
end
end
end
@ -126,14 +126,14 @@ class WopiController < ActionController::Base
@asset.unlock
@asset.lock_asset(lock)
response.headers['X-WOPI-ItemVersion'] = @asset.version
return render body: nil, status: :ok
render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
else
response.headers['X-WOPI-Lock'] = ' '
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
end
end
@ -151,15 +151,15 @@ class WopiController < ActionController::Base
create_wopi_file_activity(@user, false)
response.headers['X-WOPI-ItemVersion'] = @asset.version
return render body: nil, status: :ok
render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
else
logger.warn 'WOPI: tried to unlock non-locked file'
response.headers['X-WOPI-Lock'] = ' '
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
end
end
@ -174,14 +174,14 @@ class WopiController < ActionController::Base
@asset.refresh_lock
response.headers['X-WOPI-ItemVersion'] = @asset.version
response.headers['X-WOPI-ItemVersion'] = @asset.version
return render body: nil, status: :ok
render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
else
response.headers['X-WOPI-Lock'] = ' '
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
end
end
@ -189,7 +189,7 @@ class WopiController < ActionController::Base
def get_lock
@asset.with_lock do
response.headers['X-WOPI-Lock'] = @asset.locked? ? @asset.lock : ' '
return render body: nil, status: :ok
render body: nil, status: :ok
end
end
@ -211,11 +211,11 @@ class WopiController < ActionController::Base
@protocol&.update(updated_at: Time.now.utc)
response.headers['X-WOPI-ItemVersion'] = @asset.version
return render body: nil, status: :ok
render body: nil, status: :ok
else
logger.warn 'WOPI: wrong lock used to try and modify file'
response.headers['X-WOPI-Lock'] = @asset.lock
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
elsif !@asset.file_size.nil? && @asset.file_size.zero?
logger.warn 'WOPI: initializing empty file'
@ -227,11 +227,11 @@ class WopiController < ActionController::Base
@team.save
response.headers['X-WOPI-ItemVersion'] = @asset.version
return render body: nil, status: :ok
render body: nil, status: :ok
else
logger.warn 'WOPI: trying to modify unlocked file'
response.headers['X-WOPI-Lock'] = ' '
return render body: nil, status: :conflict
render body: nil, status: :conflict
end
end
end
@ -249,13 +249,13 @@ class WopiController < ActionController::Base
@assoc = result_assoc unless result_assoc.nil?
@assoc = repository_cell_assoc unless repository_cell_assoc.nil?
if @assoc.class == Step
if @assoc.instance_of?(Step)
@protocol = @asset.step.protocol
@team = @protocol.team
elsif @assoc.class == Result
elsif @assoc.instance_of?(Result)
@my_module = @assoc.my_module
@team = @my_module.experiment.project.team
elsif @assoc.class == RepositoryCell
elsif @assoc.instance_of?(RepositoryCell)
@repository = @assoc.repository_column.repository
@team = @repository.team
end
@ -278,8 +278,9 @@ class WopiController < ActionController::Base
# This is what we get for settings permission methods with
# current_user
@user.permission_team = @team
@current_user = @user
if @assoc.class == Step
if @assoc.instance_of?(Step)
if @protocol.in_module?
@can_read = can_read_protocol_in_module?(@protocol)
@can_write = can_manage_step?(@assoc)
@ -299,7 +300,7 @@ class WopiController < ActionController::Base
@breadcrumb_folder_name = 'Protocol managament'
end
@breadcrumb_folder_url = @close_url
elsif @assoc.class == Result
elsif @assoc.instance_of?(Result)
@can_read = can_read_experiment?(@my_module.experiment)
@can_write = can_manage_my_module?(@my_module)
@ -311,7 +312,7 @@ class WopiController < ActionController::Base
host: ENV['WOPI_USER_HOST'])
@breadcrumb_folder_name = @my_module.name
@breadcrumb_folder_url = @close_url
elsif @assoc.class == RepositoryCell
elsif @assoc.instance_of?(RepositoryCell)
@can_read = can_read_repository?(@repository)
@can_write = !@repository.is_a?(RepositorySnapshot) && can_edit_wopi_file_in_repository_rows?

View file

@ -60,7 +60,7 @@ class ProtocolLinkedChildrenDatatable < CustomDatatable
res += "<li><span class='sn-icon sn-icon-projects'></span>&nbsp;"
res += @controller.render_to_string(
partial: 'search/results/partials/project_text',
locals: { project: record.my_module.experiment.project },
locals: { project: record.my_module.experiment.project, link_to_page: :show },
formats: :html
)
res += '</li>'

View file

@ -28,11 +28,11 @@ module ApplicationHelper
if message.strip.length > len
sanitize_input("<div class='modal-tooltip'> \
#{truncate(message.strip, length: len)} \
#{message.strip} \
<span class='modal-tooltiptext'> \
#{message.strip}</span></div>")
else
truncate(message.strip, length: len)
message.strip
end
end

View file

@ -68,6 +68,17 @@ module FileIconsHelper
end
end
def sn_icon_for(asset)
file_ext = asset.file_name.split('.').last
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
'file-word'
elsif Constants::FILE_TABLE_FORMATS.include?(file_ext)
'file-excel'
elsif Constants::FILE_PRESENTATION_FORMATS.include?(file_ext)
'file-powerpoint'
end
end
# For showing in view/edit buttons (WOPI)
def file_application_icon(asset)
image_link = file_application_url(asset)

View file

@ -178,7 +178,7 @@ window.TinyMCE = (() => {
// Hide element containing HTML view of RTE field
const tinyMceContainer = $(selector).closest('form').find('.tinymce-view');
const tinyMceInitSize = tinyMceContainer.height();
$(selector).closest('.form-group')
$(selector).closest('.form-group, .tinymce-editor-container')
.before(`<div class="tinymce-placeholder" style="height:${tinyMceInitSize}px"></div>`);
tinyMceContainer.addClass('hidden');
const plugins = `

View file

@ -0,0 +1,35 @@
// Use this to register outside-click directive on a Vue component
// eslint-disable-next-line max-len
// eg v-click-outside="{handler: 'handlerToTrigger', exclude: [refs to ignore on click (eg 'searchInput', 'searchInputBtn')]}"
// eslint-enable-next-line max-len
let handleOutsideClick;
export default {
bind(el, binding, vnode) {
const { handler, exclude } = binding.value;
handleOutsideClick = (e) => {
e.stopPropagation();
let clickedOnExcludedEl = false;
exclude.forEach(refName => {
if (!clickedOnExcludedEl) {
const excludedEl = vnode.context.$refs[refName];
clickedOnExcludedEl = excludedEl?.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
vnode.context[handler]();
}
};
document.addEventListener('click', handleOutsideClick);
document.addEventListener('touchstart', handleOutsideClick);
},
unbind() {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('touchstart', handleOutsideClick);
}
};

View file

@ -0,0 +1,17 @@
import TurbolinksAdapter from 'vue-turbolinks';
import Vue from 'vue/dist/vue.esm';
import Breadcrumbs from '../../../vue/navigation/breadcrumbs/breadcrumbs.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;
window.breadcrumbsComponent = new Vue({
el: '#breadcrumbs',
name: 'BreadcrumbsContainer',
components: {
breadcrumbs: Breadcrumbs
}
});

View file

@ -18,7 +18,6 @@ function initPrintModalComponent() {
return {
showModal: false,
row_ids: [],
zebraEnabled: container.data('zebra-enabled'),
urls: {
print: container.data('print-url'),
zebraProgress: container.data('zebra-progress-url'),

View file

@ -1,9 +1,11 @@
import TurbolinksAdapter from 'vue-turbolinks';
import Vue from 'vue/dist/vue.esm';
import RepositorySearchContainer from '../../vue/repository_search/container.vue';
import outsideClick from './directives/outside_click';
Vue.use(TurbolinksAdapter);
Vue.prototype.i18n = window.I18n;
Vue.directive('click-outside', outsideClick);
window.initRepositorySearch = () => {
window.RepositorySearchComponent = new Vue({

View file

@ -0,0 +1,162 @@
<template>
<div class="breadcrumbs-container" ref="container">
<a
v-if="firstItem && firstItem.url !== lastItem.url"
:href="firstItem.url"
class="breadcrumbs-item"
:title="firstItem.label"
ref="firstItem"
>
<span
class="breadcrumbs-link"
:class="{
shortened:
state === State.SHORTENED || state === State.SHORTENED_COLLAPSED,
'plain-text': !firstItem.url
}"
>{{ firstItem.label }}</span>
<span class="delimiter">
<img :src="delimiterUrl" alt="navigate next" class="navigate_next" />
</span>
</a>
<template v-if="middleItems.length">
<BreadcrumbsDropdown
v-if="hiddenMiddleItems.length"
:items="hiddenMiddleItems"
:delimiterUrl="delimiterUrl"
/>
<a
v-for="(item, index) in visibleMiddleItems"
:key="item.url"
:href="item.url"
class="breadcrumbs-item"
:title="item.label"
:ref="`visibleMiddleItem-${index}`"
>
<span
class="breadcrumbs-link"
:class="{
shortened:
state === State.SHORTENED || state === State.SHORTENED_COLLAPSED
}"
>{{ item.label }}</span
>
<span class="delimiter">
<img :src="delimiterUrl" alt="navigate next" class="navigate_next" />
</span>
</a>
</template>
<span class="breadcrumbs-item" title="lastItem.label" ref="lastItem">
<span
class="breadcrumbs-link"
:title="lastItem.label"
:class="{
shortened:
state === State.SHORTENED || state === State.SHORTENED_COLLAPSED
}"
>{{ lastItem.label }}</span
>
</span>
</div>
</template>
<script>
import BreadcrumbsDropdown from "./breadcrumbs_dropdown.vue";
const State = Object.freeze({
INITIAL: "initial",
EXPENDED: "expended",
SHORTENED: "shortened",
SHORTENED_COLLAPSED: "shortened_collapsed"
});
const dropdownWidth = 60;
export default {
name: "Breadcrumbs",
props: {
breadcrumbsItems: String,
delimiterUrl: String
},
data() {
return {
dropdownWidth: dropdownWidth,
State,
state: State.INITIAL,
items: [],
hiddenMiddleItems: [],
visibleMiddleItems: []
};
},
components: {
BreadcrumbsDropdown
},
watch: {
breadcrumbsItems: {
immediate: true,
handler() {
this.items = JSON.parse(this.breadcrumbsItems);
this.reset();
this.$nextTick(() => {
this.updateItems();
});
}
}
},
computed: {
firstItem() {
if (this.items.length <= 1) {
return null;
}
return this.items[0];
},
lastItem() {
return this.items[this.items.length - 1];
},
middleItems() {
if (this.items.length <= 2) return [];
return this.items.slice(1, -1);
}
},
methods: {
updateItems() {
const width = this.$refs.container.clientWidth;
const scrollWidth = Array.from(this.$refs.container.children).reduce(
(totalWidth, child) => totalWidth + child.offsetWidth,
0
);
if (this.state === this.State.INITIAL) {
if (width < scrollWidth) {
this.state = this.State.SHORTENED;
this.$nextTick(() => {
this.updateItems();
});
} else {
this.state = this.State.EXPENDED;
}
} else if (this.state === this.State.SHORTENED) {
if (width < scrollWidth) {
this.state = this.State.SHORTENED_COLLAPSED;
let visibleWidth =
this.dropdownWidth +
this.$refs.firstItem.offsetWidth +
this.$refs.lastItem.offsetWidth;
let index = this.middleItems.length - 1;
while (visibleWidth <= width && index >= 0) {
visibleWidth += this.$refs[`visibleMiddleItem-${index}`][0]
.offsetWidth;
index--;
}
this.visibleMiddleItems = this.middleItems.slice(index + 2);
this.hiddenMiddleItems = this.middleItems.slice(0, index + 2);
}
}
},
reset() {
this.state = this.State.INITIAL;
this.hiddenMiddleItems = [];
this.visibleMiddleItems = this.middleItems;
}
}
};
</script>

View file

@ -0,0 +1,56 @@
<template>
<span class="breadcrumbs-collapsed-container">
<span
class="cursor-pointer text-sn-blue flex flex-nowrap whitespace-nowrap items-center gap-1"
data-toggle="dropdown"
:title="i18n.t('projects.index.breadcrumbs_collapsed')"
@click="open = !open"
>
<span class="caret"></span>
<span class="delimiter">
<img :src="delimiterUrl" alt="navigate next" class="navigate_next" />
</span>
</span>
<perfect-scrollbar>
<ul
class="absolute top-11 left-0 w-56 max-h-36 flex flex-col items-stretch gap-2 overflow-auto
bg-sn-white border border-sn-grey rounded-md shadow-md list-none p-2"
:class="{ hidden: !open }"
>
<li v-for="item in items" :key="item.url">
<a :href="item.url" class="breadcrumbs-item breadcrumb-link shortened" title="item.label">
<span
class="breadcrumbs-link shortened"
>{{ item.label }}</span>
<span class="delimiter">
<img
:src="delimiterUrl"
alt="navigate next"
class="navigate_next"
/>
</span>
</a>
</li>
</ul>
</perfect-scrollbar>
</span>
</template>
<script>
import { PerfectScrollbar } from 'vue2-perfect-scrollbar';
export default {
name: "BreadcrumbsDropdown",
props: {
items: Array,
delimiterUrl: String
},
components: { PerfectScrollbar },
data() {
return {
open: false
};
}
};
</script>

View file

@ -63,9 +63,14 @@ export default {
},
computed: {
hasChildren: function() {
return !this.item.disabled && (this.item.has_children || this.children.length > 0);
if (this.item.disabled) return false;
if (this.item.has_children) return true;
if (this.children && this.children.length > 0) return true;
return false
},
sortedMenuItems: function() {
if (!this.children) return [];
return this.children.sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;

View file

@ -31,6 +31,7 @@
<a
data-toggle="modal"
data-target="#newProtocolModal"
v-bind:data-protocol-name="protocol.attributes.name"
:class="{ disabled: !protocol.attributes.urls.save_to_repo_url }"
>
<span class="fas fa-save"></span>

View file

@ -16,7 +16,7 @@
:title="attachment.attributes.wopi_context.title"
target="_blank"
>
<img :src="attachment.attributes.wopi_context.wopi_icon"/>
<i :class="`sn-icon sn-icon-${attachment.attributes.wopi_context.sn_icon}`"></i>
{{ attachment.attributes.wopi_context.button_text }}
</a>
</li>

View file

@ -1,5 +1,7 @@
/* global HelperModule */
const FILENAME_MAX_LENGTH = 100;
export default {
methods: {
initWopiFileModal(step, requestCallback) {
@ -10,6 +12,13 @@ export default {
$wopiModal.modal('show');
$($wopiModal).find('#new-wopi-file-name').focus();
// Clear filename input error on input change if appropriate
$wopiModal.on('input', '#new-wopi-file-name', (e) => {
if (e.currentTarget.value.length <= FILENAME_MAX_LENGTH) {
$wopiModal.clearFormErrors();
}
});
$wopiModal.find('form').on(
'ajax:success',
(e, data, status) => {

View file

@ -99,8 +99,7 @@
props: {
showModal: Boolean,
row_ids: Array,
urls: Object,
zebraEnabled: Boolean
urls: Object
},
data() {
return {
@ -130,12 +129,9 @@
})
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.zebraPrinters = null;
this.$emit('close');
});
if (this.zebraEnabled) {
this.initZebraPrinter();
}
},
computed: {
availableTemplates() {
@ -167,6 +163,7 @@
watch: {
showModal() {
if (this.showModal) {
this.initZebraPrinter();
$(this.$refs.modal).modal('show');
this.validateTemplate();
}

View file

@ -1,6 +1,9 @@
<template>
<div class="flex items-center mr-3 flex-nowrap">
<button v-if="!searchOpened" class="btn btn-light btn-lg btn-black icon-btn" :title="i18n.t('repositories.show.search_button_tooltip')" @click="openSearch">
<div
class="flex items-center mr-3 flex-nowrap relative"
v-click-outside="{handler: 'closeSearchInputs', exclude: ['searchInput', 'searchInputBtn', 'barcodeSearchInput', 'barcodeSearchInputBtn']}"
>
<button :class="{hidden: searchOpened}" ref='searchInputBtn' class="btn btn-light btn-lg btn-black icon-btn" :title="i18n.t('repositories.show.search_button_tooltip')" @click="openSearch">
<i class="sn-icon sn-icon-search"></i>
</button>
<div v-if="searchOpened || barcodeSearchOpened" class="w-52 flex">
@ -11,7 +14,6 @@
type="text"
:placeholder="i18n.t('repositories.show.filter_inventory_items')"
@keyup="setValue"
@blur="closeSearch"
/>
<i class="sn-icon sn-icon-search !mr-2.5"></i>
</div>
@ -21,13 +23,12 @@
class="sci-input-field"
type="text"
:placeholder="i18n.t('repositories.show.filter_inventory_items_with_ean')"
@change="setBarcodeValue"
@blur="closeBarcodeSearch"
@keyup="setBarcodeValue"
/>
<i class='sn-icon sn-icon-barcode barcode-scanner !mr-2.5'></i>
</div>
</div>
<button v-if="!barcodeSearchOpened" class="btn btn-light btn-lg btn-black icon-btn ml-2" :title="i18n.t('repositories.show.ean_search_button_tooltip')" @click="openBarcodeSearch">
<button :class="{hidden: barcodeSearchOpened}" ref='barcodeSearchInputBtn' class="btn btn-light btn-lg btn-black icon-btn ml-2" :title="i18n.t('repositories.show.ean_search_button_tooltip')" @click="openBarcodeSearch">
<i class='sn-icon sn-icon-barcode barcode-scanner'></i>
</button>
</div>
@ -66,7 +67,7 @@ export default {
},
openBarcodeSearch() {
this.clearValues();
this.closeSearch();
this.searchOpened = false;
this.barcodeSearchOpened = true;
this.$nextTick(() => {
this.$refs.barcodeSearchInput.focus();
@ -74,7 +75,7 @@ export default {
},
openSearch() {
this.clearValues();
this.closeBarcodeSearch();
this.barcodeSearchOpened = false;
this.searchOpened = true;
this.$nextTick(() => {
this.$refs.searchInput.focus();
@ -103,6 +104,10 @@ export default {
this.barcodeValue = '';
if (this.$refs.searchInput) this.$refs.searchInput.value = '';
if (this.$refs.barcodeSearchInput) this.$refs.barcodeSearchInput.value = '';
},
closeSearchInputs() {
this.closeSearch();
this.closeBarcodeSearch();
}
}
}

View file

@ -27,7 +27,7 @@
:data-placeholder="placeholder"
:data-tinymce-init="`tinymce-${objectType}-description-${objectId}`">
</div>
<div class="flex">
<div class="flex tinymce-editor-container">
<textarea :id="`${objectType}_textarea_${objectId}`"
class="form-control hidden"
autocomplete="off"

View file

@ -1,4 +1,7 @@
# frozen_string_literal: true
class ApplicationSettings < Settings
def load_values_from_env
ENV.select { |name, _| name =~ /^APP_STTG_[A-Z0-9_]*/ }.transform_keys(&:downcase)
end
end

View file

@ -19,10 +19,6 @@ class LabelPrinter < ApplicationRecord
validates :type_of, presence: true
validates :language_type, presence: true
def self.zebra_print_enabled?
RepositoryBase.stock_management_enabled?.present?
end
def done?
current_print_job_ids.blank? && ready?
end

View file

@ -16,7 +16,7 @@ class LabelTemplate < ApplicationRecord
validate :ensure_single_default_template!
def self.enabled?
RepositoryBase.stock_management_enabled?
ApplicationSettings.instance.values['label_templates_enabled'] == true
end
def icon

View file

@ -172,19 +172,6 @@ class Project < ApplicationRecord
ProjectComment.from(comments, :comments).order(created_at: :asc)
end
def unassigned_users
User.joins(:user_teams)
.joins(
"LEFT OUTER JOIN user_assignments ON user_assignments.user_id = users.id "\
"AND user_assignments.assignable_id = #{id} "\
"AND user_assignments.assignable_type = 'Project'"
)
.where(user_teams: { team_id: team_id })
.where(user_assignments: { id: nil })
.where.not(confirmed_at: nil)
.distinct
end
def user_role(user)
user_assignments.includes(:user_role).references(:user_role).find_by(user: user)&.user_role&.name
end
@ -289,7 +276,7 @@ class Project < ApplicationRecord
tables = parsed_html.css('.hot-table-contents')
.zip(parsed_html.css('.hot-table-container'), parsed_html.css('.hot-table-metadata'))
tables.each do |table_input, table_container, metadata|
is_plate_template = JSON.parse(metadata['value'])['plateTemplate'] if metadata.present?
is_plate_template = JSON.parse(metadata['value'])['plateTemplate'] if metadata && metadata['value'].present?
table_vals = JSON.parse(table_input['value'])
table_data = table_vals['data']
table_headers = table_vals['headers']

View file

@ -122,7 +122,7 @@ class Report < ApplicationRecord
)
end
report = Report.new
report = Report.new(skip_user_assignments: true)
report.name = loop do
dummy_name = SecureRandom.hex(10)
break dummy_name unless Report.exists?(name: dummy_name)

View file

@ -4,4 +4,16 @@ class Settings < ApplicationRecord
def self.instance
first || new
end
def values
merged_values = super
self.class.instance_methods(false).grep(/^load_values_from_[A-Z0-9_]*/).each do |method|
merged_values = merged_values.merge(public_send(method))
end
merged_values
end
def load_values_from_env
raise NotImplementedError
end
end

View file

@ -105,6 +105,10 @@ class Table < ApplicationRecord
end
end
def metadata
attributes['metadata'].is_a?(String) ? JSON.parse(attributes['metadata']) : attributes['metadata']
end
def contents_utf_8
contents.present? ? contents.force_encoding(Encoding::UTF_8) : nil
end

View file

@ -31,7 +31,6 @@ class Team < ApplicationRecord
foreign_key: 'last_modified_by_id',
class_name: 'User',
optional: true
has_many :user_teams, inverse_of: :team, dependent: :destroy
has_many :users, through: :user_assignments
has_many :projects, inverse_of: :team
has_many :project_folders, inverse_of: :team, dependent: :destroy
@ -171,8 +170,8 @@ class Team < ApplicationRecord
def self.global_activity_filter(filters, search_query)
query = where('name ILIKE ?', "%#{search_query}%")
if filters[:users]
users_team = User.where(id: filters[:users]).joins(:user_teams).group(:team_id).pluck(:team_id)
query = query.where(id: users_team)
user_teams = User.where(id: filters[:users]).joins(:teams).group(:team_id).select(:team_id)
query = query.where(id: user_teams)
end
query = query.where(id: team_by_subject(filters[:subjects])) if filters[:subjects]
query.select(:id, :name).map { |i| { value: i[:id], label: ApplicationController.helpers.escape_input(i[:name]) } }

View file

@ -58,7 +58,6 @@ class User < ApplicationRecord
# Relations
has_many :user_identities, inverse_of: :user
has_many :user_teams, inverse_of: :user
has_many :user_assignments, dependent: :destroy
has_many :user_projects, inverse_of: :user
has_many :teams, through: :user_assignments, source: :assignable, source_type: 'Team'
@ -157,8 +156,8 @@ class User < ApplicationRecord
has_many :tokens,
class_name: 'Token',
foreign_key: 'user_id',
inverse_of: :user
inverse_of: :user,
dependent: :destroy
has_many :modified_tags,
class_name: 'Tag',
@ -166,9 +165,6 @@ class User < ApplicationRecord
has_many :assigned_user_my_modules,
class_name: 'UserMyModule',
foreign_key: 'assigned_by_id'
has_many :assigned_user_teams,
class_name: 'UserTeam',
foreign_key: 'assigned_by_id'
has_many :assigned_user_projects,
class_name: 'UserProject',
foreign_key: 'assigned_by_id'
@ -311,7 +307,6 @@ class User < ApplicationRecord
has_many :user_notifications, inverse_of: :user
has_many :notifications, through: :user_notifications
has_many :zip_exports, inverse_of: :user, dependent: :destroy
has_many :datatables_teams, class_name: '::Views::Datatables::DatatablesTeam'
has_many :view_states, dependent: :destroy
has_many :access_grants, class_name: 'Doorkeeper::AccessGrant',
@ -434,46 +429,6 @@ class User < ApplicationRecord
end
end
def projects_by_teams(team_id = 0, sort_by = nil, archived = false)
archived = archived ? true : false
query = Project.all.joins(:user_projects)
sql = 'projects.team_id IN (SELECT DISTINCT team_id ' \
'FROM user_teams WHERE user_teams.user_id = :user_id)'
if team_id.zero? || !user_teams.find_by(team_id: team_id).try(:admin?)
# Admins see all projects of team
sql += ' AND (projects.visibility=1 OR user_projects.user_id=:user_id)'
end
sql += ' AND projects.archived = :archived '
sort =
case sort_by
when 'old'
{ created_at: :asc }
when 'atoz'
{ name: :asc }
when 'ztoa'
{ name: :desc }
else
{ created_at: :desc }
end
if team_id > 0
result = query
.where('projects.team_id = ?', team_id)
.where(sql, user_id: id, archived: archived)
.order(sort)
.distinct
.group_by(&:team)
else
result = query
.where(sql, user_id: id, archived: archived)
.order(sort)
.distinct
.group_by(&:team)
end
result || []
end
# Finds all activities of user that is assigned to project. If user
# is not an owner of the project, user must be also assigned to
# module.
@ -499,11 +454,9 @@ class User < ApplicationRecord
def self.find_by_valid_wopi_token(token)
Rails.logger.warn "WOPI: searching by token #{token}"
User
.joins('LEFT OUTER JOIN tokens ON user_id = users.id')
.where(tokens: { token: token })
.where('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i)
.first
User.joins(:tokens)
.where(tokens: { token: token })
.find_by('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i)
end
def get_wopi_token

View file

@ -1,67 +0,0 @@
# frozen_string_literal: true
class UserTeam < ApplicationRecord
enum role: { guest: 0, normal_user: 1, admin: 2 }
validates :role, :user, :team, presence: true
belongs_to :user, inverse_of: :user_teams, touch: true
belongs_to :assigned_by, foreign_key: 'assigned_by_id', class_name: 'User', optional: true
belongs_to :team, inverse_of: :user_teams
after_create :assign_user_to_visible_projects
before_destroy :destroy_associations
def role_str
I18n.t("user_teams.enums.role.#{role}")
end
def destroy_associations
# Destroy the user from all team's projects
user.user_projects.joins(:project).where(project: team.projects).destroy_all
# destroy all assignments
UserAssignments::RemoveUserAssignmentJob.perform_now(user, team)
end
# returns user_teams where the user is in team
def self.user_in_team(user, team)
where(user: user, team: team)
end
def destroy(new_owner)
return super() unless new_owner
# Also, make new owner author of all protocols that belong
# to the departing user and belong to this team.
p_ids = user.added_protocols.where(team: team).pluck(:id)
Protocol.find(p_ids).each do |protocol|
protocol.record_timestamps = false
protocol.added_by = new_owner
if protocol.archived_by != nil
protocol.archived_by = new_owner
end
if protocol.restored_by != nil
protocol.restored_by = new_owner
end
protocol.save
end
# Make new owner author of all inventory items that were added
# by departing user and belong to this team.
RepositoryRow.change_owner(team, user, new_owner)
super()
end
private
def assign_user_to_visible_projects
team.projects.visible.each do |project|
UserAssignments::ProjectGroupAssignmentJob.perform_later(
team,
project,
assigned_by
)
end
end
end

View file

@ -92,8 +92,7 @@ Canaid::Permissions.register_for(Repository) do
# repository: create/update/delete filters
can :manage_repository_filters do |user, repository|
((repository.team == user.current_team) && can_manage_team?(user, repository.team)) ||
(repository.shared_with_write?(user.current_team) && can_manage_team?(user, user.current_team))
repository.permission_granted?(user, RepositoryPermissions::FILTERS_MANAGE)
end
can :manage_repository_stock do |user, repository|

View file

@ -8,7 +8,7 @@ module Api
has_many :checklist_items, serializer: ChecklistItemSerializer
def position
object.step_orderable_element.position
object&.step_orderable_element&.position
end
include TimestampableModel

View file

@ -13,7 +13,7 @@ module Api
end
def position
object.step_orderable_element.position
object&.step_orderable_element&.position
end
end
end

View file

@ -13,7 +13,7 @@ module Api
end
def position
object.step_table&.step_orderable_element&.position
object&.step_table&.step_orderable_element&.position
end
end
end

View file

@ -64,7 +64,8 @@ class AssetSerializer < ActiveModel::Serializer
edit_supported: edit_supported,
title: title,
button_text: wopi_button_text(object, 'edit'),
wopi_icon: ActionController::Base.helpers.image_path(file_application_url(object))
wopi_icon: ActionController::Base.helpers.image_path(file_application_url(object)),
sn_icon: sn_icon_for(object)
}
end
end

View file

@ -86,7 +86,7 @@ class ProjectsOverviewService
def fetch_project_records
@team.projects
.includes(user_assignments: %i(user user_role), team: :user_teams)
.includes(:team, user_assignments: %i(user user_role))
.includes(:project_comments, experiments: { my_modules: { my_module_status: :my_module_status_implications } })
.visible_to(@user, @team)
.left_outer_joins(:project_comments)
@ -97,7 +97,7 @@ class ProjectsOverviewService
def fetch_project_folder_records
project_folders = @team.project_folders
.includes(team: :user_teams)
.includes(:team)
.joins('LEFT OUTER JOIN project_folders child_folders
ON child_folders.parent_folder_id = project_folders.id')
.left_outer_joins(:projects)

View file

@ -8,13 +8,27 @@ module Reports::Docx::DrawResultTable
obj = self
table_data = JSON.parse(table.contents_utf_8)['data']
table_data = obj.add_headers_to_table(table_data, false)
if table.metadata.present?
table.metadata['cells']&.each do |cell|
next unless cell['row'].present? && cell['col'].present?
row_index = cell['row'].to_i + 1
col_index = cell['col'].to_i + 1
calculated_value = cell['calculated']
if calculated_value.present?
table_data[row_index][col_index] = calculated_value
end
end
end
@docx.p
@docx.table table_data, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE do
cell_style rows[0], bold: true, background: color[:concrete]
cell_style cols[0], bold: true, background: color[:concrete]
if table.metadata.present?
JSON.parse(table.metadata)['cells']&.each do |cell|
table.metadata['cells']&.each do |cell|
next unless cell.present? && cell['row'].present? && cell['col'].present? && cell['className'].present?
cell_style rows.dig(cell['row'].to_i + 1, cell['col'].to_i + 1),

View file

@ -7,6 +7,20 @@ module Reports::Docx::DrawStepTable
obj = self
table_data = JSON.parse(table.contents_utf_8)['data']
table_data = obj.add_headers_to_table(table_data, table_type == 'step_well_plates_table')
if table.metadata.present?
table.metadata['cells']&.each do |cell|
next unless cell['row'].present? && cell['col'].present?
row_index = cell['row'].to_i + 1
col_index = cell['col'].to_i + 1
calculated_value = cell['calculated']
if calculated_value.present?
table_data[row_index][col_index] = calculated_value
end
end
end
@docx.p
@docx.table table_data, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE do
cell_style rows[0], bold: true, background: color[:concrete]

View file

@ -43,9 +43,9 @@ module SmartAnnotations
def parse_users_annotations(user, team, text)
@text = text.gsub(USER_REGEX) do |el|
match = el.match(USER_REGEX)
user = User.find_by_id(match[2].base62_decode)
user = team.users.find_by(id: match[2].base62_decode)
next unless user
next if UserTeam.where(user: user, team: team).blank?
user.full_name
end
end

View file

@ -63,7 +63,7 @@ module Toolbars
label: I18n.t('libraries.index.buttons.export'),
button_id: 'exportRepoBtn',
icon: 'sn-icon sn-icon-export',
path: export_modal_team_repositories_path(@current_team),
path: export_modal_team_repositories_path(@current_team, counter: @repositories.length),
type: 'remote-modal'
}
end

View file

@ -79,7 +79,7 @@ module Toolbars
{
name: 'assign',
label: I18n.t('repositories.assign_record'),
icon: 'sn-icon sn-icon-files',
icon: 'sn-icon sn-icon-assign-to-task',
button_class: 'assign-repository-rows-btn',
button_id: 'assignRepositoryRecords',
type: :legacy

View file

@ -74,7 +74,6 @@ class UserDataDeletion
destroy_protocol(protocol)
end
team.protocol_keywords.destroy_all
team.user_teams.delete_all
User.where(current_team_id: team).each do |user|
user.update(current_team_id: nil)
end

View file

@ -43,10 +43,9 @@
<% elsif asset.previewable? %>
<div class="image-container">
<%= image_tag asset.large_preview,
class: 'asset-inline-image',
class: 'asset-preview-image',
style: 'opacity: 0' %>
</div>
<%= javascript_include_tag 'assets/asset_inline' %>
<% else %>
<div class="general-file-container">
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>

View file

@ -16,9 +16,8 @@
<div class="attachment-preview <%= asset.file.attached? ? asset.file.metadata['asset_type'] : '' %>">
<% if asset.previewable? %>
<%= image_tag asset.medium_preview,
class: 'asset-thumbnail-image',
class: 'asset-preview-image',
style: 'opacity: 0' %>
<%= javascript_include_tag 'assets/asset_thumbnail' %>
<% else %>
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
<% end %>

View file

@ -7,13 +7,11 @@
data-print-validation-url="<%= validate_label_template_columns_repository_rows_path %>"
data-label-preview-url="<%= zpl_preview_label_templates_path %>"
data-fluics-info-url="<%= Constants::SCINOTE_FLUICS_URL %>"
data-zebra-enabled="<%= LabelPrinter.zebra_print_enabled? %>"
>
<print-modal-container
:show-modal = "showModal"
:row_ids = "row_ids"
:urls = "urls"
:zebra-enabled = "zebraEnabled"
@close="closeModal"
></print-modal-container>
</div>

View file

@ -1,4 +1,2 @@
<% if LabelPrinter.zebra_print_enabled? %>
<%= javascript_include_tag 'BrowserPrint-3.0.216.min' %>
<%= javascript_include_tag 'BrowserPrint-Zebra-1.0.216.min' %>
<% end %>
<%= javascript_include_tag 'BrowserPrint-3.0.216.min' %>
<%= javascript_include_tag 'BrowserPrint-Zebra-1.0.216.min' %>

View file

@ -9,7 +9,7 @@
<h1 class="printer-title">
<% if @printer_type == 'fluics' %>
<%= t("users.settings.account.label_printer.fluics_printer") %>
<% elsif @printer_type == 'zebra' && LabelPrinter.zebra_print_enabled? %>
<% elsif @printer_type == 'zebra' %>
<%= t("users.settings.account.label_printer.zebra_printer") %>
<% end %>
</h1>
@ -18,7 +18,7 @@
<% if @printer_type == 'fluics' %>
<%= render "fluics_settings" %>
<% elsif @printer_type == 'zebra' && LabelPrinter.zebra_print_enabled? %>
<% elsif @printer_type == 'zebra' %>
<%= render "zebra_settings" %>
<% end %>
</div>

View file

@ -21,13 +21,13 @@
<div class="flex items-center uppercase text-bold">
<% if can_read_experiment?(@my_module.experiment) %>
<a class="p-4 border-b-4 border-transparent text-sn-grey hover:no-underline <%= "border-sn-blue" if is_module_protocols? %>"
href="<%= protocols_my_module_url(@my_module) %>"
href="<%= protocols_my_module_url(@my_module, view_mode: params[:view_mode]) %>"
title="<%= t("nav2.modules.steps") %>"
>
<%=t "nav2.modules.steps" %>
</a>
<a class="p-4 border-b-4 border-transparent text-sn-grey hover:no-underline <%= "border-sn-blue" if is_module_results? %>"
href="<%= results_my_module_url(@my_module) %>"
href="<%= results_my_module_url(@my_module, view_mode: params[:view_mode]) %>"
title="<%= t("nav2.modules.results") %>"
>
<%= t("nav2.modules.results") %>

View file

@ -23,7 +23,7 @@
<% if @my_module.completed? %>
<div class="flex-block date-block" >
<div class="flex-block-label">
<span class="fas block-icon fa-calendar-day"></span>
<span class="sn-icon sn-icon-calendar mr-2.5"></span>
<span class="hidden-xs hidden-sm hidden-md"><%= t('my_modules.details.completed_date') %></span>
</div>
<div class="datetime-container">

View file

@ -1,5 +1,5 @@
<%= form_with url: update_consumption_my_module_repository_path(@my_module, @repository, module_row_id: @module_repository_row),
method: :post,
<%= form_with url: update_consumption_my_module_repository_path(@my_module, @repository, module_row_id: @module_repository_row),
method: :post,
html: { data: { remote: true } } do |f| %>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>">
@ -42,7 +42,7 @@
<span class="units"><%= @stock_value.repository_stock_unit_item&.data %></span>
</div>
<div class="stock-arrow">
<i class="sn-icon sn-icon-move"></i>
<i class="sn-icon sn-icon-arrow-right"></i>
</div>
<div class="stock-final-container ">
<span class="subtitle"><%= t('repository_stock_values.manage_modal.new_stock') %></span>

View file

@ -25,7 +25,7 @@
</div>
<div class="report-element-body">
<input type="hidden" class="hot-table-contents" value="<%= table.contents_utf_8 %>" />
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata %>" />
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata.to_json %>" />
<div class="hot-table-container"></div>
<table class="report-common-table-format"></table>
</div>

View file

@ -14,10 +14,10 @@
</span>
<div class="move-buttons sci-btn-group">
<button class="btn btn-light icon-btn move-up">
<i class="sn-icon sn-icon-sort-up"></i>
<i class="sn-icon sn-icon-arrow-up"></i>
</button>
<button class="btn btn-light icon-btn move-down">
<i class="sn-icon sn-icon-sort-down"></i>
<i class="sn-icon sn-icon-arrow-down"></i>
</button>
</div>
</div>

View file

@ -11,7 +11,8 @@
<h4 class="modal-title"><%= t('repositories.index.modal_export.title') %></h4>
</div>
<div class="modal-body">
<p class="description-p1" data-team-name="<%= team_name %>">
<p class="description-p1">
<%= t('repositories.index.modal_export.description_p1_html', team_name: team_name, count: counter) %>
</p>
<p class="bg-sn-super-light-blue p-3"><%= t('repositories.index.modal_export.description_alert') %></p>
<p class="mt-3"><%= t('repositories.index.modal_export.description_p2') %></p>

View file

@ -59,7 +59,7 @@
<%= "data-items-url=#{items_repository_repository_columns_list_column_path(repository, column)}" if column.repository_list_value? %>
<%= "data-items-url=#{items_repository_repository_columns_status_column_path(repository, column)}" if column.repository_status_value? %>
>
<%= display_tooltip(column.name) %>
<div class="truncate"><%= display_tooltip(column.name) %></div>
</th>
<% end %>
</tr>

View file

@ -1,70 +1,9 @@
<% if @breadcrumbs_items&.length %>
<% shortened = @breadcrumbs_items.length > 4 %>
<% if shortened %>
<% first_breadcrumb_item = @breadcrumbs_items.shift
last_breadcrumb_items = @breadcrumbs_items.pop(2) %>
<%= link_to(first_breadcrumb_item[:label], first_breadcrumb_item[:url],
class: "breadcrumbs-link",
title: first_breadcrumb_item[:label]) %>
<span class="delimiter">
<%= image_tag "icon_small/navigate_next.svg",
alt: "navigate next",
class: "navigate_next" %>
</span>
<span class="breadcrumbs-collapsed-container">
<span class="show-breadcrumbs" data-toggle="dropdown" title="<%= t('projects.index.breadcrumbs_collapsed') %>">
•••
<span class="caret pull-right"></span>
</span>
<ul class="dropdown-menu breadcrumbs-dropdown" role="menu">
<% @breadcrumbs_items.each do |item| %>
<li>
<%= link_to(item[:label], item[:url]) %>
</li>
<% end %>
</ul>
</span>
<span class="delimiter">
<%= image_tag "icon_small/navigate_next.svg",
alt: "navigate next",
class: "navigate_next" %>
</span>
<% item = last_breadcrumb_items.first %>
<%= link_to(item[:label], item[:url],
class: "breadcrumbs-link",
title: item[:label]) %>
<span class="delimiter">
<%= image_tag "icon_small/navigate_next.svg",
alt: "navigate next",
class: "navigate_next" %>
</span>
<% last_item = last_breadcrumb_items.last %>
<span class="breadcrumbs-link" title="<%= last_item[:label] %>">
<%= last_item[:label] %>
</span>
<% else %>
<% last_item = @breadcrumbs_items.pop %>
<% @breadcrumbs_items.each do |item| %>
<% if item[:url] %>
<%= link_to(item[:label], item[:url],
class: "breadcrumbs-link",
title: item[:label]) %>
<% else %>
<%= content_tag(:span, item[:label],
class: "breadcrumbs-link plain-text",
title: item[:label]) %>
<% end %>
<span class="delimiter">
<img src="<%= asset_path "icon_small/navigate_next.svg"%>"
alt="navigate next"
class="navigate_next">
</span>
<% end %>
<span class="breadcrumbs-link" title="<%= last_item[:label] %>">
<%= last_item[:label] %>
</span>
<% end %>
<div id="breadcrumbs" data-behaviour="vue">
<breadcrumbs
breadcrumbs-items="<%= @breadcrumbs_items.to_json %>"
delimiter-url="<%= asset_path "icon_small/navigate_next.svg" %>"
/>
</div>
<%= javascript_include_tag 'vue_navigation_breadcrumbs' %>
<% end %>

View file

@ -7,10 +7,10 @@
</div>
<div class="items">
<% experiment_group[:experiments].each do |experiment| %>
<li class="item" data-name="<%= experiment.name %>" data-id="<%= experiment.id.base62_encode %>" data-type="exp">
<li class="item" data-name="<%= sanitize_input(experiment.name) %>" data-id="<%= experiment.id.base62_encode %>" data-type="exp">
<span class='sa-type'><%= experiment.code %></span>
<span class="dot">&middot;</span>
<span class="item-text"><%= experiment.name %></span>
<span class="item-text"><%= sanitize_input(experiment.name) %></span>
<%= render partial: 'shared/smart_annotation/atwho_control_buttons' %>
</li>
<% end %>

View file

@ -9,10 +9,10 @@
</div>
<div class="items">
<% task_group[:tasks].each do |task| %>
<li class="item" data-name="<%= task.name %>" data-id="<%= task.id.base62_encode %>" data-type="tsk">
<li class="item" data-name="<%= sanitize_input(task.name) %>" data-id="<%= task.id.base62_encode %>" data-type="tsk">
<span class='sa-type'><%= task.code %></span>
<span class="dot">&middot;</span>
<span class="item-text"><%= task.name %></span>
<span class="item-text"><%= sanitize_input(task.name) %></span>
<%= render partial: 'shared/smart_annotation/atwho_control_buttons' %>
</li>
<% end %>

View file

@ -1,10 +1,10 @@
<% limit_reached = projects.length == Constants::ATWHO_SEARCH_LIMIT + 1 %>
<div class="atwho-scroll-container">
<% projects.limit(Constants::ATWHO_SEARCH_LIMIT).each do |project| %>
<li class="item" data-name="<%= project.name %>" data-id="<%= project.id.base62_encode %>" data-type="prj">
<li class="item" data-name="<%= sanitize_input(project.name) %>" data-id="<%= project.id.base62_encode %>" data-type="prj">
<span class='sa-type'><%= project.code %></span>
<span class="dot">&middot;</span>
<span class="item-text"><%= project.name %></span>
<span class="item-text"><%= sanitize_input(project.name) %></span>
<%= render partial: 'shared/smart_annotation/atwho_control_buttons' %>
</li>
<% end %>

View file

@ -1,10 +1,10 @@
<% limit_reached = repository_rows.length == Constants::ATWHO_SEARCH_LIMIT + 1 %>
<div class="atwho-scroll-container">
<% repository_rows.take(Constants::ATWHO_SEARCH_LIMIT).each do |row| %>
<li class="item" data-name="<%= row[:name] %>" data-id="<%= row[:id_encoded] %>" data-type="rep_item">
<li class="item" data-name="<%= sanitize_input(row[:name]) %>" data-id="<%= row[:id_encoded] %>" data-type="rep_item">
<span class='sa-type'><%= row[:code] %></span>
<span class="dot">&middot;</span>
<span class="item-text"><%= row[:name] %></span>
<span class="item-text"><%= sanitize_input(row[:name]) %></span>
<%= render partial: 'shared/smart_annotation/atwho_control_buttons', locals: { row: row, repository: repository } %>
</li>
<% end %>

View file

@ -5,10 +5,10 @@
</div>
<div class="atwho-scroll-container">
<% users.limit(Constants::ATWHO_SEARCH_LIMIT).each do |user| %>
<li class="atwho-user" data-full-name="<%= user.full_name %>" data-id="<%= user.id.base62_encode %>" data-type="rep_item">
<li class="atwho-user" data-full-name="<%= sanitize_input(user.full_name) %>" data-id="<%= user.id.base62_encode %>" data-type="rep_item">
<img src="<%= avatar_path(user, :icon_small) %>" class="avatar" />
<div class="user-info">
<div class="user-name item-text"><%= user.full_name %></div>
<div class="user-name item-text"><%= sanitize_input(user.full_name) %></div>
<div class="user-email item-text"><%= user.email %></div>
</div>
</li>

View file

@ -20,25 +20,23 @@
<div class="row printer-settings">
<div class="col-xs-12 col-sm-12">
<h2 class="addons-subtitle"><%= t('users.settings.account.addons.label_printers') %></h2>
<% if LabelPrinter.zebra_print_enabled? %>
<div class="printers-container">
<div class="printer">
<div class="header">
<div class="title">
<%= t('users.settings.account.addons.zebra_printer.title') %>
</div>
<div class="control">
<%= t('users.settings.account.addons.printers.enabled') %>
<i class="sn-icon sn-icon-check"></i>
</div>
<div class="printers-container">
<div class="printer">
<div class="header">
<div class="title">
<%= t('users.settings.account.addons.zebra_printer.title') %>
</div>
<div class="description">
<%= t('users.settings.account.addons.zebra_printer.description') %>
<div class="control">
<%= t('users.settings.account.addons.printers.enabled') %>
<i class="sn-icon sn-icon-check"></i>
</div>
<%= link_to t('users.settings.account.addons.printers.details'), zebra_settings_path(), class: 'printer-details' %>
</div>
<div class="description">
<%= t('users.settings.account.addons.zebra_printer.description') %>
</div>
<%= link_to t('users.settings.account.addons.printers.details'), zebra_settings_path(), class: 'printer-details' %>
</div>
<% end %>
</div>
<div class="printers-container">
<div class="printer">
<div class="header">

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