mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-02-27 09:13:46 +08:00
Merge branch 'features/task-flows' into develop_to_task_flows
This commit is contained in:
commit
7c14da70d9
55 changed files with 1313 additions and 431 deletions
|
@ -3,7 +3,7 @@
|
|||
|
||||
var DasboardCurrentTasksWidget = (function() {
|
||||
var sortFilter = '.curent-tasks-filters .sort-filter';
|
||||
var viewFilter = '.curent-tasks-filters .view-filter';
|
||||
var statusFilter = '.curent-tasks-filters .view-filter';
|
||||
var projectFilter = '.curent-tasks-filters .project-filter';
|
||||
var experimentFilter = '.curent-tasks-filters .experiment-filter';
|
||||
|
||||
|
@ -36,7 +36,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
params.project_id = dropdownSelector.getValues(projectFilter);
|
||||
params.experiment_id = dropdownSelector.getValues(experimentFilter);
|
||||
params.sort = dropdownSelector.getValues(sortFilter);
|
||||
params.view = dropdownSelector.getValues(viewFilter);
|
||||
params.statuses = dropdownSelector.getValues(statusFilter);
|
||||
params.query = $('.current-tasks-widget .task-search-field').val();
|
||||
params.mode = $('.current-tasks-navbar .active').data('mode');
|
||||
return params;
|
||||
|
@ -48,7 +48,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
return dropdownSelector.getValues(experimentFilter)
|
||||
|| dropdownSelector.getValues(projectFilter)
|
||||
|| $('.current-tasks-widget .task-search-field').val().length > 0
|
||||
|| dropdownSelector.getValues(viewFilter) !== 'uncompleted';
|
||||
|| dropdownSelector.getValues(statusFilter) !== 'uncompleted';
|
||||
}
|
||||
|
||||
function loadCurrentTasksList(newList) {
|
||||
|
@ -57,7 +57,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
project_id: dropdownSelector.getValues(projectFilter),
|
||||
experiment_id: dropdownSelector.getValues(experimentFilter),
|
||||
sort: dropdownSelector.getValues(sortFilter),
|
||||
view: dropdownSelector.getValues(viewFilter),
|
||||
statuses: dropdownSelector.getValues(statusFilter),
|
||||
query: $('.current-tasks-widget .task-search-field').val(),
|
||||
mode: $('.current-tasks-navbar .active').data('mode')
|
||||
};
|
||||
|
@ -85,7 +85,6 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropdownSelector.selectValue(sortFilter, 'due_date');
|
||||
dropdownSelector.selectValue(viewFilter, 'uncompleted');
|
||||
dropdownSelector.clearData(projectFilter);
|
||||
dropdownSelector.clearData(experimentFilter);
|
||||
});
|
||||
|
@ -98,12 +97,9 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
disableSearch: true
|
||||
});
|
||||
|
||||
dropdownSelector.init(viewFilter, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
dropdownSelector.init(statusFilter, {
|
||||
selectAppearance: 'simple',
|
||||
disableSearch: true
|
||||
optionClass: 'checkbox-icon'
|
||||
});
|
||||
|
||||
dropdownSelector.init(projectFilter, {
|
||||
|
@ -143,7 +139,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropdownSelector.closeDropdown(sortFilter);
|
||||
dropdownSelector.closeDropdown(viewFilter);
|
||||
dropdownSelector.closeDropdown(statusFilter);
|
||||
dropdownSelector.closeDropdown(projectFilter);
|
||||
dropdownSelector.closeDropdown(experimentFilter);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global I18n dropdownSelector */
|
||||
/* global I18n dropdownSelector HelperModule animateSpinner */
|
||||
/* eslint-disable no-use-before-define */
|
||||
|
||||
function initTaskCollapseState() {
|
||||
|
@ -226,34 +226,33 @@ function bindEditTagsAjax() {
|
|||
});
|
||||
}
|
||||
|
||||
// Sets callback for completing/uncompleting task
|
||||
function applyTaskCompletedCallBack() {
|
||||
$("[data-action='complete-task'], [data-action='uncomplete-task']")
|
||||
.on('click', function() {
|
||||
var button = $(this);
|
||||
$.ajax({
|
||||
url: button.data('link-url'),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.completed === true) {
|
||||
button.attr('data-action', 'uncomplete-task');
|
||||
button.find('.btn')
|
||||
.removeClass('btn-primary').addClass('btn-default');
|
||||
} else {
|
||||
button.attr('data-action', 'complete-task');
|
||||
button.find('.btn')
|
||||
.removeClass('btn-default').addClass('btn-primary');
|
||||
}
|
||||
$('#dueDateContainer').html(data.module_header_due_date);
|
||||
initDueDatePicker();
|
||||
$('.task-state-label').html(data.module_state_label);
|
||||
button.find('button').replaceWith(data.new_btn);
|
||||
},
|
||||
error: function() {
|
||||
function applyTaskStatusChangeCallBack() {
|
||||
$('.task-flows').on('click', '#dropdownTaskFlowList > li[data-state-id]', function() {
|
||||
var list = $('#dropdownTaskFlowList');
|
||||
var item = $(this);
|
||||
var container = list.closest('.task-flows');
|
||||
animateSpinner();
|
||||
$.ajax({
|
||||
url: list.data('link-url'),
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { my_module: { status_id: item.data('state-id') } },
|
||||
success: function(data) {
|
||||
container.html(data.content);
|
||||
animateSpinner(null, false);
|
||||
},
|
||||
error: function(e) {
|
||||
animateSpinner(null, false);
|
||||
if (e.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('my_module_statuses.update_status.error.no_permission'), 'danger');
|
||||
} else if (e.status === 422) {
|
||||
HelperModule.flashAlertMsg(e.errors, 'danger');
|
||||
} else {
|
||||
HelperModule.flashAlertMsg('error', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initTagsSelector() {
|
||||
|
@ -380,7 +379,7 @@ function initAssignedUsersSelector() {
|
|||
}
|
||||
|
||||
initTaskCollapseState();
|
||||
applyTaskCompletedCallBack();
|
||||
applyTaskStatusChangeCallBack();
|
||||
initTagsSelector();
|
||||
bindEditTagsAjax();
|
||||
initStartDatePicker();
|
||||
|
|
16
app/assets/javascripts/my_modules/status_flow.js
Normal file
16
app/assets/javascripts/my_modules/status_flow.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* global animateSpinner */
|
||||
|
||||
(function() {
|
||||
$('.task-flows').on('click', '#viewTaskFlow', function() {
|
||||
$('#statusFlowModal').modal('show');
|
||||
});
|
||||
|
||||
$('#statusFlowModal').on('show.bs.modal', function() {
|
||||
var $modalBody = $(this).find('.modal-body');
|
||||
animateSpinner($modalBody);
|
||||
$.get($(this).data('status-flow-url'), function(result) {
|
||||
animateSpinner($modalBody, false);
|
||||
$modalBody.html(result.html);
|
||||
});
|
||||
});
|
||||
}());
|
|
@ -179,9 +179,20 @@ var dropdownSelector = (function() {
|
|||
}
|
||||
|
||||
// Add selected option to value
|
||||
function addSelectedOption(selector, container) {
|
||||
setData(selector, [convertOptionToJson($(selector).find('option:selected')[0])], true);
|
||||
function addSelectedOptions(selector, container) {
|
||||
var selectedOptions = [];
|
||||
$.each($(selector).find('option:selected'), function(i, option) {
|
||||
selectedOptions.push(convertOptionToJson(option));
|
||||
if (selector.data('config').singleSelect) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!selectedOptions.length) return false;
|
||||
|
||||
setData(selector, selectedOptions, true);
|
||||
return true;
|
||||
}
|
||||
//
|
||||
|
||||
// Prepare custom dropdown icon
|
||||
function prepareCustomDropdownIcon(config) {
|
||||
|
@ -422,8 +433,8 @@ var dropdownSelector = (function() {
|
|||
}
|
||||
|
||||
// Select default value
|
||||
if (config.noEmptyOption && config.singleSelect) {
|
||||
addSelectedOption(selectElement, dropdownContainer);
|
||||
if (!selectElement.data('ajax-url')) {
|
||||
addSelectedOptions(selectElement, dropdownContainer);
|
||||
}
|
||||
|
||||
// Enable simple mode for dropdown selector
|
||||
|
|
|
@ -503,6 +503,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
.task-information {
|
||||
column-gap: 1em;
|
||||
display: grid;
|
||||
grid-template-columns: auto max-content;
|
||||
|
||||
.task-section-header {
|
||||
grid-column: 1 / span 1;
|
||||
}
|
||||
|
||||
.task-details {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
|
||||
.task-flows {
|
||||
grid-column: 2 / span 1;
|
||||
grid-row: 1 / span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.my-module-protocol-status {
|
||||
.status-info-dropdown {
|
||||
|
@ -518,4 +538,18 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-information {
|
||||
grid-template-columns: auto;
|
||||
row-gap: .5em;
|
||||
|
||||
.task-details {
|
||||
grid-row: 3 / span 1;
|
||||
}
|
||||
|
||||
.task-flows {
|
||||
grid-column: unset;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
92
app/assets/stylesheets/my_modules/status_flow.scss
Normal file
92
app/assets/stylesheets/my_modules/status_flow.scss
Normal file
|
@ -0,0 +1,92 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
// scss-lint:disable SelectorFormat
|
||||
// scss-lint:disable ImportantRule
|
||||
|
||||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
.content-pane.my-modules-protocols-index {
|
||||
.status-flow-dropdown {
|
||||
.dropdown-toggle {
|
||||
color: $color-white;
|
||||
text-align: left;
|
||||
width: 15em;
|
||||
|
||||
.caret {
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu > li {
|
||||
line-height: 35px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#statusFlowModal {
|
||||
.status-flow {
|
||||
padding: 2em;
|
||||
|
||||
.status-container {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content 1fr;
|
||||
justify-content: space-around;
|
||||
position: relative;
|
||||
|
||||
.current-status {
|
||||
@include font-small;
|
||||
justify-self: end;
|
||||
|
||||
.fas {
|
||||
margin: 0 .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.status-block {
|
||||
border-radius: $border-radius-tag;
|
||||
color: $color-white;
|
||||
font-weight: bold;
|
||||
line-height: 1em;
|
||||
padding: .5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-comment {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
padding-left: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.connector {
|
||||
background: $color-black;
|
||||
height: 2em;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
width: 2px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
border-left: .2em solid transparent;
|
||||
border-right: .2em solid transparent;
|
||||
content: '';
|
||||
display: block;
|
||||
margin-left: -.1em;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-top: .2em solid $color-black;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-bottom: .2em solid $color-black;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -316,10 +316,10 @@ path, ._jsPlumb_endpoint {
|
|||
|
||||
.module-large .tags-container,
|
||||
.module-medium .tags-container {
|
||||
padding-top: 2px;
|
||||
padding-top: 4px;
|
||||
|
||||
div {
|
||||
font-size: 22pt;
|
||||
font-size: 20px;
|
||||
width: 4px;
|
||||
height: 0px;
|
||||
display: inline-block;
|
||||
|
@ -335,9 +335,9 @@ path, ._jsPlumb_endpoint {
|
|||
}
|
||||
|
||||
& span.badge {
|
||||
margin-left: -8px;
|
||||
margin-top: -10px;
|
||||
margin-left: -12px;
|
||||
margin-right: 4px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -280,10 +280,17 @@ label {
|
|||
|
||||
.module-start-date,
|
||||
.module-due-date {
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.module-status {
|
||||
.status-block {
|
||||
border-radius: $border-radius-tag;
|
||||
color: $color-white;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-tags {
|
||||
margin-left: 0;
|
||||
margin-top: 10px;
|
||||
|
@ -389,6 +396,16 @@ label {
|
|||
&:hover > .report-element-body .step-name {
|
||||
color: $brand-primary;
|
||||
}
|
||||
|
||||
.step-label-default {
|
||||
@include font-h3;
|
||||
color: $color-alto;
|
||||
}
|
||||
|
||||
.step-label-success {
|
||||
@include font-h3;
|
||||
color: $brand-success;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step attachment style (table, asset or checklist) */
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
border-color: $brand-focus;
|
||||
|
||||
.caret {
|
||||
transform: rotateX(180deg)
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -742,6 +742,44 @@ ul.double-line > li {
|
|||
}
|
||||
}
|
||||
|
||||
#canvas-container {
|
||||
.panel-heading {
|
||||
padding: 10px 15px 4px;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 6px 15px;
|
||||
|
||||
.status-label {
|
||||
background-color: var(--state-color);
|
||||
color: $color-white;
|
||||
margin: 3px 0;
|
||||
padding: 2px 8px;
|
||||
white-space: nowrap;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
.nav > li > a {
|
||||
padding: 6px 15px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.badge-indicator {
|
||||
background: transparent;
|
||||
color: $color-silver-chalice;
|
||||
font-size: 12px;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-options {
|
||||
position: relative;
|
||||
bottom: 8px;
|
||||
|
|
|
@ -25,7 +25,7 @@ module Dashboard
|
|||
tasks = tasks.left_outer_joins(:user_my_modules).where(user_my_modules: { user_id: current_user.id })
|
||||
end
|
||||
|
||||
tasks = filter_by_state(tasks)
|
||||
#tasks = filter_by_state(tasks)
|
||||
|
||||
case task_filters[:sort]
|
||||
when 'start_date'
|
||||
|
|
23
app/controllers/my_module_status_flow_controller.rb
Normal file
23
app/controllers/my_module_status_flow_controller.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusFlowController < ApplicationController
|
||||
before_action :load_my_module
|
||||
before_action :check_view_permissions
|
||||
|
||||
def show
|
||||
my_module_statuses = @my_module.my_module_status_flow.my_module_statuses.sort_by_position
|
||||
render json: { html: render_to_string(partial: 'my_modules/modals/status_flow_modal_body.html.erb',
|
||||
locals: { my_module_statuses: my_module_statuses }) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_my_module
|
||||
@my_module = MyModule.find_by(id: params[:my_module_id])
|
||||
render_404 unless @my_module
|
||||
end
|
||||
|
||||
def check_view_permissions
|
||||
render_403 unless can_read_experiment?(@my_module.experiment)
|
||||
end
|
||||
end
|
|
@ -13,7 +13,7 @@ class MyModulesController < ApplicationController
|
|||
before_action :check_manage_permissions, only: %i(description due_date update_description update_protocol_description)
|
||||
before_action :check_view_permissions, except: %i(update update_description update_protocol_description
|
||||
toggle_task_state)
|
||||
before_action :check_complete_module_permission, only: %i(complete_my_module toggle_task_state)
|
||||
before_action :check_update_state_permissions, only: :update_state
|
||||
before_action :set_inline_name_editing, only: %i(protocols results activities archive)
|
||||
|
||||
layout 'fluid'.freeze
|
||||
|
@ -259,99 +259,24 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def archive
|
||||
@archived_results = @my_module.archived_results
|
||||
current_team_switch(@my_module
|
||||
.experiment
|
||||
.project
|
||||
.team)
|
||||
current_team_switch(@my_module.experiment.project.team)
|
||||
end
|
||||
|
||||
# Complete/uncomplete task
|
||||
def toggle_task_state
|
||||
respond_to do |format|
|
||||
@my_module.completed? ? @my_module.uncompleted! : @my_module.completed!
|
||||
task_completion_activity
|
||||
def update_state
|
||||
new_status = @my_module.my_module_status_flow.my_module_statuses.find_by(id: update_status_params[:status_id])
|
||||
return render_404 unless new_status
|
||||
|
||||
# Render new button HTML
|
||||
new_btn_partial = if @my_module.completed?
|
||||
'my_modules/state_button_uncomplete.html.erb'
|
||||
else
|
||||
'my_modules/state_button_complete.html.erb'
|
||||
end
|
||||
@my_module.update(my_module_status: new_status)
|
||||
|
||||
format.json do
|
||||
render json: {
|
||||
new_btn: render_to_string(partial: new_btn_partial),
|
||||
completed: @my_module.completed?,
|
||||
module_header_due_date: render_to_string(
|
||||
partial: 'my_modules/module_header_due_date.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
),
|
||||
module_state_label: render_to_string(
|
||||
partial: 'my_modules/module_state_label.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
render json: { content: render_to_string(
|
||||
partial: 'my_modules/status_flow/task_flow_button.html.erb',
|
||||
locals: { my_module: @my_module })
|
||||
}, status: :ok
|
||||
|
||||
def complete_my_module
|
||||
respond_to do |format|
|
||||
if @my_module.uncompleted? && @my_module.check_completness_status
|
||||
@my_module.completed!
|
||||
task_completion_activity
|
||||
format.json do
|
||||
render json: {
|
||||
task_button_title: t('my_modules.buttons.uncomplete'),
|
||||
module_header_due_date: render_to_string(
|
||||
partial: 'my_modules/module_header_due_date.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
),
|
||||
module_state_label: render_to_string(
|
||||
partial: 'my_modules/module_state_label.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
)
|
||||
}, status: :ok
|
||||
end
|
||||
else
|
||||
format.json { render json: {}, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def task_completion_activity
|
||||
completed = @my_module.completed?
|
||||
log_activity(completed ? :complete_task : :uncomplete_task)
|
||||
start_work_on_next_task_notification
|
||||
end
|
||||
|
||||
def start_work_on_next_task_notification
|
||||
if @my_module.completed?
|
||||
title = t('notifications.start_work_on_next_task',
|
||||
user: current_user.full_name,
|
||||
module: @my_module.name)
|
||||
message = t('notifications.start_work_on_next_task_message',
|
||||
project: link_to(@project.name, project_url(@project)),
|
||||
experiment: link_to(@experiment.name,
|
||||
canvas_experiment_url(@experiment)),
|
||||
my_module: link_to(@my_module.name,
|
||||
protocols_my_module_url(@my_module)))
|
||||
notification = Notification.create(
|
||||
type_of: :recent_changes,
|
||||
title: sanitize_input(title, %w(strong a)),
|
||||
message: sanitize_input(message, %w(strong a)),
|
||||
generator_user_id: current_user.id
|
||||
)
|
||||
# create notification for all users on the next modules in the workflow
|
||||
@my_module.my_modules.map(&:users).flatten.uniq.each do |target_user|
|
||||
next if target_user == current_user || !target_user.recent_notification
|
||||
UserNotification.create(notification: notification, user: target_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@my_module = MyModule.find_by_id(params[:id])
|
||||
if @my_module
|
||||
|
@ -384,8 +309,9 @@ class MyModulesController < ApplicationController
|
|||
render_403 unless can_read_experiment?(@my_module.experiment)
|
||||
end
|
||||
|
||||
def check_complete_module_permission
|
||||
render_403 unless can_complete_module?(@my_module)
|
||||
def check_update_state_permissions
|
||||
return render_403 unless can_change_my_module_flow_status?(@my_module)
|
||||
render_404 unless @my_module.my_module_status
|
||||
end
|
||||
|
||||
def set_inline_name_editing
|
||||
|
@ -414,6 +340,10 @@ class MyModulesController < ApplicationController
|
|||
update_params
|
||||
end
|
||||
|
||||
def update_status_params
|
||||
params.require(:my_module).permit(:status_id)
|
||||
end
|
||||
|
||||
def log_start_date_change_activity(start_date_changes)
|
||||
type_of = if start_date_changes[0].nil? # set started_on
|
||||
message_items = { my_module_started_on: @my_module.started_on }
|
||||
|
|
|
@ -154,7 +154,7 @@ module ReportsHelper
|
|||
style = 'default'
|
||||
text = t('protocols.steps.uncompleted')
|
||||
end
|
||||
"<span class=\"label label-#{style}\">#{text}</span>".html_safe
|
||||
"<span class=\"label step-label-#{style}\">[#{text}]</span>".html_safe
|
||||
end
|
||||
|
||||
# Fixes issues with avatar images in reports
|
||||
|
|
|
@ -8,6 +8,8 @@ class MyModule < ApplicationRecord
|
|||
|
||||
before_create :create_blank_protocol
|
||||
before_validation :set_completed_on, if: :state_changed?
|
||||
before_create :assign_default_status_flow
|
||||
before_save :exec_status_consequences, if: :my_module_status_id_changed?
|
||||
|
||||
auto_strip_attributes :name, :description, nullify: false
|
||||
validates :name,
|
||||
|
@ -20,6 +22,9 @@ class MyModule < ApplicationRecord
|
|||
validate :coordinates_uniqueness_check, if: :active?
|
||||
validates :completed_on, presence: true, if: proc { |mm| mm.completed? }
|
||||
|
||||
validate :check_status_conditions, if: :my_module_status_id_changed?
|
||||
validate :check_status_implications, unless: :my_module_status_id_changed?
|
||||
|
||||
belongs_to :created_by,
|
||||
foreign_key: 'created_by_id',
|
||||
class_name: 'User',
|
||||
|
@ -38,6 +43,8 @@ class MyModule < ApplicationRecord
|
|||
optional: true
|
||||
belongs_to :experiment, inverse_of: :my_modules, touch: true
|
||||
belongs_to :my_module_group, inverse_of: :my_modules, optional: true
|
||||
belongs_to :my_module_status, optional: true
|
||||
delegate :my_module_status_flow, to: :my_module_status, allow_nil: true
|
||||
has_many :results, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :my_module_tags, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :tags, through: :my_module_tags
|
||||
|
@ -375,40 +382,6 @@ class MyModule < ApplicationRecord
|
|||
final
|
||||
end
|
||||
|
||||
|
||||
# Generate the samples belonging to this module
|
||||
# in JSON form, suitable for display in handsontable.js
|
||||
def samples_json_hot(order)
|
||||
data = []
|
||||
samples.order(created_at: order).each do |sample|
|
||||
sample_json = []
|
||||
sample_json << sample.name
|
||||
if sample.sample_type.present?
|
||||
sample_json << sample.sample_type.name
|
||||
else
|
||||
sample_json << I18n.t("samples.table.no_type")
|
||||
end
|
||||
if sample.sample_group.present?
|
||||
sample_json << sample.sample_group.name
|
||||
else
|
||||
sample_json << I18n.t("samples.table.no_group")
|
||||
end
|
||||
sample_json << I18n.l(sample.created_at, format: :full)
|
||||
sample_json << sample.user.full_name
|
||||
data << sample_json
|
||||
end
|
||||
|
||||
# Prepare column headers
|
||||
headers = [
|
||||
I18n.t("samples.table.sample_name"),
|
||||
I18n.t("samples.table.sample_type"),
|
||||
I18n.t("samples.table.sample_group"),
|
||||
I18n.t("samples.table.added_on"),
|
||||
I18n.t("samples.table.added_by")
|
||||
]
|
||||
{ data: data, headers: headers }
|
||||
end
|
||||
|
||||
# Generate the repository rows belonging to this module
|
||||
# in JSON form, suitable for display in handsontable.js
|
||||
def repository_json_hot(repository, order)
|
||||
|
@ -552,4 +525,34 @@ class MyModule < ApplicationRecord
|
|||
errors.add(:position, I18n.t('activerecord.errors.models.my_module.attributes.position.not_unique'))
|
||||
end
|
||||
end
|
||||
|
||||
def assign_default_status_flow
|
||||
return unless MyModuleStatusFlow.global.any?
|
||||
|
||||
self.my_module_status = MyModuleStatusFlow.global.first.initial_status
|
||||
end
|
||||
|
||||
def check_status_conditions
|
||||
return if my_module_status.blank?
|
||||
|
||||
my_module_status.my_module_status_conditions.each do |condition|
|
||||
condition.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
def check_status_implications
|
||||
return if my_module_status.blank?
|
||||
|
||||
my_module_status.my_module_status_implications.each do |implication|
|
||||
implication.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
def exec_status_consequences
|
||||
return if my_module_status.blank?
|
||||
|
||||
my_module_status.my_module_status_consequences.each do |consequence|
|
||||
consequence.call(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
59
app/models/my_module_status.rb
Normal file
59
app/models/my_module_status.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatus < ApplicationRecord
|
||||
has_many :my_modules, dependent: :nullify
|
||||
has_many :my_module_status_conditions, dependent: :destroy
|
||||
has_many :my_module_status_consequences, dependent: :destroy
|
||||
has_many :my_module_status_implications, dependent: :destroy
|
||||
belongs_to :my_module_status_flow
|
||||
belongs_to :created_by, class_name: 'User', optional: true
|
||||
belongs_to :last_modified_by, class_name: 'User', optional: true
|
||||
has_one :next_status, class_name: 'MyModuleStatus',
|
||||
foreign_key: 'previous_status_id',
|
||||
inverse_of: :previous_status,
|
||||
dependent: :nullify
|
||||
belongs_to :previous_status, class_name: 'MyModuleStatus', inverse_of: :next_status, optional: true
|
||||
|
||||
validates :name, presence: true, length: { minimum: Constants::NAME_MIN_LENGTH, maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :color, presence: true
|
||||
validates :description, length: { maximum: Constants::TEXT_MAX_LENGTH }
|
||||
validates :next_status, uniqueness: true, if: -> { next_status.present? }
|
||||
validates :previous_status, uniqueness: true, if: -> { previous_status.present? }
|
||||
validate :next_in_same_flow, if: -> { next_status.present? }
|
||||
validate :previous_in_same_flow, if: -> { previous_status.present? }
|
||||
|
||||
def initial_status?
|
||||
my_module_status_flow.initial_status == self
|
||||
end
|
||||
|
||||
def final_status?
|
||||
my_module_status_flow.final_status == self
|
||||
end
|
||||
|
||||
def self.sort_by_position(order = :asc)
|
||||
ordered_statuses, statuses = all.to_a.partition { |i| i.previous_status_id.nil? }
|
||||
|
||||
return [] if ordered_statuses.empty?
|
||||
|
||||
until statuses.empty?
|
||||
next_element, statuses = statuses.partition { |i| ordered_statuses.last.id == i.previous_status_id }
|
||||
if next_element.empty?
|
||||
break
|
||||
else
|
||||
ordered_statuses.concat(next_element)
|
||||
end
|
||||
end
|
||||
ordered_statuses = ordered_statuses.reverse if order == :desc
|
||||
ordered_statuses
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def next_in_same_flow
|
||||
errors.add(:next_status, :different_flow) unless next_status.my_module_status_flow == my_module_status_flow
|
||||
end
|
||||
|
||||
def previous_in_same_flow
|
||||
errors.add(:previous_status, :different_flow) unless previous_status.my_module_status_flow == my_module_status_flow
|
||||
end
|
||||
end
|
5
app/models/my_module_status_condition.rb
Normal file
5
app/models/my_module_status_condition.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusCondition < ApplicationRecord
|
||||
belongs_to :my_module_status
|
||||
end
|
10
app/models/my_module_status_conditions/active.rb
Normal file
10
app/models/my_module_status_conditions/active.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusConditions
|
||||
class Active < MyModuleStatusCondition
|
||||
def call(my_module)
|
||||
my_module.errors.add(:status_conditions, 'MyModule should be active') unless my_module.active?
|
||||
end
|
||||
end
|
||||
end
|
5
app/models/my_module_status_consequence.rb
Normal file
5
app/models/my_module_status_consequence.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusConsequence < ApplicationRecord
|
||||
belongs_to :my_module_status
|
||||
end
|
11
app/models/my_module_status_consequences/change_activity.rb
Normal file
11
app/models/my_module_status_consequences/change_activity.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusConsequences
|
||||
class ChangeActivity < MyModuleStatusConsequence
|
||||
def call(my_module)
|
||||
# Create new activity here
|
||||
puts "State changed to #{my_module_status.name}} for #{my_module.name}"
|
||||
end
|
||||
end
|
||||
end
|
24
app/models/my_module_status_flow.rb
Normal file
24
app/models/my_module_status_flow.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusFlow < ApplicationRecord
|
||||
enum visibility: { global: 0, in_team: 1 }
|
||||
|
||||
has_many :my_module_statuses, dependent: :destroy
|
||||
belongs_to :team, optional: true
|
||||
belongs_to :created_by, class_name: 'User', optional: true
|
||||
belongs_to :last_modified_by, class_name: 'User', optional: true
|
||||
|
||||
validates :visibility, presence: true
|
||||
validates :team, presence: true, if: :in_team?
|
||||
validates :name, uniqueness: { scope: :team_id, case_sensitive: false }, if: :in_team?
|
||||
validates :name, presence: true, length: { minimum: Constants::NAME_MIN_LENGTH, maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :description, length: { maximum: Constants::TEXT_MAX_LENGTH }
|
||||
|
||||
def initial_status
|
||||
my_module_statuses.find_by(previous_status: nil)
|
||||
end
|
||||
|
||||
def final_status
|
||||
my_module_statuses.left_outer_joins(:next_status).find_by('next_statuses_my_module_statuses.id': nil)
|
||||
end
|
||||
end
|
5
app/models/my_module_status_implication.rb
Normal file
5
app/models/my_module_status_implication.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModuleStatusImplication < ApplicationRecord
|
||||
belongs_to :my_module_status
|
||||
end
|
11
app/models/my_module_status_implications/read_only.rb
Normal file
11
app/models/my_module_status_implications/read_only.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Just an example, to be replaced with an actual implementation
|
||||
module MyModuleStatusImplications
|
||||
class ReadOnly < MyModuleStatusImplication
|
||||
def call(my_module)
|
||||
my_module.errors.add(:status_implication, 'Is read only')
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25,7 +25,14 @@ Canaid::Permissions.register_for(Experiment) do
|
|||
# module: create, copy, reposition, create/update/delete connection,
|
||||
# assign/reassign/unassign tags
|
||||
can :manage_experiment do |user, experiment|
|
||||
user.is_user_or_higher_of_project?(experiment.project)
|
||||
user.is_user_or_higher_of_project?(experiment.project) &&
|
||||
MyModule.joins(:experiment).where(experiment: experiment).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
|
||||
|
||||
# experiment: archive
|
||||
|
@ -53,79 +60,6 @@ Canaid::Permissions.register_for(Experiment) do
|
|||
end
|
||||
end
|
||||
|
||||
Canaid::Permissions.register_for(MyModule) do
|
||||
# Module, its experiment and its project must be active for all the specified
|
||||
# permissions
|
||||
%i(manage_module
|
||||
manage_users_in_module
|
||||
assign_repository_rows_to_module
|
||||
assign_sample_to_module
|
||||
complete_module
|
||||
create_comments_in_module
|
||||
create_my_module_repository_snapshot
|
||||
manage_my_module_repository_snapshots)
|
||||
.each do |perm|
|
||||
can perm do |_, my_module|
|
||||
my_module.active? &&
|
||||
my_module.experiment.active? &&
|
||||
my_module.experiment.project.active?
|
||||
end
|
||||
end
|
||||
|
||||
# module: update, archive, move
|
||||
# result: create, update
|
||||
can :manage_module do |user, my_module|
|
||||
can_manage_experiment?(user, my_module.experiment)
|
||||
end
|
||||
|
||||
# NOTE: Must not be dependent on canaid parmision for which we check if it's
|
||||
# active
|
||||
# module: restore
|
||||
can :restore_module do |user, my_module|
|
||||
user.is_user_or_higher_of_project?(my_module.experiment.project) &&
|
||||
my_module.archived?
|
||||
end
|
||||
|
||||
# module: assign/reassign/unassign users
|
||||
can :manage_users_in_module do |user, my_module|
|
||||
user.is_owner_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: assign/unassign repository record
|
||||
# NOTE: Use 'module_page? &&' before calling this permission!
|
||||
can :assign_repository_rows_to_module do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: assign/unassign sample
|
||||
# NOTE: Use 'module_page? &&' before calling this permission!
|
||||
can :assign_sample_to_module do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: complete/uncomplete
|
||||
can :complete_module do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: create comment
|
||||
# result: create comment
|
||||
# step: create comment
|
||||
can :create_comments_in_module do |user, my_module|
|
||||
can_create_comments_in_project?(user, my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: create a snapshot of repository item
|
||||
can :create_my_module_repository_snapshot do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: make a repository snapshot selected
|
||||
can :manage_my_module_repository_snapshots do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
end
|
||||
|
||||
Canaid::Permissions.register_for(Protocol) do
|
||||
# Protocol needs to be in a module for all Protocol permissions below
|
||||
# experiment level
|
||||
|
@ -167,7 +101,7 @@ Canaid::Permissions.register_for(Protocol) do
|
|||
|
||||
# step: complete/uncomplete
|
||||
can :complete_or_checkbox_step do |user, protocol|
|
||||
can_complete_module?(user, protocol.my_module)
|
||||
can_change_my_module_flow_status?(user, protocol.my_module)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
72
app/permissions/my_module.rb
Normal file
72
app/permissions/my_module.rb
Normal file
|
@ -0,0 +1,72 @@
|
|||
Canaid::Permissions.register_for(MyModule) do
|
||||
# Module, its experiment and its project must be active for all the specified
|
||||
# permissions
|
||||
%i(manage_module
|
||||
manage_users_in_module
|
||||
assign_repository_rows_to_module
|
||||
assign_sample_to_module
|
||||
change_my_module_flow_status
|
||||
create_comments_in_module
|
||||
create_my_module_repository_snapshot
|
||||
manage_my_module_repository_snapshots)
|
||||
.each do |perm|
|
||||
can perm do |_, my_module|
|
||||
my_module.active? &&
|
||||
my_module.experiment.active? &&
|
||||
my_module.experiment.project.active?
|
||||
end
|
||||
end
|
||||
|
||||
# module: update, archive, move
|
||||
# result: create, update
|
||||
can :manage_module do |user, my_module|
|
||||
can_manage_experiment?(user, my_module.experiment)
|
||||
end
|
||||
|
||||
# NOTE: Must not be dependent on canaid parmision for which we check if it's
|
||||
# active
|
||||
# module: restore
|
||||
can :restore_module do |user, my_module|
|
||||
user.is_user_or_higher_of_project?(my_module.experiment.project) &&
|
||||
my_module.archived?
|
||||
end
|
||||
|
||||
# module: assign/reassign/unassign users
|
||||
can :manage_users_in_module do |user, my_module|
|
||||
user.is_owner_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: assign/unassign repository record
|
||||
# NOTE: Use 'module_page? &&' before calling this permission!
|
||||
can :assign_repository_rows_to_module do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: assign/unassign sample
|
||||
# NOTE: Use 'module_page? &&' before calling this permission!
|
||||
can :assign_sample_to_module do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: change_flow_status
|
||||
can :change_my_module_flow_status do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: create comment
|
||||
# result: create comment
|
||||
# step: create comment
|
||||
can :create_comments_in_module do |user, my_module|
|
||||
can_create_comments_in_project?(user, my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: create a snapshot of repository item
|
||||
can :create_my_module_repository_snapshot do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
|
||||
# module: make a repository snapshot selected
|
||||
can :manage_my_module_repository_snapshots do |user, my_module|
|
||||
user.is_technician_or_higher_of_project?(my_module.experiment.project)
|
||||
end
|
||||
end
|
|
@ -37,7 +37,14 @@ Canaid::Permissions.register_for(Project) do
|
|||
|
||||
# project: update/delete, assign/reassign/unassign users
|
||||
can :manage_project do |user, project|
|
||||
user.is_owner_of_project?(project)
|
||||
user.is_owner_of_project?(project) &&
|
||||
MyModule.joins(experiment: :project).where(experiments: { project: project }).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
|
||||
|
||||
# project: archive
|
||||
|
|
|
@ -13,17 +13,6 @@ module Reports::Docx::DrawMyModule
|
|||
@docx.p do
|
||||
text I18n.t('projects.reports.elements.module.user_time',
|
||||
timestamp: I18n.l(my_module.created_at, format: :full)), color: color[:gray]
|
||||
text ' | '
|
||||
if my_module.due_date.present?
|
||||
text I18n.t('projects.reports.elements.module.due_date',
|
||||
due_date: I18n.l(my_module.due_date, format: :full)), color: color[:gray]
|
||||
else
|
||||
text I18n.t('projects.reports.elements.module.no_due_date'), color: color[:gray]
|
||||
end
|
||||
if my_module.completed?
|
||||
text " #{I18n.t('my_modules.states.completed')}", bold: true, color: color[:green]
|
||||
text " #{I18n.l(my_module.completed_on, format: :full)}", color: color[:gray]
|
||||
end
|
||||
if my_module.archived?
|
||||
text ' | '
|
||||
text I18n.t('search.index.archived'), color: color[:gray]
|
||||
|
@ -33,15 +22,34 @@ module Reports::Docx::DrawMyModule
|
|||
scinote_url + Rails.application.routes.url_helpers.protocols_my_module_path(my_module),
|
||||
link_style
|
||||
end
|
||||
if my_module.description.present?
|
||||
html = custom_auto_link(my_module.description, team: @report_team)
|
||||
html_to_word_converter(html)
|
||||
else
|
||||
@docx.p I18n.t 'projects.reports.elements.module.no_description'
|
||||
|
||||
@docx.p do
|
||||
if my_module.started_on.present?
|
||||
text I18n.t('projects.reports.elements.module.started_on',
|
||||
started_on: I18n.l(my_module.started_on, format: :full))
|
||||
else
|
||||
text I18n.t('projects.reports.elements.module.no_due_date')
|
||||
end
|
||||
end
|
||||
|
||||
@docx.p do
|
||||
text I18n.t 'projects.reports.elements.module.tags_header'
|
||||
if my_module.due_date.present?
|
||||
text I18n.t('projects.reports.elements.module.due_date',
|
||||
due_date: I18n.l(my_module.due_date, format: :full))
|
||||
else
|
||||
text I18n.t('projects.reports.elements.module.no_due_date')
|
||||
end
|
||||
end
|
||||
|
||||
status = my_module.my_module_status
|
||||
@docx.p do
|
||||
text I18n.t('projects.reports.elements.module.status')
|
||||
text ' '
|
||||
text "[#{status.name}]", color: status.color.delete('#')
|
||||
end
|
||||
|
||||
@docx.p do
|
||||
text I18n.t('projects.reports.elements.module.tags_header')
|
||||
if tags.any?
|
||||
my_module.tags.each do |tag|
|
||||
text ' '
|
||||
|
@ -49,10 +57,17 @@ module Reports::Docx::DrawMyModule
|
|||
end
|
||||
else
|
||||
text ' '
|
||||
text I18n.t 'projects.reports.elements.module.no_tags'
|
||||
text I18n.t('projects.reports.elements.module.no_tags')
|
||||
end
|
||||
end
|
||||
|
||||
if my_module.description.present?
|
||||
html = custom_auto_link(my_module.description, team: @report_team)
|
||||
html_to_word_converter(html)
|
||||
else
|
||||
@docx.p I18n.t('projects.reports.elements.module.no_description')
|
||||
end
|
||||
|
||||
@docx.p
|
||||
subject['children'].each do |child|
|
||||
public_send("draw_#{child['type_of']}", child, my_module)
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
<% else %>
|
||||
<%= render partial: "my_modules/card_due_date_label.html.erb", locals: { my_module: my_module, format: :full_date } %>
|
||||
<% end %>
|
||||
<div class="status-label" style="--state-color: #2DBE61">
|
||||
Completed
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer panel-footer-scinote buttons-container">
|
||||
|
|
|
@ -20,9 +20,18 @@
|
|||
</div>
|
||||
<div class="select-block">
|
||||
<label><%= t("dashboard.current_tasks.filter.display") %></label>
|
||||
<select class="view-filter">
|
||||
<option value="uncompleted" ><%= t("dashboard.current_tasks.filter.uncompleted_tasks") %></option>
|
||||
<option value="completed" ><%= t("dashboard.current_tasks.filter.completed_tasks") %></option>
|
||||
<select class="view-filter"
|
||||
data-combine-tags="true"
|
||||
data-placeholder="<%= t("dashboard.current_tasks.filter.statuses.placeholder") %>"
|
||||
data-select-multiple-all-selected="<%= t("dashboard.current_tasks.filter.statuses.all_selected") %>"
|
||||
data-select-multiple-name="<%= t("dashboard.current_tasks.filter.statuses.selected") %>"
|
||||
multiple
|
||||
>
|
||||
<% MyModuleStatusFlow.find_each do |status_flow| %>
|
||||
<% status_flow.my_module_statuses.each do |status| %>
|
||||
<option value="<%= status.id %>" selected><%= status.name %></option>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-block">
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
<div class="modal fade"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
data-url="<%= complete_my_module_my_module_path(@my_module) %>"
|
||||
id="completed-task-modal">
|
||||
<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">×</span></button>
|
||||
<h4 class="modal-title"><%=t 'my_modules.buttons.complete' %></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><%=t 'my_modules.complete_modal.description' %></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="modal"><%=t 'my_modules.complete_modal.leave_uncompleted' %></button>
|
||||
<button type="button"
|
||||
class="btn btn-success"
|
||||
data-action="complete"><%=t 'my_modules.buttons.complete' %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
<% if my_module.completed? %>
|
||||
<strong class="alert-green">
|
||||
<%= t('my_modules.states.completed_on', date: l(my_module.completed_on, format: :full_date)) %>
|
||||
</strong>
|
||||
<% else %>
|
||||
<strong><%= t('my_modules.states.in_progress') %></strong>
|
||||
<% end %>
|
|
@ -20,17 +20,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-block">
|
||||
<div class="flex-block-label">
|
||||
<span class="fas block-icon fa-tachometer-alt"></span>
|
||||
<%= t('my_modules.states.state_label') %>
|
||||
</div>
|
||||
<span class="task-state-label">
|
||||
<%= render partial: "module_state_label.html.erb",
|
||||
locals: { my_module: @my_module } %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-block">
|
||||
<div class="flex-block-label">
|
||||
<span class="fas block-icon fa-users"></span>
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<button class="btn btn-primary">
|
||||
<i class="fas fa-check"></i>
|
||||
<%= t("my_modules.buttons.complete") %>
|
||||
</button>
|
|
@ -1,4 +0,0 @@
|
|||
<button class="btn btn-secondary ">
|
||||
<i class="fas fa-undo-alt"></i>
|
||||
<%= t("my_modules.buttons.uncomplete") %>
|
||||
</button>
|
|
@ -1,16 +0,0 @@
|
|||
<div class="pull-right my_module-state-buttons">
|
||||
<% if can_complete_module?(@my_module) %>
|
||||
<div class="btn-group">
|
||||
<% if !@my_module.completed? %>
|
||||
<div data-action="complete-task" data-link-url="<%= toggle_task_state_my_module_path(@my_module) %>">
|
||||
<%= render 'my_modules/state_button_complete.html.erb' %>
|
||||
</div>
|
||||
<% else @my_module.completed? %>
|
||||
<div data-action="uncomplete-task" data-link-url="<%= toggle_task_state_my_module_path(@my_module) %>">
|
||||
<%= render 'my_modules/state_button_uncomplete.html.erb' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<span data-hook="my_module-state-buttons"></span>
|
||||
</div>
|
14
app/views/my_modules/modals/_status_flow_modal.html.erb
Normal file
14
app/views/my_modules/modals/_status_flow_modal.html.erb
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div class="modal" id="statusFlowModal" tabindex="-1" role="dialog" aria-labelledby="manage-module-users-modal-label" data-status-flow-url="<%= my_module_status_flow_path(@my_module) %>">
|
||||
<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">×</span></button>
|
||||
<h2 class="modal-title"><%= t('my_modules.modals.status_flow_modal.title', status_flow: @my_module.my_module_status_flow.name) %></h2>
|
||||
</div>
|
||||
<div class="modal-body"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t('my_modules.modals.status_flow_modal.done') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
22
app/views/my_modules/modals/_status_flow_modal_body.html.erb
Normal file
22
app/views/my_modules/modals/_status_flow_modal_body.html.erb
Normal file
|
@ -0,0 +1,22 @@
|
|||
<div class="status-flow">
|
||||
<% my_module_statuses.each_with_index do |status, i| %>
|
||||
|
||||
<% unless i.zero? %>
|
||||
<div class="connector"></div>
|
||||
<% end %>
|
||||
|
||||
<div class="status-container">
|
||||
<div class="current-status">
|
||||
<% if status.id == @my_module.my_module_status_id %>
|
||||
<%= t('my_modules.modals.status_flow_modal.current_status') %><i class="fas fa-long-arrow-alt-right"></i>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="status-block" style="background: <%= status[:color] %>">
|
||||
<%= status[:name] %>
|
||||
</div>
|
||||
<div class="status-comment"><%= status[:status_comment] %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<div class="content-pane my-modules-protocols-index" data-task-id="<%= @my_module.id %>">
|
||||
<!-- Details -->
|
||||
<div class="task-section">
|
||||
<div class="task-section task-information">
|
||||
<div class="task-section-header">
|
||||
<span id="taskDetailsLabel" class="task-section-title">
|
||||
<h2>
|
||||
|
@ -20,12 +20,13 @@
|
|||
</div>
|
||||
</span>
|
||||
|
||||
<div class="actions-block">
|
||||
<%= render partial: "my_modules/state_buttons.html.erb" %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="task-details">
|
||||
<%= render partial: "my_module_details" %>
|
||||
<%= render partial: 'my_module_details' %>
|
||||
</div>
|
||||
<div class="task-flows">
|
||||
<%= render partial: 'my_modules/status_flow/task_flow_button', locals: { my_module: @my_module } if @my_module.my_module_status_flow && can_change_my_module_flow_status?(@my_module) %>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Notes -->
|
||||
|
@ -118,13 +119,16 @@
|
|||
<!-- Import protocol elements -->
|
||||
<%= render partial: "protocols/import_export/import_elements.html.erb" %>
|
||||
|
||||
<!-- Complete task modal -->
|
||||
<%= render partial: 'my_modules/complete_task_modal.html.erb' %>
|
||||
<!-- Status flow modal -->
|
||||
<% if @my_module.my_module_status_flow %>
|
||||
<%= render partial: 'my_modules/modals/status_flow_modal.html.erb' %>
|
||||
<% end %>
|
||||
|
||||
<!-- Create new office file modal -->
|
||||
<%= render partial: 'assets/wopi/create_wopi_file_modal.html.erb' %>
|
||||
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag("my_modules/protocols") %>
|
||||
<%= javascript_include_tag("my_modules/status_flow") %>
|
||||
<%= javascript_pack_tag 'emoji_button' %>
|
||||
<%= javascript_include_tag("my_modules/repositories") %>
|
||||
|
|
31
app/views/my_modules/status_flow/_task_flow_button.html.erb
Normal file
31
app/views/my_modules/status_flow/_task_flow_button.html.erb
Normal file
|
@ -0,0 +1,31 @@
|
|||
<% status = my_module.my_module_status %>
|
||||
<div class="status-label"><%= t('my_module_statuses.dropdown.status_label') %></div>
|
||||
<div class="dropdown sci-dropdown status-flow-dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
style="<%= "background-color: #{status.color}" %>;">
|
||||
<span><%= status.name %></span>
|
||||
<span class="caret pull-right"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownTaskFlow" id="dropdownTaskFlowList" data-link-url="<%= update_state_my_module_url(my_module) %>">
|
||||
<% unless status.initial_status? %>
|
||||
<% previous_s = status.previous_status %>
|
||||
<li data-state-id="<%= previous_s.id %>">
|
||||
<span><%= t 'my_module_statuses.dropdown.return_label' %></span> <span style="<%= "background-color: #{previous_s.color}" %>"><%= previous_s.name %></span>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% unless status.final_status? %>
|
||||
<% next_s = status.next_status %>
|
||||
<li data-state-id="<%= next_s.id %>">
|
||||
<span><%= t('my_module_statuses.dropdown.move_label') %></span> <span style="<%= "background-color: #{next_s.color}" %>"><%= next_s.name %></span>
|
||||
</li>
|
||||
<% end %>
|
||||
<li id="viewTaskFlow">
|
||||
<%= t('my_module_statuses.dropdown.view_flow_label') %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -6,7 +6,7 @@
|
|||
<div class="report-element-header">
|
||||
<div class="row">
|
||||
<div class="pull-left user-time">
|
||||
<%=t "projects.reports.elements.module.user_time", timestamp: l(timestamp, format: :full) %>
|
||||
<%= t("projects.reports.elements.module.user_time", timestamp: l(timestamp, format: :full)) %>
|
||||
</div>
|
||||
<div class="pull-right controls">
|
||||
<%= render partial: "reports/elements/element_controls.html.erb", locals: { show_sort: true } %>
|
||||
|
@ -24,6 +24,7 @@
|
|||
<% end %>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="pull-right module-start-date">
|
||||
<% if my_module.started_on.present? %>
|
||||
<%= t('projects.reports.elements.module.started_on', started_on: l(my_module.started_on, format: :full)) %>
|
||||
|
@ -55,9 +56,28 @@
|
|||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<p class="module-start-date">
|
||||
<% if my_module.started_on.present? %>
|
||||
<%= t('projects.reports.elements.module.started_on', started_on: l(my_module.started_on, format: :full)) %>
|
||||
<% else %>
|
||||
<em><%= t("projects.reports.elements.module.no_start_date") %></em>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="module-due-date">
|
||||
<% if my_module.due_date.present? %>
|
||||
<%= t("projects.reports.elements.module.due_date", due_date: l(my_module.due_date, format: :full)) %>
|
||||
<% else %>
|
||||
<em><%= t("projects.reports.elements.module.no_due_date") %></em>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="module-status">
|
||||
<% status = my_module.my_module_status %>
|
||||
<%= t("projects.reports.elements.module.status") %>
|
||||
<span class="status-block" style="background: <%= status.color %>"><%= status.name %></span>
|
||||
</p>
|
||||
<div class="row module-tags">
|
||||
<div class="pull-left">
|
||||
<%=t "projects.reports.elements.module.tags_header" %>
|
||||
<%= t("projects.reports.elements.module.tags_header") %>
|
||||
</div>
|
||||
<% if my_module.tags.any? %>
|
||||
<% my_module.tags.each do |tag| %>
|
||||
|
@ -67,10 +87,21 @@
|
|||
<% end %>
|
||||
<% else %>
|
||||
<div class="pull-left module-no-tag">
|
||||
<em><%=t "projects.reports.elements.module.no_tags" %></em>
|
||||
<em><%= t("projects.reports.elements.module.no_tags") %></em>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<% if my_module.description.present? %>
|
||||
<%= custom_auto_link(my_module.prepare_for_report(:description, for_export_all),
|
||||
team: current_team,
|
||||
base64_encoded_imgs: for_export_all) %>
|
||||
<% else %>
|
||||
<em><%= t("projects.reports.elements.module.no_description") %></em>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-element-children">
|
||||
<%= children if (defined? children and children.present?) %>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<% if my_module.blank? and @my_module.present? then my_module = @my_module end %>
|
||||
<% if order.blank? and @order.present? then order = @order end %>
|
||||
<% timestamp = Time.current + 1.year - 1.days %>
|
||||
<% samples_json = my_module.samples_json_hot(order) %>
|
||||
<div class="report-element report-module-samples-element" data-sort-hot="3" data-ts="<%= timestamp.to_i %>" data-type="my_module_samples" data-id='{ "my_module_id": <%= my_module.id %> }' data-scroll-id="<%= my_module.id %>" data-order="<%= order == :asc ? "asc" : "desc" %>" data-name="<%=t "projects.reports.elements.module_samples.sidebar_name" %>" data-icon-class="fas fa-tint">
|
||||
<div class="report-element-header">
|
||||
<div class="row">
|
||||
|
|
|
@ -34,6 +34,7 @@ Rails.application.config.assets.precompile +=
|
|||
Rails.application.config.assets.precompile += %w(my_modules/activities.js)
|
||||
Rails.application.config.assets.precompile += %w(my_modules/protocols.js)
|
||||
Rails.application.config.assets.precompile += %w(my_modules/repositories.js)
|
||||
Rails.application.config.assets.precompile += %w(my_modules/status_flow.js)
|
||||
Rails.application.config.assets.precompile +=
|
||||
%w(my_modules/protocols/protocol_status_bar.js)
|
||||
Rails.application.config.assets.precompile += %w(my_modules/results.js)
|
||||
|
|
|
@ -14,7 +14,11 @@ en:
|
|||
due_date: "Due date"
|
||||
atoz: "From A to Z"
|
||||
ztoa: "From Z to A"
|
||||
display: "Display"
|
||||
display: "Display statuses"
|
||||
statuses:
|
||||
placeholder: "Select statuses"
|
||||
all_selected: "All selected"
|
||||
selected: "selected"
|
||||
uncompleted_tasks: "Tasks in progress"
|
||||
completed_tasks: "Tasks completed"
|
||||
project: "Project"
|
||||
|
|
|
@ -110,6 +110,12 @@ en:
|
|||
attributes:
|
||||
position:
|
||||
not_unique: "X and Y position has already been taken by another task in the experiment."
|
||||
my_module_status:
|
||||
attributes:
|
||||
next_status:
|
||||
different_flow: "Should belong to the same flow"
|
||||
previous_status:
|
||||
different_flow: "Should belong to the same flow"
|
||||
asset:
|
||||
attributes:
|
||||
file:
|
||||
|
@ -527,6 +533,7 @@ en:
|
|||
module:
|
||||
user_time: "Task created on %{timestamp}."
|
||||
started_on: "Start date: %{started_on}"
|
||||
status: "Status:"
|
||||
no_start_date: "No start date"
|
||||
due_date: "Due date: %{due_date}"
|
||||
no_due_date: "No due date"
|
||||
|
@ -658,12 +665,6 @@ en:
|
|||
import: "Import protocol"
|
||||
export: "Export protocol"
|
||||
save_to_repo: "Save to repository"
|
||||
buttons:
|
||||
complete: "Complete task"
|
||||
uncomplete: "Uncomplete task"
|
||||
complete_modal:
|
||||
description: 'You have completed all steps in the task. Would you like to mark entire task as completed?'
|
||||
leave_uncompleted: 'Leave task in progress'
|
||||
description:
|
||||
title: "Edit task %{module} description"
|
||||
label: "Description"
|
||||
|
@ -838,6 +839,10 @@ en:
|
|||
assign_and_unassign_from_task_and_downstream_html: "Successfully assigned <strong>%{assigned_items}</strong> and unassigned <strong>%{unassigned_items}</strong> item(s) from the task and downstream tasks."
|
||||
update_error: "There was an error in updating your item(s)."
|
||||
modals:
|
||||
status_flow_modal:
|
||||
title: "Task status flow: %{status_flow}"
|
||||
current_status: "Current status"
|
||||
done: "Done"
|
||||
update_repository_record:
|
||||
title: "Update %{repository_name} items to %{my_module_name} task"
|
||||
message: "Do you want to update %{size} items only from this task, or update them from this task & downstream tasks in the workflow also?"
|
||||
|
@ -2238,8 +2243,6 @@ en:
|
|||
assign_user_to_team: "<i>%{assigned_user}</i> was added as %{role} to team <strong>%{team}</strong> by <i>%{assigned_by_user}</i>."
|
||||
unassign_user_from_team: "<i>%{unassigned_user}</i> was removed from team <strong>%{team}</strong> by <i>%{unassigned_by_user}</i>."
|
||||
task_completed: "%{user} completed task %{module}. %{date} | Project: %{project} | Experiment: %{experiment}"
|
||||
start_work_on_next_task: "<i>%{user}</i> has completed the task <strong>%{module}</strong>. You can now start working on the next task in the workflow."
|
||||
start_work_on_next_task_message: "Project: %{project} | Experiment: %{experiment} | Task: %{my_module}"
|
||||
|
||||
assets:
|
||||
head_title:
|
||||
|
|
10
config/locales/my_module_statuses/en.yml
Normal file
10
config/locales/my_module_statuses/en.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
en:
|
||||
my_module_statuses:
|
||||
dropdown:
|
||||
status_label: Status
|
||||
move_label: Move to ->
|
||||
return_label: Return to ->
|
||||
view_flow_label: View task flow
|
||||
update_status:
|
||||
error:
|
||||
no_permission: You dont have permission to change the status
|
|
@ -18,28 +18,6 @@ Rails.application.routes.draw do
|
|||
|
||||
root 'dashboards#show'
|
||||
|
||||
# # Client APP endpoints
|
||||
# get '/settings', to: 'client_api/settings#index'
|
||||
# get '/settings/*all', to: 'client_api/settings#index'
|
||||
#
|
||||
# namespace :client_api, defaults: { format: 'json' } do
|
||||
# post '/premissions', to: 'permissions#status'
|
||||
# %i(activities teams notifications users configurations).each do |path|
|
||||
# draw path
|
||||
# end
|
||||
# end
|
||||
|
||||
# Save sample table state
|
||||
# post '/state_save/:team_id/:user_id',
|
||||
# to: 'user_samples#save_samples_table_status',
|
||||
# as: 'save_samples_table_status',
|
||||
# defaults: { format: 'json' }
|
||||
#
|
||||
# post '/state_load/:team_id/:user_id',
|
||||
# to: 'user_samples#load_samples_table_status',
|
||||
# as: 'load_samples_table_status',
|
||||
# defaults: { format: 'json' }
|
||||
|
||||
resources :activities, only: [:index]
|
||||
|
||||
get 'forbidden', to: 'application#forbidden', as: 'forbidden'
|
||||
|
@ -187,18 +165,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
end
|
||||
# resources :samples, only: [:new, :create]
|
||||
# resources :sample_types, except: [:show, :new] do
|
||||
# get 'sample_type_element', to: 'sample_types#sample_type_element'
|
||||
# get 'destroy_confirmation', to: 'sample_types#destroy_confirmation'
|
||||
# end
|
||||
# resources :sample_groups, except: [:show, :new] do
|
||||
# get 'sample_group_element', to: 'sample_groups#sample_group_element'
|
||||
# get 'destroy_confirmation', to: 'sample_groups#destroy_confirmation'
|
||||
# end
|
||||
# resources :custom_fields, only: [:create, :edit, :update, :destroy] do
|
||||
# get 'destroy_html'
|
||||
# end
|
||||
|
||||
member do
|
||||
post 'parse_sheet', defaults: { format: 'json' }
|
||||
post 'export_repository', to: 'repositories#export_repository'
|
||||
|
@ -374,6 +341,9 @@ Rails.application.routes.draw do
|
|||
get :index_old
|
||||
end
|
||||
end
|
||||
|
||||
resource :status_flow, controller: :my_module_status_flow, only: :show
|
||||
|
||||
resources :my_module_comments,
|
||||
path: '/comments',
|
||||
only: [:index, :create, :edit, :update, :destroy]
|
||||
|
@ -426,11 +396,10 @@ Rails.application.routes.draw do
|
|||
patch 'protocol_description',
|
||||
to: 'my_modules#update_protocol_description',
|
||||
as: 'update_protocol_description'
|
||||
patch 'state', to: 'my_modules#update_state', as: 'update_state'
|
||||
get 'protocols' # Protocols view for single module
|
||||
get 'results' # Results view for single module
|
||||
get 'archive' # Archive view for single module
|
||||
get 'complete_my_module'
|
||||
post 'toggle_task_state'
|
||||
end
|
||||
|
||||
# Those routes are defined outside of member block
|
||||
|
|
53
db/migrate/20200713142353_create_task_flows_models.rb
Normal file
53
db/migrate/20200713142353_create_task_flows_models.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateTaskFlowsModels < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
change_table :my_modules do |t|
|
||||
t.references :my_module_status
|
||||
end
|
||||
|
||||
create_table :my_module_status_flows do |t|
|
||||
t.string :name, null: false
|
||||
t.string :description
|
||||
t.integer :visibility, index: true, default: 0
|
||||
t.references :team
|
||||
t.references :created_by, index: false, foreign_key: { to_table: :users }
|
||||
t.references :last_modified_by, index: false, foreign_key: { to_table: :users }
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :my_module_statuses do |t|
|
||||
t.string :name, null: false
|
||||
t.string :description
|
||||
t.string :color, null: false
|
||||
t.references :my_module_status_flow, index: true
|
||||
t.references :previous_status, index: { unique: true }, foreign_key: { to_table: :my_module_statuses }
|
||||
t.references :created_by, index: false, foreign_key: { to_table: :users }
|
||||
t.references :last_modified_by, index: false, foreign_key: { to_table: :users }
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :my_module_status_consequences do |t|
|
||||
t.references :my_module_status
|
||||
t.string :type
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :my_module_status_conditions do |t|
|
||||
t.references :my_module_status
|
||||
t.string :type
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :my_module_status_implications do |t|
|
||||
t.references :my_module_status
|
||||
t.string :type
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
346
db/structure.sql
346
db/structure.sql
|
@ -623,6 +623,175 @@ CREATE SEQUENCE public.my_module_repository_rows_id_seq
|
|||
ALTER SEQUENCE public.my_module_repository_rows_id_seq OWNED BY public.my_module_repository_rows.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_conditions; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.my_module_status_conditions (
|
||||
id bigint NOT NULL,
|
||||
my_module_status_id bigint,
|
||||
type character varying,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_conditions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.my_module_status_conditions_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_conditions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.my_module_status_conditions_id_seq OWNED BY public.my_module_status_conditions.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_consequences; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.my_module_status_consequences (
|
||||
id bigint NOT NULL,
|
||||
my_module_status_id bigint,
|
||||
type character varying,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_consequences_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.my_module_status_consequences_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_consequences_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.my_module_status_consequences_id_seq OWNED BY public.my_module_status_consequences.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_flows; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.my_module_status_flows (
|
||||
id bigint NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
description character varying,
|
||||
visibility integer DEFAULT 0,
|
||||
team_id bigint,
|
||||
created_by_id bigint,
|
||||
last_modified_by_id bigint,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_flows_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.my_module_status_flows_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_flows_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.my_module_status_flows_id_seq OWNED BY public.my_module_status_flows.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_implications; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.my_module_status_implications (
|
||||
id bigint NOT NULL,
|
||||
my_module_status_id bigint,
|
||||
type character varying,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_implications_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.my_module_status_implications_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_implications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.my_module_status_implications_id_seq OWNED BY public.my_module_status_implications.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.my_module_statuses (
|
||||
id bigint NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
description character varying,
|
||||
color character varying NOT NULL,
|
||||
my_module_status_flow_id bigint,
|
||||
previous_status_id bigint,
|
||||
created_by_id bigint,
|
||||
last_modified_by_id bigint,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.my_module_statuses_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.my_module_statuses_id_seq OWNED BY public.my_module_statuses.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_tags; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -681,7 +850,8 @@ CREATE TABLE public.my_modules (
|
|||
experiment_id bigint DEFAULT 0 NOT NULL,
|
||||
state smallint DEFAULT 0,
|
||||
completed_on timestamp without time zone,
|
||||
started_on timestamp without time zone
|
||||
started_on timestamp without time zone,
|
||||
my_module_status_id bigint
|
||||
);
|
||||
|
||||
|
||||
|
@ -2951,6 +3121,41 @@ ALTER TABLE ONLY public.my_module_groups ALTER COLUMN id SET DEFAULT nextval('pu
|
|||
ALTER TABLE ONLY public.my_module_repository_rows ALTER COLUMN id SET DEFAULT nextval('public.my_module_repository_rows_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_conditions id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_conditions ALTER COLUMN id SET DEFAULT nextval('public.my_module_status_conditions_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_consequences id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_consequences ALTER COLUMN id SET DEFAULT nextval('public.my_module_status_consequences_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_flows id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_flows ALTER COLUMN id SET DEFAULT nextval('public.my_module_status_flows_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_implications id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_implications ALTER COLUMN id SET DEFAULT nextval('public.my_module_status_implications_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_statuses ALTER COLUMN id SET DEFAULT nextval('public.my_module_statuses_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_tags id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -3512,6 +3717,46 @@ ALTER TABLE ONLY public.my_module_repository_rows
|
|||
ADD CONSTRAINT my_module_repository_rows_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_conditions my_module_status_conditions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_conditions
|
||||
ADD CONSTRAINT my_module_status_conditions_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_consequences my_module_status_consequences_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_consequences
|
||||
ADD CONSTRAINT my_module_status_consequences_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_flows my_module_status_flows_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_flows
|
||||
ADD CONSTRAINT my_module_status_flows_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_implications my_module_status_implications_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_implications
|
||||
ADD CONSTRAINT my_module_status_implications_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses my_module_statuses_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_statuses
|
||||
ADD CONSTRAINT my_module_statuses_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_tags my_module_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -4367,6 +4612,55 @@ CREATE INDEX index_my_module_ids_repository_row_ids ON public.my_module_reposito
|
|||
CREATE INDEX index_my_module_repository_rows_on_repository_row_id ON public.my_module_repository_rows USING btree (repository_row_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_status_conditions_on_my_module_status_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_my_module_status_conditions_on_my_module_status_id ON public.my_module_status_conditions USING btree (my_module_status_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_status_consequences_on_my_module_status_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_my_module_status_consequences_on_my_module_status_id ON public.my_module_status_consequences USING btree (my_module_status_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_status_flows_on_team_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_my_module_status_flows_on_team_id ON public.my_module_status_flows USING btree (team_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_status_flows_on_visibility; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_my_module_status_flows_on_visibility ON public.my_module_status_flows USING btree (visibility);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_status_implications_on_my_module_status_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_my_module_status_implications_on_my_module_status_id ON public.my_module_status_implications USING btree (my_module_status_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_statuses_on_my_module_status_flow_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_my_module_statuses_on_my_module_status_flow_id ON public.my_module_statuses USING btree (my_module_status_flow_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_statuses_on_previous_status_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX index_my_module_statuses_on_previous_status_id ON public.my_module_statuses USING btree (previous_status_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_module_tags_on_created_by_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -4430,6 +4724,13 @@ CREATE INDEX index_my_modules_on_last_modified_by_id ON public.my_modules USING
|
|||
CREATE INDEX index_my_modules_on_my_module_group_id ON public.my_modules USING btree (my_module_group_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_modules_on_my_module_status_id; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX index_my_modules_on_my_module_status_id ON public.my_modules USING btree (my_module_status_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_my_modules_on_name; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -6048,6 +6349,14 @@ ALTER TABLE ONLY public.oauth_access_grants
|
|||
ADD CONSTRAINT fk_rails_330c32d8d9 FOREIGN KEY (resource_owner_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses fk_rails_357ee33309; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_statuses
|
||||
ADD CONSTRAINT fk_rails_357ee33309 FOREIGN KEY (last_modified_by_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: experiments fk_rails_35ad21e487; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -6552,6 +6861,14 @@ ALTER TABLE ONLY public.results
|
|||
ADD CONSTRAINT fk_rails_9be849c454 FOREIGN KEY (archived_by_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_flows fk_rails_9c3936bd7a; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_flows
|
||||
ADD CONSTRAINT fk_rails_9c3936bd7a FOREIGN KEY (last_modified_by_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: repository_status_values fk_rails_9d357798c5; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -6632,6 +6949,14 @@ ALTER TABLE ONLY public.repository_status_values
|
|||
ADD CONSTRAINT fk_rails_a3a2aede5b FOREIGN KEY (repository_status_item_id) REFERENCES public.repository_status_items(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses fk_rails_a3f7cd509a; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_statuses
|
||||
ADD CONSTRAINT fk_rails_a3f7cd509a FOREIGN KEY (previous_status_id) REFERENCES public.my_module_statuses(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: result_assets fk_rails_a418904d39; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -6680,6 +7005,14 @@ ALTER TABLE ONLY public.repository_list_items
|
|||
ADD CONSTRAINT fk_rails_ace46bca57 FOREIGN KEY (repository_column_id) REFERENCES public.repository_columns(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_statuses fk_rails_b024d15104; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_statuses
|
||||
ADD CONSTRAINT fk_rails_b024d15104 FOREIGN KEY (created_by_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: protocols fk_rails_b2c86b4f11; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -6720,6 +7053,14 @@ ALTER TABLE ONLY public.repository_asset_values
|
|||
ADD CONSTRAINT fk_rails_bb983a4d66 FOREIGN KEY (created_by_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: my_module_status_flows fk_rails_c19dc6b9e9; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.my_module_status_flows
|
||||
ADD CONSTRAINT fk_rails_c19dc6b9e9 FOREIGN KEY (created_by_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sample_types fk_rails_c227b918b2; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -7283,6 +7624,5 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20200622140843'),
|
||||
('20200622155632'),
|
||||
('20200709142830'),
|
||||
('20200713142353'),
|
||||
('20200714082503');
|
||||
|
||||
|
||||
|
|
|
@ -105,16 +105,3 @@ Given default screen size2
|
|||
And I fill in "I will go to Krn one day." in "#my_module_description_textarea" rich text editor field
|
||||
And I click element with css ".tinymce-save-button"
|
||||
Then I should see "I will go to Krn one day."
|
||||
|
||||
@javascript
|
||||
Scenario: Successful Complete task
|
||||
Given I'm on the Protocols page of a "Experiment design" task
|
||||
And I click "Complete task" button
|
||||
Then I should see "Uncomplete task"
|
||||
|
||||
@javascript
|
||||
Scenario: Successful Uncomplete task
|
||||
Given I'm on the Protocols page of a "Experiment design" task
|
||||
And I click "Complete task" button
|
||||
And I click "Uncomplete task" button
|
||||
Then I should see "Complete task"
|
||||
|
|
|
@ -123,39 +123,77 @@ describe MyModulesController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'POST toggle_task_state' do
|
||||
let(:action) { post :toggle_task_state, params: params, format: :json }
|
||||
let(:params) { { id: my_module.id } }
|
||||
describe 'PUT update_state' do
|
||||
let(:action) { put :update_state, params: params, format: :json }
|
||||
let(:my_module_id) { my_module.id }
|
||||
let(:status_id) { 'some-state-id' }
|
||||
let(:params) do
|
||||
{
|
||||
id: my_module_id,
|
||||
my_module: { status_id: status_id }
|
||||
}
|
||||
end
|
||||
let(:my_module_status_flow) { create :my_module_status_flow, :in_team, team: team}
|
||||
let(:status1) {create :my_module_status, my_module_status_flow: my_module_status_flow}
|
||||
let(:status2) {create :my_module_status, my_module_status_flow: my_module_status_flow}
|
||||
|
||||
context 'when completing task' do
|
||||
let(:my_module) do
|
||||
create :my_module, state: 'uncompleted', experiment: experiment
|
||||
context 'when states updated' do
|
||||
let(:status_id) { status2.id }
|
||||
|
||||
before do
|
||||
my_module.update(my_module_status: status1)
|
||||
end
|
||||
|
||||
it 'calls create activity for completing task' do
|
||||
expect(Activities::CreateActivityService)
|
||||
.to(receive(:call)
|
||||
.with(hash_including(activity_type: :complete_task)))
|
||||
it 'changes status' do
|
||||
action
|
||||
|
||||
expect(my_module.reload.my_module_status.name).to be_eql(status2.name)
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
end
|
||||
|
||||
context 'when uncompleting task' do
|
||||
let(:my_module) do
|
||||
create :my_module, state: 'completed', experiment: experiment
|
||||
context 'when status not found' do
|
||||
let(:status_id) { -1 }
|
||||
|
||||
before do
|
||||
my_module.update(my_module_status: status1)
|
||||
end
|
||||
|
||||
it 'calls create activity for uncompleting task' do
|
||||
expect(Activities::CreateActivityService)
|
||||
.to(receive(:call)
|
||||
.with(hash_including(activity_type: :uncomplete_task)))
|
||||
it 'renders 404' do
|
||||
action
|
||||
|
||||
expect(response).to have_http_status 404
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds activity in DB' do
|
||||
expect { action }
|
||||
.to(change { Activity.count })
|
||||
context 'when my_module does not have assign flow yet' do
|
||||
let(:status_id) { -1 }
|
||||
|
||||
it 'renders 404' do
|
||||
action
|
||||
|
||||
expect(response).to have_http_status 404
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permissions' do
|
||||
it 'renders 403' do
|
||||
# Remove user from project
|
||||
UserProject.where(user: user, project: project).destroy_all
|
||||
action
|
||||
|
||||
expect(response).to have_http_status 403
|
||||
end
|
||||
end
|
||||
|
||||
context 'when my_module not found' do
|
||||
let(:my_module_id) { -1 }
|
||||
|
||||
it 'renders 404' do
|
||||
action
|
||||
|
||||
expect(response).to have_http_status 404
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
12
spec/factories/my_module_status_flows.rb
Normal file
12
spec/factories/my_module_status_flows.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :my_module_status_flow do
|
||||
name { Faker::Name.unique.name }
|
||||
description { Faker::Lorem.sentence }
|
||||
trait :in_team do
|
||||
team { create :team }
|
||||
visibility { :in_team }
|
||||
end
|
||||
end
|
||||
end
|
10
spec/factories/my_module_statuses.rb
Normal file
10
spec/factories/my_module_statuses.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :my_module_status do
|
||||
name { Faker::Name.unique.name }
|
||||
description { Faker::Lorem.sentence }
|
||||
color { Faker::Color.hex_color }
|
||||
my_module_status_flow
|
||||
end
|
||||
end
|
|
@ -36,6 +36,7 @@ describe MyModule, type: :model do
|
|||
it { should have_db_column :state }
|
||||
it { should have_db_column :completed_on }
|
||||
it { should have_db_column :started_on }
|
||||
it { should have_db_column :my_module_status_id }
|
||||
end
|
||||
|
||||
describe 'Relations' do
|
||||
|
|
49
spec/models/my_module_status_flow_spec.rb
Normal file
49
spec/models/my_module_status_flow_spec.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe MyModuleStatusFlow, type: :model do
|
||||
let(:my_module_global_workflow) { build :my_module_status_flow }
|
||||
let(:my_module_team_workflow) { build :my_module_status_flow, :in_team }
|
||||
|
||||
it 'is valid' do
|
||||
expect(my_module_global_workflow).to be_valid
|
||||
end
|
||||
|
||||
it 'should be of class MyModuleStatusFlow' do
|
||||
expect(subject.class).to eq MyModuleStatusFlow
|
||||
end
|
||||
|
||||
describe 'Database table' do
|
||||
it { should have_db_column :name }
|
||||
it { should have_db_column :description }
|
||||
it { should have_db_column :visibility }
|
||||
it { should have_db_column :created_by_id }
|
||||
it { should have_db_column :last_modified_by_id }
|
||||
it { should have_db_column :created_at }
|
||||
it { should have_db_column :updated_at }
|
||||
end
|
||||
|
||||
describe 'Relations' do
|
||||
it { should have_many(:my_module_statuses).dependent(:destroy) }
|
||||
end
|
||||
|
||||
describe 'Validations' do
|
||||
describe '#visibility' do
|
||||
it { is_expected.to validate_presence_of :visibility }
|
||||
end
|
||||
|
||||
describe '#name' do
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH) }
|
||||
it { expect(my_module_team_workflow).to validate_uniqueness_of(:name).scoped_to(:team_id).case_insensitive }
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it { is_expected.to validate_length_of(:description).is_at_most(Constants::TEXT_MAX_LENGTH) }
|
||||
end
|
||||
|
||||
describe '#team' do
|
||||
it { expect(my_module_team_workflow).to validate_presence_of :team }
|
||||
end
|
||||
end
|
||||
end
|
45
spec/models/my_module_status_spec.rb
Normal file
45
spec/models/my_module_status_spec.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe MyModuleStatus, type: :model do
|
||||
let(:my_module_status) { build :my_module_status }
|
||||
|
||||
it 'is valid' do
|
||||
expect(my_module_status).to be_valid
|
||||
end
|
||||
|
||||
it 'should be of class MyModuleStatus' do
|
||||
expect(subject.class).to eq MyModuleStatus
|
||||
end
|
||||
|
||||
describe 'Database table' do
|
||||
it { should have_db_column :name }
|
||||
it { should have_db_column :description }
|
||||
it { should have_db_column :color }
|
||||
it { should have_db_column :my_module_status_flow_id }
|
||||
it { should have_db_column :created_by_id }
|
||||
it { should have_db_column :last_modified_by_id }
|
||||
it { should have_db_column :previous_status_id }
|
||||
it { should have_db_column :created_at }
|
||||
it { should have_db_column :updated_at }
|
||||
end
|
||||
|
||||
describe 'Relations' do
|
||||
it { should belong_to :my_module_status_flow }
|
||||
it { should have_many(:my_modules).dependent(:nullify) }
|
||||
it { should have_many(:my_module_status_conditions).dependent(:destroy) }
|
||||
it { should have_many(:my_module_status_consequences).dependent(:destroy) }
|
||||
it { should have_many(:my_module_status_implications).dependent(:destroy) }
|
||||
end
|
||||
|
||||
describe 'Validations' do
|
||||
describe '#name' do
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH) }
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it { is_expected.to validate_length_of(:description).is_at_most(Constants::TEXT_MAX_LENGTH) }
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue