Merge branch 'develop' of github.com:biosistemika/scinote-web into features/repository-filters

This commit is contained in:
Martin Artnik 2022-01-20 13:26:08 +01:00
commit abac837cd4
99 changed files with 2176 additions and 337 deletions

View file

@ -1,7 +1,7 @@
FROM ruby:2.7.2-buster
FROM ruby:2.7.5-bullseye
MAINTAINER BioSistemika <info@biosistemika.com>
ARG WKHTMLTOPDF_PACKAGE_URL=https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb
ARG WKHTMLTOPDF_PACKAGE_URL=https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_amd64.deb
# additional dependecies
# libreoffice for file preview generation

View file

@ -1,7 +1,7 @@
FROM ruby:2.7.2-buster
FROM ruby:2.7.5-bullseye
MAINTAINER BioSistemika <info@biosistemika.com>
ARG WKHTMLTOPDF_PACKAGE_URL=https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb
ARG WKHTMLTOPDF_PACKAGE_URL=https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_amd64.deb
# additional dependecies
# libreoffice for file preview generation

View file

@ -2,7 +2,7 @@
source 'http://rubygems.org'
ruby '2.7.2'
ruby '2.7.5'
gem 'bootsnap', require: false
gem 'bootstrap-sass', '~> 3.4.1'
@ -13,6 +13,7 @@ gem 'figaro'
gem 'pg', '~> 1.1'
gem 'pg_search' # PostgreSQL full text search
gem 'rails', '~> 6.1.4'
gem 'psych', '< 4.0'
gem 'view_component', require: 'view_component/engine'
gem 'recaptcha', require: 'recaptcha/rails'
gem 'sanitize', '~> 5.2'

View file

@ -435,8 +435,9 @@ GEM
pry (~> 0.13.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
psych (3.3.2)
public_suffix (4.0.6)
puma (5.5.1)
puma (5.5.2)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.0)
@ -699,6 +700,7 @@ DEPENDENCIES
pry
pry-byebug
pry-rails
psych (< 4.0)
puma
rack-attack
rack-cors
@ -742,7 +744,7 @@ DEPENDENCIES
yomu!
RUBY VERSION
ruby 2.7.2p137
ruby 2.7.5p203
BUNDLED WITH
2.1.4

View file

@ -1 +1 @@
1.22.4.1
1.23.7

View file

@ -0,0 +1,13 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" version="1.1" id="svg4615" sodipodi:docname="check-circle-solid.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" viewBox="8 8 496 496">
<metadata id="metadata4621">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs4619"/>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1848" inkscape:window-height="1016" id="namedview4617" showgrid="false" inkscape:zoom="0.4609375" inkscape:cx="-129.08475" inkscape:cy="256" inkscape:window-x="72" inkscape:window-y="27" inkscape:window-maximized="1" inkscape:current-layer="svg4615"/>
<path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" id="path4613" style="fill:#2dbe61;fill-opacity:1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,13 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-square" class="svg-inline--fa fa-check-square fa-w-14" role="img" version="1.1" id="svg851" sodipodi:docname="check-square-solid.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" viewBox="0 32 448 448">
<metadata id="metadata857">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs855"/>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1848" inkscape:window-height="1016" id="namedview853" showgrid="false" inkscape:zoom="1.6367188" inkscape:cx="224" inkscape:cy="256" inkscape:window-x="72" inkscape:window-y="27" inkscape:window-maximized="1" inkscape:current-layer="svg851"/>
<path fill="currentColor" d="M400 480H48c-26.51 0-48-21.49-48-48V80c0-26.51 21.49-48 48-48h352c26.51 0 48 21.49 48 48v352c0 26.51-21.49 48-48 48zm-204.686-98.059l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.248-16.379-6.249-22.628 0L184 302.745l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.25 16.379 6.25 22.628.001z" id="path849" style="fill:#104da9;fill-opacity:1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -45,6 +45,7 @@
//= require activestorage
//= require global_activities/side_pane
//= require protocols/header
//= require protocols/print
//= require marvinjslauncher
//= require jstree.min
//= require_tree ./repositories/renderers
@ -56,7 +57,6 @@
//= require sidebar
//= require turbolinks
// Initialize links for submitting forms. This is useful for submitting
// forms with clicking on links outside form in cases when other than
// GET method is used.

View file

@ -0,0 +1,3 @@
$(document).on('submit', '#print-protocol-form', function() {
$('#print-protocol-modal').modal('hide');
});

View file

@ -5,8 +5,9 @@
// Sets callbacks for toggling checkboxes
function applyCheckboxCallBack() {
$("[data-action='check-item']").off().on('click', function(e){
$("[data-action='check-item']").off().on('click', function (e) {
var checkboxitem = $(this).find("input");
if (checkboxitem.prop("disabled")) { return; }
var checked = checkboxitem.is(":checked");
$.ajax({
url: checkboxitem.data("link-url"),

View file

@ -54,7 +54,7 @@ var RepositoryStatusColumnType = (function() {
var picker = new EmojiButton({ rootElement: document.getElementById('manage-repository-column') });
var iconElement = this;
picker.on('emoji', emoji => {
$(iconElement).attr('emoji', emoji).html(twemoji.parse(emoji));
$(iconElement).attr('emoji', emoji.emoji).html(twemoji.parse(emoji.emoji));
validateForm();
});
@ -63,7 +63,9 @@ var RepositoryStatusColumnType = (function() {
} else {
picker.showPicker(iconElement);
}
twemoji.parse($('.emoji-picker').last().find('.emoji-picker__tab-body')[1]);
$.each($('.emoji-picker__emojis').last().find('.emoji-picker__container'), function(i, container) {
twemoji.parse(container);
});
})
.on('click', '.emoji-picker__tab-body.active .emoji-picker__emoji', function() {
if ($('.emoji-picker__variant-popup').length) {
@ -73,7 +75,7 @@ var RepositoryStatusColumnType = (function() {
.on('click', '.emoji-picker__tab', function() {
$.each($('.emoji-picker__tab'), (i, tab) => {
if ($(tab).hasClass('active')) {
twemoji.parse($('.emoji-picker__tab-body')[i]);
twemoji.parse($('.emoji-picker__container')[i]);
}
});
});

View file

@ -125,6 +125,7 @@ var bioEddieEditor = (function() {
open_new: (objectId, objectType, container) => {
bioEddieModal.data('object_id', objectId);
bioEddieModal.data('object_type', objectType);
bioEddieModal.data('molecule', null);
bioEddieModal.data('assets_container', container);
bioEddieModal.find('.file-name input').val('');
bioEddieModal.modal('show');

View file

@ -2,6 +2,7 @@
/* eslint-disable no-underscore-dangle */
var ImageEditorModal = (function() {
function updateFabricControls() {
fabric.Object.prototype.drawBorders = function(ctx, styleOverride = {}) {
var wh = this._calculateCurrentDimensions();
@ -174,30 +175,10 @@ var ImageEditorModal = (function() {
'downloadButton.fontFamily': '\'Noto Sans\', sans-serif',
'downloadButton.fontSize': '12px',
// main icons
'menu.normalIcon.path': '/images/icon-d.svg',
'menu.normalIcon.name': 'icon-d',
'menu.activeIcon.path': '/images/icon-b.svg',
'menu.activeIcon.name': 'icon-b',
'menu.disabledIcon.path': '/images/icon-a.svg',
'menu.disabledIcon.name': 'icon-a',
'menu.hoverIcon.path': '/images/icon-c.svg',
'menu.hoverIcon.name': 'icon-c',
'menu.iconSize.width': '24px',
'menu.iconSize.height': '24px',
// submenu primary color
'submenu.backgroundColor': '#1e1e1e',
'submenu.partition.color': '#3c3c3c',
// submenu icons
'submenu.normalIcon.path': '/images/icon-d.svg',
'submenu.normalIcon.name': 'icon-d',
'submenu.activeIcon.path': '/images/icon-c.svg',
'submenu.activeIcon.name': 'icon-c',
'submenu.iconSize.width': '32px',
'submenu.iconSize.height': '32px',
// submenu labels
'submenu.normalLabel.color': '#8a8a8a',
'submenu.normalLabel.fontWeight': 'lighter',
@ -261,6 +242,7 @@ var ImageEditorModal = (function() {
});
ps = new PerfectScrollbar($('.tui-image-editor-wrap')[0], { wheelSpeed: 0.5 });
/*
$('#tui-image-editor .tui-image-editor').on('mousewheel', (e) => {
var imageOriginalSize = {
width: imageEditor._graphics.canvasImage.width,
@ -329,6 +311,7 @@ var ImageEditorModal = (function() {
});
$('.tui-image-editor-wrap')[0].onwheel = function() { return false; };
$('.tui-image-editor-wrap').css('height', 'calc(100% - 150px)');
*/
$('#fileEditModal').find('.file-name').text('Editing: ' + data.filename);
$('#fileEditModal').modal('show');
@ -397,7 +380,7 @@ var ImageEditorModal = (function() {
function preInitImageEditor() {
$(document).on('click', '.image-edit-button', function() {
var editButton = $(this);
updateFabricControls();
//updateFabricControls();
$.get(editButton.data('image-url'), function(responseData) {
var fileUrl = responseData;
var data = {

View file

@ -9,10 +9,6 @@
}
}
window.recaptchaCallback = function recaptchaCallback(response) {
$('#recaptcha-invite-modal').val(response);
};
function initializeModal(modal) {
var modalDialog = modal.find('.modal-dialog');
var type = modal.attr('data-type');
@ -130,7 +126,7 @@
emails: dropdownSelector.getValues(emailsInput),
team_ids: dropdownSelector.getValues(teamsInput),
role: dropdownSelector.getValues(roleInput),
'g-recaptcha-response': $('#recaptcha-invite-modal').val()
'g-recaptcha-response': $('.g-recaptcha-response').val()
};
animateSpinner(modalDialog);
@ -191,18 +187,11 @@
});
});
}).on('shown.bs.modal', function() {
var script = document.createElement('script');
emailsInput.focus();
recaptchaErrorMsgDiv.addClass('hidden');
script.type = 'text/javascript';
script.src = 'https://www.google.com/recaptcha/api.js?hl=en';
$(script).insertAfter('#recaptcha-service');
// Remove 'data-invited="true"' status
dropdownSelector.init(teamsInput, {
optionClass: 'checkbox-icon'
});
// Remove 'data-invited="true"' status
modal.removeAttr('data-invited');
}).on('hide.bs.modal', function() {
// 'Reset' modal state

View file

@ -24,14 +24,6 @@
width: 200px;
}
.permission-object-tag {
@include font-small;
background: $color-concrete;
border-radius: $border-radius-tag;
cursor: pointer;
padding: .25em;
}
.member-item,
.user-assignment-info,
.user-assignment-controls {
@ -49,6 +41,7 @@
.user-assignment-remove {
margin-left: 1em;
min-width: 7em;
}
a,
@ -76,3 +69,11 @@
margin: 1em 0;
}
}
.permission-object-tag {
@include font-small;
background: $color-concrete;
border-radius: $border-radius-tag;
cursor: pointer;
padding: .25em;
}

View file

@ -0,0 +1,245 @@
@import "../shared_styles/constants/*";
@import "../constants";
@page {
size: A4;
margin: 8mm;
}
.page-break {
clear: both;
}
body {
font-family: Lato, "Open Sans", Arial, Helvetica, sans-serif;
font-size: 16px;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.print-protocol-header {
font-size: .8em;
img {
height: .8em;
}
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.3em;
.fa-check-circle {
color: $brand-success;
font-size: 1.3em;
}
}
h3 {
font-size: 1em;
}
hr {
background-color: $color-alto;
border-width: 0;
height: 1px;
}
.print-step {
margin: 5em 0;
}
.step-check-circle {
border: .15em solid $color-alto;
border-radius: 50%;
color: $color-white;
display: inline-block;
height: 1.1em;
text-align: center;
vertical-align: text-bottom;
width: 1.1em;
}
.step-check-circle-checked {
img {
height: 1.4em;
vertical-align: text-bottom;
width: 1.4em;
}
}
.print-checklist-item {
margin-bottom: 1em;
span {
vertical-align: top;
}
}
.checklist-checkbox {
&.checked {
img {
height: 1.3em;
text-align: center;
vertical-align: middle;
width: 1.3em;
}
}
&.not-checked {
border: .15em solid $color-alto;
border-radius: .2em;
display: inline-block;
height: 1em;
text-align: center;
width: 1em;
}
}
.print-table {
table {
border-collapse: collapse;
text-align: center;
width: 100%;
&,
td,
th {
border: 1px solid $color-silver-chalice;
}
th {
font-weight: normal;
}
th,
td:first-child {
background: $color-concrete;
}
td {
padding: .15em .3em;
}
}
}
.print-asset {
margin: 1em;
p {
font-style: italic;
}
&.thumbnail {
align-items: flex-end;
display: flex;
justify-content: center;
padding: 1em 1em 0;
text-align: center;
width: 15%;
img {
max-width: 100%;
}
.print-asset-image {
flex-basis: .25;
}
}
&.thumbnail,
&.list {
border: 1px solid $color-silver-chalice;
}
&.list {
padding: .5em 1em;
}
&.inline {
margin: 5em 0;
text-align: center;
img {
max-width: 100%;
}
}
}
.print-thumbnails {
align-items: stretch;
display: flex;
flex-wrap: wrap;
justify-content: left;
}
.print-asset-icon {
display: inline-block;
font-size: $font-size-h2;
text-align: center;
width: 24px;
.fa-file-pdf {
color: $pdf-color;
}
.fa-image {
color: $brand-primary;
}
}
.print-comments {
margin-bottom: 5em;
}
.print-comment-container {
margin-bottom: 1em;
}
.print-comment-header {
.user-avatar {
float: left;
margin-right: 1em;
}
.user-name {
color: $color-silver-chalice;
}
}
.print-comment-footer {
color: $color-silver-chalice;
font-size: .8em;
text-align: right;
}
.global-avatar-container.smart-annotation {
img {
margin-right: .2em;
vertical-align: middle;
width: 1.2em;
}
}
.ht_clone_top,
.ht_clone_bottom,
.ht_clone_left,
.ht_clone_top_left_corner {
display: none;
}
.sa-type {
font-size: 10px;
font-weight: 600;
padding-left: 2px;
text-decoration: none;
text-transform: uppercase;
vertical-align: super;
&:hover {
text-decoration: none;
}
}

View file

@ -168,7 +168,7 @@
}
.emoji-picker__emojis {
height: 14rem;
height: 28rem;
}
.emoji-picker__variant-popup {

View file

@ -8,6 +8,7 @@
display: inline-block;
height: var(--sci-checkbox-size);
position: relative;
vertical-align: middle;
width: var(--sci-checkbox-size);
}

View file

@ -4,10 +4,16 @@ module AccessPermissions
class ProjectsController < ApplicationController
before_action :set_project
before_action :check_read_permissions, only: %i(show)
before_action :check_manage_permissions, only: %i(new create edit update destroy)
before_action :check_manage_permissions, except: %i(show)
def new
available_users = current_team.users.where.not(id: @project.users.pluck(:id))
# automatically assigned or not assigned to project
available_users = current_team.users.where(
id: @project.user_assignments.automatically_assigned.select(:user_id)
).or(
current_team.users.where.not(id: @project.users.select(:id))
)
@form = AccessPermissions::NewUserProjectForm.new(
current_user,
@project,
@ -75,8 +81,17 @@ module AccessPermissions
end
end
def update_default_public_user_role
@project.update!(permitted_default_public_user_role_params)
UserAssignments::GroupAssignmentJob.perform_later(current_team, @project, current_user)
end
private
def permitted_default_public_user_role_params
params.require(:project).permit(:default_public_user_role_id)
end
def permitted_update_params
params.require(:project_member)
.permit(%i(user_role_id user_id))

View file

@ -12,7 +12,7 @@ module Api
def index
cells = @inventory_item.repository_cells
.preload(value: @inventory.cell_preload_includes)
.preload(:repository_column, value: @inventory.cell_preload_includes)
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: cells, each_serializer: InventoryCellSerializer

View file

@ -44,7 +44,7 @@ module Api
project_member = ProjectMember.new(user, @project, current_user)
project_member.assign = true
project_member.user_role_id = user_project_params[:user_role_id]
project_member.create
project_member.save
render jsonapi: project_member.user_assignment.reload,
serializer: UserAssignmentSerializer,

View file

@ -114,7 +114,7 @@ class CanvasController < ApplicationController
# Okay, JSON parsed!
unless to_move.is_a?(Hash) &&
to_move.keys.all? do |id|
!is_int?(id) || can_manage_my_module?(MyModule.find_by(id: id))
!is_int?(id) || can_move_my_module?(MyModule.find_by(id: id))
end &&
to_move.values.all? do |exp_id|
can_manage_experiment?(Experiment.find_by(id: exp_id))

View file

@ -20,7 +20,7 @@ module Dashboard
end
def project_filter
projects = Project.managable_by_user(current_user)
projects = Project.readable_by_user(current_user)
.search(current_user, false, params[:query], 1, current_team)
.select(:id, :name)
projects = projects.map { |i| { value: i.id, label: escape_input(i.name) } }
@ -39,7 +39,9 @@ module Dashboard
.search(current_user, false, params[:query], 1, current_team)
.select(:id, :name)
experiments = experiments.map { |i| { value: i.id, label: escape_input(i.name) } }
if (experiments.map { |i| i[:label] }.exclude? params[:query]) && params[:query].present?
if (experiments.map { |i| i[:label] }.exclude? params[:query]) &&
params[:query].present? &&
can_create_project_experiments?(@project)
experiments = [{ value: 0, label: params[:query] }] + experiments
end
end
@ -57,7 +59,7 @@ module Dashboard
end
def load_project
@project = current_team.projects.managable_by_user(current_user).find_by(id: params.dig(:project, :id))
@project = current_team.projects.readable_by_user(current_user).find_by(id: params.dig(:project, :id))
end
def load_experiment

View file

@ -238,15 +238,13 @@ class ExperimentsController < ApplicationController
.call(experiment_id: @experiment.id,
project_id: move_experiment_param,
user_id: current_user.id)
if service.succeed?
flash[:success] = t('experiments.move.success_flash',
experiment: @experiment.name)
path = canvas_experiment_url(@experiment)
status = :ok
else
message = t('experiments.move.error_flash',
experiment: escape_input(@experiment.name))
message = service.errors.values.join(', ')
status = :unprocessable_entity
end

View file

@ -114,10 +114,10 @@ class MyModuleTagsController < ApplicationController
end
def search_tags
assigned_tags = @my_module.my_module_tags.pluck(:tag_id)
assigned_tags = @my_module.my_module_tags.select(:tag_id)
all_tags = @my_module.experiment.project.tags
tags = all_tags.where.not(id: assigned_tags)
.search(current_user, false, params[:query])
.where_attributes_like(:name, params[:query])
.select(:id, :name, :color)
.limit(6)

View file

@ -333,7 +333,11 @@ class MyModulesController < ApplicationController
end
def load_experiment_my_modules
@experiment_my_modules = @my_module.experiment.my_modules.where(archived: @my_module.archived?).order(:name)
@experiment_my_modules = if @my_module.experiment.archived_branch?
@my_module.experiment.my_modules.order(:name)
else
@my_module.experiment.my_modules.where(archived: @my_module.archived?).order(:name)
end
end
def check_manage_permissions

View file

@ -108,6 +108,12 @@ class ProtocolsController < ApplicationController
end
end
def print
@protocol = Protocol.find(params[:id])
render_403 && return unless @protocol.my_module.blank? || can_read_protocol_in_module?(@protocol)
render layout: 'protocols/print'
end
def linked_children
respond_to do |format|
format.json do

View file

@ -360,6 +360,7 @@ class ReportsController < ApplicationController
.with_granted_permissions(current_user, ProjectPermissions::READ)
.merge(Experiment.active)
.merge(MyModule.active)
.group(:id)
.select(:id, :name)
end

View file

@ -6,7 +6,8 @@ class RepositoryRowsController < ApplicationController
MAX_PRINTABLE_ITEM_NAME_LENGTH = 64
before_action :load_repository, except: :show
before_action :load_repository, except: %i(show print_modal print)
before_action :load_repository_or_snapshot, only: %i(print_modal print)
before_action :load_repository_row, only: %i(update assigned_task_list)
before_action :check_read_permissions, except: %i(show create update delete_records copy_records)
before_action :check_snapshotting_status, only: %i(create update delete_records copy_records)
@ -213,12 +214,12 @@ class RepositoryRowsController < ApplicationController
params[:query],
whole_phrase: true
)
viewable_modules = assigned_modules.viewable_by_user(current_user, current_user.teams)
private_modules = assigned_modules - viewable_modules
viewable_modules = assigned_modules.viewable_by_user(current_user, current_team)
private_modules_number = assigned_modules.where.not(id: viewable_modules).count
render json: {
html: render_to_string(partial: 'shared/my_modules_list_partial.html.erb', locals: {
my_modules: viewable_modules,
private_modules: private_modules
private_modules_number: private_modules_number
})
}
end
@ -261,6 +262,13 @@ class RepositoryRowsController < ApplicationController
render_404 unless @repository
end
def load_repository_or_snapshot
@repository = Repository.accessible_by_teams(current_team).find_by(id: params[:repository_id])
@repository ||= RepositorySnapshot.find_by(id: params[:repository_id])
render_404 unless @repository
end
def load_repository_row
@repository_row = @repository.repository_rows.eager_load(:repository_columns).find_by(id: params[:id])
render_404 unless @repository_row

View file

@ -57,6 +57,7 @@ module Users
elsif provider_conf[:auto_link_on_sign_in]
# Link to existing local account
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
user.update!(confirmed_at: user.created_at) if user.confirmed_at.blank?
sign_in_and_redirect(user)
else
# Cannot do anything with it, so just return an error

View file

@ -139,7 +139,8 @@ module Users
@user_t.destroy(new_owner)
end
rescue Exception
rescue StandardError => e
Rails.logger.error e.message
invalid = true
end
end

View file

@ -20,7 +20,7 @@ module AccessPermissions
if @error
false
else
@resource_members.map(&:create)
@resource_members.map(&:save)
true
end
end

View file

@ -274,6 +274,6 @@ module CommentHelper
end
def has_unseen_comments?(commentable)
commentable.comments.where('? = ANY (unseen_by)', current_user.id).any?
commentable.comments.any? { |comment| comment.unseen_by.include?(current_user.id) }
end
end

View file

@ -1,10 +1,14 @@
module FormTagHelper
def recaptcha_input_tag
def recaptcha_input_tag(include_description: true)
if Rails.configuration.x.enable_recaptcha
res = "<div class='form-group sci-input-container"
res << 'has-error' if flash[:recaptcha_error]
res << "'>"
res << label_tag(:recaptcha_label, I18n.t('users.registrations.new.captcha_description'))
if include_description
res << label_tag(:recaptcha_label, I18n.t('users.registrations.new.captcha_description'))
end
res << recaptcha_tags
if flash[:recaptcha_error]
res << "<span class='help-block'>"

View file

@ -1,4 +1,7 @@
global.twemoji = require('twemoji').default;
global.twemoji.base = '/images/twemoji/';
global.twemoji.size = '24x24';
global.EmojiButton = require('@joeattardi/emoji-button/dist/index');
import { EmojiButton } from '@joeattardi/emoji-button';
global.EmojiButton = EmojiButton;

View file

@ -9,7 +9,7 @@ module Reports
discard_on StandardError do |job, error|
report = Report.find_by(id: job.arguments.first)
return if report.blank?
next unless report
ActiveRecord::Base.no_touching do
report.docx_error!

View file

@ -5,6 +5,7 @@ module Reports
extend InputSanitizeHelper
include InputSanitizeHelper
include ReportsHelper
include Canaid::Helpers::PermissionsHelper
PDFUNITE_ENCRYPTED_PDF_ERROR_STRING = 'Unimplemented Feature: Could not merge encrypted files'
@ -12,7 +13,7 @@ module Reports
discard_on StandardError do |job, error|
report = Report.find_by(id: job.arguments.first)
return if report.blank?
next unless report
ActiveRecord::Base.no_touching do
report.pdf_error!
@ -75,7 +76,7 @@ module Reports
file = prepend_title_page(file, template, report, renderer)
file = append_result_asset_previews(report, file) if report.settings.dig(:task, :file_results_previews)
file = append_result_asset_previews(report, file, user) if report.settings.dig(:task, :file_results_previews)
report.pdf_file.attach(io: file, filename: 'report.pdf')
report.pdf_ready!
@ -98,9 +99,11 @@ module Reports
private
def append_result_asset_previews(report, report_file)
def append_result_asset_previews(report, report_file, user)
Dir.mktmpdir do |tmp_dir|
report.report_elements.my_module.each do |my_module_element|
next unless can_read_my_module?(user, my_module_element.my_module)
results = my_module_element.my_module.results
order_results_for_report(results, report.settings.dig(:task, :result_order)).each do |result|
next unless result.is_asset && PREVIEW_EXTENSIONS.include?(result.asset.file.blob.filename.extension)

View file

@ -9,14 +9,19 @@ module UserAssignments
@assigned_by = assigned_by
ActiveRecord::Base.transaction do
team.users.where.not(id: project.users.pluck(:id)).where.not(id: assigned_by.id).find_each do |user|
UserAssignment.create!(
team.users.where.not(id: assigned_by.id).find_each do |user|
user_assignment = UserAssignment.find_or_initialize_by(
user: user,
assignable: project,
user_role: project.default_public_user_role,
assigned_by: @assigned_by,
assigned: :automatically
assignable: project
)
next if user_assignment.manually_assigned?
user_assignment.update!(
user_role: project.default_public_user_role,
assigned_by: @assigned_by
)
# make sure all related experiments and my modules are assigned
UserAssignments::PropagateAssignmentJob.perform_later(
project,

View file

@ -4,11 +4,12 @@ module UserAssignments
class PropagateAssignmentJob < ApplicationJob
queue_as :high_priority
def perform(resource, user, user_role, assigned_by, destroy: false)
def perform(resource, user, user_role, assigned_by, options = {})
@user = user
@user_role = user_role
@assigned_by = assigned_by
@destroy = destroy
@destroy = options.fetch(:destroy, false)
@remove_from_team = options.fetch(:remove_from_team, false)
@resource = resource
ActiveRecord::Base.transaction do
@ -36,13 +37,15 @@ module UserAssignments
child_associations.find_each do |child_association|
if @destroy
destroy_user_assignment(child_association)
destroy_or_update_user_assignment(child_association)
else
create_or_update_user_assignment(child_association)
end
sync_resource_user_associations(child_association)
end
destroy_or_update_user_assignment(resource) if resource.is_a?(Project) && @destroy
end
def create_or_update_user_assignment(object)
@ -55,11 +58,25 @@ module UserAssignments
user_assignment.save!
end
def destroy_user_assignment(object)
def destroy_or_update_user_assignment(object)
# also destroy user designations if it's a MyModule
object.user_my_modules.where(user: @user).destroy_all if object.is_a?(MyModule)
UserAssignment.where(user: @user, assignable: object).destroy_all
user_assignment = object.user_assignments.find { |ua| ua.user_id == @user.id }
return if user_assignment.blank?
if !object.is_a?(Project) && object.project.visible? && !@remove_from_team
# if project is public, the assignment
# will reset to the default public role
user_assignment.update!(
user_role_id: object.project.default_public_user_role_id,
assigned: :automatically,
assigned_by: @assigned_by
)
else
user_assignment.destroy!
end
end
end
end

View file

@ -7,8 +7,8 @@ module UserAssignments
def perform(user, team)
ActiveRecord::Base.transaction do
team.projects.each do |project|
UserAssignments::PropagateAssignmentJob.perform_now(project, user, nil, nil, destroy: true)
UserAssignment.where(user: user, assignable: project).destroy_all
UserAssignments::PropagateAssignmentJob
.perform_now(project, user, nil, nil, destroy: true, remove_from_team: true)
end
end
end

View file

@ -112,14 +112,14 @@ class Activity < ApplicationRecord
def self.url_search_query(filters)
result = []
filters.each do |filter, values|
result.push(values.to_query(filter))
result.push(values.map { |k, v| { k => v.collect(&:id) } }.to_query(filter))
end
if filters[:subjects]
subject_labels = []
filters[:subjects].each do |object, values|
values.each do |value|
label = object.to_s.constantize.find_by_id(value).name
subject_labels.push({ value: value, label: label, object: object.downcase, group: '' }.as_json)
label = value.name
subject_labels.push({ value: value.id, label: label, object: object.downcase, group: '' }.as_json)
end
end
result.push(subject_labels.to_query('subject_labels'))

View file

@ -10,8 +10,6 @@ module Assignable
has_many :user_assignments, as: :assignable, dependent: :destroy
default_scope { includes(user_assignments: :user_role).distinct }
scope :readable_by_user, lambda { |user|
joins(user_assignments: :user_role)
.where(user_assignments: { user: user })
@ -36,12 +34,16 @@ module Assignable
user_assignments.find_by(user: user)&.user_role
end
def manually_assigned_users
User.joins(:user_assignments).where(user_assignments: { assigned: :manually, assignable: self })
end
private
def create_users_assignments
return if skip_user_assignments
role = if self.class == Project
role = if is_a?(Project)
UserRole.find_by(name: I18n.t('user_roles.predefined.owner'))
else
permission_parent.user_assignments.find_by(user: created_by).user_role
@ -50,7 +52,7 @@ module Assignable
UserAssignment.create!(
user: created_by,
assignable: self,
assigned: :automatically,
assigned: is_a?(Project) ? :manually : :automatically,
user_role: role
)

View file

@ -13,7 +13,7 @@ module User::TeamRoles
is_guest_of_team?
) do |proxy, *args, &block|
if args[0]
@user_team = user_teams.where(team: args[0]).take
@user_team = args[0]&.user_teams&.find { |ut| ut.user == self }
@user_team ? proxy.call(*args, &block) : false
else
false
@ -41,4 +41,4 @@ module User::TeamRoles
def is_guest_of_team?(team)
@user_team.guest?
end
end
end

View file

@ -20,7 +20,7 @@ module ViewableModel
end
def current_view_state(user)
state = view_states.where(user: user).take
state = view_states.find_by(user: user)
state || view_states.create!(user: user, state: default_view_state)
end
end

View file

@ -3,4 +3,26 @@
class Connection < ApplicationRecord
belongs_to :to, class_name: 'MyModule', foreign_key: 'input_id', inverse_of: :inputs
belongs_to :from, class_name: 'MyModule', foreign_key: 'output_id', inverse_of: :outputs
validate :ensure_non_cyclical
private
def ensure_non_cyclical
connections = Connection.where(
input_id: to.experiment.my_modules.select(:id)
).pluck(:input_id, :output_id).to_h
visited_nodes = [output_id]
current_input_id = output_id
while (current_input_id = connections[current_input_id])
if current_input_id == input_id || visited_nodes.include?(current_input_id)
errors.add(:output_id, :creates_cycle) and return
end
visited_nodes.push(current_input_id)
end
end
end

View file

@ -47,6 +47,14 @@ class ExperimentMember
.user_role
user_assignment.update!(user_role: @user_role, assigned: :automatically)
UserAssignments::PropagateAssignmentJob.perform_later(
@experiment,
@user,
user_role,
current_user
)
log_change_activity
end
end

View file

@ -40,6 +40,7 @@ class MyModule < ApplicationRecord
belongs_to :archived_by, foreign_key: 'archived_by_id', class_name: 'User', optional: true
belongs_to :restored_by, foreign_key: 'restored_by_id', class_name: 'User', optional: true
belongs_to :experiment, inverse_of: :my_modules, touch: true
has_one :project, through: :experiment, autosave: false
belongs_to :my_module_group, inverse_of: :my_modules, optional: true
belongs_to :my_module_status, optional: true
belongs_to :changing_from_my_module_status, optional: true, class_name: 'MyModuleStatus'
@ -437,7 +438,7 @@ class MyModule < ApplicationRecord
def assign_default_status_flow
return if my_module_status.present? || MyModuleStatusFlow.global.blank?
self.my_module_status = MyModuleStatusFlow.global.first.initial_status
self.my_module_status = MyModuleStatusFlow.global.last.initial_status
end
def check_status_conditions

View file

@ -54,8 +54,6 @@ class Project < ApplicationRecord
has_many :reports, inverse_of: :project, dependent: :destroy
has_many :report_elements, inverse_of: :project, dependent: :destroy
default_scope { includes(user_assignments: :user_role) }
accepts_nested_attributes_for :user_assignments,
allow_destroy: true,
reject_if: :all_blank

View file

@ -11,7 +11,6 @@ class ProjectMember
validates :user, :project, presence: true, if: -> { assign }
validates :user_role_id, presence: true, if: -> { assign }
validate :validate_role_presence, if: -> { assign }
validate :validate_user_assignment_presence, if: -> { assign }
def initialize(user, project, current_user = nil)
@user = user
@ -20,17 +19,21 @@ class ProjectMember
@user_assignment = UserAssignment.find_by(assignable: @project, user: @user)
end
def create
def save
return unless assign
ActiveRecord::Base.transaction do
@user_assignment = UserAssignment.create!(
@user_assignment = UserAssignment.find_or_initialize_by(
assignable: @project,
user: @user,
user: @user
)
@user_assignment.update!(
user_role_id: user_role_id,
assigned_by: current_user,
assigned: :manually
)
log_activity(:assign_user_to_project)
UserAssignments::PropagateAssignmentJob.perform_later(
@ -66,9 +69,17 @@ class ProjectMember
return false if last_project_owner?
ActiveRecord::Base.transaction do
user_assignment.destroy!
user_project&.destroy!
log_activity(:unassign_user_from_project)
# if project is public, the assignment
# will reset to the default public role
if @project.visible?
user_assignment.update!(
user_role: @project.default_public_user_role,
assigned: :automatically
)
else
user_assignment.destroy!
user_project&.destroy!
end
UserAssignments::PropagateAssignmentJob.perform_later(
@project,
@ -77,6 +88,8 @@ class ProjectMember
current_user,
destroy: true
)
log_activity(:unassign_user_from_project)
end
end
@ -106,12 +119,6 @@ class ProjectMember
errors.add(:user_role_id, :not_found) if UserRole.find_by(id: user_role_id).nil?
end
def validate_user_assignment_presence
return if UserAssignment.find_by(assignable: @project, user: @user).nil?
errors.add(:user_role_id, :already_present)
end
def project_owners
@project_owners ||= @project.user_assignments
.includes(:user_role)

View file

@ -11,7 +11,7 @@ class RepositoryChecklistItem < ApplicationRecord
validate :validate_per_column_limit
validates :data, presence: true,
uniqueness: { scope: :repository_column_id, case_sensitive: false },
uniqueness: { scope: :repository_column_id },
length: { maximum: Constants::NAME_MAX_LENGTH }
private

View file

@ -8,7 +8,7 @@ class RepositoryListItem < ApplicationRecord
validate :validate_per_column_limit
validates :data,
presence: true,
uniqueness: { scope: :repository_column_id, case_sensitive: false },
uniqueness: { scope: :repository_column_id },
length: { maximum: Constants::TEXT_MAX_LENGTH }
private

View file

@ -16,15 +16,9 @@ class UserTeam < ApplicationRecord
I18n.t("user_teams.enums.role.#{role}")
end
def destroy_associations
# Destroy the user from all team's projects
team.projects.each do |project|
up2 = (project.user_projects.select { |up| up.user == self.user }).first
if up2.present?
up2.destroy
end
end
user.user_projects.joins(:project).where(project: team.projects).destroy_all
# destroy all assignments
UserAssignments::RemoveUserAssignmentJob.perform_now(user, team)
end

View file

@ -19,10 +19,7 @@ Canaid::Permissions.register_for(Experiment) do
# assign/reassign/unassign tags
can :manage_experiment do |user, experiment|
experiment.permission_granted?(user, ExperimentPermissions::MANAGE) &&
MyModule.joins(:experiment)
.where(experiment: experiment)
.preload(my_module_status: :my_module_status_implications)
.all? do |my_module|
experiment.my_modules.all? do |my_module|
if my_module.my_module_status
my_module.my_module_status.my_module_status_implications.all? { |implication| implication.call(my_module) }
else
@ -60,7 +57,7 @@ Canaid::Permissions.register_for(Experiment) do
end
can :manage_all_experiment_my_modules do |user, experiment|
experiment.my_modules == experiment.my_modules.managable_by_user(user)
experiment.my_modules.where.not(id: experiment.my_modules.managable_by_user(user)).none?
end
can :archive_experiment do |user, experiment|

View file

@ -4,12 +4,28 @@ Canaid::Permissions.register_for(MyModule) do
# Module, its experiment and its project must be active for all the specified
# permissions
%i(manage_my_module
manage_my_module_protocol
manage_my_module_users
manage_my_module_designated_users
assign_my_module_repository_rows
manage_my_module_repository_rows
create_results
create_my_module_comments
create_comments_in_my_module_steps
create_my_module_result_comments
create_my_module_repository_snapshots
manage_my_module_repository_snapshots
update_my_module_status)
update_my_module_start_date
update_my_module_due_date
complete_my_module
update_my_module_description
manage_my_module_tags
update_my_module_status
manage_my_module_steps
complete_my_module_steps
uncomplete_my_module_steps
check_my_module_steps
uncheck_my_module_steps)
.each do |perm|
can perm do |_, my_module|
my_module.active? &&
@ -35,6 +51,10 @@ Canaid::Permissions.register_for(MyModule) do
!my_module.archived? && my_module.permission_granted?(user, MyModulePermissions::MANAGE)
end
can :move_my_module do |user, my_module|
my_module.permission_granted?(user, MyModulePermissions::MANAGE)
end
can :update_my_module_start_date do |user, my_module|
my_module.permission_granted?(user, MyModulePermissions::UPDATE_START_DATE)
end

View file

@ -26,14 +26,13 @@ Canaid::Permissions.register_for(Project) do
can :manage_project do |user, project|
project.permission_granted?(user, ProjectPermissions::MANAGE) &&
MyModule.joins(experiment: :project)
.where(experiments: { project: project })
.preload(my_module_status: :my_module_status_implications)
.all? do |my_module|
if my_module.my_module_status
my_module.my_module_status.my_module_status_implications.all? { |implication| implication.call(my_module) }
else
true
project.experiments.each do |experiment|
experiment.my_modules.all? do |my_module|
if my_module.my_module_status
my_module.my_module_status.my_module_status_implications.all? { |implication| implication.call(my_module) }
else
true
end
end
end
end

View file

@ -5,9 +5,9 @@ class ActivitiesService
# Create condition for view permissions checking first
visible_teams = user.teams.where(id: teams)
visible_projects = Project.viewable_by_user(user, visible_teams)
visible_by_teams = Activity.where(project: nil, team_id: visible_teams.pluck(:id))
visible_by_teams = Activity.where(project: nil, team_id: visible_teams.select(:id))
.order(created_at: :desc)
visible_by_projects = Activity.where(project_id: visible_projects.pluck(:id))
visible_by_projects = Activity.where(project_id: visible_projects.select(:id))
.order(created_at: :desc)
query = Activity.from("((#{visible_by_teams.to_sql}) UNION ALL (#{visible_by_projects.to_sql})) AS activities")

View file

@ -15,6 +15,7 @@ module Dashboard
def call
all_activities = @team.activities.where(owner_id: @user.id)
all_activities = join_project_user_roles(all_activities)
all_activities = join_report_project_user_roles(all_activities)
all_activities = join_experiment_user_roles(all_activities)
all_activities = join_my_module_user_roles(all_activities)
all_activities = join_result_user_roles(all_activities)
@ -25,6 +26,9 @@ module Dashboard
project_activities = all_activities.where(project_user_assignments: { user_id: @user.id })
.where('project_user_roles.permissions @> ARRAY[?]::varchar[]',
[ProjectPermissions::ACTIVITIES_READ])
report_activities = all_activities.where(report_project_user_assignments: { user_id: @user.id })
.where('report_project_user_roles.permissions @> ARRAY[?]::varchar[]',
[ProjectPermissions::ACTIVITIES_READ])
experiment_activities = all_activities.where(experiment_user_assignments: { user_id: @user.id })
.where('experiment_user_roles.permissions @> ARRAY[?]::varchar[]',
[ExperimentPermissions::ACTIVITIES_READ])
@ -37,16 +41,20 @@ module Dashboard
protocol_activities = all_activities.where(protocol_my_module_user_assignments: { user_id: @user.id })
.where('protocol_my_module_user_roles.permissions @> ARRAY[?]::varchar[]',
[MyModulePermissions::ACTIVITIES_READ])
protocol_repository_activities = all_activities.where(project_id: nil, subject_type: 'Protocol')
step_activities = all_activities.where(step_my_module_user_assignments: { user_id: @user.id })
.where('step_my_module_user_roles.permissions @> ARRAY[?]::varchar[]',
[MyModulePermissions::ACTIVITIES_READ])
activities = team_activities.or(project_activities)
.or(report_activities)
.or(experiment_activities)
.or(my_module_activities)
.or(result_activities)
.or(protocol_activities)
.or(step_activities)
.or(protocol_repository_activities)
activities = activities.where.not(type_of: Extends::DASHBOARD_BLACKLIST_ACTIVITY_TYPES)
.select('MAX(activities.created_at) AS last_change', :subject_id, :subject_type)
@ -141,6 +149,16 @@ module Dashboard
ON project_user_roles.id = project_user_assignments.user_role_id")
end
def join_report_project_user_roles(activities)
activities.joins("LEFT OUTER JOIN projects report_project_subjects
ON report_project_subjects.id = activities.project_id AND activities.subject_type='Report'")
.joins("LEFT OUTER JOIN user_assignments report_project_user_assignments
ON report_project_user_assignments.assignable_type = 'Project'
AND report_project_user_assignments.assignable_id = report_project_subjects.id
LEFT OUTER JOIN user_roles report_project_user_roles
ON report_project_user_roles.id = report_project_user_assignments.user_role_id")
end
def join_experiment_user_roles(activities)
activities.joins("LEFT OUTER JOIN experiments experiment_subjects
ON experiment_subjects.id = activities.subject_id AND activities.subject_type='Experiment'")

View file

@ -17,8 +17,7 @@ module Experiments
splines: true,
center: true,
pack: true,
bgcolor: Constants::COLOR_CONCRETE,
mode: 'ipsep'
bgcolor: Constants::COLOR_CONCRETE
}
@node_params = {
color: Constants::COLOR_VOLCANO,

View file

@ -26,9 +26,12 @@ module Experiments
ActiveRecord::Base.transaction do
@exp.project = @project
@exp.my_modules.each do |my_module|
raise unless can_manage_my_module?(@user, my_module)
unless can_move_my_module?(@user, my_module)
@errors[:main] = I18n.t('move_to_project_service.my_modules_permission_error')
raise
end
sync_user_assignments(my_module)
clean_up_user_my_modules(my_module)
move_tags!(my_module)
end
@ -36,8 +39,8 @@ module Experiments
@exp.save!
sync_user_assignments(@exp)
rescue StandardError
if @exp.valid?
@errors[:main] = I18n.t('move_to_project_service.my_modules_permission_error')
if @exp.valid? && @errors.none?
@errors[:main] = I18n.t('move_to_project_service.general_error')
else
@errors.merge!(@exp.errors.to_hash)
end
@ -117,6 +120,10 @@ module Experiments
UserAssignments::GenerateUserAssignmentsJob.perform_later(object, @user)
end
def clean_up_user_my_modules(my_module)
my_module.user_my_modules.where.not(user_id: @project.users.select(:id)).destroy_all
end
def track_activity
Activities::CreateActivityService
.call(activity_type: :move_experiment,

View file

@ -35,6 +35,8 @@ class ExperimentsOverviewService
def fetch_records
@project.experiments
.joins(:project)
.includes(my_modules: { my_module_status: :my_module_status_implications })
.includes(workflowimg_attachment: :blob, user_assignments: %i(user_role user))
.joins('LEFT OUTER JOIN my_modules AS active_tasks ON active_tasks.experiment_id = experiments.id ' \
'AND active_tasks.archived = FALSE')
.joins('LEFT OUTER JOIN my_modules AS active_completed_tasks ON active_completed_tasks.experiment_id '\

View file

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

View file

@ -14,7 +14,7 @@ module Repositories
def call
return self unless valid?
ActiveRecord::Base.transaction do
ActiveRecord::Base.transaction(requires_new: true) do
repository = @repository_snapshot.original_repository
repository.repository_columns.each do |column|
@ -30,8 +30,12 @@ module Repositories
end
@repository_snapshot.ready!
rescue ActiveRecord::RecordInvalid => e
@errors[e.record.class.name.underscore] = e.record.errors.full_messages
rescue StandardError => e
if e.is_a?(ActiveRecord::RecordInvalid)
@errors[e.record.class.name.underscore] = e.record.errors.full_messages
else
@errors[:general] = e.message
end
Rails.logger.error e.message
raise ActiveRecord::Rollback
end

View file

@ -190,6 +190,12 @@ class TeamImporter
update_smart_annotations_in_project(project)
# handle the permissions for newly created experiment
user = User.find(user_id)
UserAssignments::GenerateUserAssignmentsJob.perform_now(experiment, user)
experiment.my_modules.find_each do |my_module|
UserAssignments::GenerateUserAssignmentsJob.perform_now(my_module, user)
end
puts "Imported experiment: #{experiment.id}"
return experiment
end

View file

@ -26,8 +26,9 @@ class TemplatesService
)
end
end
owner = tmpl_project.user_projects
.where(role: 'owner')
owner_role_id = UserRole.find_by(name: I18n.t('user_roles.predefined.owner')).id
owner = tmpl_project.user_assignments
.where(user_role_id: owner_role_id)
.order(:created_at)
.first&.user
return unless owner.present?

View file

@ -0,0 +1,24 @@
<%= form_with(model: project, url: update_default_public_user_role_access_permissions_project_path(project), method: :put, remote: true, html: { class: 'row member-item', id: 'public_assignments', data: { action: 'replace-form autosave-form', object_type: :project } }) do |f| %>
<div class="user-assignment-info">
<div class="global-avatar-container">
<%= image_tag "icon/team.png", class: 'img-circle pull-left' %>
</div>
<div>
<%= t('access_permissions.everyone_else', team_name: f.object.team.name) %>
<br>
<small class="text-muted">
<%= f.object.default_public_user_role.name %>
<span class="permission-object-tag" title="<%= t("access_permissions.partials.project_tooltip") %>"">
<%= t("access_permissions.partials.project") %>
</span>
</small>
</div>
</div>
<div class="user-assignment-controls">
<div class="user-assignment-role">
<%= f.select :default_public_user_role_id, options_for_select(user_roles_collection, selected: f.object.default_public_user_role_id), {}, class: 'form-control selectpicker', title: t('user_assignment.change_project_role'), data: { 'selected-text-format' => 'static' } %>
</div>
<div class="user-assignment-remove">
</div>
</div>
<% end %>

View file

@ -6,10 +6,12 @@
<h4 class="modal-title"><%= t '.title', resource_name: project.name %></h4>
</div>
<div class="modal-body">
<% project.users.each do |user| %>
<%= render('access_permissions/partials/project_member_field.html.erb', user: user, project: project, update_path: update_path) %>
<% project.manually_assigned_users.each do |user| %>
<%= render('access_permissions/partials/project_member_field', user: user, project: project, update_path: update_path) %>
<% end %>
<%= render('access_permissions/partials/default_public_user_role_form', project: project) if project.visible? %>
</div>
<div class="modal-footer">
<%= link_to new_resource_path, class: 'btn btn-default pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
<i class="fas fa-plus"></i>

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
json.form controller.render_to_string(
partial: 'access_permissions/partials/default_public_user_role_form',
formats: [:html],
locals: {
project: @project
},
layout: false
)

View file

@ -27,7 +27,7 @@
<a class ="clone-module" href="" data-module-id="<%= my_module.id %>"><%= t('experiments.canvas.edit.clone_module') %></a>
</li>
<% end %>
<% if can_manage_my_module?(my_module) %>
<% if can_move_my_module?(my_module) %>
<li>
<a class="move-module" href="" data-module-id="<%= my_module.id %>"><%= t('experiments.canvas.edit.move_module') %></a>
</li>
@ -47,7 +47,7 @@
<a class ="clone-module-group" href="" data-module-id="<%= my_module.id %>"><%= t('experiments.canvas.edit.clone_module_group') %></a>
</li>
<% end %>
<% if module_group&.my_modules&.all? { |my_module| can_manage_my_module?(my_module) } %>
<% if module_group&.my_modules&.all? { |my_module| can_move_my_module?(my_module) } %>
<li>
<a class="move-module-group" href="" data-module-id="<%= my_module.id %>"><%= t('experiments.canvas.edit.move_module_group') %></a>
</li>

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title><%= t("protocols.print.title") %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag "handsontable.full" %>
<%= render 'shared/formulas_libraries' %>
<%= stylesheet_link_tag 'layouts/print_protocol', media: 'print, screen' %>
<body>
<%= yield %>
<script>
$("[data-role='hot-table']").each(function() {
var $container = $(this).find("[data-role='step-hot-table']");
var contents = $(this).find('.hot-contents');
$container.handsontable({
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
rowHeaders: true,
colHeaders: true,
fillHandle: false,
formulas: true,
readOnly: true
});
var hot = $container.handsontable('getInstance');
if (contents.attr("value")) {
var data = JSON.parse(contents.attr("value"));
if (Array.isArray(data.data)) hot.loadData(data.data);
setTimeout(() => {
hot.render()
}, 0)
}
});
window.print();
</script>
</body>
</html>

View file

@ -2,10 +2,10 @@
<% provide(:sidebar_title, t("sidebar.my_module.sidebar_title")) %>
<%= content_for :sidebar do %>
<%= render partial: 'shared/sidebar/my_module.html.erb',
<%= render partial: "shared/sidebar/#{@my_module.archived_branch? ? 'archived_my_module' : 'my_module'}.html.erb",
locals: {
my_modules: @experiment_my_modules,
experiment: @experiment,
experiment: @my_module.experiment,
current_my_module: @my_module
}
%>

View file

@ -12,7 +12,7 @@
<%= content_for :sidebar do %>
<%= render partial: "shared/sidebar/#{@my_module.archived? ? 'archived_my_module' : 'my_module'}.html.erb",
<%= render partial: "shared/sidebar/#{@my_module.archived_branch? ? 'archived_my_module' : 'my_module'}.html.erb",
locals: {
my_modules: @experiment_my_modules,
experiment: @my_module.experiment,
@ -121,6 +121,7 @@
<span><%=t "protocols.steps.new_step" %></span>
</a>
<% end %>
<%= render partial: "my_modules/protocols/print_protocol_button", locals: { protocol: @protocol } %>
<%= render partial: "my_modules/protocols/protocol_options_dropdown" %>
</div>
</div>

View file

@ -0,0 +1,35 @@
<a href="#" class="btn btn-default" data-toggle="modal" data-target="#print-protocol-modal">
<span class="fas fa-print" aria-hidden="true"></span>
<span><%=t "protocols.print.button" %></span>
</a>
<div class="modal"
id="print-protocol-modal"
tabindex="-1"
role="dialog"
aria-labelledby="print-protocol-modal-label">
<%= bootstrap_form_tag({ url: print_protocol_path(protocol), method: :get, html: { class: 'print-protocol-form', id: "print-protocol-form", target: '_blank' } }) do %>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="print-protocol-modal-label"><%= t("protocols.print.modal.title") %></h5>
</div>
<div class="modal-body">
<p><%= t("protocols.print.modal.content") %></p>
<div class="sci-checkbox-container">
<%= check_box_tag :include_comments, 1, true, { class: "sci-checkbox" } %>
<span class="sci-checkbox-label"></span>
</div>
<small><%= t("protocols.print.modal.include_comments") %></small>
</div>
<div class="modal-footer">
<div class="pull-right">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
<input type="submit" value="<%= t("protocols.print.title") %>", class="btn btn-primary">
</div>
</div>
</div>
</div>
<% end %>
</div>

View file

@ -32,7 +32,7 @@
<% end %>
</div>
<% end %>
<% if @repository.present? && live_items_present && can_create_my_module_repository_snapshot?(@my_module) %>
<% if @repository.present? && live_items_present && can_create_my_module_repository_snapshots?(@my_module) %>
<div class="create-snapshot-item">
<p class="info <%= 'hidden' unless @repository_snapshots.blank? %>">
<%= t('my_modules.repository.snapshots.full_view.no_snapshots_label') %>

View file

@ -2,7 +2,7 @@
<% provide(:sidebar_title, t("sidebar.my_module.sidebar_title")) %>
<%= content_for :sidebar do %>
<%= render partial: 'shared/sidebar/my_module.html.erb',
<%= render partial: "shared/sidebar/#{@my_module.archived_branch? ? 'archived_my_module' : 'my_module'}.html.erb",
locals: {
my_modules: @experiment_my_modules,
experiment: @my_module.experiment,

View file

@ -74,7 +74,7 @@
</li>
<!-- Open activities -->
<li>
<a href="/global_activities?<%= Activity.url_search_query({ subjects: { Project: [project.id] } }) %>">
<a href="/global_activities?<%= Activity.url_search_query({ subjects: { Project: [project] } }) %>">
<i class="fas fa-list"></i>
<span><%= t('projects.index.activities_option') %></span>
</a>

View file

@ -7,7 +7,7 @@
data-restorable="<%= experiment.archived? && can_restore_experiment?(experiment) %>"
data-duplicable="<%= can_clone_experiment?(experiment) %>">
<div class="checkbox-cell table-cell">
<% if can_manage_project?(experiment.project) %>
<% if project_is_managable %>
<div class="sci-checkbox-container">
<input value="1" type="checkbox" class="sci-checkbox experiment-card-selector">
<span class="sci-checkbox-label"></span>

View file

@ -2,7 +2,7 @@
<div class="archived-icon-plceholder">
<i class="fas fa-archive"></i>
</div>
<% elsif experiment.my_modules.active.any? %>
<% elsif experiment.my_modules.any?(&:active?) %>
<% if experiment.workflowimg.attached? %>
<div class="workflowimg-container" data-workflowimg-present="true">
<%= render partial: 'projects/show/workflow_img.html.erb', locals: { experiment: experiment } %>

View file

@ -5,9 +5,11 @@
<div class="no-results-description"><%= t('projects.index.no_results_description') %></div>
</div>
<% else %>
<% project_is_managable = can_manage_project?(@project) %>
<% cards.each do |card| %>
<% cache [current_user, card] do %>
<%= render partial: 'projects/show/experiment_card', locals: { experiment: card, project: @project } %>
<%= render partial: 'projects/show/experiment_card',
locals: { experiment: card, project: @project, project_is_managable: project_is_managable } %>
<% end %>
<% end %>
<% end %>

View file

@ -0,0 +1,133 @@
<p class="print-protocol-header">
<span><%= t('.header.printed_from') %></span>
<span class="print-protocol-header__logo">
<%= image_tag 'logo.png' %>
</span>
<span><%= t('.header.print_info', datetime: l(DateTime.current, format: :full), full_name: current_user.full_name) %></span>
</p>
<h1><%= @protocol.linked? ? protocol_name(@protocol.parent) : @protocol.name || @protocol.my_module.name %></h1>
<div>
<% if @protocol.description.present? %>
<%= custom_auto_link(@protocol.tinymce_render(:description),
simple_format: false,
tags: %w(img),
team: current_team) %>
<% else %>
<em><%= t('my_modules.protocols.protocol_status_bar.no_description') %></em>
<% end %>
</div>
<% @protocol.steps.order(position: :asc).each do |step| %>
<div class="print-step">
<h2 class="step-check-circle-checked">
<% if step.completed_on %>
<%= image_tag "check-circle-solid.svg" %>
<% else %>
<div class="step-check-circle"></div>
<% end %>
<%= step.position + 1 %>. <%= step.name %>
</h2>
<div>
<% if step.description.blank? %>
<em><%= t('protocols.steps.no_description') %></em>
<% else %>
<div class="ql-editor">
<%= custom_auto_link(step.tinymce_render(:description),
simple_format: false,
tags: %w(img),
team: current_team) %>
</div>
<% end %>
</div>
<% step.checklists.each do |checklist| %>
<div class="print-checklist">
<h3><%= smart_annotation_parser(checklist.name, current_team).html_safe %></h3>
<% checklist.checklist_items.order(position: :asc).each do |checklist_item| %>
<div class="print-checklist-item">
<span class="checklist-checkbox checked">
<% if checklist_item.checked %>
<%= image_tag "check-square-solid.svg" %>
<% else %>
<span class="checklist-checkbox not-checked"></span>
<% end %>
<%= smart_annotation_parser(checklist_item.text, current_team).html_safe %>
</span>
</div>
<% end %>
</div>
<% end %>
<% step.tables.each do |table| %>
<strong>
<%= auto_link(simple_format(table.name),
link: :urls,
html: { target: '_blank' }) %>
</strong>
<div class="print-table">
<div class="page-break"></div>
<div data-role="hot-table" class="hot-table">
<%= hidden_field(table, :contents, value: table.contents_utf_8, class: "hot-contents") %>
<div data-role="step-hot-table" class="step-result-hot-table"></div>
</div>
</div>
<% end %>
<% step.assets.where(view_mode: "inline").each do |asset| %>
<div class="print-asset inline">
<div class="print-asset-image">
<% if asset.previewable? %>
<%= image_tag asset.large_preview %>
<% end %>
<p><%= asset.render_file_name %></p>
</div>
</div>
<div class="page-break"></div>
<% end %>
<% step.assets.where(view_mode: "list").each do |asset| %>
<div class="print-asset list">
<span class="print-asset-icon"><%= file_extension_icon_html(asset) %></span>
<span><%= asset.render_file_name %></span>
</div>
<% end %>
<div class="print-thumbnails">
<% step.assets.where(view_mode: "thumbnail").each do |asset| %>
<div class="print-asset thumbnail">
<div class="print-asset-image">
<% if asset.previewable? %>
<%= image_tag asset.blob.representation(resize_to_limit: Constants::MEDIUM_PIC_FORMAT).processed %>
<% end %>
<p><%= asset.render_file_name %></p>
</div>
</div>
<% end %>
</div>
</div>
<% if params[:include_comments] && step.comments.present? %>
<div class="print-comments">
<h3><%= t('Comments') %>:</h3>
<% step.step_comments.each do |comment| %>
<div class="print-comment-container">
<div class="print-comment-header">
<%= image_tag avatar_path(comment.user, :icon_small), class: 'user-avatar' %>
<div class="user-name">
<%= comment.user.full_name %>
</div>
</div>
<div class="print-comment-body">
<div class="comment-message">
<%= smart_annotation_parser(comment.message, current_team).html_safe %>
</div>
<div class="print-comment-footer">
<div class="print-comment-create-date">
<%= I18n.l(comment.created_at, format: :full) %>
</div>
</div>
</div>
</div>
<% end %>
</div>
<% end %>
<hr>
<div class="page-break"></div>
<% end %>

View file

@ -124,10 +124,7 @@ invite_with_team_selector = type.in?(%w(invite_new_members invite_with_team_sele
</div>
<% end %>
<% if ENV['ENABLE_RECAPTCHA'] == 'true' %>
<div id="recaptcha-service" class="g-recaptcha"
data-callback="recaptchaCallback"
data-sitekey=<%= ENV['RECAPTCHA_SITE_KEY'] %>></div>
<input type="hidden" id="recaptcha-invite-modal" value="">
<%= recaptcha_input_tag include_description: false %>
<div class="form-group has-error hidden" id="recaptcha-error-msg" >
<span class="has-error help-block"></span>
</div>

View file

@ -1,43 +1,44 @@
<div class="my-modules-list-partial">
<% grouped_by_prj_exp(my_modules).each do |task_group| %>
<div class="task-group">
<div class="header">
<% if task_group[:project_archived]%>
<span class="archived"><%= t('general.archived') %></span>
<% end %>
<span class="project" title="<%= task_group[:project_name] %>"><%= task_group[:project_name] %></span>
<span class="slash">/</span>
<% if task_group[:experiment_archived] %>
<span class="archived"><%= t('general.archived') %></span>
<% end %>
<span class="experiment" title="<%= task_group[:experiment_name] %>"><%= task_group[:experiment_name] %></span>
</div>
<div class="tasks">
<% task_group[:tasks].each do |task| %>
<div class="task">
<%= draw_custom_icon('task-icon') %>
<% if task.archived %>
<% grouped_my_modules = grouped_by_prj_exp(my_modules) %>
<% grouped_my_modules.each do |task_group| %>
<div class="task-group">
<div class="header">
<% if task_group[:project_archived] %>
<span class="archived"><%= t('general.archived') %></span>
<% end %>
<%= link_to(task.name, protocols_my_module_path(task.id), {class: "task-link", title: task.name, target: "_blank"}) %>
<span class="project" title="<%= task_group[:project_name] %>"><%= task_group[:project_name] %></span>
<span class="slash">/</span>
<% if task_group[:experiment_archived] %>
<span class="archived"><%= t('general.archived') %></span>
<% end %>
<span class="experiment" title="<%= task_group[:experiment_name] %>"><%= task_group[:experiment_name] %></span>
</div>
<div class="tasks">
<% task_group[:tasks].each do |task| %>
<div class="task">
<%= draw_custom_icon('task-icon') %>
<% if task.archived? %>
<span class="archived"><%= t('general.archived') %></span>
<% end %>
<%= link_to(task.name, protocols_my_module_path(task.id), { class: 'task-link', title: task.name, target: '_blank' }) %>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
<% end %>
<% if defined?(private_modules) && private_modules.size.positive? %>
<div class="private-tasks-counter">
<%= t('my_modules.modules_list_partial.private_tasks_html', nr: private_modules.size ) %>
</div>
<% if defined?(private_modules_number) && private_modules_number.positive? %>
<div class="private-tasks-counter">
<%= t('my_modules.modules_list_partial.private_tasks_html', nr: private_modules_number) %>
</div>
<% end %>
<% unless my_modules.any? || (defined?(private_modules) && private_modules.any?) %>
<div class="no-results-placeholder">
<span class="fa-stack">
<i class="fas fa-search fa-stack-1x"></i>
<i class="fas fa-slash fa-stack-1x fa-flip-vertical"></i>
</span>
<h2 class="title"><%= t('my_modules.modules_list_partial.no_results.title') %></h2>
<p><%= t('my_modules.modules_list_partial.no_results.description') %></p>
</div>
<% unless grouped_my_modules.present? || (defined?(private_modules_number) && private_modules_number.positive?) %>
<div class="no-results-placeholder">
<span class="fa-stack">
<i class="fas fa-search fa-stack-1x"></i>
<i class="fas fa-slash fa-stack-1x fa-flip-vertical"></i>
</span>
<h2 class="title"><%= t('my_modules.modules_list_partial.no_results.title') %></h2>
<p><%= t('my_modules.modules_list_partial.no_results.description') %></p>
</div>
<% end %>
</div>

View file

@ -148,7 +148,7 @@
<% ordered_checklist_items(checklist).each do |checklist_item| %>
<div class="checkbox" <%= @protocol.in_module? ? "data-action=check-item" : "" %>>
<label>
<% if @protocol.in_module? %>
<% if @protocol.in_module? && can_complete_or_checkbox_step?(@protocol) %>
<input type="checkbox"
value=""
data-link-url="<%=checklistitem_state_step_path(step) %>"
@ -156,7 +156,8 @@
<% else %>
<input type="checkbox"
value=""
disabled="disabled" />
disabled="disabled"
<%= "checked" if checklist_item.checked? %> />
<% end %>
<%= custom_auto_link(checklist_item.text, team: current_team) %>
</label>

View file

@ -15,8 +15,11 @@
</span>
</div>
<div class="col-xs-10" style="line-height: 15px">
<span><%= user.full_name %></span><br>
<span class="user-role"><%= @my_module.role_for_user(user).name %></span>
<span><%= user.full_name %></span>
<br>
<span class="user-role">
<small class="text-muted"><%= user_assignment_resource_role_name(user, user_my_module.my_module) %></small>
</span>
</div>
</div>
</li>

View file

@ -11,7 +11,7 @@
<% if @linked_accounts.any? %>
<% @linked_accounts.each do |provider| %>
<% if Rails.configuration.x.azure_ad_apps.find { |_,value| value[:provider] == provider } %>
<% if Rails.configuration.x.azure_ad_apps.find { |_,value| value[:provider] == provider || provider == 'giot_connect'} %>
<% if lookup_context.exists?(provider, 'users/settings/account/connected_accounts', true) %>
<%= render partial: provider %>
<% else %>

View file

@ -1,6 +1,21 @@
require_relative 'boot'
require 'rails/all'
require 'rails'
%w(
active_record/railtie
active_storage/engine
action_controller/railtie
action_view/railtie
action_mailer/railtie
active_job/railtie
sprockets/railtie
).each do |railtie|
begin
require railtie
rescue LoadError
end
end
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.

View file

@ -17,8 +17,8 @@
default: &default
adapter: postgresql
encoding: unicode
database: postgres
pool: <%= ENV['DB_POOL'] || ENV['RAILS_MAX_THREADS'] || 5 %>
url: <%= ENV['DATABASE_URL'] %>
# For details on connection pooling, see rails configuration guide
# http://guides.rubyonrails.org/configuring.html#database-pooling
@ -80,7 +80,7 @@ test: &test
# url: <%= ENV['DATABASE_URL'] %>
#
production:
url: <%= ENV['DATABASE_URL'] %>
<<: *default
cucumber:
<<: *test

View file

@ -60,6 +60,7 @@ Rails.application.config.assets.precompile += %w(protocols/edit.js)
Rails.application.config.assets.precompile += %w(protocols/import_export/eln_table.js)
Rails.application.config.assets.precompile += %w(protocols/import_export/import.js)
Rails.application.config.assets.precompile += %w(protocols/import_export/export.js)
Rails.application.config.assets.precompile += %w(layouts/print_protocol.css)
Rails.application.config.assets.precompile += %w(datatables.js)
Rails.application.config.assets.precompile += %w(search/index.js)
Rails.application.config.assets.precompile += %w(global_activities/side_pane.js)

View file

@ -295,7 +295,7 @@ Devise.setup do |config|
config.omniauth :linkedin, ENV['LINKEDIN_KEY'], ENV['LINKEDIN_SECRET'], scope: 'r_liteprofile r_emailaddress'
end
if [ENV['OKTA_CLIENT_ID'], ENV['OKTA_CLIENT_SECRET'], ENV['OKTA_DOMAIN'], ENV['OKTA_AUTH_SERVER_ID']].all?
if [ENV['OKTA_CLIENT_ID'], ENV['OKTA_CLIENT_SECRET'], ENV['OKTA_DOMAIN'], ENV['OKTA_AUTH_SERVER_ID']].all?(&:present?)
config.omniauth(
:okta,
ENV['OKTA_CLIENT_ID'],

View file

@ -372,6 +372,7 @@ class Extends
NOTIFIABLE_ACTIVITIES = %w(
assign_user_to_project
unassign_user_from_project
change_user_role_on_project
edit_module_comment
delete_module_comment

View file

@ -170,6 +170,10 @@ en:
disabled: 'Webhooks are disabled'
url:
not_valid: 'Not valid URL'
connection:
attributes:
output_id:
creates_cycle: "mustn't create cycle"
helpers:
label:
@ -2110,6 +2114,16 @@ en:
unchangable_error_message: "Predefined roles can not be changed!"
protocols:
print:
title: "Print protocol"
button: "Print"
modal:
title: "Print protocol"
content: "Select what to include in the printed version of this protocol."
include_comments: "Include comments when printing"
header:
printed_from: "Printed from"
print_info: "on %{datetime} by %{full_name}"
protocols_io_import:
title_too_long: "... Text is too long so we had to cut it off."
too_long: "... <span class='label label-warning'>Text is too long so we had to cut it off.</span>"
@ -2622,8 +2636,10 @@ en:
move_to_project_service:
project_permission_error: "No permission to create experiments at project"
my_modules_permission_error: "No manage permissions on tasks"
general_error: "Something went wrong"
access_permissions:
everyone_else: "Everyone else at %{team_name}"
create:
success:
one: "You have successfully granted access to %{count} member to the project."
@ -2710,6 +2726,8 @@ en:
webhook_updated: "Webhook successfully updated"
webhook_deleted: "Webhook successfully deleted"
delete_webhook_confimration: "Are you sure you want to delete the webhook?"
secret_key: "Secret key"
secret_key_hint: "(Optional) A secret key that will be included in the Webhook-Secret-Key header, for authentication purposes."
# This section contains general words that can be used in any parts of
# application.
tiny_mce:

View file

@ -261,6 +261,7 @@ Rails.application.routes.draw do
namespace :access_permissions do
resources :projects, defaults: { format: 'json' } do
put :update_default_public_user_role, on: :member
resources :experiments, only: %i(show update edit) do
resources :my_modules, only: %i(show update edit)
end
@ -492,6 +493,7 @@ Rails.application.routes.draw do
resources :protocols, only: [:index, :edit, :create] do
resources :steps, only: [:new, :create]
member do
get 'print', to: 'protocols#print'
get 'linked_children', to: 'protocols#linked_children'
post 'linked_children_datatable',
to: 'protocols#linked_children_datatable'

View file

@ -30,11 +30,11 @@ class MigrateToNewUserRoles < ActiveRecord::Migration[6.1]
private
def new_user_assignment(user, assignable, user_role)
def new_user_assignment(user, assignable, user_role, assigned)
UserAssignment.new(
user: user,
assignable: assignable,
assigned: :automatically,
assigned: assigned,
user_role: user_role
)
end
@ -52,11 +52,11 @@ class MigrateToNewUserRoles < ActiveRecord::Migration[6.1]
unassigned_users = team.users.reject { |u| already_assigned_user_ids.include?(u.id) }
unassigned_users.each do |user|
user_assignments << new_user_assignment(user, project, viewer_role)
user_assignments << new_user_assignment(user, project, viewer_role, :automatically)
project.experiments.each do |experiment|
user_assignments << new_user_assignment(user, experiment, viewer_role)
user_assignments << new_user_assignment(user, experiment, viewer_role, :automatically)
experiment.my_modules.each do |my_module|
user_assignments << new_user_assignment(user, my_module, viewer_role)
user_assignments << new_user_assignment(user, my_module, viewer_role, :automatically)
end
end
end
@ -69,11 +69,11 @@ class MigrateToNewUserRoles < ActiveRecord::Migration[6.1]
user_projects.includes(:user, :project).find_in_batches(batch_size: 100) do |user_project_batch|
user_assignments = []
user_project_batch.each do |user_project|
user_assignments << new_user_assignment(user_project.user, user_project.project, user_role)
user_assignments << new_user_assignment(user_project.user, user_project.project, user_role, :manually)
user_project.project.experiments.preload(:my_modules).each do |experiment|
user_assignments << new_user_assignment(user_project.user, experiment, user_role)
user_assignments << new_user_assignment(user_project.user, experiment, user_role, :automatically)
experiment.my_modules.each do |my_module|
user_assignments << new_user_assignment(user_project.user, my_module, user_role)
user_assignments << new_user_assignment(user_project.user, my_module, user_role, :automatically)
end
end
end

View file

@ -50,8 +50,6 @@ CREATE FUNCTION public.trim_html_tags(input text, OUT output text) RETURNS text
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: active_storage_attachments; Type: TABLE; Schema: public; Owner: -
--

View file

@ -51,7 +51,7 @@
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@fortawesome/fontawesome-free": "^5.2.0",
"@joeattardi/emoji-button": "^2.5.4",
"@joeattardi/emoji-button": "^4.6.2",
"@rails/webpacker": "^4.0.7",
"autoprefixer": "^7.2.6",
"axios": "0.21.2",
@ -110,7 +110,7 @@
"styled-components": "^2.4.1",
"tui-code-snippet": "^1.5.0",
"tui-color-picker": "^2.2.0",
"tui-image-editor": "git://github.com/biosistemika/tui.image-editor",
"tui-image-editor": "github:biosistemika/tui.image-editor#3_15_2_updated",
"twemoji": "^12.1.4",
"typeface-lato": "^0.0.75",
"vue": "^2.6.11",

View file

@ -5,68 +5,109 @@
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body {
background-color: #EFEFEF;
background-color: #E5E5E5;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
div.navbar {
background: #FFFFFF;
box-shadow: 0px 1px 4px rgba(35, 31, 32, 0.15);
height: 52px;
left: 0%;
position: absolute;
right: 0%;
top: 0%;
}
div.navbar > img {
width: 121px;
height: 24px;
left: 21px;
position: absolute;
top: calc(50% - 24px/2 - 0px);
}
div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #B00100 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
div.dialog > h1 {
color: #404048;
font-family: Lato;
font-style: normal;
font-weight: bold;
font-size: 1.5em;
line-height: 29px;
margin: 0.5em;
text-align: center;
}
div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
color: #404048;
font-family: Lato;
font-style: normal;
font-weight: normal;
font-size: 1.1em;
margin: 0.5em;
}
div.dialog > img {
margin: 1em;
}
div.back-button {
margin-top: 3em;
text-align: center;
}
a.back-button {
background: #104DA9;
border-radius: 4px;
color: #FFFFFF;
display: inline-block;
font-family: Lato;
font-style: normal;
font-weight: normal;
font-size: 0.9em;
margin-top: 3em;
padding: 0.8em 1em;
text-decoration: none;
}
a.back-button > img {
filter: invert(100%);
width: 1em;
height: 1em;
margin-right: 0.5em;
vertical-align:middle;
}
a.back-button > span {
color: #FFFFFF;
width: 1em;
height: 1em;
}
</style>
</head>
<body>
<!-- This file lives in public/403.html -->
<div class="navbar">
<img id="logo" src="/images/scinote_icon.svg" title="SciNote">
</div>
<div class="dialog">
<div>
<h1>Access to this page is denied (403).</h1>
<p>You do not have permission to view this project page using the
credentials that you supplied.</p>
</div>
<p>
Ask project owner to grant you permission to access this project.<br />
<a href="/">home page</a>
<a href="/search/new">or try searching.</a>
</p>
<img src="/images/undraw_through_the_park.svg">
<h1>It would seem that you are not allowed in here.</h1>
<p>You should request access from your team administrators.</p>
<a class="back-button" href="/dashboard">
<img src="/images/arrow-left.svg">
<span>Go back</span>
</a>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L7 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 114.8c9.8 9.3 10 24.8.4 34.3z"/></svg>

After

Width:  |  Height:  |  Size: 508 B

View file

@ -0,0 +1,91 @@
<svg width="319" height="224" viewBox="0 0 319 224" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_105_3721)">
<path d="M166.021 125.296H144.015V126.006H147.447V132.75H148.157V126.006H161.524V132.75H162.234V126.006H166.021V125.296Z" fill="#3F3D56"/>
<path d="M174.185 107.904H168.506V113.583H171.138V132.718H171.848V113.583H174.185V107.904Z" fill="#3F3D56"/>
<path d="M166.064 123.172H144.058V123.882H166.064V123.172Z" fill="#3F3D56"/>
<path d="M166.064 121.397H144.058V122.107H166.064V121.397Z" fill="#3F3D56"/>
<path d="M166.064 119.623H144.058V120.333H166.064V119.623Z" fill="#3F3D56"/>
<path d="M222.456 134.796H221.746C221.746 101.133 194.36 73.7463 160.697 73.7463C127.034 73.7463 99.6478 101.133 99.6478 134.796H98.9379C98.9379 100.741 126.643 73.0364 160.697 73.0364C194.751 73.0364 222.456 100.741 222.456 134.796Z" fill="#3F3D56"/>
<path d="M58.0323 116.065C90.0827 116.065 116.065 90.0827 116.065 58.0323C116.065 25.982 90.0827 0 58.0323 0C25.982 0 0 25.982 0 58.0323C0 90.0827 25.982 116.065 58.0323 116.065Z" fill="#104DA9"/>
<path opacity="0.2" d="M101.2 19.2712C106.614 31.9335 107.348 46.108 103.272 59.2618C99.1965 72.4156 90.5764 83.6916 78.9522 91.0749C67.328 98.4582 53.4572 101.468 39.8185 99.5653C26.1797 97.6631 13.6617 90.9731 4.50137 80.691C7.80811 88.4271 12.7572 95.3518 19.0059 100.985C25.2545 106.619 32.6532 110.826 40.6894 113.317C48.7256 115.807 57.2072 116.52 65.5464 115.407C73.8856 114.294 81.8832 111.381 88.9849 106.87C96.0866 102.36 102.123 96.3588 106.675 89.2836C111.227 82.2084 114.187 74.2281 115.349 65.8955C116.511 57.563 115.848 49.0774 113.404 41.0267C110.961 32.9761 106.797 25.5528 101.2 19.2712Z" fill="black"/>
<path d="M58.1918 58.0325H58.3511L61.2208 222.564H55.1627L58.1918 58.0325Z" fill="#3F3D56"/>
<path d="M68.7584 147.652L67.4215 145.112L57.8286 150.163L59.1655 152.702L68.7584 147.652Z" fill="#3F3D56"/>
<path d="M133.042 123.099C137.126 123.099 140.436 119.789 140.436 115.705C140.436 111.621 137.126 108.311 133.042 108.311C128.958 108.311 125.648 111.621 125.648 115.705C125.648 119.789 128.958 123.099 133.042 123.099Z" fill="#104DA9"/>
<path opacity="0.2" d="M138.542 110.766C139.232 112.38 139.326 114.186 138.806 115.862C138.287 117.538 137.189 118.974 135.708 119.915C134.227 120.856 132.459 121.239 130.721 120.997C128.984 120.755 127.389 119.902 126.222 118.592C126.643 119.578 127.273 120.46 128.07 121.178C128.866 121.896 129.809 122.432 130.832 122.749C131.856 123.066 132.937 123.157 134 123.015C135.062 122.874 136.081 122.502 136.986 121.928C137.891 121.353 138.66 120.588 139.24 119.687C139.82 118.785 140.197 117.769 140.345 116.707C140.493 115.645 140.409 114.564 140.097 113.538C139.786 112.513 139.255 111.567 138.542 110.766Z" fill="black"/>
<path d="M133.063 115.705H133.083L133.448 136.669H132.677L133.063 115.705Z" fill="#3F3D56"/>
<path d="M134.409 127.124L134.238 126.8L133.016 127.444L133.187 127.767L134.409 127.124Z" fill="#3F3D56"/>
<path d="M201.368 110.803C211.822 110.803 220.297 102.328 220.297 91.8736C220.297 81.419 211.822 72.944 201.368 72.944C190.913 72.944 182.438 81.419 182.438 91.8736C182.438 102.328 190.913 110.803 201.368 110.803Z" fill="#104DA9"/>
<path opacity="0.2" d="M215.449 79.23C217.214 83.3603 217.454 87.9839 216.125 92.2745C214.795 96.5652 211.983 100.243 208.192 102.652C204.4 105.06 199.875 106.042 195.426 105.421C190.978 104.801 186.894 102.618 183.906 99.2645C184.985 101.788 186.599 104.047 188.638 105.884C190.676 107.722 193.089 109.094 195.711 109.907C198.332 110.719 201.099 110.952 203.819 110.589C206.539 110.226 209.148 109.275 211.464 107.804C213.781 106.333 215.75 104.375 217.235 102.067C218.719 99.7595 219.685 97.1564 220.064 94.4384C220.443 91.7204 220.226 88.9525 219.43 86.3264C218.633 83.7004 217.274 81.279 215.449 79.23Z" fill="black"/>
<path d="M201.42 91.8735H201.472L202.408 145.542H200.432L201.42 91.8735Z" fill="#3F3D56"/>
<path d="M204.866 121.106L204.43 120.278L201.301 121.926L201.737 122.754L204.866 121.106Z" fill="#3F3D56"/>
<path d="M71.0418 214.373C71.0418 214.373 71.2869 209.237 76.3116 209.834L71.0418 214.373Z" fill="#3F3D56"/>
<path d="M69.6221 209.486C71.011 209.486 72.1369 208.36 72.1369 206.971C72.1369 205.582 71.011 204.456 69.6221 204.456C68.2332 204.456 67.1073 205.582 67.1073 206.971C67.1073 208.36 68.2332 209.486 69.6221 209.486Z" fill="#104DA9"/>
<path d="M69.9228 211.206H69.2129V216.175H69.9228V211.206Z" fill="#3F3D56"/>
<path d="M88.7887 202.66C88.7887 202.66 89.0339 197.524 94.0585 198.121L88.7887 202.66Z" fill="#3F3D56"/>
<path d="M87.369 197.773C88.7579 197.773 89.8838 196.647 89.8838 195.258C89.8838 193.869 88.7579 192.743 87.369 192.743C85.9801 192.743 84.8542 193.869 84.8542 195.258C84.8542 196.647 85.9801 197.773 87.369 197.773Z" fill="#104DA9"/>
<path d="M87.6697 199.493H86.9598V204.462H87.6697V199.493Z" fill="#3F3D56"/>
<path d="M142.285 162.923C142.285 162.923 142.479 158.844 146.47 159.318L142.285 162.923Z" fill="#3F3D56"/>
<path d="M141.157 159.041C142.26 159.041 143.154 158.147 143.154 157.044C143.154 155.941 142.26 155.046 141.157 155.046C140.054 155.046 139.16 155.941 139.16 157.044C139.16 158.147 140.054 159.041 141.157 159.041Z" fill="#104DA9"/>
<path d="M141.396 160.407H140.832V164.354H141.396V160.407Z" fill="#3F3D56"/>
<path d="M220.371 150.145C220.371 150.145 220.566 146.066 224.557 146.54L220.371 150.145Z" fill="#3F3D56"/>
<path d="M219.243 146.263C220.347 146.263 221.241 145.369 221.241 144.266C221.241 143.163 220.347 142.268 219.243 142.268C218.14 142.268 217.246 143.163 217.246 144.266C217.246 145.369 218.14 146.263 219.243 146.263Z" fill="#104DA9"/>
<path d="M219.482 147.629H218.918V151.576H219.482V147.629Z" fill="#3F3D56"/>
<path d="M145.834 173.571C145.834 173.571 146.029 169.492 150.02 169.966L145.834 173.571Z" fill="#3F3D56"/>
<path d="M144.706 169.689C145.81 169.689 146.704 168.795 146.704 167.692C146.704 166.589 145.81 165.694 144.706 165.694C143.603 165.694 142.709 166.589 142.709 167.692C142.709 168.795 143.603 169.689 144.706 169.689Z" fill="#104DA9"/>
<path d="M144.945 171.055H144.381V175.002H144.945V171.055Z" fill="#3F3D56"/>
<path d="M137.316 181.38C137.316 181.38 137.51 177.3 141.501 177.775L137.316 181.38Z" fill="#3F3D56"/>
<path d="M136.188 177.498C137.291 177.498 138.185 176.603 138.185 175.5C138.185 174.397 137.291 173.503 136.188 173.503C135.085 173.503 134.191 174.397 134.191 175.5C134.191 176.603 135.085 177.498 136.188 177.498Z" fill="#104DA9"/>
<path d="M136.427 178.864H135.863V182.811H136.427V178.864Z" fill="#3F3D56"/>
<path d="M232.439 156.889C232.439 156.889 232.634 152.81 236.624 153.284L232.439 156.889Z" fill="#3F3D56"/>
<path d="M231.311 153.007C232.414 153.007 233.309 152.113 233.309 151.01C233.309 149.906 232.414 149.012 231.311 149.012C230.208 149.012 229.314 149.906 229.314 151.01C229.314 152.113 230.208 153.007 231.311 153.007Z" fill="#104DA9"/>
<path d="M231.55 154.373H230.986V158.32H231.55V154.373Z" fill="#3F3D56"/>
<path d="M117.439 165.762C117.439 165.762 117.634 161.683 121.625 162.157L117.439 165.762Z" fill="#3F3D56"/>
<path d="M116.311 161.881C117.415 161.881 118.309 160.986 118.309 159.883C118.309 158.78 117.415 157.886 116.311 157.886C115.208 157.886 114.314 158.78 114.314 159.883C114.314 160.986 115.208 161.881 116.311 161.881Z" fill="#104DA9"/>
<path d="M116.55 163.247H115.986V167.193H116.55V163.247Z" fill="#3F3D56"/>
<path d="M209.723 145.531C209.723 145.531 209.918 141.452 213.908 141.926L209.723 145.531Z" fill="#3F3D56"/>
<path d="M208.595 141.649C209.698 141.649 210.593 140.755 210.593 139.652C210.593 138.548 209.698 137.654 208.595 137.654C207.492 137.654 206.598 138.548 206.598 139.652C206.598 140.755 207.492 141.649 208.595 141.649Z" fill="#104DA9"/>
<path d="M208.834 143.015H208.27V146.962H208.834V143.015Z" fill="#3F3D56"/>
<path d="M40.1622 215.438C40.1622 215.438 40.4073 210.302 45.432 210.899L40.1622 215.438Z" fill="#3F3D56"/>
<path d="M38.7425 210.55C40.1314 210.55 41.2573 209.424 41.2573 208.036C41.2573 206.647 40.1314 205.521 38.7425 205.521C37.3536 205.521 36.2277 206.647 36.2277 208.036C36.2277 209.424 37.3536 210.55 38.7425 210.55Z" fill="#104DA9"/>
<path d="M39.0432 212.27H38.3333V217.24H39.0432V212.27Z" fill="#3F3D56"/>
<path d="M284.714 214.018C284.714 214.018 284.96 208.882 289.984 209.479L284.714 214.018Z" fill="#3F3D56"/>
<path d="M283.295 209.131C284.684 209.131 285.81 208.005 285.81 206.616C285.81 205.227 284.684 204.101 283.295 204.101C281.906 204.101 280.78 205.227 280.78 206.616C280.78 208.005 281.906 209.131 283.295 209.131Z" fill="#104DA9"/>
<path d="M283.595 210.851H282.886V215.82H283.595V210.851Z" fill="#3F3D56"/>
<path d="M264.128 201.95C264.128 201.95 264.373 196.814 269.398 197.411L264.128 201.95Z" fill="#3F3D56"/>
<path d="M262.708 197.063C264.097 197.063 265.223 195.937 265.223 194.548C265.223 193.159 264.097 192.033 262.708 192.033C261.319 192.033 260.193 193.159 260.193 194.548C260.193 195.937 261.319 197.063 262.708 197.063Z" fill="#104DA9"/>
<path d="M263.009 198.783H262.299V203.752H263.009V198.783Z" fill="#3F3D56"/>
<path d="M261.289 220.052C261.289 220.052 261.534 214.916 266.558 215.513L261.289 220.052Z" fill="#3F3D56"/>
<path d="M259.869 215.165C261.258 215.165 262.384 214.039 262.384 212.65C262.384 211.261 261.258 210.135 259.869 210.135C258.48 210.135 257.354 211.261 257.354 212.65C257.354 214.039 258.48 215.165 259.869 215.165Z" fill="#104DA9"/>
<path d="M260.169 216.885H259.46V221.854H260.169V216.885Z" fill="#3F3D56"/>
<path d="M44.2481 223.885C44.2481 223.885 156.054 194.425 154.989 164.256C153.037 155.914 139.549 153.785 139.549 153.785C139.549 153.785 107.605 149.526 124.996 139.232C142.388 128.939 189.95 131.424 189.95 131.424C189.95 131.424 140.969 139.232 162.62 143.492C180.012 144.556 202.373 149.171 202.373 149.171C202.373 149.171 247.805 158.754 254.194 182.18C260.583 205.606 255.259 223.708 255.259 223.708L44.2481 223.885Z" fill="#D0D0D8"/>
<path d="M166.024 224L165.605 223.415C166.196 222.992 166.779 222.571 167.353 222.153L167.776 222.735C167.2 223.154 166.616 223.576 166.024 224Z" fill="white"/>
<path d="M171.242 220.161L170.808 219.588C171.97 218.708 173.115 217.822 174.21 216.956L174.657 217.52C173.557 218.389 172.408 219.278 171.242 220.161ZM178.012 214.802L177.553 214.249C178.678 213.314 179.778 212.375 180.822 211.457L181.297 211.998C180.248 212.92 179.142 213.863 178.012 214.802ZM184.498 209.095L184.005 208.57C185.079 207.563 186.114 206.555 187.082 205.573L187.594 206.078C186.62 207.066 185.578 208.081 184.498 209.095ZM190.561 202.93L190.026 202.449C191.012 201.351 191.945 200.25 192.798 199.179L193.361 199.627C192.499 200.709 191.557 201.821 190.561 202.93ZM195.939 196.144L195.345 195.739C196.181 194.513 196.936 193.29 197.587 192.103L198.218 192.449C197.555 193.656 196.788 194.9 195.939 196.144ZM200.075 188.514L199.406 188.251C199.927 186.935 200.336 185.577 200.627 184.191L201.332 184.335C201.032 185.762 200.612 187.16 200.075 188.514V188.514ZM201.036 179.991C201.014 178.593 200.82 177.203 200.457 175.853L201.152 175.665C201.53 177.073 201.732 178.522 201.755 179.979L201.036 179.991ZM198.884 171.97C198.2 170.736 197.399 169.572 196.49 168.494L197.041 168.033C197.98 169.147 198.809 170.349 199.514 171.624L198.884 171.97ZM193.517 165.458C192.448 164.517 191.329 163.635 190.163 162.816L190.578 162.228C191.765 163.062 192.906 163.961 193.994 164.92L193.517 165.458ZM186.558 160.501C185.382 159.809 184.112 159.119 182.787 158.45L183.11 157.807C184.45 158.484 185.733 159.181 186.923 159.88L186.558 160.501ZM178.899 156.611C177.647 156.055 176.313 155.495 174.935 154.947L175.201 154.279C176.588 154.83 177.93 155.394 179.191 155.954L178.899 156.611ZM170.901 153.418C169.62 152.953 168.25 152.476 166.83 152L167.058 151.318C168.484 151.796 169.859 152.275 171.147 152.741L170.901 153.418ZM162.726 150.668C161.472 150.272 160.123 149.856 158.6 149.395L158.809 148.706C160.334 149.168 161.686 149.585 162.942 149.981L162.726 150.668ZM154.461 148.152C153.006 147.717 151.619 147.301 150.32 146.901L150.531 146.214C151.829 146.614 153.214 147.029 154.667 147.463L154.461 148.152ZM146.195 145.567C144.811 145.111 143.458 144.566 142.143 143.937L142.467 143.294C143.752 143.909 145.076 144.441 146.43 144.887L146.195 145.567ZM140.763 140.852L140.25 140.348C140.906 139.681 142.203 139.029 144.215 138.354L144.444 139.036C142.568 139.666 141.329 140.277 140.763 140.852L140.763 140.852ZM148.559 137.865L148.385 137.166C149.629 136.857 151.046 136.532 152.597 136.203L152.747 136.907C151.204 137.234 149.795 137.557 148.559 137.865V137.865ZM156.964 136.06L156.829 135.353C158.155 135.1 159.582 134.837 161.072 134.573L161.198 135.281C159.711 135.545 158.287 135.807 156.964 136.06V136.06ZM165.441 134.551L165.322 133.841C166.678 133.614 168.096 133.382 169.578 133.144L169.691 133.854C168.211 134.092 166.795 134.324 165.441 134.551Z" fill="white"/>
<path d="M174.029 133.172L173.919 132.461C174.618 132.353 175.329 132.243 176.053 132.133L176.162 132.844C175.438 132.954 174.727 133.063 174.029 133.172Z" fill="white"/>
<path d="M189.949 60.1065L193.216 57.4931C190.678 57.2131 189.635 58.5975 189.208 59.6932C187.225 58.8697 185.066 59.9489 185.066 59.9489L191.604 62.3223C191.274 61.4413 190.7 60.6727 189.949 60.1065Z" fill="#3F3D56"/>
<path d="M129.254 80.338L132.522 77.7247C129.983 77.4447 128.941 78.829 128.514 79.9248C126.531 79.1013 124.372 80.1805 124.372 80.1805L130.909 82.5538C130.58 81.6729 130.005 80.9043 129.254 80.338Z" fill="#3F3D56"/>
<path d="M145.227 38.4554L148.494 35.842C145.956 35.562 144.913 36.9463 144.486 38.0421C142.503 37.2186 140.344 38.2978 140.344 38.2978L146.882 40.6711C146.552 39.7902 145.978 39.0216 145.227 38.4554Z" fill="#3F3D56"/>
<path opacity="0.1" d="M186.277 191C217.91 191 243.553 189.017 243.553 186.57C243.553 184.123 217.91 182.14 186.277 182.14C154.644 182.14 129 184.123 129 186.57C129 189.017 154.644 191 186.277 191Z" fill="black"/>
<path d="M231.801 101.338L230.054 99.5914C229.035 98.5726 227.654 98.0002 226.213 98.0002C224.772 98.0002 223.39 98.5726 222.371 99.5914L188.492 133.471L154.612 99.5914C153.593 98.5726 152.212 98.0002 150.771 98.0002C149.33 98.0002 147.948 98.5726 146.929 99.5914L145.183 101.338C144.678 101.843 144.278 102.441 144.005 103.101C143.732 103.76 143.592 104.466 143.592 105.179C143.592 105.893 143.732 106.599 144.005 107.258C144.278 107.918 144.678 108.516 145.183 109.021L179.062 142.9L145.183 176.78C144.678 177.285 144.278 177.883 144.005 178.542C143.732 179.202 143.592 179.908 143.592 180.621C143.592 181.335 143.732 182.041 144.005 182.7C144.278 183.359 144.678 183.958 145.183 184.463L146.929 186.209C147.948 187.228 149.33 187.801 150.771 187.801C152.212 187.801 153.593 187.228 154.612 186.209L188.492 152.33L222.371 186.209C223.39 187.228 224.772 187.801 226.213 187.801C227.654 187.801 229.035 187.228 230.054 186.209L231.801 184.463C232.82 183.444 233.392 182.062 233.392 180.621C233.392 179.181 232.82 177.799 231.801 176.78L197.921 142.9L231.801 109.021C232.82 108.002 233.392 106.62 233.392 105.179C233.392 103.739 232.82 102.357 231.801 101.338Z" fill="#FF6584"/>
<path opacity="0.1" d="M144.714 102.439C145.733 101.42 147.115 100.848 148.556 100.848C149.997 100.848 151.378 101.42 152.397 102.439L186.277 136.319L220.156 102.439C221.175 101.42 222.557 100.848 223.998 100.848C225.438 100.848 226.82 101.42 227.839 102.439L229.586 104.186C230.335 104.935 230.849 105.888 231.064 106.925C231.278 107.963 231.185 109.041 230.795 110.027L231.801 109.021C232.305 108.516 232.705 107.917 232.979 107.258C233.252 106.599 233.392 105.893 233.392 105.179C233.392 104.466 233.252 103.759 232.979 103.1C232.705 102.441 232.305 101.842 231.801 101.338L230.054 99.5912C229.035 98.5724 227.654 98 226.213 98C224.772 98 223.39 98.5724 222.371 99.5912L188.492 133.471L154.612 99.5912C153.593 98.5724 152.212 98 150.771 98C149.33 98 147.948 98.5724 146.929 99.5912L145.183 101.338C144.658 101.862 144.246 102.489 143.974 103.18L144.714 102.439Z" fill="black"/>
<path opacity="0.1" d="M198.238 143.217L195.706 145.748L229.586 179.628C230.335 180.377 230.849 181.33 231.064 182.368C231.278 183.405 231.185 184.483 230.795 185.469L231.801 184.463C232.305 183.958 232.706 183.359 232.979 182.7C233.252 182.041 233.392 181.335 233.392 180.621C233.392 179.908 233.252 179.201 232.979 178.542C232.706 177.883 232.305 177.284 231.801 176.78L198.238 143.217Z" fill="black"/>
<path d="M271.645 129.623C297.586 129.623 318.615 108.594 318.615 82.6529C318.615 56.7123 297.586 35.6832 271.645 35.6832C245.705 35.6832 224.676 56.7123 224.676 82.6529C224.676 108.594 245.705 129.623 271.645 129.623Z" fill="#104DA9"/>
<path opacity="0.2" d="M306.584 51.2809C310.966 61.5293 311.56 73.0018 308.261 83.6481C304.963 94.2944 297.986 103.421 288.577 109.397C279.169 115.372 267.942 117.808 256.904 116.269C245.865 114.729 235.733 109.314 228.319 100.992C230.995 107.254 235.001 112.858 240.059 117.418C245.116 121.977 251.104 125.383 257.609 127.398C264.113 129.414 270.978 129.991 277.727 129.09C284.477 128.189 290.95 125.832 296.698 122.181C302.445 118.53 307.331 113.673 311.015 107.947C314.7 102.22 317.095 95.7614 318.036 89.0172C318.976 82.2731 318.439 75.4051 316.462 68.8891C314.485 62.3732 311.114 56.365 306.584 51.2809Z" fill="black"/>
<path d="M271.774 82.6528H271.903L274.226 215.82H269.323L271.774 82.6528Z" fill="#3F3D56"/>
<path d="M280.327 155.188L279.245 153.133L271.481 157.221L272.563 159.276L280.327 155.188Z" fill="#3F3D56"/>
<path d="M180.747 154.814C180.747 154.814 177.917 154.814 177.602 156.544C177.288 158.273 176.816 177.141 177.131 179.499C177.445 181.857 177.445 181.386 177.445 181.386C177.445 181.386 176.818 185.867 178.232 186.064C179.646 186.26 179.646 180.914 179.646 180.914L180.747 161.732V154.814Z" fill="#9E616A"/>
<path d="M193.011 154.814C193.011 154.814 195.841 154.814 196.155 156.544C196.47 158.273 196.941 177.141 196.627 179.499C196.312 181.857 196.312 181.386 196.312 181.386C196.312 181.386 196.939 185.867 195.525 186.064C194.111 186.26 194.111 180.914 194.111 180.914L193.011 161.732V154.814Z" fill="#9E616A"/>
<path d="M180.275 220.536H179.017C178.646 220.536 178.278 220.609 177.934 220.751C177.591 220.893 177.279 221.102 177.016 221.364C176.753 221.627 176.545 221.939 176.403 222.283C176.26 222.626 176.187 222.994 176.187 223.366V223.366H183.891V219.749H180.275V220.536Z" fill="#2F2E41"/>
<path d="M192.382 220.536H193.639C194.39 220.536 195.11 220.834 195.641 221.364C196.171 221.895 196.47 222.615 196.47 223.366V223.366H188.765V219.749H192.382V220.536Z" fill="#2F2E41"/>
<path d="M185.621 151.198C187.878 151.198 189.709 149.368 189.709 147.11C189.709 144.852 187.878 143.022 185.621 143.022C183.363 143.022 181.533 144.852 181.533 147.11C181.533 149.368 183.363 151.198 185.621 151.198Z" fill="#9E616A"/>
<path d="M184.677 149.311C184.677 149.311 184.206 154.5 183.577 154.972C182.948 155.443 190.18 154.657 190.18 154.657C190.18 154.657 187.665 150.884 188.136 149.626C188.608 148.368 184.677 149.311 184.677 149.311Z" fill="#9E616A"/>
<path d="M193.954 154.814C193.954 154.814 185.306 151.198 179.646 154.814C179.646 154.814 178.231 158.43 179.332 160.474C180.432 162.518 182.162 167.392 180.432 170.694C178.703 173.996 180.589 172.581 180.589 172.581C180.589 172.581 178.388 181.386 178.074 188.304C177.759 195.222 179.017 220.064 179.961 220.221C180.904 220.378 183.891 220.693 184.206 220.221C184.52 219.749 186.721 186.889 186.721 186.889C186.721 186.889 187.665 220.536 188.608 220.536C189.552 220.536 192.067 220.536 192.539 219.749C193.011 218.963 196.941 188.618 195.526 182.329C194.111 176.04 193.168 172.895 193.168 172.895C193.168 172.895 194.897 171.795 194.426 171.166C193.954 170.537 191.595 163.776 192.696 162.676C193.797 161.575 193.954 154.814 193.954 154.814Z" fill="#2F2E41"/>
<path d="M188.215 151.25C191.862 151.25 194.819 148.293 194.819 144.646C194.819 140.999 191.862 138.043 188.215 138.043C184.568 138.043 181.612 140.999 181.612 144.646C181.612 148.293 184.568 151.25 188.215 151.25Z" fill="#2F2E41"/>
<path d="M192.339 141.34C191.226 141.079 190.256 140.399 189.632 139.442C189.007 138.485 188.774 137.324 188.982 136.2C188.963 136.267 188.945 136.336 188.929 136.405C188.66 137.553 188.858 138.761 189.479 139.764C190.1 140.766 191.094 141.48 192.242 141.75C193.39 142.02 194.599 141.823 195.601 141.202C196.604 140.581 197.319 139.588 197.59 138.44C197.606 138.371 197.62 138.302 197.633 138.232C197.319 139.332 196.594 140.268 195.608 140.846C194.622 141.425 193.451 141.602 192.339 141.34Z" fill="#2F2E41"/>
<path d="M192.607 142.201C194.392 142.201 195.839 140.754 195.839 138.969C195.839 137.185 194.392 135.738 192.607 135.738C190.823 135.738 189.376 137.185 189.376 138.969C189.376 140.754 190.823 142.201 192.607 142.201Z" fill="#2F2E41"/>
</g>
<defs>
<clipPath id="clip0_105_3721">
<rect width="318.615" height="224" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

1138
yarn.lock

File diff suppressed because it is too large Load diff