mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Merge branch 'master' into ml-sci-2940
This commit is contained in:
commit
71e4cc4dbd
|
@ -350,6 +350,9 @@ Style/WhenThen:
|
|||
Metrics/AbcSize:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
ExcludedMethods: ['describe', 'context']
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ FROM ruby:2.4.5
|
|||
MAINTAINER BioSistemika <info@biosistemika.com>
|
||||
|
||||
# additional dependecies
|
||||
# libSSL-1.0 is required by wkhtmltopdf binary
|
||||
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
|
||||
apt-get update -qq && \
|
||||
apt-get install -y \
|
||||
libjemalloc1 \
|
||||
libssl1.0-dev \
|
||||
nodejs \
|
||||
postgresql-client \
|
||||
default-jre-headless \
|
||||
|
|
|
@ -2,10 +2,12 @@ FROM ruby:2.4.5
|
|||
MAINTAINER BioSistemika <info@biosistemika.com>
|
||||
|
||||
# additional dependecies
|
||||
# libSSL-1.0 is required by wkhtmltopdf binary
|
||||
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
|
||||
apt-get update -qq && \
|
||||
apt-get install -y \
|
||||
libjemalloc1 \
|
||||
libssl1.0-dev \
|
||||
nodejs \
|
||||
groff-base \
|
||||
awscli \
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
//= require underscore
|
||||
//= require i18n.js
|
||||
//= require i18n/translations
|
||||
//= require users/settings/teams/invite_users_modal
|
||||
//= require turbolinks
|
||||
|
||||
|
||||
|
|
|
@ -112,22 +112,34 @@
|
|||
}
|
||||
// Initialize no description edit link
|
||||
function initEditNoDescription(){
|
||||
var modal = "#edit-experiment-modal-";
|
||||
$.each($(".experiment-no-description a"), function(){
|
||||
var modal = '#edit-experiment-modal-';
|
||||
$.each($('.experiment-no-description'), function() {
|
||||
var id = modal + $(this).data("id");
|
||||
initializeModal($(this), id);
|
||||
});
|
||||
}
|
||||
// Bind modal to new-experiment action
|
||||
initializeModal($("#new-experiment"), '#new-experiment-modal');
|
||||
|
||||
// Bind modal to big-plus new experiment actions
|
||||
initializeModal('.big-plus', '#new-experiment-modal');
|
||||
$(document).on('turbolinks:load', function() {
|
||||
// Bind modal to new-experiment action
|
||||
initializeModal($('#new-experiment'), '#new-experiment-modal');
|
||||
|
||||
// Bind modal to all actions listed on dropdown accesible from experiment
|
||||
// panel
|
||||
initializeDropdownActions();
|
||||
// Bind modal to big-plus new experiment actions
|
||||
initializeModal('.big-plus', '#new-experiment-modal');
|
||||
|
||||
// init
|
||||
initEditNoDescription();
|
||||
// Bind modal to new-exp-title action
|
||||
initializeModal('.new-exp-title', '#new-experiment-modal');
|
||||
|
||||
// Bind modals to all clone-experiment actions
|
||||
$.each($('.clone-experiment'), function() {
|
||||
var id = $(this).closest('.experiment-panel').data('id');
|
||||
initializeModal($(this), '#clone-experiment-modal-' + id);
|
||||
});
|
||||
|
||||
// Bind modal to all actions listed on dropdown accesible from experiment
|
||||
// panel
|
||||
initializeDropdownActions();
|
||||
|
||||
// init
|
||||
initEditNoDescription();
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
I18n setupSidebarTree */
|
||||
|
||||
//= require comments
|
||||
(function() {
|
||||
(function(global) {
|
||||
var newProjectModal = null;
|
||||
var newProjectModalForm = null;
|
||||
var newProjectModalBody = null;
|
||||
|
@ -203,7 +203,7 @@
|
|||
}
|
||||
|
||||
// Initialize users editing modal remote loading.
|
||||
function initUsersEditLink($el) {
|
||||
global.initUsersEditLink = function($el) {
|
||||
$el.find('.manage-users-link')
|
||||
.on('ajax:before', function() {
|
||||
var projectId = $(this).closest('.panel-default').attr('id');
|
||||
|
@ -771,4 +771,4 @@
|
|||
initProjectsViewModeSwitch();
|
||||
initSorting();
|
||||
loadCardsView();
|
||||
}());
|
||||
}(window));
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
var timestamp = container.data("timestamp");
|
||||
var img_url = container.data('updated-img');
|
||||
|
||||
animateSpinner(container, true);
|
||||
animateSpinner(container, true, { color: '#555', top: '60%', zIndex: '100' });
|
||||
checkUpdatedImg(img_url, url, timestamp, container);
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@
|
|||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
el.children('img').remove();
|
||||
el.append(data.workflowimg);
|
||||
},
|
||||
error: function (ev) {
|
||||
|
|
|
@ -270,7 +270,7 @@ function importProtocolFromFile(
|
|||
element.getAttribute('fileref'));
|
||||
var image_tag = "<img style='max-width:300px; max-height:300px;' src='data:" + element.children[1].innerHTML + ";base64," + assetBytes + "' />"
|
||||
description = description.replace(match, image_tag); // replace the token with image
|
||||
}).bind(this);
|
||||
});
|
||||
// I know is crazy but is the only way I found to pass valid HTML
|
||||
return $('<div></div>').html(
|
||||
description.replace('<!--[CDATA[ ', '')
|
||||
|
|
|
@ -1284,6 +1284,7 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
reorderer.order(listIds, false);
|
||||
loadColumnsNames();
|
||||
initRowSelection();
|
||||
}
|
||||
});
|
||||
$('.sorting').on('click', checkAvailableColumns);
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
function reloadRecaptchaIfExists() {
|
||||
if (typeof grecaptcha !== 'undefined') {
|
||||
grecaptcha.reset();
|
||||
}
|
||||
}
|
||||
|
||||
window.recaptchaCallback = function recaptchaCallback(response) {
|
||||
$('#recaptcha-invite-modal').val(response);
|
||||
};
|
||||
|
||||
function initializeModal(modal) {
|
||||
var modalDialog = modal.find('.modal-dialog');
|
||||
var type = modal.attr('data-type');
|
||||
|
@ -14,6 +24,9 @@
|
|||
var teamSelectorDropdown = modal.find('[data-role=team-selector-dropdown]');
|
||||
var teamSelectorDropdown2 = $();
|
||||
var tagsInput = modal.find('[data-role=tags-input]');
|
||||
var recaptchaInput = modal.find('#recaptcha-invite-modal');
|
||||
var recaptchaErrorMsgDiv = modal.find('#recaptcha-error-msg');
|
||||
var recaptchaErrorText = modal.find('#recaptcha-error-msg>span');
|
||||
|
||||
// Set max tags
|
||||
tagsInput.tagsinput({
|
||||
|
@ -85,7 +98,8 @@
|
|||
// Click action
|
||||
modal.find('[data-action=invite]').off('click').on('click', function() {
|
||||
var data = {
|
||||
emails: tagsInput.val()
|
||||
emails: tagsInput.val(),
|
||||
'g-recaptcha-response': recaptchaInput.val()
|
||||
};
|
||||
|
||||
animateSpinner(modalDialog);
|
||||
|
@ -130,16 +144,23 @@
|
|||
// Add 'data-invited="true"' status to modal element
|
||||
modal.attr('data-invited', 'true');
|
||||
},
|
||||
error: function() {
|
||||
error: function(jqXHR) {
|
||||
// ReCaptcha fails
|
||||
if (jqXHR.status === 422) {
|
||||
recaptchaErrorMsgDiv.removeClass('hidden');
|
||||
recaptchaErrorText.text(jqXHR.responseJSON.recaptcha_error);
|
||||
} else {
|
||||
modal.modal('hide');
|
||||
alert('Error inviting users.');
|
||||
}
|
||||
reloadRecaptchaIfExists();
|
||||
animateSpinner(modalDialog, false);
|
||||
modal.modal('hide');
|
||||
alert('Error inviting users.');
|
||||
}
|
||||
});
|
||||
});
|
||||
}).on('shown.bs.modal', function() {
|
||||
tagsInput.tagsinput('focus');
|
||||
|
||||
recaptchaErrorMsgDiv.addClass('hidden');
|
||||
// Remove 'data-invited="true"' status
|
||||
modal.removeAttr('data-invited');
|
||||
}).on('hide.bs.modal', function() {
|
||||
|
@ -150,6 +171,7 @@
|
|||
inviteWithRoleBtn.attr('disabled', 'disabled');
|
||||
teamSelectorDropdown2.addClass('disabled');
|
||||
animateSpinner(modalDialog, false);
|
||||
recaptchaErrorMsgDiv.addClass('hidden');
|
||||
|
||||
// Unbind event listeners
|
||||
teamSelectorCheckbox.off('change');
|
||||
|
@ -160,6 +182,8 @@
|
|||
stepResultsDiv.html('');
|
||||
stepResults.hide();
|
||||
stepForm.show();
|
||||
reloadRecaptchaIfExists();
|
||||
recaptchaInput.val('');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,400italic&subset=latin,latin-ext);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,400i,700&subset=latin-ext);
|
||||
|
||||
//==============================================================================
|
||||
// Colors
|
||||
|
@ -58,7 +58,7 @@ $link-hover-color: darken($link-color, 15%);
|
|||
$link-hover-decoration: underline;
|
||||
|
||||
// Typography
|
||||
$font-family-lato: lato, "Open Sans", Arial, Helvetica, sans-serif;
|
||||
$font-family-lato: Lato, "Open Sans", Arial, Helvetica, sans-serif;
|
||||
$font-family-sans-serif: "Open Sans", Arial, Helvetica, sans-serif;
|
||||
$font-family-serif: Georgia, "Times New Roman", Times, serif;
|
||||
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
|
|
|
@ -463,69 +463,110 @@ li.module-hover {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#project-show {
|
||||
@media (min-width: 1400px) {
|
||||
.col-md-6 {
|
||||
width: 33.33%; // fallback if needed
|
||||
width: calc(100% / 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EXPERIMENT PANEL
|
||||
.experiment-panel {
|
||||
@include box-shadow(0 4px 8px 0 $color-dove-gray);
|
||||
height: 550px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 400px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 35px;
|
||||
margin-top: 45px;
|
||||
max-width: 700px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.panel-title {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.panel-heading .clone-experiment,
|
||||
.panel-heading .dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .clone-experiment,
|
||||
&:hover .dropdown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.panel-date {
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
|
||||
.panel-heading > .clone-experiment {
|
||||
color: $color-silver-chalice;
|
||||
padding-left: 4px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
color: $color-silver-chalice;
|
||||
|
||||
button {
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
a {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.experiment-description {
|
||||
border: 1px solid $color-alto;
|
||||
border-radius: 4px;
|
||||
height: 100px;
|
||||
margin-top: 10px;
|
||||
max-height: 86px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 10px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.experiment-no-description {
|
||||
color: $color-alto;
|
||||
display: block;
|
||||
font-size: $font-size-h4;
|
||||
font-size: $font-size-large;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: $color-alto;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.no-workflowimg {
|
||||
color: $color-alto;
|
||||
display: block;
|
||||
font-size: 26px;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
height: 300px;
|
||||
margin: 15px 0;
|
||||
padding-top: 150px;
|
||||
max-height: 200px;
|
||||
padding-bottom: 70px;
|
||||
padding-top: 50px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: $color-alto;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workflowimg-container {
|
||||
height: 300px;
|
||||
margin: 15px 0;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
max-height: 300px;
|
||||
max-height: 190px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -540,7 +581,7 @@ li.module-hover {
|
|||
.big-plus {
|
||||
color: $color-gainsboro;
|
||||
display: block;
|
||||
font-size: 250px;
|
||||
font-size: 180px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
|
||||
|
|
|
@ -652,7 +652,7 @@ ul.double-line > li {
|
|||
}
|
||||
|
||||
.panel-default > .panel-heading {
|
||||
background-color: $color-gainsboro;
|
||||
background-color: $color-white;
|
||||
|
||||
&>.panel-title {
|
||||
overflow: hidden;
|
||||
|
@ -783,7 +783,9 @@ ul.double-line > li {
|
|||
}
|
||||
|
||||
.manage-users-link {
|
||||
display: block;
|
||||
padding-left: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1403,6 +1405,10 @@ table.dataTable {
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.g-recaptcha {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// Image preview modal
|
||||
|
|
|
@ -75,8 +75,10 @@ class ApplicationController < ActionController::Base
|
|||
private
|
||||
|
||||
def update_current_team
|
||||
if current_user.current_team_id.blank? &&
|
||||
current_user.teams.count > 0
|
||||
current_team = Team.find_by_id(current_user.current_team_id)
|
||||
if (current_team.nil? || !current_user.is_member_of_team?(current_team)) &&
|
||||
current_user.teams.count.positive?
|
||||
|
||||
current_user.update(
|
||||
current_team_id: current_user.teams.first.id
|
||||
)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExperimentsController < ApplicationController
|
||||
include SampleActions
|
||||
include TeamsHelper
|
||||
|
@ -183,37 +185,15 @@ class ExperimentsController < ApplicationController
|
|||
|
||||
# POST: clone_experiment(id)
|
||||
def clone
|
||||
project = Project.find_by_id(params[:experiment].try(:[], :project_id))
|
||||
|
||||
# Try to clone the experiment
|
||||
success = true
|
||||
if @experiment.projects_with_role_above_user(current_user).include?(project)
|
||||
cloned_experiment = @experiment.deep_clone_to_project(current_user,
|
||||
project)
|
||||
success = cloned_experiment.valid?
|
||||
# Create workflow image
|
||||
cloned_experiment.delay.generate_workflow_img if success
|
||||
else
|
||||
success = false
|
||||
end
|
||||
|
||||
if success
|
||||
Activity.create(
|
||||
type_of: :clone_experiment,
|
||||
project: project,
|
||||
experiment: @experiment,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.clone_experiment',
|
||||
user: current_user.full_name,
|
||||
experiment_new: cloned_experiment.name,
|
||||
experiment_original: @experiment.name
|
||||
)
|
||||
)
|
||||
service = Experiments::CopyExperimentAsTemplateService
|
||||
.call(experiment_id: @experiment.id,
|
||||
project_id: move_experiment_param,
|
||||
user_id: current_user.id)
|
||||
|
||||
if service.succeed?
|
||||
flash[:success] = t('experiments.clone.success_flash',
|
||||
experiment: @experiment.name)
|
||||
redirect_to canvas_experiment_path(cloned_experiment)
|
||||
redirect_to canvas_experiment_path(service.cloned_experiment)
|
||||
else
|
||||
flash[:error] = t('experiments.clone.error_flash',
|
||||
experiment: @experiment.name)
|
||||
|
@ -237,49 +217,23 @@ class ExperimentsController < ApplicationController
|
|||
|
||||
# POST: move_experiment(id)
|
||||
def move
|
||||
project = Project.find_by_id(params[:experiment].try(:[], :project_id))
|
||||
old_project = @experiment.project
|
||||
|
||||
# Try to move the experiment
|
||||
success = true
|
||||
if @experiment.moveable_projects(current_user).include?(project)
|
||||
success = @experiment.move_to_project(project)
|
||||
else
|
||||
success = false
|
||||
end
|
||||
|
||||
if success
|
||||
Activity.create(
|
||||
type_of: :move_experiment,
|
||||
project: project,
|
||||
experiment: @experiment,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.move_experiment',
|
||||
user: current_user.full_name,
|
||||
experiment: @experiment.name,
|
||||
project_new: project.name,
|
||||
project_original: old_project.name
|
||||
)
|
||||
)
|
||||
service = Experiments::MoveToProjectService
|
||||
.call(experiment_id: @experiment.id,
|
||||
project_id: move_experiment_param,
|
||||
user_id: current_user.id)
|
||||
|
||||
if service.succeed?
|
||||
flash[:success] = t('experiments.move.success_flash',
|
||||
experiment: @experiment.name)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { path: canvas_experiment_url(@experiment) }, status: :ok
|
||||
end
|
||||
end
|
||||
path = canvas_experiment_url(@experiment)
|
||||
status = :ok
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: t('experiments.move.error_flash',
|
||||
experiment:
|
||||
escape_input(@experiment.name)) },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
message = t('experiments.move.error_flash',
|
||||
experiment: escape_input(@experiment.name))
|
||||
status = :unprocessable_entity
|
||||
end
|
||||
|
||||
render json: { message: message, path: path }, status: status
|
||||
end
|
||||
|
||||
def module_archive
|
||||
|
@ -348,6 +302,10 @@ class ExperimentsController < ApplicationController
|
|||
params.require(:experiment).permit(:name, :description, :archived)
|
||||
end
|
||||
|
||||
def move_experiment_param
|
||||
params.require(:experiment).require(:project_id)
|
||||
end
|
||||
|
||||
def load_projects_tree
|
||||
# Switch to correct team
|
||||
current_team_switch(@experiment.project.team) unless @experiment.project.nil?
|
||||
|
|
|
@ -574,8 +574,10 @@ class ProtocolsController < ApplicationController
|
|||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
protocol = import_new_protocol(@protocol_json, @team, @type, current_user)
|
||||
rescue Exception
|
||||
protocol =
|
||||
import_new_protocol(@protocol_json, @team, @type, current_user)
|
||||
rescue StandardError => ex
|
||||
Rails.logger.error ex.message
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
|
|
|
@ -229,8 +229,7 @@ class TeamsController < ApplicationController
|
|||
|
||||
def export_projects
|
||||
unless export_proj_requests_exceeded?
|
||||
current_user.export_vars['num_of_export_all_last_24_hours'] += 1
|
||||
current_user.save
|
||||
current_user.increase_daily_exports_counter!
|
||||
|
||||
generate_export_projects_zip
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ module Users
|
|||
|
||||
prepend_before_action :check_captcha, only: [:update]
|
||||
|
||||
prepend_before_action :check_captcha_for_invite, only: [:invite_users]
|
||||
|
||||
before_action :check_invite_users_permission, only: :invite_users
|
||||
|
||||
before_action :update_sanitized_params, only: :update
|
||||
|
@ -188,6 +190,15 @@ module Users
|
|||
end
|
||||
end
|
||||
|
||||
def check_captcha_for_invite
|
||||
if Rails.configuration.x.enable_recaptcha
|
||||
unless verify_recaptcha
|
||||
render json: { recaptcha_error: t('invite_users.errors.recaptcha') },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_invite_users_permission
|
||||
@user = current_user
|
||||
@emails = params[:emails].map(&:downcase)
|
||||
|
|
|
@ -242,12 +242,12 @@ class Asset < ApplicationRecord
|
|||
file_path = fa.path
|
||||
end
|
||||
|
||||
unless Yomu.class_eval('@@server_pid')
|
||||
Yomu.server(:text, nil)
|
||||
sleep(5)
|
||||
# Start Tika as a server
|
||||
if !ENV['NO_TIKA_SERVER'] && Yomu.class_variable_get(:@@server_pid).nil?
|
||||
Yomu.server(:text)
|
||||
end
|
||||
y = Yomu.new file_path
|
||||
|
||||
y = Yomu.new file_path
|
||||
text_data = y.text
|
||||
|
||||
if asset.asset_text_datum.present?
|
||||
|
|
|
@ -225,54 +225,6 @@ class Experiment < ApplicationRecord
|
|||
Experiments::GenerateWorkflowImageService.call(experiment_id: id)
|
||||
end
|
||||
|
||||
# Clone this experiment to given project
|
||||
def deep_clone_to_project(current_user, project)
|
||||
# First we have to find unique name for our little experiment
|
||||
experiment_names = project.experiments.map(&:name)
|
||||
format = 'Clone %d - %s'
|
||||
|
||||
i = 1
|
||||
i += 1 while experiment_names.include?(format(format, i, name))
|
||||
|
||||
clone = Experiment.new(
|
||||
name: format(format, i, name).truncate(Constants::NAME_MAX_LENGTH),
|
||||
description: description,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
project: project
|
||||
)
|
||||
|
||||
# Copy all workflows
|
||||
my_module_groups.each do |g|
|
||||
clone.my_module_groups << g.deep_clone_to_experiment(current_user, clone)
|
||||
end
|
||||
|
||||
# Copy modules without group
|
||||
clone.my_modules << my_modules.without_group.map do |m|
|
||||
m.deep_clone_to_experiment(current_user, clone)
|
||||
end
|
||||
clone.save
|
||||
clone
|
||||
end
|
||||
|
||||
def move_to_project(project)
|
||||
self.project = project
|
||||
|
||||
my_modules.each do |m|
|
||||
new_tags = []
|
||||
m.tags.each do |t|
|
||||
new_tags << t.deep_clone_to_project(project)
|
||||
end
|
||||
m.my_module_tags.destroy_all
|
||||
|
||||
project.tags << new_tags
|
||||
m.tags << new_tags
|
||||
end
|
||||
result = save
|
||||
touch(:workflowimg_updated_at) if result
|
||||
result
|
||||
end
|
||||
|
||||
# Get projects where user is either owner or user in the same team
|
||||
# as this experiment
|
||||
def projects_with_role_above_user(current_user)
|
||||
|
|
|
@ -246,6 +246,8 @@ class Project < ApplicationRecord
|
|||
parsed_page_html = Nokogiri::HTML(page_html_string)
|
||||
parsed_html = parsed_page_html.at_css('#report-content')
|
||||
|
||||
# Style tables (mimick frontend processing)
|
||||
|
||||
tables = parsed_html.css('.hot-table-contents')
|
||||
.zip(parsed_html.css('.hot-table-container'))
|
||||
tables.each do |table_input, table_container|
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Tag < ApplicationRecord
|
||||
include SearchableModel
|
||||
|
||||
|
@ -48,7 +50,10 @@ class Tag < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def deep_clone_to_project(project)
|
||||
def clone_to_project_or_return_existing(project)
|
||||
tag = Tag.find_by(project: project, name: name, color: color)
|
||||
return tag if tag
|
||||
|
||||
Tag.create(
|
||||
name: name,
|
||||
color: color,
|
||||
|
|
|
@ -129,6 +129,18 @@ class TeamZipExport < ZipExport
|
|||
)
|
||||
file = FileUtils.touch("#{project_path}/#{html_name}").first
|
||||
File.open(file, 'wb') { |f| f.write(project_report_pdf) }
|
||||
|
||||
# Add Handsontable and dependent JS files (mimick frontend formula
|
||||
# processing).
|
||||
required_js = %w(handsontable.full.min.js lodash.js numeral.js numeric.js
|
||||
md5.js jstat.js formula.js parser.js ruleJS.js
|
||||
handsontable.formula.js big.min.js)
|
||||
required_js.each do |filename|
|
||||
filepath = File.join(Rails.root,
|
||||
"vendor/assets/javascripts/#{filename}/")
|
||||
dest_folder = "#{project_path}/"
|
||||
FileUtils.cp(filepath, dest_folder)
|
||||
end
|
||||
end
|
||||
|
||||
# Change current dir outside tmp_dir, since tmp_dir will be deleted
|
||||
|
|
|
@ -56,7 +56,8 @@ class User < ApplicationRecord
|
|||
|
||||
default_variables(
|
||||
export_vars: {
|
||||
num_of_export_all_last_24_hours: 0
|
||||
num_of_export_all_last_24_hours: 0,
|
||||
last_export_timestamp: Date.today.to_time.to_i
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -505,6 +506,16 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def increase_daily_exports_counter!
|
||||
if Time.at(export_vars['last_export_timestamp'] || 0).to_date == Date.today
|
||||
export_vars['num_of_export_all_last_24_hours'] += 1
|
||||
else
|
||||
export_vars['last_export_timestamp'] = Date.today.to_time.to_i
|
||||
export_vars['num_of_export_all_last_24_hours'] = 1
|
||||
end
|
||||
save
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def confirmation_required?
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class JsonbHashSerializer
|
||||
def self.dump(hash)
|
||||
hash.to_json
|
||||
hash.nil? ? '{}' : hash.to_json
|
||||
end
|
||||
|
||||
def self.load(hash)
|
||||
|
|
103
app/services/experiments/copy_experiment_as_template_service.rb
Normal file
103
app/services/experiments/copy_experiment_as_template_service.rb
Normal file
|
@ -0,0 +1,103 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Experiments
|
||||
class CopyExperimentAsTemplateService
|
||||
extend Service
|
||||
|
||||
attr_reader :errors, :c_exp
|
||||
alias cloned_experiment c_exp
|
||||
|
||||
def initialize(experiment_id:, project_id:, user_id:)
|
||||
@exp = Experiment.find experiment_id
|
||||
@project = Project.find project_id
|
||||
@user = User.find user_id
|
||||
@original_project = @exp&.project
|
||||
@c_exp = nil
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
def call
|
||||
return self unless valid?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@c_exp = Experiment.new(
|
||||
name: find_uniq_name,
|
||||
description: @exp.description,
|
||||
created_by: @user,
|
||||
last_modified_by: @user,
|
||||
project: @project
|
||||
)
|
||||
|
||||
# Copy all signle taskas
|
||||
@c_exp.my_modules << @exp.my_modules.without_group.map do |m|
|
||||
m.deep_clone_to_experiment(@user, @c_exp)
|
||||
end
|
||||
|
||||
# Copy all grouped tasks
|
||||
@exp.my_module_groups.each do |g|
|
||||
@c_exp.my_module_groups << g.deep_clone_to_experiment(@user, @c_exp)
|
||||
end
|
||||
|
||||
raise ActiveRecord::Rollback unless @c_exp.save
|
||||
end
|
||||
@errors.merge!(@c_exp.errors.to_hash) unless @c_exp.valid?
|
||||
|
||||
@c_exp = nil unless succeed?
|
||||
@c_exp.delay.generate_workflow_img if succeed?
|
||||
track_activity if succeed?
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def succeed?
|
||||
@errors.none?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_uniq_name
|
||||
experiment_names = @project.experiments.map(&:name)
|
||||
format = 'Clone %d - %s'
|
||||
free_index = 1
|
||||
free_index += 1 while experiment_names
|
||||
.include?(format(format, free_index, @exp.name))
|
||||
format(format, free_index, @exp.name).truncate(Constants::NAME_MAX_LENGTH)
|
||||
end
|
||||
|
||||
def valid?
|
||||
unless @exp && @project && @user
|
||||
@errors[:invalid_arguments] =
|
||||
{ 'experiment': @exp,
|
||||
'project': @project,
|
||||
'user': @user }
|
||||
.map do |key, value|
|
||||
"Can't find #{key.capitalize}" if value.nil?
|
||||
end.compact
|
||||
return false
|
||||
end
|
||||
|
||||
if @exp.projects_with_role_above_user(@user).include?(@project)
|
||||
true
|
||||
else
|
||||
@errors[:user_without_permissions] =
|
||||
['You are not allowed to copy this experiment to this project']
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def track_activity
|
||||
Activity.create(
|
||||
type_of: :clone_experiment,
|
||||
project: @project,
|
||||
experiment: @exp,
|
||||
user: @user,
|
||||
message: I18n.t(
|
||||
'activities.clone_experiment',
|
||||
user: @user,
|
||||
experiment_new: @c_exp.name,
|
||||
experiment_original: @exp.name
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
91
app/services/experiments/move_to_project_service.rb
Normal file
91
app/services/experiments/move_to_project_service.rb
Normal file
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Experiments
|
||||
class MoveToProjectService
|
||||
extend Service
|
||||
|
||||
attr_reader :errors
|
||||
|
||||
def initialize(experiment_id:, project_id:, user_id:)
|
||||
@exp = Experiment.find experiment_id
|
||||
@project = Project.find project_id
|
||||
@user = User.find user_id
|
||||
@original_project = @exp&.project
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
def call
|
||||
return self unless valid?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@exp.project = @project
|
||||
|
||||
@exp.my_modules.each do |m|
|
||||
new_tags = m.tags.map do |t|
|
||||
t.clone_to_project_or_return_existing(@project)
|
||||
end
|
||||
m.my_module_tags.delete_all
|
||||
m.tags = new_tags
|
||||
end
|
||||
|
||||
raise ActiveRecord::Rollback unless @exp.save
|
||||
# To pass the ExperimentsController#updated_img check
|
||||
@exp.update(workflowimg_updated_at: @exp.updated_at)
|
||||
end
|
||||
|
||||
@errors.merge!(@exp.errors.to_hash) unless @exp.valid?
|
||||
|
||||
track_activity if succeed?
|
||||
self
|
||||
end
|
||||
|
||||
def succeed?
|
||||
@errors.none?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid?
|
||||
unless @exp && @project && @user
|
||||
@errors[:invalid_arguments] =
|
||||
{ 'experiment': @exp,
|
||||
'project': @project,
|
||||
'user': @user }
|
||||
.map do |key, value|
|
||||
"Can't find #{key.capitalize}" if value.nil?
|
||||
end.compact
|
||||
return false
|
||||
end
|
||||
|
||||
e = Experiment.find_by(name: @exp.name, project: @project)
|
||||
|
||||
if e
|
||||
@errors[:project_with_exp] =
|
||||
['Project already contains experiment with this name']
|
||||
false
|
||||
elsif !@exp.moveable_projects(@user).include?(@project)
|
||||
@errors[:target_project_not_valid] =
|
||||
['Experiment cannot be moved to this project']
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def track_activity
|
||||
Activity.create(
|
||||
type_of: :move_experiment,
|
||||
project: @project,
|
||||
experiment: @exp,
|
||||
user: @user,
|
||||
message: I18n.t(
|
||||
'activities.move_experiment',
|
||||
user: @user,
|
||||
experiment: @exp.name,
|
||||
project_new: @project.name,
|
||||
project_original: @original_project.name
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
<div class="panel panel-default module-large
|
||||
<%= get_task_alert_color(my_module) %>
|
||||
<%= get_task_alert_color(my_module) %>"
|
||||
id="<%= my_module.id %>"
|
||||
data-module-id="<%= my_module.id %>"
|
||||
data-module-name="<%= my_module.name %>"
|
||||
|
@ -9,6 +9,7 @@
|
|||
data-module-x="<%= my_module.x %>"
|
||||
data-module-y="<%= my_module.y %>"
|
||||
data-module-conns="<%= construct_module_connections(my_module) %>"
|
||||
data-module-users-tab-url="<%= my_module_user_my_modules_url(my_module_id: my_module.id, format: :json) %>"
|
||||
data-module-tags-url="<%= my_module_tags_experiment_path(my_module.experiment, format: :json) %>">
|
||||
|
||||
<% if can_manage_module?(my_module) %>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
tooltipcontent: I18n.t('tooltips.text.experiment.move')
|
||||
} %></li>
|
||||
<% end %>
|
||||
<% if can_manage_experiment?(experiment) %>
|
||||
<% if can_archive_experiment?(experiment) %>
|
||||
<li><%= link_to t('experiments.archive.label_title'),
|
||||
archive_experiment_url(experiment),
|
||||
type: 'button',
|
||||
|
|
|
@ -44,8 +44,6 @@
|
|||
<% @project.sorted_active_experiments(@current_sort).each_with_index do |experiment, index| %>
|
||||
<%= render partial: 'projects/show/experiment',
|
||||
locals: { experiment: experiment } %>
|
||||
|
||||
<%= content_tag(:div, '', class: 'clearfix visible-lg-block') if (index + 1) % 2 == 0 %>
|
||||
<% end %>
|
||||
<% if can_create_experiments?(@project) %>
|
||||
<%= render 'projects/show/new_experiment' %>
|
||||
|
|
|
@ -17,50 +17,68 @@
|
|||
<%= render partial: 'experiments/dropdown_actions.html.erb',
|
||||
locals: { project: @project, experiment: experiment } %>
|
||||
</div>
|
||||
<% end %>
|
||||
<h3 class="panel-title"><%= link_to experiment.name, canvas_experiment_path(experiment) %></h3>
|
||||
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% if experiment.active_modules.length > 0 %>
|
||||
<div class="workflowimg-container"
|
||||
data-check-img="<%= updated_img_experiment_url(experiment) %>"
|
||||
data-updated-img="<%= fetch_workflow_img_experiment_url(experiment) %>"
|
||||
data-timestamp="<%= experiment.updated_at %>" >
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="no-workflowimg">
|
||||
<% if can_manage_experiment?(experiment) %>
|
||||
<p><%= link_to( t('experiments.edit.add_task'),
|
||||
canvas_experiment_path(experiment,
|
||||
editMode: true)) %></p>
|
||||
<% else %>
|
||||
<p><%= t('experiments.edit.no_workflowimg') %></p>
|
||||
<% if can_clone_experiment?(experiment) %>
|
||||
<%= link_to clone_modal_experiment_url(experiment),
|
||||
remote: true, type: 'button',
|
||||
class: 'clone-experiment help_tooltips pull-right',
|
||||
data: {
|
||||
tooltiplink: I18n.t('tooltips.link.experiment.copy'),
|
||||
tooltipcontent: I18n.t('tooltips.text.experiment.copy')
|
||||
} do %>
|
||||
<span class="fas fa-copy"></span>
|
||||
<%= t('experiments.clone.label_title') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<span class="help_tooltips" data-tooltiplink="<%= I18n.t('tooltips.link.experiment.dates') %>" data-tooltipcontent="<%= I18n.t('tooltips.text.experiment.dates') %>">
|
||||
|
||||
<div class="panel-title"><%= link_to experiment.name, canvas_experiment_path(experiment) %></div>
|
||||
|
||||
<span class="help_tooltips panel-date" data-tooltiplink="<%= I18n.t('tooltips.link.experiment.dates') %>" data-tooltipcontent="<%= I18n.t('tooltips.text.experiment.dates') %>">
|
||||
<span class="fas fa-calendar-alt" aria-hidden="true"></span>
|
||||
<%= l(experiment.created_at, format: :full_date) %> - <%= l(experiment.updated_at, format: :full_date) %>
|
||||
</span>
|
||||
<div data-hook="experiment-card-body">
|
||||
|
||||
<div data-hook="experiment-card-description">
|
||||
<% if experiment.description? %>
|
||||
<div class='experiment-description'>
|
||||
<%= custom_auto_link(experiment.description, team: current_team) %>
|
||||
</div>
|
||||
<% else %>
|
||||
<span class='experiment-no-description'>
|
||||
<% if can_manage_experiment?(experiment) %>
|
||||
<%= link_to t('experiments.edit.add-description'),
|
||||
edit_project_experiment_url(@project, experiment),
|
||||
remote: true,
|
||||
data: { id: experiment.id } %>
|
||||
<% else %>
|
||||
<p><%= t('experiments.edit.no-description') %></p>
|
||||
<% end %>
|
||||
</span>
|
||||
<% if can_manage_experiment?(experiment) %>
|
||||
<%= link_to t('experiments.edit.add-description'),
|
||||
edit_project_experiment_url(@project, experiment),
|
||||
remote: true,
|
||||
data: { id: experiment.id },
|
||||
class: 'experiment-no-description' %>
|
||||
<% else %>
|
||||
<p class='experiment-no-description'><%= t('experiments.edit.no-description') %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<% if experiment.active_modules.length > 0 %>
|
||||
<%= link_to canvas_experiment_path(experiment) do %>
|
||||
<div class="workflowimg-container"
|
||||
data-check-img="<%= updated_img_experiment_url(experiment) %>"
|
||||
data-updated-img="<%= fetch_workflow_img_experiment_url(experiment) %>"
|
||||
data-timestamp="<%= experiment.updated_at %>" >
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% if can_manage_experiment?(experiment) %>
|
||||
<%= link_to( t('experiments.edit.add_task'),
|
||||
canvas_experiment_path(experiment, editMode: true),
|
||||
class: 'no-workflowimg') %>
|
||||
<% else %>
|
||||
<div class="no-workflowimg">
|
||||
<p><%= t('experiments.edit.no_workflowimg') %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<div class="col-md-6">
|
||||
<div class="panel panel-default experiment-panel">
|
||||
<div class="panel-heading">
|
||||
|
||||
<h3 class="panel-title"><%= t('experiments.new.modal_title') %></h3>
|
||||
|
||||
<%= link_to t('experiments.new.modal_title'),
|
||||
new_project_experiment_url(@project),
|
||||
remote: true,
|
||||
class: 'panel-title new-exp-title' %>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<%= link_to new_project_experiment_url(@project),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<%= link_to image_tag(@experiment.workflowimg.expiring_url(
|
||||
<%= image_tag(@experiment.workflowimg.expiring_url(
|
||||
Constants::URL_SHORT_EXPIRE_TIME
|
||||
),
|
||||
class: 'img-responsive center-block'),
|
||||
canvas_experiment_path(@experiment) %>
|
||||
class: 'img-responsive center-block') %>
|
||||
|
|
|
@ -99,6 +99,16 @@ invite_to_team = type.in?(%w(invite_to_team invite_to_team_with_role))
|
|||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if ENV['ENABLE_RECAPTCHA'] %>
|
||||
<div id="recaptcha-service" class="g-recaptcha"
|
||||
data-callback="recaptchaCallback"
|
||||
data-sitekey=<%= ENV['RECAPTCHA_SITE_KEY'] %>></div>
|
||||
<script type="text/javascript" src="https://www.google.com/recaptcha/api.js?hl=en"></script>
|
||||
<input type="hidden" id="recaptcha-invite-modal" value="">
|
||||
<div class="form-group has-error hidden" id="recaptcha-error-msg" >
|
||||
<span class="has-error help-block"></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="results-container" data-role="step-results" data-clear="true">
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,41 @@
|
|||
.force_encoding(Encoding::UTF_8)
|
||||
.html_safe %>
|
||||
</style>
|
||||
|
||||
<script src="handsontable.full.min.js"></script>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<script src="lodash.js"></script>
|
||||
<script src="numeral.js"></script>
|
||||
<script src="numeric.js"></script>
|
||||
<script src="md5.js"></script>
|
||||
<script src="jstat.js"></script>
|
||||
<script src="formula.js"></script>
|
||||
<script src="parser.js"></script>
|
||||
<script src="ruleJS.js"></script>
|
||||
<script src="handsontable.formula.js"></script>
|
||||
<script src="big.min.js"></script>
|
||||
|
||||
<!-- Init Handsontables -->
|
||||
<script>
|
||||
window.onload = function() {
|
||||
var tables = document.getElementsByClassName('hot-table-container');
|
||||
var tableVals = document.getElementsByClassName('hot-table-contents');
|
||||
|
||||
for (i = 0; i < tables.length; i++) {
|
||||
tables[i].innerHTML=''
|
||||
new Handsontable(tables[i], {
|
||||
data: JSON.parse(tableVals[i].value).data,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
filters: true,
|
||||
dropdownMenu: true,
|
||||
formulas: true
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<title><%= title %></title>
|
||||
</head>
|
||||
<body class='print-report-body'>
|
||||
|
@ -17,4 +52,4 @@
|
|||
<%= content.force_encoding(Encoding::UTF_8).html_safe %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h5 class="text-center"><%= t("projects.index.users_tab") %></h5>
|
||||
<hr>
|
||||
<ul class="no-style content-users">
|
||||
<ul class="no-style content-users" data-hook="project-users-tab-list">
|
||||
<% if @users.size == 0 then %>
|
||||
<li><em><%= t 'projects.index.no_users' %></em></li>
|
||||
<% else %>
|
||||
|
|
|
@ -137,5 +137,4 @@
|
|||
<%= render partial: 'users/settings/user_teams/destroy_user_team_modal.html.erb' %>
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag 'users/settings/teams/show' %>
|
||||
<%= javascript_include_tag 'users/settings/teams/invite_users_modal' %>
|
||||
<span data-hook="team-bottom"></span>
|
||||
|
|
|
@ -769,7 +769,7 @@ en:
|
|||
label_title: 'Archive'
|
||||
clone:
|
||||
modal_title: 'Copy experiment %{experiment} as template'
|
||||
label_title: 'Copy as template (only Protocols steps copied)'
|
||||
label_title: 'Copy as template'
|
||||
modal_submit: 'Copy'
|
||||
success_flash: 'Successfully copied experiment %{experiment} as template.'
|
||||
error_flash: 'Could not copy the experiment as template.'
|
||||
|
@ -1831,6 +1831,8 @@ en:
|
|||
invite_guest: "As Guests"
|
||||
invite_user: "As Normal Users"
|
||||
invite_admin: "As Administrators"
|
||||
errors:
|
||||
recaptcha: "reCAPTCHA verification failed, please try again"
|
||||
results:
|
||||
heading: "Invitation results:"
|
||||
user_exists: "User is already a member of SciNote."
|
||||
|
|
195
db/migrate/20190117155006_change_indices_from_int_to_bigint.rb
Normal file
195
db/migrate/20190117155006_change_indices_from_int_to_bigint.rb
Normal file
|
@ -0,0 +1,195 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeIndicesFromIntToBigint < ActiveRecord::Migration[5.1]
|
||||
def up
|
||||
drop_view :datatables_teams
|
||||
|
||||
change_column :activities, :id, :bigint
|
||||
change_column :assets, :id, :bigint
|
||||
change_column :asset_text_data, :id, :bigint
|
||||
change_column :checklist_items, :id, :bigint
|
||||
change_column :checklists, :id, :bigint
|
||||
change_column :comments, :id, :bigint
|
||||
change_column :connections, :id, :bigint
|
||||
change_column :custom_fields, :id, :bigint
|
||||
change_column :delayed_jobs, :id, :bigint
|
||||
change_column :experiments, :id, :bigint
|
||||
change_column :my_module_groups, :id, :bigint
|
||||
change_column :my_module_repository_rows, :id, :bigint
|
||||
change_column :my_modules, :id, :bigint
|
||||
change_column :my_module_tags, :id, :bigint
|
||||
change_column :notifications, :id, :bigint
|
||||
change_column :projects, :id, :bigint
|
||||
change_column :protocol_keywords, :id, :bigint
|
||||
change_column :protocol_protocol_keywords, :id, :bigint
|
||||
change_column :protocols, :id, :bigint
|
||||
change_column :report_elements, :id, :bigint
|
||||
change_column :reports, :id, :bigint
|
||||
change_column :repositories, :id, :bigint
|
||||
change_column :repository_table_states, :id, :bigint
|
||||
change_column :result_assets, :id, :bigint
|
||||
change_column :results, :id, :bigint
|
||||
change_column :result_tables, :id, :bigint
|
||||
change_column :result_texts, :id, :bigint
|
||||
change_column :sample_custom_fields, :id, :bigint
|
||||
change_column :sample_groups, :id, :bigint
|
||||
change_column :sample_my_modules, :id, :bigint
|
||||
change_column :samples, :id, :bigint
|
||||
change_column :samples_tables, :id, :bigint
|
||||
change_column :sample_types, :id, :bigint
|
||||
change_column :settings, :id, :bigint
|
||||
change_column :step_assets, :id, :bigint
|
||||
change_column :steps, :id, :bigint
|
||||
change_column :step_tables, :id, :bigint
|
||||
change_column :tables, :id, :bigint
|
||||
change_column :tags, :id, :bigint
|
||||
change_column :teams, :id, :bigint
|
||||
change_column :temp_files, :id, :bigint
|
||||
change_column :tiny_mce_assets, :id, :bigint
|
||||
change_column :tokens, :id, :bigint
|
||||
change_column :user_identities, :id, :bigint
|
||||
change_column :user_my_modules, :id, :bigint
|
||||
change_column :user_notifications, :id, :bigint
|
||||
change_column :user_projects, :id, :bigint
|
||||
change_column :users, :id, :bigint
|
||||
change_column :user_teams, :id, :bigint
|
||||
change_column :wopi_actions, :id, :bigint
|
||||
change_column :wopi_apps, :id, :bigint
|
||||
change_column :wopi_discoveries, :id, :bigint
|
||||
change_column :zip_exports, :id, :bigint
|
||||
change_column :activities, :user_id, :bigint
|
||||
change_column :activities, :my_module_id, :bigint
|
||||
change_column :activities, :project_id, :bigint
|
||||
change_column :activities, :experiment_id, :bigint
|
||||
change_column :assets, :created_by_id, :bigint
|
||||
change_column :assets, :last_modified_by_id, :bigint
|
||||
change_column :asset_text_data, :asset_id, :bigint
|
||||
change_column :checklist_items, :checklist_id, :bigint
|
||||
change_column :checklist_items, :last_modified_by_id, :bigint
|
||||
change_column :checklist_items, :created_by_id, :bigint
|
||||
change_column :checklists, :step_id, :bigint
|
||||
change_column :checklists, :created_by_id, :bigint
|
||||
change_column :checklists, :last_modified_by_id, :bigint
|
||||
change_column :comments, :last_modified_by_id, :bigint
|
||||
change_column :comments, :user_id, :bigint
|
||||
change_column :connections, :output_id, :bigint
|
||||
change_column :connections, :input_id, :bigint
|
||||
change_column :custom_fields, :team_id, :bigint
|
||||
change_column :custom_fields, :user_id, :bigint
|
||||
change_column :custom_fields, :last_modified_by_id, :bigint
|
||||
change_column :experiments, :created_by_id, :bigint
|
||||
change_column :experiments, :restored_by_id, :bigint
|
||||
change_column :experiments, :archived_by_id, :bigint
|
||||
change_column :experiments, :last_modified_by_id, :bigint
|
||||
change_column :my_module_groups, :experiment_id, :bigint
|
||||
change_column :my_module_groups, :created_by_id, :bigint
|
||||
change_column :my_module_repository_rows, :assigned_by_id, :bigint
|
||||
change_column :my_modules, :last_modified_by_id, :bigint
|
||||
change_column :my_modules, :created_by_id, :bigint
|
||||
change_column :my_modules, :experiment_id, :bigint
|
||||
change_column :my_modules, :my_module_group_id, :bigint
|
||||
change_column :my_modules, :archived_by_id, :bigint
|
||||
change_column :my_modules, :restored_by_id, :bigint
|
||||
change_column :my_module_tags, :created_by_id, :bigint
|
||||
change_column :notifications, :generator_user_id, :bigint
|
||||
change_column :oauth_access_grants, :resource_owner_id, :bigint
|
||||
change_column :oauth_access_tokens, :resource_owner_id, :bigint
|
||||
change_column :projects, :team_id, :bigint
|
||||
change_column :projects, :archived_by_id, :bigint
|
||||
change_column :projects, :restored_by_id, :bigint
|
||||
change_column :projects, :created_by_id, :bigint
|
||||
change_column :projects, :last_modified_by_id, :bigint
|
||||
change_column :protocol_keywords, :team_id, :bigint
|
||||
change_column :protocol_protocol_keywords, :protocol_keyword_id, :bigint
|
||||
change_column :protocol_protocol_keywords, :protocol_id, :bigint
|
||||
change_column :protocols, :team_id, :bigint
|
||||
change_column :protocols, :added_by_id, :bigint
|
||||
change_column :protocols, :parent_id, :bigint
|
||||
change_column :protocols, :restored_by_id, :bigint
|
||||
change_column :protocols, :archived_by_id, :bigint
|
||||
change_column :protocols, :my_module_id, :bigint
|
||||
change_column :report_elements, :report_id, :bigint
|
||||
change_column :report_elements, :experiment_id, :bigint
|
||||
change_column :report_elements, :table_id, :bigint
|
||||
change_column :report_elements, :asset_id, :bigint
|
||||
change_column :report_elements, :checklist_id, :bigint
|
||||
change_column :report_elements, :result_id, :bigint
|
||||
change_column :report_elements, :step_id, :bigint
|
||||
change_column :report_elements, :my_module_id, :bigint
|
||||
change_column :report_elements, :project_id, :bigint
|
||||
change_column :reports, :user_id, :bigint
|
||||
change_column :reports, :last_modified_by_id, :bigint
|
||||
change_column :reports, :project_id, :bigint
|
||||
change_column :repositories, :created_by_id, :bigint
|
||||
change_column :repository_columns, :created_by_id, :bigint
|
||||
change_column :repository_date_values, :last_modified_by_id, :bigint
|
||||
change_column :repository_date_values, :created_by_id, :bigint
|
||||
change_column :repository_rows, :last_modified_by_id, :bigint
|
||||
change_column :repository_rows, :created_by_id, :bigint
|
||||
change_column :repository_text_values, :created_by_id, :bigint
|
||||
change_column :repository_text_values, :last_modified_by_id, :bigint
|
||||
change_column :result_assets, :result_id, :bigint
|
||||
change_column :result_assets, :asset_id, :bigint
|
||||
change_column :results, :archived_by_id, :bigint
|
||||
change_column :results, :user_id, :bigint
|
||||
change_column :results, :last_modified_by_id, :bigint
|
||||
change_column :results, :restored_by_id, :bigint
|
||||
change_column :results, :my_module_id, :bigint
|
||||
change_column :result_tables, :result_id, :bigint
|
||||
change_column :result_tables, :table_id, :bigint
|
||||
change_column :result_texts, :result_id, :bigint
|
||||
change_column :sample_custom_fields, :custom_field_id, :bigint
|
||||
change_column :sample_custom_fields, :sample_id, :bigint
|
||||
change_column :sample_groups, :team_id, :bigint
|
||||
change_column :sample_groups, :last_modified_by_id, :bigint
|
||||
change_column :sample_groups, :created_by_id, :bigint
|
||||
change_column :sample_my_modules, :my_module_id, :bigint
|
||||
change_column :sample_my_modules, :sample_id, :bigint
|
||||
change_column :sample_my_modules, :assigned_by_id, :bigint
|
||||
change_column :samples, :sample_group_id, :bigint
|
||||
change_column :samples, :last_modified_by_id, :bigint
|
||||
change_column :samples, :sample_type_id, :bigint
|
||||
change_column :samples, :team_id, :bigint
|
||||
change_column :samples, :user_id, :bigint
|
||||
change_column :sample_types, :created_by_id, :bigint
|
||||
change_column :sample_types, :team_id, :bigint
|
||||
change_column :sample_types, :last_modified_by_id, :bigint
|
||||
change_column :step_assets, :asset_id, :bigint
|
||||
change_column :step_assets, :step_id, :bigint
|
||||
change_column :steps, :last_modified_by_id, :bigint
|
||||
change_column :steps, :user_id, :bigint
|
||||
change_column :steps, :protocol_id, :bigint
|
||||
change_column :step_tables, :step_id, :bigint
|
||||
change_column :step_tables, :table_id, :bigint
|
||||
change_column :tables, :created_by_id, :bigint
|
||||
change_column :tables, :last_modified_by_id, :bigint
|
||||
change_column :tags, :last_modified_by_id, :bigint
|
||||
change_column :tags, :created_by_id, :bigint
|
||||
change_column :tags, :project_id, :bigint
|
||||
change_column :teams, :last_modified_by_id, :bigint
|
||||
change_column :teams, :created_by_id, :bigint
|
||||
change_column :tokens, :user_id, :bigint
|
||||
change_column :user_my_modules, :my_module_id, :bigint
|
||||
change_column :user_my_modules, :assigned_by_id, :bigint
|
||||
change_column :user_my_modules, :user_id, :bigint
|
||||
change_column :user_notifications, :user_id, :bigint
|
||||
change_column :user_notifications, :notification_id, :bigint
|
||||
change_column :user_projects, :assigned_by_id, :bigint
|
||||
change_column :user_projects, :user_id, :bigint
|
||||
change_column :user_projects, :project_id, :bigint
|
||||
change_column :users, :current_team_id, :bigint
|
||||
change_column :user_teams, :user_id, :bigint
|
||||
change_column :user_teams, :team_id, :bigint
|
||||
change_column :user_teams, :assigned_by_id, :bigint
|
||||
change_column :wopi_actions, :wopi_app_id, :bigint
|
||||
change_column :wopi_apps, :wopi_discovery_id, :bigint
|
||||
change_column :zip_exports, :user_id, :bigint
|
||||
|
||||
create_view :datatables_teams
|
||||
end
|
||||
|
||||
def down
|
||||
# Bad, bad things can happen if we reverse this migration, so we'll
|
||||
# simply skip it
|
||||
end
|
||||
end
|
362
db/schema.rb
362
db/schema.rb
|
@ -10,22 +10,22 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20181212162649) do
|
||||
ActiveRecord::Schema.define(version: 20190117155006) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
enable_extension "pg_trgm"
|
||||
enable_extension "btree_gist"
|
||||
|
||||
create_table "activities", id: :serial, force: :cascade do |t|
|
||||
t.integer "my_module_id"
|
||||
t.integer "user_id"
|
||||
create_table "activities", force: :cascade do |t|
|
||||
t.bigint "my_module_id"
|
||||
t.bigint "user_id"
|
||||
t.integer "type_of", null: false
|
||||
t.string "message", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "project_id", null: false
|
||||
t.integer "experiment_id"
|
||||
t.bigint "project_id", null: false
|
||||
t.bigint "experiment_id"
|
||||
t.index ["created_at"], name: "index_activities_on_created_at"
|
||||
t.index ["experiment_id"], name: "index_activities_on_experiment_id"
|
||||
t.index ["my_module_id"], name: "index_activities_on_my_module_id"
|
||||
|
@ -34,9 +34,9 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_activities_on_user_id"
|
||||
end
|
||||
|
||||
create_table "asset_text_data", id: :serial, force: :cascade do |t|
|
||||
create_table "asset_text_data", force: :cascade do |t|
|
||||
t.text "data", null: false
|
||||
t.integer "asset_id", null: false
|
||||
t.bigint "asset_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.tsvector "data_vector"
|
||||
|
@ -44,15 +44,15 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["data_vector"], name: "index_asset_text_data_on_data_vector", using: :gin
|
||||
end
|
||||
|
||||
create_table "assets", id: :serial, force: :cascade do |t|
|
||||
create_table "assets", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "file_file_name"
|
||||
t.string "file_content_type"
|
||||
t.integer "file_file_size"
|
||||
t.datetime "file_updated_at"
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.integer "estimated_size", default: 0, null: false
|
||||
t.boolean "file_present", default: false, null: false
|
||||
t.string "lock", limit: 1024
|
||||
|
@ -67,14 +67,14 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["team_id"], name: "index_assets_on_team_id"
|
||||
end
|
||||
|
||||
create_table "checklist_items", id: :serial, force: :cascade do |t|
|
||||
create_table "checklist_items", force: :cascade do |t|
|
||||
t.string "text", null: false
|
||||
t.boolean "checked", default: false, null: false
|
||||
t.integer "checklist_id", null: false
|
||||
t.bigint "checklist_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.integer "position"
|
||||
t.index "trim_html_tags((text)::text) gin_trgm_ops", name: "index_checklist_items_on_text", using: :gin
|
||||
t.index ["checklist_id"], name: "index_checklist_items_on_checklist_id"
|
||||
|
@ -82,25 +82,25 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["last_modified_by_id"], name: "index_checklist_items_on_last_modified_by_id"
|
||||
end
|
||||
|
||||
create_table "checklists", id: :serial, force: :cascade do |t|
|
||||
create_table "checklists", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.integer "step_id", null: false
|
||||
t.bigint "step_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_checklists_on_name", using: :gin
|
||||
t.index ["created_by_id"], name: "index_checklists_on_created_by_id"
|
||||
t.index ["last_modified_by_id"], name: "index_checklists_on_last_modified_by_id"
|
||||
t.index ["step_id"], name: "index_checklists_on_step_id"
|
||||
end
|
||||
|
||||
create_table "comments", id: :serial, force: :cascade do |t|
|
||||
create_table "comments", force: :cascade do |t|
|
||||
t.string "message", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.string "type"
|
||||
t.integer "associated_id"
|
||||
t.index "trim_html_tags((message)::text) gin_trgm_ops", name: "index_comments_on_message", using: :gin
|
||||
|
@ -111,26 +111,26 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_comments_on_user_id"
|
||||
end
|
||||
|
||||
create_table "connections", id: :serial, force: :cascade do |t|
|
||||
t.integer "input_id", null: false
|
||||
t.integer "output_id", null: false
|
||||
create_table "connections", force: :cascade do |t|
|
||||
t.bigint "input_id", null: false
|
||||
t.bigint "output_id", null: false
|
||||
t.index ["input_id"], name: "index_connections_on_input_id"
|
||||
t.index ["output_id"], name: "index_connections_on_output_id"
|
||||
end
|
||||
|
||||
create_table "custom_fields", id: :serial, force: :cascade do |t|
|
||||
create_table "custom_fields", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "team_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.index ["last_modified_by_id"], name: "index_custom_fields_on_last_modified_by_id"
|
||||
t.index ["team_id"], name: "index_custom_fields_on_team_id"
|
||||
t.index ["user_id"], name: "index_custom_fields_on_user_id"
|
||||
end
|
||||
|
||||
create_table "delayed_jobs", id: :serial, force: :cascade do |t|
|
||||
create_table "delayed_jobs", force: :cascade do |t|
|
||||
t.integer "priority", default: 0, null: false
|
||||
t.integer "attempts", default: 0, null: false
|
||||
t.text "handler", null: false
|
||||
|
@ -146,16 +146,16 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["queue"], name: "delayed_jobs_queue"
|
||||
end
|
||||
|
||||
create_table "experiments", id: :serial, force: :cascade do |t|
|
||||
create_table "experiments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.text "description"
|
||||
t.integer "project_id", null: false
|
||||
t.integer "created_by_id", null: false
|
||||
t.integer "last_modified_by_id", null: false
|
||||
t.bigint "created_by_id", null: false
|
||||
t.bigint "last_modified_by_id", null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.integer "archived_by_id"
|
||||
t.bigint "archived_by_id"
|
||||
t.datetime "archived_on"
|
||||
t.integer "restored_by_id"
|
||||
t.bigint "restored_by_id"
|
||||
t.datetime "restored_on"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
@ -171,53 +171,53 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["restored_by_id"], name: "index_experiments_on_restored_by_id"
|
||||
end
|
||||
|
||||
create_table "my_module_groups", id: :serial, force: :cascade do |t|
|
||||
create_table "my_module_groups", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "experiment_id", default: 0, null: false
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "experiment_id", default: 0, null: false
|
||||
t.index ["created_by_id"], name: "index_my_module_groups_on_created_by_id"
|
||||
t.index ["experiment_id"], name: "index_my_module_groups_on_experiment_id"
|
||||
end
|
||||
|
||||
create_table "my_module_repository_rows", id: :serial, force: :cascade do |t|
|
||||
create_table "my_module_repository_rows", force: :cascade do |t|
|
||||
t.bigint "repository_row_id", null: false
|
||||
t.integer "my_module_id"
|
||||
t.integer "assigned_by_id", null: false
|
||||
t.bigint "assigned_by_id", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["my_module_id", "repository_row_id"], name: "index_my_module_ids_repository_row_ids"
|
||||
t.index ["repository_row_id"], name: "index_my_module_repository_rows_on_repository_row_id"
|
||||
end
|
||||
|
||||
create_table "my_module_tags", id: :serial, force: :cascade do |t|
|
||||
create_table "my_module_tags", force: :cascade do |t|
|
||||
t.integer "my_module_id"
|
||||
t.integer "tag_id"
|
||||
t.integer "created_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.index ["created_by_id"], name: "index_my_module_tags_on_created_by_id"
|
||||
t.index ["my_module_id"], name: "index_my_module_tags_on_my_module_id"
|
||||
t.index ["tag_id"], name: "index_my_module_tags_on_tag_id"
|
||||
end
|
||||
|
||||
create_table "my_modules", id: :serial, force: :cascade do |t|
|
||||
create_table "my_modules", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.datetime "due_date"
|
||||
t.string "description"
|
||||
t.integer "x", default: 0, null: false
|
||||
t.integer "y", default: 0, null: false
|
||||
t.integer "my_module_group_id"
|
||||
t.bigint "my_module_group_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.datetime "archived_on"
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "archived_by_id"
|
||||
t.integer "restored_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.bigint "archived_by_id"
|
||||
t.bigint "restored_by_id"
|
||||
t.datetime "restored_on"
|
||||
t.integer "nr_of_assigned_samples", default: 0
|
||||
t.integer "workflow_order", default: -1, null: false
|
||||
t.integer "experiment_id", default: 0, null: false
|
||||
t.bigint "experiment_id", default: 0, null: false
|
||||
t.integer "state", limit: 2, default: 0
|
||||
t.datetime "completed_on"
|
||||
t.index "trim_html_tags((description)::text) gin_trgm_ops", name: "index_my_modules_on_description", using: :gin
|
||||
|
@ -230,18 +230,18 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["restored_by_id"], name: "index_my_modules_on_restored_by_id"
|
||||
end
|
||||
|
||||
create_table "notifications", id: :serial, force: :cascade do |t|
|
||||
create_table "notifications", force: :cascade do |t|
|
||||
t.string "title"
|
||||
t.string "message"
|
||||
t.integer "type_of", null: false
|
||||
t.integer "generator_user_id"
|
||||
t.bigint "generator_user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["created_at"], name: "index_notifications_on_created_at"
|
||||
end
|
||||
|
||||
create_table "oauth_access_grants", force: :cascade do |t|
|
||||
t.integer "resource_owner_id", null: false
|
||||
t.bigint "resource_owner_id", null: false
|
||||
t.bigint "application_id", null: false
|
||||
t.string "token", null: false
|
||||
t.integer "expires_in", null: false
|
||||
|
@ -254,7 +254,7 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
end
|
||||
|
||||
create_table "oauth_access_tokens", force: :cascade do |t|
|
||||
t.integer "resource_owner_id"
|
||||
t.bigint "resource_owner_id"
|
||||
t.bigint "application_id"
|
||||
t.text "token", null: false
|
||||
t.string "refresh_token"
|
||||
|
@ -281,19 +281,19 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
|
||||
end
|
||||
|
||||
create_table "projects", id: :serial, force: :cascade do |t|
|
||||
create_table "projects", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.integer "visibility", default: 0, null: false
|
||||
t.datetime "due_date"
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "team_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.datetime "archived_on"
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "archived_by_id"
|
||||
t.integer "restored_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.bigint "archived_by_id"
|
||||
t.bigint "restored_by_id"
|
||||
t.datetime "restored_on"
|
||||
t.string "experiments_order"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_projects_on_name", using: :gin
|
||||
|
@ -304,36 +304,36 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["team_id"], name: "index_projects_on_team_id"
|
||||
end
|
||||
|
||||
create_table "protocol_keywords", id: :serial, force: :cascade do |t|
|
||||
create_table "protocol_keywords", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "nr_of_protocols", default: 0
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "team_id", null: false
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_protocol_keywords_on_name", using: :gin
|
||||
t.index ["team_id"], name: "index_protocol_keywords_on_team_id"
|
||||
end
|
||||
|
||||
create_table "protocol_protocol_keywords", id: :serial, force: :cascade do |t|
|
||||
t.integer "protocol_id", null: false
|
||||
t.integer "protocol_keyword_id", null: false
|
||||
create_table "protocol_protocol_keywords", force: :cascade do |t|
|
||||
t.bigint "protocol_id", null: false
|
||||
t.bigint "protocol_keyword_id", null: false
|
||||
t.index ["protocol_id"], name: "index_protocol_protocol_keywords_on_protocol_id"
|
||||
t.index ["protocol_keyword_id"], name: "index_protocol_protocol_keywords_on_protocol_keyword_id"
|
||||
end
|
||||
|
||||
create_table "protocols", id: :serial, force: :cascade do |t|
|
||||
create_table "protocols", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.text "authors"
|
||||
t.text "description"
|
||||
t.integer "added_by_id"
|
||||
t.integer "my_module_id"
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "added_by_id"
|
||||
t.bigint "my_module_id"
|
||||
t.bigint "team_id", null: false
|
||||
t.integer "protocol_type", default: 0, null: false
|
||||
t.integer "parent_id"
|
||||
t.bigint "parent_id"
|
||||
t.datetime "parent_updated_at"
|
||||
t.integer "archived_by_id"
|
||||
t.bigint "archived_by_id"
|
||||
t.datetime "archived_on"
|
||||
t.integer "restored_by_id"
|
||||
t.bigint "restored_by_id"
|
||||
t.datetime "restored_on"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
@ -351,22 +351,22 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["team_id"], name: "index_protocols_on_team_id"
|
||||
end
|
||||
|
||||
create_table "report_elements", id: :serial, force: :cascade do |t|
|
||||
create_table "report_elements", force: :cascade do |t|
|
||||
t.integer "position", null: false
|
||||
t.integer "type_of", null: false
|
||||
t.integer "sort_order", default: 0
|
||||
t.integer "report_id"
|
||||
t.bigint "report_id"
|
||||
t.integer "parent_id"
|
||||
t.integer "project_id"
|
||||
t.integer "my_module_id"
|
||||
t.integer "step_id"
|
||||
t.integer "result_id"
|
||||
t.integer "checklist_id"
|
||||
t.integer "asset_id"
|
||||
t.integer "table_id"
|
||||
t.bigint "project_id"
|
||||
t.bigint "my_module_id"
|
||||
t.bigint "step_id"
|
||||
t.bigint "result_id"
|
||||
t.bigint "checklist_id"
|
||||
t.bigint "asset_id"
|
||||
t.bigint "table_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "experiment_id"
|
||||
t.bigint "experiment_id"
|
||||
t.integer "repository_id"
|
||||
t.index ["asset_id"], name: "index_report_elements_on_asset_id"
|
||||
t.index ["checklist_id"], name: "index_report_elements_on_checklist_id"
|
||||
|
@ -381,14 +381,14 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["table_id"], name: "index_report_elements_on_table_id"
|
||||
end
|
||||
|
||||
create_table "reports", id: :serial, force: :cascade do |t|
|
||||
create_table "reports", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "description"
|
||||
t.integer "project_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "project_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.bigint "team_id"
|
||||
t.index "trim_html_tags((description)::text) gin_trgm_ops", name: "index_reports_on_description", using: :gin
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_reports_on_name", using: :gin
|
||||
|
@ -398,9 +398,9 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_reports_on_user_id"
|
||||
end
|
||||
|
||||
create_table "repositories", id: :serial, force: :cascade do |t|
|
||||
create_table "repositories", force: :cascade do |t|
|
||||
t.integer "team_id"
|
||||
t.integer "created_by_id", null: false
|
||||
t.bigint "created_by_id", null: false
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
|
@ -434,7 +434,7 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
|
||||
create_table "repository_columns", force: :cascade do |t|
|
||||
t.integer "repository_id"
|
||||
t.integer "created_by_id", null: false
|
||||
t.bigint "created_by_id", null: false
|
||||
t.string "name"
|
||||
t.integer "data_type", null: false
|
||||
t.datetime "created_at"
|
||||
|
@ -446,8 +446,8 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.datetime "data"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "created_by_id", null: false
|
||||
t.integer "last_modified_by_id", null: false
|
||||
t.bigint "created_by_id", null: false
|
||||
t.bigint "last_modified_by_id", null: false
|
||||
end
|
||||
|
||||
create_table "repository_list_items", force: :cascade do |t|
|
||||
|
@ -478,8 +478,8 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
|
||||
create_table "repository_rows", force: :cascade do |t|
|
||||
t.integer "repository_id"
|
||||
t.integer "created_by_id", null: false
|
||||
t.integer "last_modified_by_id", null: false
|
||||
t.bigint "created_by_id", null: false
|
||||
t.bigint "last_modified_by_id", null: false
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
|
@ -487,7 +487,7 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["repository_id"], name: "index_repository_rows_on_repository_id"
|
||||
end
|
||||
|
||||
create_table "repository_table_states", id: :serial, force: :cascade do |t|
|
||||
create_table "repository_table_states", force: :cascade do |t|
|
||||
t.jsonb "state", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.integer "repository_id", null: false
|
||||
|
@ -501,41 +501,41 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.string "data"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "created_by_id", null: false
|
||||
t.integer "last_modified_by_id", null: false
|
||||
t.bigint "created_by_id", null: false
|
||||
t.bigint "last_modified_by_id", null: false
|
||||
t.index "trim_html_tags((data)::text) gin_trgm_ops", name: "index_repository_text_values_on_data", using: :gin
|
||||
end
|
||||
|
||||
create_table "result_assets", id: :serial, force: :cascade do |t|
|
||||
t.integer "result_id", null: false
|
||||
t.integer "asset_id", null: false
|
||||
create_table "result_assets", force: :cascade do |t|
|
||||
t.bigint "result_id", null: false
|
||||
t.bigint "asset_id", null: false
|
||||
t.index ["result_id", "asset_id"], name: "index_result_assets_on_result_id_and_asset_id"
|
||||
end
|
||||
|
||||
create_table "result_tables", id: :serial, force: :cascade do |t|
|
||||
t.integer "result_id", null: false
|
||||
t.integer "table_id", null: false
|
||||
create_table "result_tables", force: :cascade do |t|
|
||||
t.bigint "result_id", null: false
|
||||
t.bigint "table_id", null: false
|
||||
t.index ["result_id", "table_id"], name: "index_result_tables_on_result_id_and_table_id"
|
||||
end
|
||||
|
||||
create_table "result_texts", id: :serial, force: :cascade do |t|
|
||||
create_table "result_texts", force: :cascade do |t|
|
||||
t.string "text", null: false
|
||||
t.integer "result_id", null: false
|
||||
t.bigint "result_id", null: false
|
||||
t.index "trim_html_tags((text)::text) gin_trgm_ops", name: "index_result_texts_on_text", using: :gin
|
||||
t.index ["result_id"], name: "index_result_texts_on_result_id"
|
||||
end
|
||||
|
||||
create_table "results", id: :serial, force: :cascade do |t|
|
||||
create_table "results", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.integer "my_module_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "my_module_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.datetime "archived_on"
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "archived_by_id"
|
||||
t.integer "restored_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.bigint "archived_by_id"
|
||||
t.bigint "restored_by_id"
|
||||
t.datetime "restored_on"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_results_on_name", using: :gin
|
||||
t.index ["archived_by_id"], name: "index_results_on_archived_by_id"
|
||||
|
@ -546,10 +546,10 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_results_on_user_id"
|
||||
end
|
||||
|
||||
create_table "sample_custom_fields", id: :serial, force: :cascade do |t|
|
||||
create_table "sample_custom_fields", force: :cascade do |t|
|
||||
t.string "value", null: false
|
||||
t.integer "custom_field_id", null: false
|
||||
t.integer "sample_id"
|
||||
t.bigint "custom_field_id", null: false
|
||||
t.bigint "sample_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index "trim_html_tags((value)::text) gin_trgm_ops", name: "index_sample_custom_fields_on_value", using: :gin
|
||||
|
@ -557,52 +557,52 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["sample_id"], name: "index_sample_custom_fields_on_sample_id"
|
||||
end
|
||||
|
||||
create_table "sample_groups", id: :serial, force: :cascade do |t|
|
||||
create_table "sample_groups", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "color", default: "#ff0000", null: false
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "team_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_sample_groups_on_name", using: :gin
|
||||
t.index ["created_by_id"], name: "index_sample_groups_on_created_by_id"
|
||||
t.index ["last_modified_by_id"], name: "index_sample_groups_on_last_modified_by_id"
|
||||
t.index ["team_id"], name: "index_sample_groups_on_team_id"
|
||||
end
|
||||
|
||||
create_table "sample_my_modules", id: :serial, force: :cascade do |t|
|
||||
t.integer "sample_id", null: false
|
||||
t.integer "my_module_id", null: false
|
||||
t.integer "assigned_by_id"
|
||||
create_table "sample_my_modules", force: :cascade do |t|
|
||||
t.bigint "sample_id", null: false
|
||||
t.bigint "my_module_id", null: false
|
||||
t.bigint "assigned_by_id"
|
||||
t.datetime "assigned_on"
|
||||
t.index ["assigned_by_id"], name: "index_sample_my_modules_on_assigned_by_id"
|
||||
t.index ["my_module_id"], name: "index_sample_my_modules_on_my_module_id"
|
||||
t.index ["sample_id", "my_module_id"], name: "index_sample_my_modules_on_sample_id_and_my_module_id"
|
||||
end
|
||||
|
||||
create_table "sample_types", id: :serial, force: :cascade do |t|
|
||||
create_table "sample_types", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "team_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_sample_types_on_name", using: :gin
|
||||
t.index ["created_by_id"], name: "index_sample_types_on_created_by_id"
|
||||
t.index ["last_modified_by_id"], name: "index_sample_types_on_last_modified_by_id"
|
||||
t.index ["team_id"], name: "index_sample_types_on_team_id"
|
||||
end
|
||||
|
||||
create_table "samples", id: :serial, force: :cascade do |t|
|
||||
create_table "samples", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "team_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "sample_group_id"
|
||||
t.integer "sample_type_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "sample_group_id"
|
||||
t.bigint "sample_type_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.integer "nr_of_modules_assigned_to", default: 0
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_samples_on_name", using: :gin
|
||||
t.index ["last_modified_by_id"], name: "index_samples_on_last_modified_by_id"
|
||||
|
@ -612,7 +612,7 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_samples_on_user_id"
|
||||
end
|
||||
|
||||
create_table "samples_tables", id: :serial, force: :cascade do |t|
|
||||
create_table "samples_tables", force: :cascade do |t|
|
||||
t.jsonb "status", default: {"time"=>0, "order"=>[[2, "desc"]], "start"=>0, "length"=>10, "search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "columns"=>[{"search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "visible"=>true}, {"search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "visible"=>true}, {"search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "visible"=>true}, {"search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "visible"=>true}, {"search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "visible"=>true}, {"search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "visible"=>true}, {"search"=>{"regex"=>false, "smart"=>true, "search"=>"", "caseInsensitive"=>true}, "visible"=>true}], "assigned"=>"all", "ColReorder"=>[0, 1, 2, 3, 4, 5, 6]}, null: false
|
||||
t.integer "user_id", null: false
|
||||
t.integer "team_id", null: false
|
||||
|
@ -622,35 +622,35 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_samples_tables_on_user_id"
|
||||
end
|
||||
|
||||
create_table "settings", id: :serial, force: :cascade do |t|
|
||||
create_table "settings", force: :cascade do |t|
|
||||
t.text "type", null: false
|
||||
t.jsonb "values", default: {}, null: false
|
||||
t.index ["type"], name: "index_settings_on_type", unique: true
|
||||
end
|
||||
|
||||
create_table "step_assets", id: :serial, force: :cascade do |t|
|
||||
t.integer "step_id", null: false
|
||||
t.integer "asset_id", null: false
|
||||
create_table "step_assets", force: :cascade do |t|
|
||||
t.bigint "step_id", null: false
|
||||
t.bigint "asset_id", null: false
|
||||
t.index ["step_id", "asset_id"], name: "index_step_assets_on_step_id_and_asset_id"
|
||||
end
|
||||
|
||||
create_table "step_tables", id: :serial, force: :cascade do |t|
|
||||
t.integer "step_id", null: false
|
||||
t.integer "table_id", null: false
|
||||
create_table "step_tables", force: :cascade do |t|
|
||||
t.bigint "step_id", null: false
|
||||
t.bigint "table_id", null: false
|
||||
t.index ["step_id", "table_id"], name: "index_step_tables_on_step_id_and_table_id", unique: true
|
||||
end
|
||||
|
||||
create_table "steps", id: :serial, force: :cascade do |t|
|
||||
create_table "steps", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "description"
|
||||
t.integer "position", null: false
|
||||
t.boolean "completed", null: false
|
||||
t.datetime "completed_on"
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "protocol_id", null: false
|
||||
t.bigint "last_modified_by_id"
|
||||
t.bigint "protocol_id", null: false
|
||||
t.index "trim_html_tags((description)::text) gin_trgm_ops", name: "index_steps_on_description", using: :gin
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_steps_on_name", using: :gin
|
||||
t.index ["created_at"], name: "index_steps_on_created_at"
|
||||
|
@ -660,12 +660,12 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_steps_on_user_id"
|
||||
end
|
||||
|
||||
create_table "tables", id: :serial, force: :cascade do |t|
|
||||
create_table "tables", force: :cascade do |t|
|
||||
t.binary "contents", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.tsvector "data_vector"
|
||||
t.string "name", default: ""
|
||||
t.integer "team_id"
|
||||
|
@ -677,26 +677,26 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["team_id"], name: "index_tables_on_team_id"
|
||||
end
|
||||
|
||||
create_table "tags", id: :serial, force: :cascade do |t|
|
||||
create_table "tags", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "color", default: "#ff0000", null: false
|
||||
t.integer "project_id", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "project_id", null: false
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_tags_on_name", using: :gin
|
||||
t.index ["created_by_id"], name: "index_tags_on_created_by_id"
|
||||
t.index ["last_modified_by_id"], name: "index_tags_on_last_modified_by_id"
|
||||
t.index ["project_id"], name: "index_tags_on_project_id"
|
||||
end
|
||||
|
||||
create_table "teams", id: :serial, force: :cascade do |t|
|
||||
create_table "teams", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.bigint "created_by_id"
|
||||
t.bigint "last_modified_by_id"
|
||||
t.string "description"
|
||||
t.bigint "space_taken", default: 1048576, null: false
|
||||
t.index ["created_by_id"], name: "index_teams_on_created_by_id"
|
||||
|
@ -704,7 +704,7 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["name"], name: "index_teams_on_name"
|
||||
end
|
||||
|
||||
create_table "temp_files", id: :serial, force: :cascade do |t|
|
||||
create_table "temp_files", force: :cascade do |t|
|
||||
t.string "session_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
@ -714,7 +714,7 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.datetime "file_updated_at"
|
||||
end
|
||||
|
||||
create_table "tiny_mce_assets", id: :serial, force: :cascade do |t|
|
||||
create_table "tiny_mce_assets", force: :cascade do |t|
|
||||
t.string "image_file_name"
|
||||
t.string "image_content_type"
|
||||
t.integer "image_file_size"
|
||||
|
@ -730,13 +730,13 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["team_id"], name: "index_tiny_mce_assets_on_team_id"
|
||||
end
|
||||
|
||||
create_table "tokens", id: :serial, force: :cascade do |t|
|
||||
create_table "tokens", force: :cascade do |t|
|
||||
t.string "token", null: false
|
||||
t.integer "ttl", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
end
|
||||
|
||||
create_table "user_identities", id: :serial, force: :cascade do |t|
|
||||
create_table "user_identities", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.string "provider", null: false
|
||||
t.string "uid", null: false
|
||||
|
@ -747,20 +747,20 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_user_identities_on_user_id"
|
||||
end
|
||||
|
||||
create_table "user_my_modules", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "my_module_id", null: false
|
||||
create_table "user_my_modules", force: :cascade do |t|
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "my_module_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "assigned_by_id"
|
||||
t.bigint "assigned_by_id"
|
||||
t.index ["assigned_by_id"], name: "index_user_my_modules_on_assigned_by_id"
|
||||
t.index ["my_module_id"], name: "index_user_my_modules_on_my_module_id"
|
||||
t.index ["user_id"], name: "index_user_my_modules_on_user_id"
|
||||
end
|
||||
|
||||
create_table "user_notifications", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "notification_id"
|
||||
create_table "user_notifications", force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.bigint "notification_id"
|
||||
t.boolean "checked", default: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
@ -769,31 +769,31 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["user_id"], name: "index_user_notifications_on_user_id"
|
||||
end
|
||||
|
||||
create_table "user_projects", id: :serial, force: :cascade do |t|
|
||||
create_table "user_projects", force: :cascade do |t|
|
||||
t.integer "role"
|
||||
t.integer "user_id", null: false
|
||||
t.integer "project_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "project_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "assigned_by_id"
|
||||
t.bigint "assigned_by_id"
|
||||
t.index ["assigned_by_id"], name: "index_user_projects_on_assigned_by_id"
|
||||
t.index ["project_id"], name: "index_user_projects_on_project_id"
|
||||
t.index ["user_id"], name: "index_user_projects_on_user_id"
|
||||
end
|
||||
|
||||
create_table "user_teams", id: :serial, force: :cascade do |t|
|
||||
create_table "user_teams", force: :cascade do |t|
|
||||
t.integer "role", default: 1, null: false
|
||||
t.integer "user_id", null: false
|
||||
t.integer "team_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "team_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "assigned_by_id"
|
||||
t.bigint "assigned_by_id"
|
||||
t.index ["assigned_by_id"], name: "index_user_teams_on_assigned_by_id"
|
||||
t.index ["team_id"], name: "index_user_teams_on_team_id"
|
||||
t.index ["user_id"], name: "index_user_teams_on_user_id"
|
||||
end
|
||||
|
||||
create_table "users", id: :serial, force: :cascade do |t|
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "full_name", null: false
|
||||
t.string "initials", null: false
|
||||
t.string "email", default: "", null: false
|
||||
|
@ -824,7 +824,7 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.string "invited_by_type"
|
||||
t.integer "invited_by_id"
|
||||
t.integer "invitations_count", default: 0
|
||||
t.integer "current_team_id"
|
||||
t.bigint "current_team_id"
|
||||
t.string "authentication_token", limit: 30
|
||||
t.jsonb "settings", default: {}, null: false
|
||||
t.jsonb "variables", default: {}, null: false
|
||||
|
@ -849,21 +849,21 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.index ["viewable_type", "viewable_id"], name: "index_view_states_on_viewable_type_and_viewable_id"
|
||||
end
|
||||
|
||||
create_table "wopi_actions", id: :serial, force: :cascade do |t|
|
||||
create_table "wopi_actions", force: :cascade do |t|
|
||||
t.string "action", null: false
|
||||
t.string "extension", null: false
|
||||
t.string "urlsrc", null: false
|
||||
t.integer "wopi_app_id", null: false
|
||||
t.bigint "wopi_app_id", null: false
|
||||
t.index ["extension", "action"], name: "index_wopi_actions_on_extension_and_action"
|
||||
end
|
||||
|
||||
create_table "wopi_apps", id: :serial, force: :cascade do |t|
|
||||
create_table "wopi_apps", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "icon", null: false
|
||||
t.integer "wopi_discovery_id", null: false
|
||||
t.bigint "wopi_discovery_id", null: false
|
||||
end
|
||||
|
||||
create_table "wopi_discoveries", id: :serial, force: :cascade do |t|
|
||||
create_table "wopi_discoveries", force: :cascade do |t|
|
||||
t.integer "expires", null: false
|
||||
t.string "proof_key_mod", null: false
|
||||
t.string "proof_key_exp", null: false
|
||||
|
@ -871,8 +871,8 @@ ActiveRecord::Schema.define(version: 20181212162649) do
|
|||
t.string "proof_key_old_exp", null: false
|
||||
end
|
||||
|
||||
create_table "zip_exports", id: :serial, force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
create_table "zip_exports", force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "zip_file_file_name"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :exportable_items do
|
||||
desc 'Removes exportable zip files'
|
||||
task cleanup: :environment do
|
||||
|
@ -6,23 +8,4 @@ namespace :exportable_items do
|
|||
puts "All exportable zip files older than " \
|
||||
"'#{num.days.ago}' have been removed"
|
||||
end
|
||||
|
||||
desc 'Resets export project counter to 0'
|
||||
task reset_export_projects_counter: :environment do
|
||||
User.find_each do |user|
|
||||
User.transaction do
|
||||
begin
|
||||
user.export_vars['num_of_export_all_last_24_hours'] = 0
|
||||
user.save
|
||||
rescue ActiveRecord::ActiveRecordError,
|
||||
ArgumentError,
|
||||
ActiveRecord::RecordNotSaved => e
|
||||
puts "Error resetting users num_of_export_all_last_24_hours " \
|
||||
"variable to 0, transaction reverted: #{e}"
|
||||
end
|
||||
end
|
||||
end
|
||||
puts 'Export project counter successfully ' \
|
||||
'reset on all users'
|
||||
end
|
||||
end
|
||||
|
|
10
pull_request_template.md
Normal file
10
pull_request_template.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
Jira ticket: [SCI-xxxx](https://biosistemika.atlassian.net/browse/SCI-xxxx)
|
||||
|
||||
### What was done
|
||||
_In a sentence or two describe what was done, focus on the tech aspect_
|
||||
|
||||
#### ToDo:
|
||||
_(Optional) If you have discovered an oppurtunity for refactor or another task that was not part of the jira issue, please write it here_
|
||||
|
||||
### Note:
|
||||
_(Optional) Note any other remarks, problems you were facing during the development, suggestion etc_
|
|
@ -54,11 +54,15 @@ describe ProjectsController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Security/Eval
|
||||
# rubocop:disable Style/EvalWithLocation
|
||||
(1..PROJECTS_CNT).each do |i|
|
||||
let!("user_projects_#{i}") do
|
||||
create :user_project, project: eval("project_#{i}"), user: user
|
||||
create :user_project, :owner, project: eval("project_#{i}"), user: user
|
||||
end
|
||||
end
|
||||
# rubocop:enable Security/Eval
|
||||
# rubocop:enable Style/EvalWithLocation
|
||||
|
||||
describe '#index' do
|
||||
context 'in JSON format' do
|
||||
|
|
|
@ -12,7 +12,7 @@ FactoryBot.define do
|
|||
project { create :project, created_by: user }
|
||||
factory :experiment_with_tasks do
|
||||
after(:create) do |e|
|
||||
create_list :my_module, 3, experiment: e
|
||||
create_list :my_module, 3, :with_tag, experiment: e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,5 +8,8 @@ FactoryBot.define do
|
|||
workflow_order { MyModule.where(experiment_id: experiment.id).count + 1 }
|
||||
experiment
|
||||
my_module_group { create :my_module_group, experiment: experiment }
|
||||
trait :with_tag do
|
||||
tags { create_list :tag, 3, project: experiment.project }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
9
spec/factories/tags.rb
Normal file
9
spec/factories/tags.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :tag do
|
||||
sequence(:name) { |n| "My tag-#{n}" }
|
||||
project
|
||||
color { Faker::Color.hex_color }
|
||||
end
|
||||
end
|
|
@ -7,5 +7,8 @@ FactoryBot.define do
|
|||
description { Faker::Lorem.sentence }
|
||||
space_taken { 1048576 }
|
||||
without_intro_demo true
|
||||
trait :with_members do
|
||||
users { create_list :user, 3 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
FactoryBot.define do
|
||||
factory :user_project do
|
||||
role 'owner'
|
||||
end
|
||||
end
|
21
spec/factories/user_projects.rb
Normal file
21
spec/factories/user_projects.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :user_project do
|
||||
user
|
||||
project
|
||||
assigned_by { user }
|
||||
trait :owner do
|
||||
role { UserProject.roles[:owner] }
|
||||
end
|
||||
trait :normal_user do
|
||||
role { UserProject.roles[:normal_user] }
|
||||
end
|
||||
trait :technician do
|
||||
role { UserProject.roles[:technician] }
|
||||
end
|
||||
trait :viewer do
|
||||
role { UserProject.roles[:viewer] }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Experiment, type: :model do
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Tag, type: :model do
|
||||
|
@ -23,15 +25,46 @@ describe Tag, type: :model do
|
|||
it { should have_many :my_modules }
|
||||
end
|
||||
|
||||
describe 'Should be a valid object' do
|
||||
it { should validate_presence_of :name }
|
||||
it { should validate_presence_of :color }
|
||||
it { should validate_presence_of :project }
|
||||
it do
|
||||
should validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH)
|
||||
describe 'Validations' do
|
||||
describe '#name' do
|
||||
it { should validate_presence_of :name }
|
||||
it do
|
||||
should validate_length_of(:name)
|
||||
.is_at_most(Constants::NAME_MAX_LENGTH)
|
||||
end
|
||||
end
|
||||
it do
|
||||
should validate_length_of(:color).is_at_most(Constants::COLOR_MAX_LENGTH)
|
||||
|
||||
describe '#color' do
|
||||
it { should validate_presence_of :color }
|
||||
it do
|
||||
should validate_length_of(:color)
|
||||
.is_at_most(Constants::COLOR_MAX_LENGTH)
|
||||
end
|
||||
end
|
||||
describe '#projects' do
|
||||
it { should validate_presence_of :project }
|
||||
end
|
||||
end
|
||||
describe '.clone_to_project_or_return_existing' do
|
||||
let(:project) { create :project }
|
||||
let(:tag) { create :tag }
|
||||
|
||||
context 'when tag does not exits' do
|
||||
it 'does create new tag for project' do
|
||||
expect do
|
||||
tag.clone_to_project_or_return_existing(project)
|
||||
end.to(change { Tag.where(project_id: project.id).count })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tag already exists' do
|
||||
it 'return existing tag for project' do
|
||||
Tag.create(name: tag.name, color: tag.color, project: project)
|
||||
|
||||
expect do
|
||||
tag.clone_to_project_or_return_existing(project)
|
||||
end.to_not(change { Tag.where(project_id: project.id).count })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -187,7 +187,9 @@ describe User, type: :model do
|
|||
describe '#last_activities' do
|
||||
let!(:user) { create :user }
|
||||
let!(:project) { create :project }
|
||||
let!(:user_projects) { create :user_project, project: project, user: user }
|
||||
let!(:user_projects) do
|
||||
create :user_project, :viewer, project: project, user: user
|
||||
end
|
||||
let!(:activity_one) { create :activity, user: user, project: project }
|
||||
let!(:activity_two) { create :activity, user: user, project: project }
|
||||
|
||||
|
@ -198,4 +200,62 @@ describe User, type: :model do
|
|||
expect(activities).to include activity_two
|
||||
end
|
||||
end
|
||||
|
||||
describe '#increase_daily_exports_counter!' do
|
||||
let(:user) { create :user }
|
||||
context 'when last_export_timestamp is set' do
|
||||
it 'increases counter by 1' do
|
||||
expect { user.increase_daily_exports_counter! }
|
||||
.to change {
|
||||
user.reload.export_vars['num_of_export_all_last_24_hours']
|
||||
}.from(0).to(1)
|
||||
end
|
||||
|
||||
it 'sets last_export_timestamp on today' do
|
||||
user.export_vars['last_export_timestamp'] = Date.yesterday.to_time.to_i
|
||||
user.save
|
||||
|
||||
expect { user.increase_daily_exports_counter! }
|
||||
.to change {
|
||||
user.reload.export_vars['last_export_timestamp']
|
||||
}.to(Date.today.to_time.to_i)
|
||||
end
|
||||
|
||||
it 'sets new counter for today' do
|
||||
user.export_vars = {
|
||||
'num_of_export_all_last_24_hours': 3,
|
||||
'last_export_timestamp': Date.yesterday.to_time.to_i
|
||||
}
|
||||
user.save
|
||||
|
||||
expect { user.increase_daily_exports_counter! }
|
||||
.to change {
|
||||
user.reload.export_vars['num_of_export_all_last_24_hours']
|
||||
}.from(3).to(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when last_export_timestamp not exists (existing users)' do
|
||||
it 'sets last_export_timestamp on today' do
|
||||
user.export_vars.delete('last_export_timestamp')
|
||||
user.save
|
||||
|
||||
expect { user.increase_daily_exports_counter! }
|
||||
.to change {
|
||||
user.reload.export_vars['last_export_timestamp']
|
||||
}.from(nil).to(Date.today.to_time.to_i)
|
||||
end
|
||||
|
||||
it 'starts count reports with 1' do
|
||||
user.export_vars.delete('last_export_timestamp')
|
||||
user.export_vars['num_of_export_all_last_24_hours'] = 2
|
||||
user.save
|
||||
|
||||
expect { user.increase_daily_exports_counter! }
|
||||
.to change {
|
||||
user.reload.export_vars['num_of_export_all_last_24_hours']
|
||||
}.from(2).to(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,9 +4,9 @@ require 'rails_helper'
|
|||
|
||||
RSpec.describe 'Api::V1::UsersController', type: :request do
|
||||
before :all do
|
||||
@user1 = create(:user, email: Faker::Internet.unique.email)
|
||||
@user2 = create(:user, email: Faker::Internet.unique.email)
|
||||
@user3 = create(:user, email: Faker::Internet.unique.email)
|
||||
@user1 = create(:user)
|
||||
@user2 = create(:user)
|
||||
@user3 = create(:user)
|
||||
@team1 = create(:team, created_by: @user1)
|
||||
@team2 = create(:team, created_by: @user2)
|
||||
@team3 = create(:team, created_by: @user3)
|
||||
|
@ -40,7 +40,7 @@ RSpec.describe 'Api::V1::UsersController', type: :request do
|
|||
|
||||
it 'When invalid request, non existing user' do
|
||||
hash_body = nil
|
||||
get api_v1_user_path(id: 123), headers: @valid_headers
|
||||
get api_v1_user_path(id: -1), headers: @valid_headers
|
||||
expect(response).to have_http_status(403)
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body['errors'][0]).to include('status': 403)
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Experiments::CopyExperimentAsTemplateService do
|
||||
let(:team) { create :team, :with_members }
|
||||
let(:user_project) { create :user_project, :normal_user, user: user }
|
||||
|
||||
let(:project) do
|
||||
create :project, team: team
|
||||
end
|
||||
let(:new_project) do
|
||||
create :project, team: team, user_projects: [user_project]
|
||||
end
|
||||
let(:experiment) do
|
||||
create :experiment_with_tasks, name: 'MyExp', project: project
|
||||
end
|
||||
let(:user) { create :user }
|
||||
let(:service_call) do
|
||||
Experiments::CopyExperimentAsTemplateService
|
||||
.call(experiment_id: experiment.id,
|
||||
project_id: new_project.id,
|
||||
user_id: user.id)
|
||||
end
|
||||
|
||||
context 'when service call is successful' do
|
||||
it 'adds new experiment to target project' do
|
||||
expect { service_call }.to(change { new_project.experiments.count })
|
||||
end
|
||||
|
||||
it 'adds Activity record' do
|
||||
expect { service_call }.to(change { Activity.all.count })
|
||||
end
|
||||
|
||||
it 'copies all tasks to new experiment' do
|
||||
expect(service_call.cloned_experiment.my_modules.count)
|
||||
.to be == experiment.my_modules.count
|
||||
end
|
||||
|
||||
it 'copies all task groups to new experiment' do
|
||||
expect(service_call.cloned_experiment.my_module_groups.count)
|
||||
.to be == experiment.my_module_groups.count
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service call is not successful' do
|
||||
it 'returns an error when can\'t find experiment' do
|
||||
allow(Experiment).to receive(:find).and_return(nil)
|
||||
|
||||
expect(service_call.errors).to have_key(:invalid_arguments)
|
||||
end
|
||||
|
||||
it 'returns error when user don\'t have permissions' do
|
||||
expect_any_instance_of(Experiment)
|
||||
.to receive(:projects_with_role_above_user).and_return([])
|
||||
|
||||
expect(service_call.errors).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
98
spec/services/experiments/move_to_project_service_spec.rb
Normal file
98
spec/services/experiments/move_to_project_service_spec.rb
Normal file
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Experiments::MoveToProjectService do
|
||||
let(:team) { create :team, :with_members }
|
||||
let(:project) do
|
||||
create :project, team: team, user_projects: []
|
||||
end
|
||||
let(:new_project) do
|
||||
create :project, team: team, user_projects: [user_project2]
|
||||
end
|
||||
let(:experiment) do
|
||||
create :experiment_with_tasks, name: 'MyExp', project: project
|
||||
end
|
||||
let(:user) { create :user }
|
||||
let(:user_project2) { create :user_project, :normal_user, user: user }
|
||||
let(:service_call) do
|
||||
Experiments::MoveToProjectService.call(experiment_id: experiment.id,
|
||||
project_id: new_project.id,
|
||||
user_id: user.id)
|
||||
end
|
||||
|
||||
context 'when call with valid params' do
|
||||
it 'unnasigns experiment from project' do
|
||||
service_call
|
||||
|
||||
expect(project.experiments.pluck(:id)).to_not include(experiment.id)
|
||||
end
|
||||
|
||||
it 'assigns experiment to new_project' do
|
||||
service_call
|
||||
|
||||
expect(new_project.experiments.pluck(:id)).to include(experiment.id)
|
||||
end
|
||||
|
||||
it 'copies tags to new project' do
|
||||
expect { service_call }.to(change { new_project.tags.count })
|
||||
end
|
||||
|
||||
it 'leaves tags on an old project' do
|
||||
experiment # explicit call to create tags
|
||||
expect { service_call }.not_to(change { project.tags.count })
|
||||
end
|
||||
|
||||
it 'sets new project tags to modules' do
|
||||
service_call
|
||||
new_tags = experiment.my_modules.map { |m| m.tags.map { |t| t } }.flatten
|
||||
tags_project_id = new_tags.map(&:project_id).uniq.first
|
||||
|
||||
expect(tags_project_id).to be == new_project.id
|
||||
end
|
||||
|
||||
it 'adds Activity record' do
|
||||
expect { service_call }.to(change { Activity.all.count })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when call with invalid params' do
|
||||
it 'returns an error when can\'t find user and project' do
|
||||
allow(Project).to receive(:find).and_return(nil)
|
||||
allow(User).to receive(:find).and_return(nil)
|
||||
|
||||
expect(service_call.errors).to have_key(:invalid_arguments)
|
||||
end
|
||||
|
||||
it 'returns an error on validate' do
|
||||
FactoryBot.create :experiment, project: new_project, name: 'MyExp'
|
||||
expect(service_call.succeed?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns an error on saving' do
|
||||
expect_any_instance_of(Experiments::MoveToProjectService)
|
||||
.to receive(:valid?)
|
||||
.and_return(true)
|
||||
FactoryBot.create :experiment, project: new_project, name: 'MyExp'
|
||||
|
||||
expect(service_call.succeed?).to be_falsey
|
||||
end
|
||||
|
||||
it 'rollbacks cloned tags after unsucessful save' do
|
||||
expect_any_instance_of(Experiments::MoveToProjectService)
|
||||
.to receive(:valid?)
|
||||
.and_return(true)
|
||||
FactoryBot.create :experiment, project: new_project, name: 'MyExp'
|
||||
experiment
|
||||
|
||||
expect { service_call }.not_to(change { Tag.count })
|
||||
end
|
||||
|
||||
it 'returns error if teams is not the same' do
|
||||
t = create :team, :with_members
|
||||
project.update(team: t)
|
||||
|
||||
expect(service_call.errors).to have_key(:target_project_not_valid)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue