Merge pull request #2927 from biosistemika/features/signature-requests

Features/signature requests into develop
This commit is contained in:
Urban Rotnik 2020-10-29 14:48:25 +01:00 committed by GitHub
commit e923128c0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 216 additions and 215 deletions

View file

@ -8,19 +8,19 @@ var DasboardQuickStartWidget = (function() {
var newProjectsVisibility = '#create-task-modal .new-projects-visibility'; var newProjectsVisibility = '#create-task-modal .new-projects-visibility';
function initNewReportLink() { function initNewReportLink() {
$('.quick-start-widget .new-report').click(() => { $('.quick-start-buttons .new-report').click(() => {
sessionStorage.setItem('scinote-dashboard-new-report', Math.floor(Date.now() / 1000)); sessionStorage.setItem('scinote-dashboard-new-report', Math.floor(Date.now() / 1000));
}); });
} }
function initNewProtocolLink() { function initNewProtocolLink() {
$('.quick-start-widget .new-protocol').click(() => { $('.quick-start-buttons .new-protocol').click(() => {
sessionStorage.setItem('scinote-dashboard-new-protocol', Math.floor(Date.now() / 1000)); sessionStorage.setItem('scinote-dashboard-new-protocol', Math.floor(Date.now() / 1000));
}); });
} }
function initNewTaskModal() { function initNewTaskModal() {
$('.quick-start-widget .new-task').click(() => { $('.quick-start-buttons .new-task').click(() => {
$('#create-task-modal').modal('show'); $('#create-task-modal').modal('show');
$('#create-task-modal .select-block').attr('data-error', ''); $('#create-task-modal .select-block').attr('data-error', '');
}); });
@ -118,7 +118,7 @@ var DasboardQuickStartWidget = (function() {
return { return {
init: () => { init: () => {
if ($('.quick-start-widget').length) { if ($('.quick-start-buttons').length) {
initNewTaskModal(); initNewTaskModal();
initNewProtocolLink(); initNewProtocolLink();
initNewReportLink(); initNewReportLink();

View file

@ -5,8 +5,6 @@
.dashboard-container .calendar-widget { .dashboard-container .calendar-widget {
--calendar-day-size: 32px; --calendar-day-size: 32px;
grid-column: 10 / span 3;
grid-row: 1 / span 6;
min-height: 320px; min-height: 320px;
.dashboard-calendar { .dashboard-calendar {
@ -127,17 +125,8 @@
} }
} }
@media (max-width: 1250px) { @media (max-width: 1600px) {
.dashboard-container .calendar-widget { .dashboard-container .calendar-widget {
grid-column: 9 / span 4;
}
}
@media (max-width: 1000px) {
.dashboard-container .calendar-widget {
grid-column: 1 / span 6;
grid-row: 5 / span 4;
.clndr { .clndr {
.events-container { .events-container {
left: 0; left: 0;
@ -150,7 +139,5 @@
@media (max-width: 700px) { @media (max-width: 700px) {
.dashboard-container .calendar-widget { .dashboard-container .calendar-widget {
--calendar-day-size: 28px; --calendar-day-size: 28px;
grid-column: 1 / span 12;
grid-row: 2;
} }
} }

View file

@ -2,8 +2,6 @@
// scss-lint:disable NestingDepth // scss-lint:disable NestingDepth
.dashboard-container .current-tasks-widget { .dashboard-container .current-tasks-widget {
grid-column: 1 / span 9;
grid-row: 1 / span 6;
.title { .title {
flex-shrink: 0; flex-shrink: 0;
@ -250,8 +248,6 @@
@media (max-width: 1250px) { @media (max-width: 1250px) {
.dashboard-container .current-tasks-widget { .dashboard-container .current-tasks-widget {
grid-column: 1 / span 8;
.task-progress-container { .task-progress-container {
max-width: 150px; max-width: 150px;
} }
@ -271,9 +267,6 @@
@media (max-width: 1000px) { @media (max-width: 1000px) {
.dashboard-container .current-tasks-widget { .dashboard-container .current-tasks-widget {
grid-column: 1 / span 12;
grid-row: 1 / span 4;
.no-tasks .fas { .no-tasks .fas {
margin-left: 500px; margin-left: 500px;
} }
@ -283,7 +276,6 @@
@media (max-width: 700px) { @media (max-width: 700px) {
.dashboard-container .current-tasks-widget { .dashboard-container .current-tasks-widget {
--widget-header-size: 72px; --widget-header-size: 72px;
grid-row: 1;
min-height: 450px; min-height: 450px;
.widget-title { .widget-title {

View file

@ -1,48 +0,0 @@
// scss-lint:disable SelectorDepth
// scss-lint:disable NestingDepth
.dashboard-container .quick-start-widget {
grid-column: 1 / span 2;
grid-row: 7 / span 6;
.widget-body {
.quick-start-description {
margin: 16px 16px 24px;
}
.btn-secondary {
margin: 8px 16px;
text-align: left;
width: calc(100% - 32px);
}
}
}
@media (max-width: 1700px) {
.dashboard-container .quick-start-widget {
grid-column: 1 / span 3;
}
}
@media (max-width: 1300px) {
.dashboard-container .quick-start-widget {
grid-column: 1 / span 4;
}
}
@media (max-width: 1000px) {
.dashboard-container .quick-start-widget {
grid-column: 7 / span 6;
grid-row: 5 / span 4;
}
}
@media (max-width: 700px) {
.dashboard-container .quick-start-widget {
--widget-header-size: 36px;
grid-column: 1 / span 12;
grid-row: 4;
min-height: 300px;
}
}

View file

@ -2,9 +2,6 @@
// scss-lint:disable NestingDepth // scss-lint:disable NestingDepth
.dashboard-container .recent-work-widget { .dashboard-container .recent-work-widget {
grid-column: 3 / span 7;
grid-row: 7 / span 6;
.widget-title { .widget-title {
flex-grow: 1; flex-grow: 1;
} }
@ -58,24 +55,9 @@
} }
} }
@media (max-width: 1700px) {
.dashboard-container .recent-work-widget {
grid-column: 4 / span 6;
}
}
@media (max-width: 1300px) {
.dashboard-container .recent-work-widget {
grid-column: 5 / span 8;
}
}
@media (max-width: 1000px) { @media (max-width: 1000px) {
.dashboard-container .recent-work-widget { .dashboard-container .recent-work-widget {
grid-column: 1 / span 12;
grid-row: 9 / span 4;
.no-results { .no-results {
.no-results-arrow { .no-results-arrow {
display: none; display: none;
@ -84,14 +66,11 @@
} }
} }
@media (max-width: 700px) { @media (max-width: 1100px) {
.dashboard-container .recent-work-widget { .dashboard-container .recent-work-widget {
--widget-header-size: 72px;
grid-row: 3;
min-height: 450px; min-height: 450px;
.widget-title { .widget-title {
flex-basis: 100%;
line-height: 36px; line-height: 36px;
} }

View file

@ -1,23 +1,53 @@
// scss-lint:disable SelectorDepth // scss-lint:disable SelectorDepth
// scss-lint:disable NestingDepth // scss-lint:disable NestingDepth
.dashboard-container { .dashboard-background {
background: $color-concrete;
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
}
.dashboard-view {
--dashboard-widgets-gap: 30px; --dashboard-widgets-gap: 30px;
padding: calc(var(--dashboard-widgets-gap) / 2)
calc(var(--dashboard-widgets-gap) / 2)
var(--dashboard-widgets-gap);
}
.dashboard-header {
padding-bottom: calc(var(--dashboard-widgets-gap) / 2);
}
.dashboard-container {
--widget-header-size: 44px; --widget-header-size: 44px;
display: grid; display: grid;
grid-auto-rows: 30em;
grid-column-gap: var(--dashboard-widgets-gap); grid-column-gap: var(--dashboard-widgets-gap);
grid-row-gap: var(--dashboard-widgets-gap); grid-row-gap: var(--dashboard-widgets-gap);
grid-template-columns: repeat(12, 1fr); grid-template-columns: repeat(auto-fit, minmax(7em, 1fr));
grid-template-rows: repeat(12, 1fr);
min-height: calc(100vh - 51px);
padding: var(--dashboard-widgets-gap) calc(var(--dashboard-widgets-gap) - 15px);
width: 100%; width: 100%;
.basic-widget { .basic-widget {
background: $color-white;
border-radius: $border-radius-modal; border-radius: $border-radius-modal;
box-shadow: $flyout-shadow; box-shadow: $flyout-shadow;
position: relative; position: relative;
&.large-widget {
grid-column: auto / 9 span;
}
&.medium-widget {
grid-column: auto / 6 span;
}
&.small-widget {
grid-column: auto / 3 span;
}
.widget-header { .widget-header {
align-items: center; align-items: center;
border-bottom: $border-tertiary; border-bottom: $border-tertiary;
@ -27,6 +57,7 @@
.widget-title { .widget-title {
@include font-h2; @include font-h2;
flex-shrink: 0;
} }
} }
@ -51,16 +82,50 @@
} }
} }
@media (max-width: 1300px) { @media (max-width: 1100px) {
.dashboard-container { .dashboard-container {
--dashboard-widgets-gap: 16px; --dashboard-widgets-gap: 15px;
grid-template-columns: 100%;
.basic-widget {
&.large-widget,
&.medium-widget,
&.small-widget {
grid-column: auto / 1 span;
}
}
}
}
@media (max-height: 800px) {
.dashboard-container {
grid-auto-rows: 24em;
}
}
@media (min-height: 1300px) {
.dashboard-container {
grid-auto-rows: 36em;
} }
} }
@media (max-width: 700px) { @media (max-width: 700px) {
.dashboard-view {
--dashboard-widgets-gap: 15px;
}
.dashboard-container { .dashboard-container {
--widget-header-size: 72px; --widget-header-size: 72px;
grid-template-rows: auto; grid-auto-rows: auto;
.basic-widget {
&.large-widget,
&.medium-widget,
&.small-widget {
grid-column: 1 span;
}
}
.widget-header { .widget-header {
flex-wrap: wrap; flex-wrap: wrap;

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
class Report < ApplicationRecord class Report < ApplicationRecord
include SearchableModel include SearchableModel
include SearchableByNameModel include SearchableByNameModel
@ -58,7 +60,7 @@ class Report < ApplicationRecord
end end
def root_elements def root_elements
(report_elements.order(:position)).select { |el| el.parent.blank? } report_elements.order(:position).select { |el| el.parent.blank? }
end end
# Save the JSON represented contents to this report # Save the JSON represented contents to this report
@ -66,7 +68,7 @@ class Report < ApplicationRecord
def save_with_contents(json_contents) def save_with_contents(json_contents)
begin begin
Report.transaction do Report.transaction do
#First, save the report itself # First, save the report itself
save! save!
# Secondly, delete existing report elements # Secondly, delete existing report elements
@ -80,67 +82,17 @@ class Report < ApplicationRecord
rescue ActiveRecord::ActiveRecordError, ArgumentError rescue ActiveRecord::ActiveRecordError, ArgumentError
return false return false
end end
return true true
end end
# Clean report elements from report # Clean report elements from report
# the function runs before the report is edit # the function runs before the report is edit
def cleanup_report def cleanup_report
report_elements.each do |el| report_elements.each(&:clean_removed_or_archived_elements)
el.clean_removed_or_archived_elements
end
end end
def self.generate_whole_project_report(project, current_user, current_team) def self.generate_whole_project_report(project, current_user, current_team)
report_contents = gen_element_content(project, nil, 'project_header', true) report_contents = gen_element_content(project, Extends::EXPORT_ALL_PROJECT_ELEMENTS)
project.experiments.each do |exp|
modules = []
exp.my_modules.each do |my_module|
module_children = []
module_children += gen_element_content(my_module, nil, 'my_module_protocol', true)
my_module.protocol.steps.each do |step|
step_children =
gen_element_content(step, step.assets, 'step_asset')
step_children +=
gen_element_content(step, step.tables, 'step_table')
step_children +=
gen_element_content(step, step.checklists, 'step_checklist')
step_children +=
gen_element_content(step, nil, 'step_comments', true, 'asc')
module_children +=
gen_element_content(step, nil, 'step', true, nil, step_children)
end
my_module.results.each do |result|
result_children =
gen_element_content(result, nil, 'result_comments', true, 'asc')
result_type = if result.asset
'result_asset'
elsif result.table
'result_table'
elsif result.result_text
'result_text'
end
module_children +=
gen_element_content(result, nil, result_type, true, nil,
result_children)
end
repositories = project.assigned_repositories_and_snapshots
module_children += gen_element_content(my_module, nil, 'my_module_activity', true, 'asc')
module_children += gen_element_content(my_module, repositories, 'my_module_repository', true, 'asc')
modules += gen_element_content(my_module, nil, 'my_module', true, nil, module_children)
end
report_contents +=
gen_element_content(exp, nil, 'experiment', true, nil, modules)
end
report = Report.new report = Report.new
report.name = loop do report.name = loop do
@ -155,31 +107,29 @@ class Report < ApplicationRecord
report report
end end
def self.gen_element_content(parent_obj, association_objs, type_of, def self.gen_element_content(parent, children)
use_parent_id = false, sort_order = nil,
children = nil)
parent_type = parent_obj.class.name.underscore
type = type_of.split('_').last.singularize
extra_id_needed = use_parent_id && !association_objs.nil?
elements = [] elements = []
association_objs ||= [nil] children.each do |element|
association_objs.each do |obj| element_hash = lambda { |object|
elements << { hash_object = {
'type_of' => type_of, 'type_of' => element[:type_of] || element[:type_of_lambda].call(object),
'id' => {}.tap do |ids_hash| 'id' => { element[:id_key] => object.id },
if use_parent_id 'sort_order' => element[:sort_order],
ids_hash["#{parent_type}_id"] = parent_obj.id 'children' => gen_element_content(object, element[:children] || [])
else }
ids_hash["#{type}_id"] = obj.id hash_object['id'][element[:parent_id_key]] = parent.id if element[:parent_id_key]
end hash_object
ids_hash["#{type}_id"] = obj.id if extra_id_needed
end,
'sort_order' => sort_order.present? ? sort_order : nil,
'children' => children.present? ? children : []
} }
end
if element[:relation]
(element[:relation].inject(parent) { |p, method| p.public_send(method) }).each do |child|
elements.push(element_hash.call(child))
end
else
elements.push(element_hash.call(parent))
end
end
elements elements
end end

View file

@ -1,4 +1,4 @@
<div class="calendar-widget basic-widget"> <div class="calendar-widget basic-widget <%= widget[:size] %>" style="order: <%= widget[:position] %>">
<div class="dashboard-calendar" <div class="dashboard-calendar"
data-month-events-url="<%= dashboard_calendar_path %>" data-month-events-url="<%= dashboard_calendar_path %>"
data-day-events-url="<%= day_dashboard_calendar_path %>" data-day-events-url="<%= day_dashboard_calendar_path %>"

View file

@ -1,4 +1,4 @@
<div class="current-tasks-widget basic-widget"> <div class="current-tasks-widget basic-widget <%= widget[:size] %>" style="order: <%= widget[:position] %>">
<div class="widget-header"> <div class="widget-header">
<div class="widget-title"><%= t("dashboard.current_tasks.title") %></div> <div class="widget-title"><%= t("dashboard.current_tasks.title") %></div>
<div class="actions-container"> <div class="actions-container">

View file

@ -1,27 +1,12 @@
<div class="quick-start-widget basic-widget"> <% unless current_user.is_guest_of_team?(current_team) %>
<div class="widget-header"> <div class="quick-start-buttons">
<div class="widget-title"> <div class="new-task btn btn-secondary"><i class="fas fa-plus"></i><%= t("dashboard.quick_start.new_task") %></div>
<%= t("dashboard.quick_start.title") %> <%= link_to protocols_path, {class: "new-protocol btn btn-secondary"} do %>
</div> <i class="fas fa-edit"></i><%= t("dashboard.quick_start.new_protocol") %>
</div> <% end %>
<div class="widget-body"> <%= link_to reports_path, {class: "new-report btn btn-secondary"} do %>
<% if current_user.is_guest_of_team?(current_team) %> <i class="fas fa-clipboard-check"></i><%= t("dashboard.quick_start.new_report") %>
<div class="widget-placeholder">
<p class="widget-placeholder-title"><%= t("dashboard.quick_start.placeholder.title") %></p>
<p class="widget-placeholder-description"><%= t("dashboard.quick_start.placeholder.description") %></p>
</div>
<% else %>
<div class="quick-start-description">
<%= t("dashboard.quick_start.description") %>
</div>
<div class="new-task btn btn-secondary btn-block"><i class="fas fa-plus"></i><%= t("dashboard.quick_start.new_task") %></div>
<%= link_to protocols_path, {class: "new-protocol btn btn-secondary btn-block"} do %>
<i class="fas fa-edit"></i><%= t("dashboard.quick_start.new_protocol") %>
<% end %>
<%= link_to reports_path, {class: "new-report btn btn-secondary btn-block"} do %>
<i class="fas fa-clipboard-check"></i><%= t("dashboard.quick_start.new_report") %>
<% end %>
<% end %> <% end %>
</div> </div>
</div> <%= render "create_task_modal" %>
<%= render "create_task_modal" %> <% end %>

View file

@ -1,4 +1,4 @@
<div class="recent-work-widget basic-widget"> <div class="recent-work-widget basic-widget <%= widget[:size] %>" style="order: <%= widget[:position] %>">
<div class="widget-header"> <div class="widget-header">
<div class="widget-title"> <div class="widget-title">
<%= t('dashboard.recent_work.title') %> <%= t('dashboard.recent_work.title') %>
@ -37,4 +37,4 @@
</div> </div>
<% end %> <% end %>
</div> </div>
</template> </template>

View file

@ -1,10 +1,17 @@
<% provide :head_title, t('nav.label.dashboard') %> <% provide :head_title, t('nav.label.dashboard') %>
<% if current_team %> <% if current_team %>
<div class="dashboard-container"> <div class="dashboard-view">
<%= render "calendar" %> <div class="dashboard-background"></div>
<%= render "current_tasks" %> <div class="dashboard-header">
<%= render "recent_work" %> <%= render partial: 'quick_start' %>
<%= render "quick_start" %> </div>
<div class="dashboard-container">
<% Extends::DEFAULT_DASHBOARD_CONFIGURATION.each do |widget| %>
<% if widget[:visible] %>
<%= render partial: widget[:partial], locals: {widget: widget} %>
<% end %>
<% end %>
</div>
</div> </div>
<% end %> <% end %>

View file

@ -41,6 +41,84 @@ class Extends
my_module_repository: 17, my_module_repository: 17,
my_module_protocol: 18 } my_module_protocol: 18 }
EXPORT_ALL_PROJECT_ELEMENTS = [
{
type_of: 'project_header',
id_key: 'project_id'
},
{
type_of: 'experiment',
id_key: 'experiment_id',
relation: %w(experiments),
children: [
{
type_of: 'my_module',
id_key: 'my_module_id',
relation: %w(my_modules),
children: [
{
type_of: 'my_module_protocol',
id_key: 'my_module_id'
},
{
type_of: 'step',
relation: %w(protocol steps),
id_key: 'step_id',
children: [
{
type_of: 'step_asset',
relation: %w(assets),
id_key: 'asset_id'
},
{
type_of: 'step_table',
relation: %w(tables),
id_key: 'table_id'
},
{
type_of: 'step_checklist',
relation: %w(checklists),
id_key: 'checklist_id'
},
{
type_of: 'step_comments',
id_key: 'step_id',
sort_order: 'asc'
}
]
},
{
type_of_lambda: lambda { |result|
(result.result_asset ||
result.result_table ||
result.result_text).class.to_s.underscore
},
relation: %w(results),
id_key: 'result_id',
children: [{
type_of: 'result_comments',
id_key: 'result_id',
sort_order: 'asc'
}]
},
{
type_of: 'my_module_activity',
id_key: 'my_module_id',
sort_order: 'asc'
},
{
type_of: 'my_module_repository',
relation: %w(experiment project assigned_repositories_and_snapshots),
id_key: 'repository_id',
parent_id_key: 'my_module_id',
sort_order: 'asc'
}
]
}
]
}
]
# Data type name should match corresponding model's name # Data type name should match corresponding model's name
REPOSITORY_DATA_TYPES = { RepositoryTextValue: 0, REPOSITORY_DATA_TYPES = { RepositoryTextValue: 0,
RepositoryDateValue: 1, RepositoryDateValue: 1,
@ -123,6 +201,12 @@ class Extends
'Protocol' => :description, 'Protocol' => :description,
'MyModule' => :description } 'MyModule' => :description }
DEFAULT_DASHBOARD_CONFIGURATION = [
{ partial: 'dashboards/current_tasks', visible: true, size: 'large-widget', position: 1 },
{ partial: 'dashboards/calendar', visible: true, size: 'small-widget', position: 2 },
{ partial: 'dashboards/recent_work', visible: true, size: 'medium-widget', position: 3 }
]
ACTIVITY_SUBJECT_TYPES = %w( ACTIVITY_SUBJECT_TYPES = %w(
Team RepositoryBase Project Experiment MyModule Result Protocol Report RepositoryRow Team RepositoryBase Project Experiment MyModule Result Protocol Report RepositoryRow
).freeze ).freeze
@ -292,7 +376,7 @@ class Extends
restore_inventory: 145, restore_inventory: 145,
export_inventory_items_assigned_to_task: 146, export_inventory_items_assigned_to_task: 146,
export_inventory_snapshot_items_assigned_to_task: 147, export_inventory_snapshot_items_assigned_to_task: 147,
change_status_on_task_flow: 148 # 149, 150, 151 in AdddOn! change_status_on_task_flow: 148 # 149..157 in AdddOn!
} }
ACTIVITY_GROUPS = { ACTIVITY_GROUPS = {