Merge branch 'develop' into features/inventory-items-relationships

This commit is contained in:
Oleksii Kriuchykhin 2024-01-05 11:27:32 +01:00
commit 95ab33a58b
98 changed files with 3505 additions and 184 deletions

View file

@ -100,8 +100,18 @@
}
}
function initActionButtons() {
initUpdatePDFReport();
initGenerateDocxReport();
initUpdateDocxReport();
initEditReport();
initSaveReportPDFToInventory();
initDeleteReports();
}
function updateButtons() {
if (window.actionToolbarComponent) {
window.actionToolbarComponent.setActionsLoadedCallback(initActionButtons);
window.actionToolbarComponent.fetchActions({ report_ids: CHECKBOX_SELECTOR.selectedRows });
$('.dataTables_scrollBody').css('padding-bottom', `${CHECKBOX_SELECTOR.selectedRows.length > 0 ? 68 : 0}px`);
}
@ -263,7 +273,7 @@
}
function initUpdatePDFReport() {
$(document).on('click', '#updatePdf', function(ev) {
$('#updatePdf').on('click', function(ev) {
ev.stopPropagation();
ev.preventDefault();
@ -283,7 +293,7 @@
}
function initGenerateDocxReport() {
$(document).on('click', '#requestDocx', function(ev) {
$('#requestDocx').on('click', function(ev) {
ev.stopPropagation();
ev.preventDefault();
$(this).closest('.dropdown-menu').dropdown('toggle');
@ -292,7 +302,7 @@
}
function initUpdateDocxReport() {
$(document).on('click', '#updateDocx', function(ev) {
$('#updateDocx').on('click', function(ev) {
ev.stopPropagation();
ev.preventDefault();
@ -325,7 +335,7 @@
}
function initSaveReportPDFToInventory() {
$(document).on('click', '#savePdfToInventoryButton', function(ev) {
$('#savePdfToInventoryButton').on('click', function(ev) {
ev.preventDefault();
ev.stopPropagation();
@ -347,7 +357,7 @@
}
function initDeleteReports() {
$(document).on('click', '#delete-reports-btn', function() {
$('#delete-reports-btn').on('click', function() {
if (CHECKBOX_SELECTOR.selectedRows.length > 0) {
$('#report-ids').attr('value', '[' + CHECKBOX_SELECTOR.selectedRows + ']');
$('#delete-reports-modal').modal('show');
@ -376,10 +386,4 @@
$('#show_report_preview').click();
initDatatable();
initUpdatePDFReport();
initGenerateDocxReport();
initUpdateDocxReport();
initEditReport();
initSaveReportPDFToInventory();
initDeleteReports();
}());

View file

@ -67,7 +67,7 @@
orderable: false,
render: function() {
return `<div class="sci-checkbox-container">
<input class='repository-row-selector sci-checkbox' type='checkbox' data-e2e="e2e-CB-inventory">
<input class='repository-row-selector sci-checkbox' type='checkbox' data-e2e="e2e-CB-inventories-all">
<span class='sci-checkbox-label'></span>
</div>`;
}

View file

@ -15,7 +15,8 @@ $.fn.dataTable.render.editRowName = function(formId, cell) {
name="repository_row[name]"
value=""
placeholder="${I18n.t('repositories.table.enter_row_name')}"
data-type="RowName">
data-type="RowName"
data-e2e="e2e-IF-invInventoryEditItemTR-name">
</div>
`);
$cell.find('input').val(text);

View file

@ -13,7 +13,8 @@ $.fn.dataTable.render.newRowName = function(formId, $cell) {
name="repository_row[name]"
value=""
placeholder="${I18n.t('repositories.table.enter_row_name')}"
data-type="RowName">
data-type="RowName"
data-e2e="e2e-IF-invInventoryNewItemTR-name">
</div>
`);
};

View file

@ -671,7 +671,7 @@ var RepositoryDatatable = (function(global) {
visible: true,
render: function(data, type, row) {
return "<a href='" + row.recordInfoUrl + "'"
+ "class='record-info-link' data-e2e='e2e-TL-invInventory-Item-" + row.DT_RowId + "'>" + data + '</a>';
+ "class='record-info-link' data-e2e='e2e-TL-invInventoryTR-Item-" + row.DT_RowId + "'>" + data + '</a>';
}
}, {
targets: 4,

View file

@ -20,6 +20,7 @@
formGroup.append(
'<span class="help-block">' + XHR.responseJSON.message + '</span>'
);
HelperModule.flashAlertMsg(XHR.responseJSON.message, 'danger');
}
function handleSuccessfulSubmit(data) {

View file

@ -385,14 +385,16 @@ var RepositoryColumns = (function() {
function initBackToManageColumns() {
var $manageModal = $(manageModal);
$manageModal.on('click', '.back-to-column-modal', function() {
$manageModal.on('click', '.back-to-column-modal', function(e) {
e.stopImmediatePropagation();
var button = $(this);
initManageColumnModal(button);
});
}
function initColumnsButton() {
$(document).on('click', '.manage-repo-column-index', function() {
$(document).on('click', '.manage-repo-column-index', function(e) {
e.stopImmediatePropagation();
var button = $(this);
initManageColumnModal(button);
});

View file

@ -0,0 +1,17 @@
/* global */
(function () {
const rtf = $('.rtf-view').toArray();
for (let i = 0; i < rtf.length; i += 1) {
const container = $(rtf[i]).find('table').toArray();
for (let j = 0; j < container.length; j += 1) {
const table = $(container[j]);
if ($(table).parent().hasClass('table-wrapper')) return;
$(table).wrap(`
<div class="table-wrapper" style="overflow: auto; width: ${$($(rtf)[0]).parent().width()}px"></div>
`);
}
}
}());

View file

@ -24,7 +24,13 @@
});
}
$('.activity-filters-list').on('ajax:error', '.webhook-form', function(e, data) {
$(this).renderFormErrors('webhook', data.responseJSON.errors);
const { errors } = data.responseJSON;
// display url errors with data-error-text attribute
if (errors.url) {
$(this).find('.url-input-container').addClass('error').attr('data-error-text', `${errors.url.join(', ')}`);
delete errors.url;
}
$(this).renderFormErrors('webhook', errors);
});
$('.activity-filters-list').on('click', '.create-webhook', function() {
@ -33,6 +39,11 @@
filterElement.find('.create-webhook-container').removeClass('hidden');
});
// clear url form errors
$('.activity-filters-list').on('click', '.cancel-action, .save-webhook', () => {
$('.url-input-container').removeClass('error').attr('data-error-text', '');
});
$('.activity-filters-list').on('click', '.create-webhook-container .cancel-action', function(e) {
let webhookContainer = $(this).closest('.create-webhook-container');
e.preventDefault();

View file

@ -88,9 +88,13 @@ module Api
metadata_cells = metadata[:cells]
data = contents['data']
if data.present? && data[0].present? && (data.size * data[0].size) < metadata_cells.size
error_message = I18n.t('api.core.errors.table.metadata.detail_too_many_cells')
raise ActionController::BadRequest, error_message
if data.present? && data[0].present?
data_size = (data[0].is_a?(Array) ? data.size * data[0].size : data.size)
if data_size < metadata_cells.size
error_message = I18n.t('api.core.errors.table.metadata.detail_too_many_cells')
raise ActionController::BadRequest, error_message
end
end
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Api
module V2
class BaseController < Api::V1::BaseController
private
def load_result(key = :result_id)
@result = @task.results.find(params.require(key))
raise PermissionError.new(Result, :read) unless can_read_result?(@result)
end
def load_result_text(key = :result_text_id)
@result_text = @result.result_texts.find(params.require(key))
raise PermissionError.new(Result, :read) unless can_read_result?(@result)
end
def load_result_table(key = :table_id)
@table = @result.tables.find(params.require(key))
raise PermissionError.new(Result, :read) unless can_read_result?(@result)
end
end
end
end

View file

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Api
module V2
class ResultAssetsController < BaseController
before_action :load_team, :load_project, :load_experiment, :load_task, :load_result
before_action :check_manage_permission, only: %i(create destroy)
before_action :load_asset, only: %i(show destroy)
before_action :check_upload_type, only: :create
def index
result_assets =
timestamps_filter(@result.result_assets).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: result_assets, each_serializer: ResultAssetSerializer
end
def show
render jsonapi: @asset.result_asset, serializer: ResultAssetSerializer
end
def create
asset = if @form_multipart_upload
@result.assets.new(asset_params.merge({ team_id: @team.id }))
else
blob = ActiveStorage::Blob.create_and_upload!(
io: StringIO.new(Base64.decode64(asset_params[:file_data])),
filename: asset_params[:file_name],
content_type: asset_params[:file_type]
)
@result.assets.new(file: blob, team: @team)
end
asset.save!(context: :on_api_upload)
asset.post_process_file
render jsonapi: asset.result_asset,
serializer: ResultAssetSerializer,
status: :created
end
def destroy
@asset.destroy!
render body: nil
end
private
def asset_params
raise TypeError unless params.require(:data).require(:type) == 'attachments'
return params.require(:data).require(:attributes).permit(:file) if @form_multipart_upload
attr_list = %i(file_data file_type file_name)
params.require(:data).require(:attributes).require(attr_list)
params.require(:data).require(:attributes).permit(attr_list)
end
def load_asset
@asset = @result.assets.find(params.require(:id))
raise PermissionError.new(Result, :read) unless can_read_result?(@result)
end
def check_upload_type
@form_multipart_upload = true if params.dig(:data, :attributes, :file)
end
def check_manage_permission
raise PermissionError.new(Result, :manage) unless can_manage_result?(@result)
end
end
end
end

View file

@ -0,0 +1,102 @@
# frozen_string_literal: true
module Api
module V2
class ResultTablesController < BaseController
before_action :load_team, :load_project, :load_experiment, :load_task, :load_result
before_action only: %i(show update destroy) do
load_result_table(:id)
end
before_action :check_manage_permission, only: %i(create update destroy)
def index
result_tables = timestamps_filter(@result.result_tables).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: result_tables, each_serializer: ResultTableSerializer
end
def show
render jsonapi: @table.result_table, serializer: ResultTableSerializer
end
def create
table = @result.tables.new(table_params.merge!(team: @team, created_by: current_user))
@result.with_lock do
@result.result_orderable_elements.create!(
position: @result.result_orderable_elements.size,
orderable: table.result_table
)
table.save!
end
render jsonapi: table.result_table, serializer: ResultTableSerializer, status: :created
end
def update
@table.assign_attributes(table_params)
if @table.changed? && @table.save!
render jsonapi: @table.result_table, serializer: ResultTableSerializer
else
render body: nil, status: :no_content
end
end
def destroy
@table.destroy!
render body: nil
end
private
def check_manage_permission
raise PermissionError.new(Result, :manage) unless can_manage_result?(@result)
end
def convert_plate_template(metadata_params)
if metadata_params.present? && metadata_params['plateTemplate']
metadata_params['plateTemplate'] = ActiveRecord::Type::Boolean.new.cast(metadata_params['plateTemplate'])
end
end
def table_params
raise TypeError unless params.require(:data).require(:type) == 'tables'
attributes_params = params.require(:data).require(:attributes).permit(
:name,
:contents,
metadata: [
:plateTemplate,
{ cells: %i(col row className) }
]
)
convert_plate_template(attributes_params[:metadata])
validate_metadata_params(attributes_params)
attributes_params
end
def validate_metadata_params(attributes_params)
metadata = attributes_params[:metadata]
contents = JSON.parse(attributes_params[:contents] || '{}')
if metadata.present? && metadata[:cells].present? && contents.present?
metadata_cells = metadata[:cells]
data = contents['data']
if data.present? && data[0].present?
data_size = (data[0].is_a?(Array) ? data.size * data[0].size : data.size)
if data_size < metadata_cells.size
error_message = I18n.t('api.core.errors.table.metadata.detail_too_many_cells')
raise ActionController::BadRequest, error_message
end
end
end
end
end
end
end

View file

@ -0,0 +1,66 @@
# frozen_string_literal: true
module Api
module V2
class ResultTextsController < BaseController
before_action :load_team, :load_project, :load_experiment, :load_task, :load_result
before_action only: %i(show update destroy) do
load_result_text(:id)
end
before_action :check_manage_permission, only: %i(create update destroy)
def index
result_texts = timestamps_filter(@result.result_texts).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: result_texts, each_serializer: ResultTextSerializer
end
def show
render jsonapi: @result_text, serializer: ResultTextSerializer
end
def create
result_text = @result.result_texts.new(result_text_params)
@result.with_lock do
@result.result_orderable_elements.create!(
position: @result.result_orderable_elements.size,
orderable: result_text
)
result_text.save!
end
render jsonapi: result_text, serializer: ResultTextSerializer, status: :created
end
def update
@result_text.assign_attributes(result_text_params)
if @result_text.changed? && @result_text.save!
render jsonapi: @result_text, serializer: ResultTextSerializer, status: :ok
else
render body: nil, status: :no_content
end
end
def destroy
@result_text.destroy!
render body: nil
end
private
def check_manage_permission
raise PermissionError.new(Result, :manage) unless can_manage_result?(@result)
end
def result_text_params
raise TypeError unless params.require(:data).require(:type) == 'result_texts'
params.require(:data).require(:attributes).permit(:text, :name)
end
end
end
end

View file

@ -0,0 +1,76 @@
# frozen_string_literal: true
module Api
module V2
class ResultsController < BaseController
before_action :load_team, :load_project, :load_experiment, :load_task
before_action only: %i(show update destroy) do
load_result(:id)
end
before_action :check_create_permissions, only: :create
before_action :check_delete_permissions, only: :destroy
before_action :check_update_permissions, only: :update
def index
results = timestamps_filter(@task.results).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: results, each_serializer: ResultSerializer,
include: include_params
end
def show
render jsonapi: @result, serializer: ResultSerializer,
include: include_params
end
def create
@result = Result.create!(
user: current_user,
my_module: @task,
name: result_params[:name]
)
render jsonapi: @result, serializer: ResultSerializer
end
def update
@result.assign_attributes(result_params)
if @result.changed? && @result.save!
render jsonapi: @result, serializer: ResultSerializer
else
render body: nil, status: :no_content
end
end
def destroy
@result.destroy!
render body: nil
end
private
def check_create_permissions
raise PermissionError.new(MyModule, :manage) unless can_manage_my_module?(@task)
end
def check_delete_permissions
raise PermissionError.new(Result, :delete) unless can_delete_result?(@result)
end
def check_update_permissions
raise PermissionError.new(Result, :manage) unless can_manage_result?(@result)
end
def permitted_includes
%w(comments result_texts tables assets)
end
def result_params
raise TypeError unless params.require(:data).require(:type) == 'results'
params.require(:data).require(:attributes).require(:name)
params.require(:data).permit(attributes: %i(name archived))[:attributes]
end
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Api
module V2
module StepElements
class AssetsController < ::Api::V1::AssetsController; end
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Api
module V2
module StepElements
class ChecklistItemsController < ::Api::V1::ChecklistItemsController; end
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Api
module V2
module StepElements
class ChecklistsController < ::Api::V1::ChecklistsController; end
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Api
module V2
module StepElements
class TablesController < ::Api::V1::TablesController; end
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Api
module V2
module StepElements
class TextsController < ::Api::V1::StepTextsController; end
end
end
end

View file

@ -0,0 +1,66 @@
# frozen_string_literal: true
module Api
module V2
class StepsController < ::Api::V1::StepsController
def index
steps = timestamps_filter(@protocol.steps).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: steps, each_serializer: StepSerializer,
include: include_params,
rte_rendering: render_rte?,
team: @team
end
def show
render jsonapi: @step, serializer: StepSerializer,
include: include_params,
rte_rendering: render_rte?,
team: @team
end
def create
raise PermissionError.new(Protocol, :create) unless can_manage_protocol_in_module?(@protocol)
@protocol.transaction do
@step = @protocol.steps.create!(
step_params.merge!(completed: false,
user: current_user,
position: @protocol.number_of_steps,
last_modified_by_id: current_user.id)
)
end
render jsonapi: @step, serializer: StepSerializer, status: :created
end
def update
@step.assign_attributes(
step_params.merge!(last_modified_by_id: current_user.id)
)
if @step.changed? && @step.save!
if @step.saved_change_to_attribute?(:completed)
completed_steps = @protocol.steps.where(completed: true).count
all_steps = @protocol.steps.count
type_of = @step.saved_change_to_attribute(:completed).last ? :complete_step : :uncomplete_step
log_activity(type_of, my_module: @task.id,
num_completed: completed_steps.to_s,
num_all: all_steps.to_s)
end
render jsonapi: @step, serializer: StepSerializer, status: :ok
else
render body: nil, status: :no_content
end
end
private
def step_params
raise TypeError unless params.require(:data).require(:type) == 'steps'
params.require(:data).require(:attributes).permit(:name, :completed)
end
end
end
end

View file

@ -13,11 +13,13 @@ module ActiveStorage
def check_read_permissions
return render_404 if @blob.attachments.blank?
@blob.attachments.any? { |attachment| check_attachment_read_permissions(attachment) }
render_403 unless @blob.attachments.any? { |attachment| check_attachment_read_permissions(attachment) }
end
def check_attachment_read_permissions(attachment)
current_user.permission_team = attachment.record.team || current_team if attachment.record.respond_to?(:team)
current_user.permission_team = attachment.record.team if attachment.record.respond_to?(:team)
return false if attachment.record.blank?
case attachment.record_type
when 'Asset'
@ -25,73 +27,52 @@ module ActiveStorage
when 'TinyMceAsset'
check_tinymce_asset_read_permissions(attachment.record)
when 'Experiment'
check_experiment_read_permissions(attachment.record)
can_read_experiment?(attachment.record)
when 'Report'
check_report_read_permissions(attachment.record)
can_read_project?(attachment.record.project)
when 'User'
# No read restrictions for avatars
true
when 'ZipExport', 'TeamZipExport'
check_zip_export_read_permissions(attachment.record)
attachment.record.user == current_user
when 'TempFile'
check_temp_file_read_permissions(attachment.record)
attachment.record.session_id == request.session_options[:id].to_s
else
render_403
false
end
end
def check_asset_read_permissions(asset)
return render_403 unless asset
if asset.step
protocol = asset.step.protocol
render_403 unless can_read_protocol_in_module?(protocol) || can_read_protocol_in_repository?(protocol)
can_read_protocol_in_module?(protocol) || can_read_protocol_in_repository?(protocol)
elsif asset.result
experiment = asset.result.my_module.experiment
render_403 unless can_read_experiment?(experiment)
can_read_experiment?(experiment)
elsif asset.repository_cell
repository = asset.repository_cell.repository_column.repository
render_403 unless can_read_repository?(repository)
can_read_repository?(repository)
else
render_403
false
end
end
def check_tinymce_asset_read_permissions(asset)
return render_403 unless asset
return true if asset.object.nil? && can_read_team?(asset.team)
case asset.object_type
when 'MyModule'
render_403 unless can_read_my_module?(asset.object)
can_read_my_module?(asset.object)
when 'Protocol'
render_403 unless can_read_protocol_in_module?(asset.object) ||
can_read_protocol_in_repository?(asset.object)
can_read_protocol_in_module?(asset.object) || can_read_protocol_in_repository?(asset.object)
when 'ResultText'
render_403 unless can_read_my_module?(asset.object.result.my_module)
can_read_my_module?(asset.object.result.my_module)
when 'StepText'
render_403 unless can_read_protocol_in_module?(asset.object.step.protocol) ||
can_read_protocol_in_repository?(asset.object.step.protocol)
can_read_protocol_in_module?(asset.object.step.protocol) ||
can_read_protocol_in_repository?(asset.object.step.protocol)
else
render_403
false
end
end
def check_experiment_read_permissions(experiment)
render_403 && return unless can_read_experiment?(experiment)
end
def check_report_read_permissions(report)
render_403 && return unless can_read_project?(report.project)
end
def check_zip_export_read_permissions(zip_export)
render_403 unless zip_export.user == current_user
end
def check_temp_file_read_permissions(temp_file)
render_403 unless temp_file.session_id == request.session_options[:id].to_s
end
end
end

View file

@ -8,8 +8,8 @@ class GlobalActivitiesController < ApplicationController
def index
# Preload filter format
# {
# from_date: "YYYY-MM-DD",
# to_date: "YYYY-MM-DD",
# from_date: "YYYY-MM-DD",
# teams: [*team_ids],
# types: [*activity_type_ids],
# users: [*user_ids],
@ -21,8 +21,8 @@ class GlobalActivitiesController < ApplicationController
# Example
# {
# to_date: "2018-02-28",
# from_date: "2019-03-29",
# to_date: "2019-03-29",
# teams: [1,2],
# types: [32,33,34],
# users: [1,2,3],

View file

@ -87,7 +87,7 @@ module Users
end
if filters['to_date'] || filters['from_date']
result.push("#{t('global_activities.index.period_label')} #{filters['from_date']} - #{filters['to_date']}")
result.push("#{t('global_activities.index.period_label')} #{filters['to_date']} - #{filters['from_date']}")
end
filters['subjects']&.each do |subject, ids|

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module NotificationsHelper
def send_email_notification(user, notification)
AppMailer.delay.notification(user.id, notification)
@ -17,14 +19,17 @@ module NotificationsHelper
team: team.name,
assigned_by_user: user.name)
end
message = "#{I18n.t('search.index.team')} #{team.name}"
end
GeneralNotification.send_notifications({
type: role ? :invite_user_to_team : :remove_user_from_team,
title: sanitize_input(title),
message: sanitize_input(message),
user: target_user
})
GeneralNotification.send_notifications(
{
type: role ? :invite_user_to_team : :remove_user_from_team,
title: sanitize_input(title),
subject_id: team.id,
subject_class: team.class.name,
subject_name: team.respond_to?(:name) && team.name,
user: target_user
}
)
end
end

View file

@ -30,7 +30,7 @@ module RepositoriesDatatableHelper
'data-rename-modal-url': team_repository_rename_modal_path(team, repository_id: repository),
'data-shared': repository.shared_with?(team),
'data-i-shared': repository.i_shared?(team),
'data-e2e': "e2e-RT-inventories-tableItemRow-#{repository.id}"
'data-e2e': "e2e-TR-inventories-bodyRow-#{repository.id}"
}
)
end

View file

@ -17,7 +17,7 @@ module RepositoryDatatableHelper
row = public_send("#{repository.class.name.underscore}_default_columns", record)
row.merge!(
DT_RowId: record.id,
DT_RowAttr: { 'data-state': row_style(record), 'data-e2e': "e2e-RT-invInventory-row-#{record.id}" },
DT_RowAttr: { 'data-state': row_style(record), 'data-e2e': "e2e-TR-invInventory-bodyRow-#{record.id}" },
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(repository, record),
rowRemindersUrl:
Rails.application.routes.url_helpers
@ -62,7 +62,7 @@ module RepositoryDatatableHelper
else
{ stock_url: new_repository_stock_repository_repository_row_url(repository, record) }
end
row['stock'][:stock_managable] = stock_managable
row['stock'][:stock_managable] = stock_managable && record.active?
row['stock']['displayWarnings'] = display_stock_warnings?(repository)
row['stock'][:stock_status] = stock_cell&.value&.status

View file

@ -29,6 +29,7 @@
:data-object-type="groupAction.item_type"
:data-object-id="groupAction.item_id"
:data-action="groupAction.type"
:data-e2e="`e2e-BT-actionToolbar-${groupAction.name}`"
@click="closeExportDropdown($event); doAction(groupAction, $event);">
<span class="sn-action-toolbar__button-text">{{ groupAction.label }}</span>
</a>
@ -46,6 +47,7 @@
:data-object-type="action.actions[0].item_type"
:data-object-id="action.actions[0].item_id"
:data-action="action.actions[0].type"
:data-e2e="`e2e-BT-actionToolbar-${action.name}`"
@click="doAction(action.actions[0], $event);">
<i :class="action.actions[0].icon"></i>
<span class="sn-action-toolbar__button-text">{{ action.group_label }}</span>
@ -61,6 +63,7 @@
:data-object-type="action.item_type"
:data-object-id="action.item_id"
:data-action="action.type"
:data-e2e="`e2e-BT-actionToolbar-${action.name}`"
@click="doAction(action, $event)">
<i :class="action.icon"></i>
<span class="sn-action-toolbar__button-text">{{ action.label }}</span>
@ -85,6 +88,7 @@
multiple: false,
params: {},
reloadCallback: null,
actionsLoadedCallback: null,
loaded: false,
loading: false,
width: 0,
@ -104,6 +108,7 @@
this.actions = data.actions;
this.loading = false;
this.setButtonOverflow();
if (this.actionsLoadedCallback) this.$nextTick(this.actionsLoadedCallback);
});
}, 10);
},
@ -158,6 +163,9 @@
setReloadCallback(func) {
this.reloadCallback = func;
},
setActionsLoadedCallback(func) {
this.actionsLoadedCallback = func;
},
doAction(action, event) {
switch(action.type) {
case 'legacy':

View file

@ -98,9 +98,9 @@ export default {
},
reloadCurrentLevel: function() {
if (this.reloadCurrentLevel && (
this.currentItemId.length == 0 ||
this.menuItems.filter(item => item.id == this.currentItemId)
)) {
this.currentItemId?.length === 0
|| this.menuItems.filter((item) => item.id === this.currentItemId)
)) {
this.loadTree();
}
}

View file

@ -95,7 +95,7 @@
</a>
</div>
</div>
<div id="protocol-description-container" :class=" inRepository ? 'protocol-description collapse in' : ''" >
<div id="protocol-description-container" class="text-base" :class=" inRepository ? 'protocol-description collapse in' : ''" >
<div v-if="urls.update_protocol_description_url">
<Tinymce
:value="protocol.attributes.description"

View file

@ -211,7 +211,8 @@
{ text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'), emit: 'create:table', params: [16, 24] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'), emit: 'create:table', params: [8, 12] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'), emit: 'create:table', params: [6, 8] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_4'), emit: 'create:table', params: [6, 4] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'), emit: 'create:table', params: [4, 6] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'), emit: 'create:table', params: [3, 4]},
{ text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'), emit: 'create:table', params: [2, 3] }
]
}

View file

@ -2,7 +2,8 @@
<transition enter-from-class="translate-x-full w-0"
enter-active-class="transition-all ease-sharp duration-[588ms]"
leave-active-class="transition-all ease-sharp duration-[588ms]"
leave-to-class="translate-x-full w-0">
leave-to-class="translate-x-full w-0"
v-click-outside="handleOutsideClick">
<div ref="wrapper" v-show="isShowing" id="repository-item-sidebar-wrapper"
class='items-sidebar-wrapper bg-white gap-2.5 self-stretch rounded-tl-4 rounded-bl-4 shadow-lg h-full w-[565px]'>
@ -299,7 +300,7 @@
:class="{ 'pb-6': customColumns?.length }">
<div id="divider" class="w-500 bg-sn-light-grey flex px-8 items-center self-stretch h-px mb-6"></div>
<div id="bottom-button-wrapper" class="flex h-10 justify-end">
<button type="button" class="btn btn-primary print-label-button"
<button type="button" class="btn btn-primary print-label-button" data-e2e="e2e-BT-invInventoryItemSB-print"
:data-rows="JSON.stringify([repositoryRowId])">
{{ i18n.t('repositories.item_card.print_label') }}
</button>
@ -317,6 +318,7 @@
</template>
<script>
import { vOnClickOutside } from '@vueuse/components';
import InlineEdit from '../shared/inline_edit.vue';
import ScrollSpy from './repository_values/ScrollSpy.vue';
import CustomColumns from './customColumns.vue';
@ -333,6 +335,9 @@ export default {
'scroll-spy': ScrollSpy,
'unlink-modal': UnlinkModal,
},
directives: {
'click-outside': vOnClickOutside
},
data() {
return {
currentItemUrl: null,
@ -389,12 +394,10 @@ export default {
},
mounted() {
// Add a click event listener to the document
document.addEventListener('mousedown', this.handleOutsideClick);
this.inRepository = $('.assign-items-to-task-modal-container').length > 0;
},
beforeUnmount() {
delete window.repositoryItemSidebarComponent;
document.removeEventListener('mousedown', this.handleOutsideClick);
},
methods: {
handleOpenAddRelationshipsModal(event, relation) {
@ -424,11 +427,14 @@ export default {
handleOutsideClick(event) {
if (!this.isShowing) return;
// Check if the clicked element is not within the sidebar and it's not another item link or belogs to modals
const selectors = ['a', '.modal', '.label-printing-progress-modal', '.atwho-view'];
const allowedSelectors = ['a', '.modal', '.label-printing-progress-modal', '.atwho-view'];
const excludeSelectors = ['#myModuleRepositoryFullViewModal'];
if (!$(event.target).parents('#repository-item-sidebar-wrapper').length
&& !selectors.some((selector) => event.target.closest(selector))) {
const isOutsideSidebar = !$(event.target).parents('#repository-item-sidebar-wrapper').length;
const isAllowedClick = !allowedSelectors.some((selector) => event.target.closest(selector));
const isExcludedClick = excludeSelectors.some((selector) => event.target.closest(selector));
if (isOutsideSidebar && (isAllowedClick || isExcludedClick)) {
this.toggleShowHideSidebar(null);
}
},

View file

@ -23,7 +23,7 @@
<span v-for="(checklistItem, index) in selectedChecklistItems"
:key="index"
:id="`checklist-item-${index}`"
class="flex w-fit break-words mr-1">
class="flex w-fit break-words">
{{
index + 1 === selectedChecklistItems.length
? checklistItem?.label

View file

@ -27,15 +27,16 @@
@update="update"
className="px-3" />
</div>
<div v-else-if="colVal?.edit"
ref="textRef"
class="text-sn-dark-grey box-content text-sm font-normal leading-5 overflow-y-auto pr-3 rounded w-[calc(100%-2rem)]]"
:class="{
'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed
}"
<div v-else-if="colVal?.view"
ref="textRef"
v-html="colVal?.view"
class="text-sn-dark-grey box-content text-sm font-normal leading-5
overflow-y-auto pr-3 rounded w-[calc(100%-2rem)]]"
:class="{
'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed
}"
>
{{ colVal?.edit }}
</div>
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5 pr-3 py-2 w-[calc(100%-2rem)]]">
{{ i18n.t("repositories.item_card.repository_text_value.no_text") }}

View file

@ -191,8 +191,8 @@
this.defaultEndDate = this.endDate;
if ($('.dataTable')[0]) {
$('.dataTable').DataTable().ajax.reload(null, false);
this.reloadRepoItemSidebar();
}
this.reloadRepoItemSidebar();
}
});
},

View file

@ -1,6 +1,6 @@
<template>
<div ref="modal" class="modal fade" id="modal-print-repository-row-label" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-dialog" role="document" data-e2e="e2e-MD-printLabel">
<div class="modal-content">
<div v-if="availablePrinters.length > 0" class="printers-available">
<div class="modal-header">
@ -70,7 +70,7 @@
</div>
<div v-else class="no-printers-available">
<div class="modal-body no-printers-container">
<button type="button" class="close modal-absolute-close-button" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<button type="button" class="close modal-absolute-close-button" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close" data-e2e="e2e-BT-printLabelMD-close"></i></button>
<img src='/images/printers/no_available_printers.png'>
<p class="no-printer-title">
{{ i18n.t('repository_row.modal_print_label.no_printers.title') }}
@ -80,7 +80,7 @@
</p>
</div>
<div class="modal-footer">
<a :href="urls.fluicsInfo" target="blank" class="btn btn-primary" >
<a :href="urls.fluicsInfo" target="blank" class="btn btn-primary" data-e2e="e2e-BT-printLabelMD-visitBlog" >
{{ i18n.t('repository_row.modal_print_label.no_printers.visit_blog') }}
</a>
</div>

View file

@ -3,7 +3,7 @@
class="flex items-center mr-3 flex-nowrap relative"
v-click-outside="closeSearchInputs"
>
<button :class="{hidden: searchOpened}" ref='searchInputBtn' class="btn btn-light btn-black icon-btn" :title="i18n.t('repositories.show.search_button_tooltip')" @click="openSearch">
<button :class="{hidden: searchOpened}" ref='searchInputBtn' class="btn btn-light btn-black icon-btn" data-e2e="e2e-BT-invInventoryRT-search" :title="i18n.t('repositories.show.search_button_tooltip')" @click="openSearch">
<i class="sn-icon sn-icon-search"></i>
</button>
<div v-if="searchOpened || barcodeSearchOpened" class="w-52 flex">
@ -28,7 +28,7 @@
<i class='sn-icon sn-icon-barcode barcode-scanner !mr-2.5'></i>
</div>
</div>
<button :class="{hidden: barcodeSearchOpened}" ref='barcodeSearchInputBtn' class="btn btn-light btn-black icon-btn ml-2" :title="i18n.t('repositories.show.ean_search_button_tooltip')" @click="openBarcodeSearch">
<button :class="{hidden: barcodeSearchOpened}" ref='barcodeSearchInputBtn' class="btn btn-light btn-black icon-btn ml-2" data-e2e="e2e-BT-invInventoryRT-barcode" :title="i18n.t('repositories.show.ean_search_button_tooltip')" @click="openBarcodeSearch">
<i class='sn-icon sn-icon-barcode barcode-scanner'></i>
</button>
</div>

View file

@ -170,7 +170,8 @@
{ text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'), emit: 'create:table', params: [[16, 24], true] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'), emit: 'create:table', params: [[8, 12], true] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'), emit: 'create:table', params: [[6, 8], true] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_4'), emit: 'create:table', params: [[6, 4], true] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'), emit: 'create:table', params: [[4, 6], true] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'), emit: 'create:table', params: [[3, 4], true] },
{ text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'), emit: 'create:table', params: [[2, 3], true] }
],
editingName: false,

View file

@ -16,8 +16,9 @@
{{ attachment.attributes.file_name }}
</span>
</a>
<div v-if="attachment.attributes.medium_preview !== null" class="attachment-image-tooltip bg-white sn-shadow-menu-sm" >
<img :src="this.imageLoadError ? attachment.attributes.urls.blob : attachment.attributes.medium_preview" @error="handleImageError"/>
<div v-if="attachment.attributes.medium_preview !== null" class="attachment-image-tooltip bg-white sn-shadow-menu-sm">
<img :src="this.imageLoadError ? attachment.attributes.urls.blob : attachment.attributes.medium_preview" @error="ActiveStoragePreviews.reCheckPreview"
@load="ActiveStoragePreviews.showPreview"/>
</div>
<div class="file-metadata">
<span>

View file

@ -30,7 +30,7 @@ export default {
$wopiModal.modal('hide');
$wopiModal.find('form').off('submit');
$wopiModal.find('form').off('ajax:success');
window.open(data.edit_url, '_blank');
window.open(data.data.attributes.urls.edit_asset, '_blank');
window.focus();
} else {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');

View file

@ -35,12 +35,14 @@
:attributeName="`${i18n.t('ChecklistItem')} ${i18n.t('name')}`"
:editOnload="checklistItem.attributes.isNew"
:smartAnnotation="true"
:allowNewLine="true"
@editingEnabled="enableTextEdit"
@editingDisabled="disableTextEdit"
@update="updateText"
@delete="removeItem()"
@keypress="keyPressHandler"
@blur="onBlurHandler"
@paste="pasteHandler"
/>
<span v-if="!editingText && (!checklistItem.attributes.urls || deleteUrl)" class="absolute right-0 top-0.5 leading-6 tw-hidden group-hover/checklist-item-header:inline-block !text-sn-blue cursor-pointer" @click="showDeleteModal" tabindex="0">
<i class="sn-icon sn-icon-delete"></i>
@ -164,10 +166,16 @@
this.$emit('update', this.checklistItem, withKey);
},
keyPressHandler(e) {
if (e.key === 'Enter' && e.shiftKey) {
if (
((e.shiftKey || e.metaKey) && e.key === 'Enter')
|| ((e.ctrlKey || e.metaKey) && e.key === 'v')
) {
this.checklistItem.attributes.with_paragraphs = true;
}
},
pasteHandler() {
this.checklistItem.attributes.with_paragraphs = true;
},
}
}
</script>

View file

@ -113,7 +113,7 @@
},
computed: {
wrapTables() {
const container = $(`<span>${this.element.attributes.orderable.text_view}</span>`);
const container = $(`<span class="text-base">${this.element.attributes.orderable.text_view}</span>`);
container.find('table').toArray().forEach((table) => {
if ($(table).parent().hasClass('table-wrapper')) return;
$(table).css('float', 'none').wrapAll(`

View file

@ -15,6 +15,7 @@
@keydown="handleKeypress"
@blur="handleBlur"
@keyup.escape="cancelEdit && this.atWhoOpened"
@paste="$emit('paste', e)"
@focus="setCaretAtEnd"/>
<textarea v-else
ref="input"
@ -29,6 +30,7 @@
@keydown="handleKeypress"
@blur="handleBlur"
@keyup.escape="cancelEdit && this.atWhoOpened"
@paste="$emit('paste', e)"
@focus="setCaretAtEnd"/>
</template>
<div

View file

@ -119,7 +119,7 @@ class Asset < ApplicationRecord
new_query = new_query.where(
"(active_storage_blobs.filename #{like} ? " \
"OR asset_text_data.data_vector @@ to_tsquery(?))",
"OR asset_text_data.data_vector @@ plainto_tsquery(?))",
a_query,
s_query
)
@ -140,7 +140,7 @@ class Asset < ApplicationRecord
.tr('\'', '"')
new_query = new_query.where(
"(active_storage_blobs.filename #{like} ANY (array[?]) " \
"OR asset_text_data.data_vector @@ to_tsquery(?))",
"OR asset_text_data.data_vector @@ plainto_tsquery(?))",
a_query,
s_query
)
@ -152,9 +152,9 @@ class Asset < ApplicationRecord
.limit(Constants::SEARCH_LIMIT)
.offset((page - 1) * Constants::SEARCH_LIMIT)
Asset.select(
"assets_search.*, ts_headline(assets_search.data, to_tsquery('" +
sanitize_sql_for_conditions(s_query) +
"'), 'StartSel=<mark>, StopSel=</mark>') AS headline"
"assets_search.*, " \
"ts_headline(assets_search.data, plainto_tsquery('#{sanitize_sql_for_conditions(s_query)}'), " \
"'StartSel=<mark>, StopSel=</mark>') AS headline"
).from(new_query, 'assets_search')
else
new_query

View file

@ -142,7 +142,7 @@ class Project < ApplicationRecord
experiments: {
active: { sort: 'new' },
archived: { sort: 'new' },
view_type: 'cards'
view_type: 'table'
}
}
end

View file

@ -76,6 +76,8 @@ class RepositoryAssetValue < ApplicationRecord
def snapshot!(cell_snapshot)
value_snapshot = dup
asset_snapshot = asset.dup
# Needed to handle shared repositories from another teams
asset_snapshot.team_id = cell_snapshot.repository_column.repository.team_id
asset_snapshot.save!

View file

@ -78,7 +78,7 @@ class Team < ApplicationRecord
projects: {
active: { sort: 'new' },
archived: { sort: 'new' },
view_type: 'cards'
view_type: 'table'
}
}
end

View file

@ -6,11 +6,11 @@ class RepositoryItemDateNotification < BaseNotification
end
def title
unit = human_readable_unit(column.metadata['reminder_unit'], column.metadata['reminder_value'])
unit = human_readable_unit(params[:reminder_unit], params[:reminder_value])
I18n.t(
'notifications.content.item_date_reminder.message_html',
repository_row_name: subject.name,
value: column.metadata['reminder_value'],
value: params[:reminder_value],
units: unit
)
end
@ -21,12 +21,6 @@ class RepositoryItemDateNotification < BaseNotification
NonExistantRecord.new(params[:repository_row_name])
end
def column
RepositoryColumn.find(params[:repository_column_id])
rescue ActiveRecord::RecordNotFound
NonExistantRecord.new(params[:repository_column_name])
end
after_deliver do
if params[:repository_date_time_value_id]
RepositoryDateTimeValue.find(params[:repository_date_time_value_id]).update(notification_sent: true)

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Api
module V2
class ResultAssetSerializer < ActiveModel::Serializer
type :assets
attributes :file_id, :file_name, :file_size, :file_type, :file_url
def file_id
object.asset&.id
end
def file_name
object.asset&.file_name
end
def file_size
object.asset&.file_size
end
def file_type
object.asset&.content_type
end
def file_url
if object.asset&.file&.attached?
Rails.application.routes.url_helpers.rails_blob_path(object.asset.file, disposition: 'attachment')
end
end
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Api
module V2
class ResultOrderableElementSerializer < ActiveModel::Serializer
attributes :id, :position, :orderable, :orderable_type
def orderable
case object.orderable_type
when 'ResultTable'
ResultTableSerializer.new(object.orderable, scope: { user: @instance_options[:user] }).as_json
when 'ResultText'
ResultTextSerializer.new(object.orderable, scope: { user: @instance_options[:user] }).as_json
end
end
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Api
module V2
class ResultSerializer < ActiveModel::Serializer
type :results
attributes :name, :archived
belongs_to :user, serializer: Api::V1::UserSerializer
has_many :result_comments, key: :comments, serializer: Api::V1::CommentSerializer
has_many :result_texts, key: :result_texts, serializer: ResultTextSerializer
has_many :result_tables, key: :tables, serializer: ResultTableSerializer
has_many :result_assets, key: :assets, serializer: ResultAssetSerializer
has_many :result_orderable_elements, key: :result_elements, serializer: ResultOrderableElementSerializer
include TimestampableModel
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Api
module V2
class ResultTableSerializer < ActiveModel::Serializer
type :tables
attributes :table_id, :table_contents, :table_metadata
def table_id
object.table&.id
end
def table_contents
object.table&.contents&.force_encoding(Encoding::UTF_8)
end
def table_metadata
object.table&.metadata
end
end
end
end

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Api
module V2
class ResultTextSerializer < ActiveModel::Serializer
type :result_texts
attributes :name, :text
def text
object.tinymce_render('text')
end
end
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Api
module V2
class StepSerializer < ActiveModel::Serializer
include ApplicationHelper
include ActionView::Helpers::TextHelper
include InputSanitizeHelper
type :steps
attributes :id, :name, :position, :completed
attribute :completed_on, if: -> { object.completed? }
belongs_to :user, serializer: Api::V1::UserSerializer
belongs_to :protocol, serializer: Api::V1::ProtocolSerializer
has_many :assets, serializer: Api::V1::AssetSerializer
has_many :checklists, serializer: Api::V1::ChecklistSerializer
has_many :tables, serializer: Api::V1::TableSerializer
has_many :step_texts, serializer: Api::V1::StepTextSerializer
has_many :step_comments, key: :comments, serializer: Api::V1::CommentSerializer
has_many :step_orderable_elements, key: :step_elements, serializer: Api::V1::StepOrderableElementSerializer
include TimestampableModel
end
end
end

View file

@ -21,16 +21,16 @@ module Activities
def filter_date!
@activity_filters = @activity_filters.where(
"(CASE "\
"WHEN (filter ->> 'to_date') = '' " \
"(CASE " \
"WHEN ((filter ->> 'to_date') = '') IS NOT FALSE " \
"THEN :date >= '-infinity'::date " \
"ELSE :date >= (filter ->> 'to_date')::date " \
"END) " \
" AND " \
"(CASE "\
"WHEN (filter ->> 'from_date') = '' " \
"AND " \
"(CASE " \
"WHEN ((filter ->> 'from_date') = '') IS NOT FALSE " \
"THEN :date <= 'infinity'::date " \
"ELSE :date <= (filter ->> 'from_date')::date "\
"ELSE :date <= (filter ->> 'from_date')::date " \
"END)",
date: @activity.created_at.to_date
)

View file

@ -37,6 +37,8 @@ class ActivitiesService
to: Time.zone.parse(filters[:to_date]).beginning_of_day.utc)
elsif filters[:from_date].present? && filters[:to_date].blank?
query.where('created_at <= :from', from: Time.zone.parse(filters[:from_date]).end_of_day.utc)
elsif filters[:from_date].blank? && filters[:to_date].present?
query.where('created_at >= :to', to: Time.zone.parse(filters[:to_date]).beginning_of_day.utc)
else
query
end

View file

@ -14,6 +14,7 @@ module ProtocolImporters
@errors = {}
end
# rubocop:disable Metrics/BlockLength
def call
return self unless valid?
@ -26,7 +27,7 @@ module ProtocolImporters
step_params.symbolize_keys!
# Create step with nested attributes for tables
step = @protocol.steps.create!(step_params.slice(:name, :position, :tables_attributes)
step = @protocol.steps.create!(step_params.slice(:name, :position)
.merge(user: @user, completed: false)
.merge(last_modified_by_id: @user.id))
@ -36,21 +37,44 @@ module ProtocolImporters
position: 0,
orderable: step_text
)
TinyMceAsset.update_images(step_text, '[]', @user)
# Add tables
if step_params[:tables_attributes].present?
step_params[:tables_attributes].each do |table_attributes|
table_attributes.symbolize_keys!
table = step.tables.create!(
name: table_attributes[:name],
contents: JSON.parse(table_attributes[:contents].encode('UTF-8', 'UTF-8')).to_json,
metadata: JSON.parse(table_attributes[:metadata].presence || '{}'),
created_by: @user,
last_modified_by: @user,
team: @team
)
step.step_orderable_elements.create!(
position: step.step_orderable_elements.size,
orderable: table.step_table
)
end
end
# 'Manually' create assets here. "Accept nested attributes" won't work for assets
step.assets << AttachmentsBuilder.generate(step_params.deep_symbolize_keys, user: @user, team: @team)
step
end
rescue ActiveRecord::RecordInvalid => e
@errors[:protocol] = e.record.errors
raise ActiveRecord::Rollback
rescue StandardError => e
Rails.logger.error(e.message)
Rails.logger.error(e.backtrace.join("\n"))
@errors[:protocol] = e.message
raise ActiveRecord::Rollback
end
self
end
# rubocop:enable Metrics/BlockLength
def succeed?
@errors.none?

View file

@ -130,6 +130,7 @@ module Toolbars
def export_actions
{
name: 'export_group',
type: :group,
group_label: I18n.t('repositories.exports.export'),
actions: [export_items_action, export_consumption_action].compact

View file

@ -48,14 +48,12 @@ class ProtocolsImporter
private
def create_in_step!(step, new_orderable)
ActiveRecord::Base.transaction do
new_orderable.save!
new_orderable.save!
step.step_orderable_elements.create!(
position: step.step_orderable_elements.length,
orderable: new_orderable
)
end
step.step_orderable_elements.create!(
position: step.step_orderable_elements.length,
orderable: new_orderable
)
end
def populate_protocol(protocol, protocol_json)

View file

@ -48,13 +48,11 @@ class ProtocolsImporterV2
private
def create_in_step!(step, new_orderable)
ActiveRecord::Base.transaction do
new_orderable.save!
step.step_orderable_elements.create!(
position: step.step_orderable_elements.length,
orderable: new_orderable
)
end
new_orderable.save!
step.step_orderable_elements.create!(
position: step.step_orderable_elements.size,
orderable: new_orderable
)
end
def populate_protocol(protocol, protocol_json)

View file

@ -6,8 +6,9 @@
item_id = dom_id(user, :assignment_member)
%>
<%= form_with(model: assignment, url: update_path, method: :put, html: { class: 'member-item', id: item_id, data: { remote: true, action: 'replace-form autosave-form', object_type: :assignment_member } }) do |f| %>
<%= f.hidden_field :user_id, value: f.object.user.id %>
<% if assignment.present? %>
<%= form_with(model: assignment, url: update_path, method: :put, html: { class: 'member-item', id: item_id, data: { remote: true, action: 'replace-form autosave-form', object_type: :assignment_member } }) do |f| %>
<%= f.hidden_field :user_id, value: f.object.user.id %>
<div class="user-assignment-info">
<div class="global-avatar-container">
<%= image_tag avatar_path(user, :icon_small), title: current_assignee_name(user), class: 'img-circle pull-left' %>
@ -50,4 +51,5 @@
</button>
<% end %>
</div>
<% end %>
<% end %>

View file

@ -131,6 +131,8 @@
<%= render "users/sessions/session_end_modal" %>
<%= render "label_printers/label_printer_modal" %>
<%= render "shared/export_stock_consumption_modal" %>
<!-- Manage Stock Modal -->
<%= render partial: 'shared/manage_stock_value_modal' %>
<% end %>
<span style="display: none;" data-hook="application-body-end-html"></span>

View file

@ -41,5 +41,6 @@
<%= javascript_include_tag 'prism' %>
<%= javascript_include_tag 'shareable_links/date_formatting' %>
<%= javascript_include_tag 'shareable_links/handson_table_wraping' %>
</body>
</html>

View file

@ -1,5 +1,5 @@
<div class="task-notes">
<div class="task-notes-content">
<div class="task-notes-content text-base">
<% if can_update_my_module_description?(@my_module) %>
<%= render partial: "my_modules/description_form" %>
<% elsif @my_module.description.present? %>

View file

@ -1,7 +1,7 @@
<div class="task-sharing-and-flows flex items-center gap-2 pl-3">
<%= render partial: 'my_modules/status_flow/task_flow_button', locals: { my_module: @my_module } if @my_module.my_module_status_flow %>
<%= javascript_include_tag("my_modules/status_flow") %>
<% if current_team.shareable_links_enabled? && can_share_my_module?(@my_module) %>
<% if current_team.shareable_links_enabled? %>
<div id="share-task-container" data-behaviour="vue">
<share-task-container
shareable-link-url="<%= my_module_shareable_link_path(@my_module) %>"

View file

@ -152,9 +152,6 @@
<!-- Delete file modal -->
<%= render partial: 'assets/asset_delete_modal' %>
<!-- Manage Stock Modal -->
<%= render partial: 'shared/manage_stock_value_modal' %>
<!-- Consume Stock Modal -->
<%= render partial: 'my_modules/repositories/consume_stock_modal'%>

View file

@ -3,13 +3,13 @@
<div class="modal-dialog" role="document" data-e2e="e2e-MD-invNewInventory">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-invNewInventory-close"><i class="sn-icon sn-icon-close"></i></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-invNewInventoryMD-close"><i class="sn-icon sn-icon-close"></i></button>
<h3 class="modal-title" id="create-repo-modal-label">
<%= t("repositories.index.modal_create.title") %>
</h3>
</div>
<div class="modal-body">
<div class="form-group sci-input-container" data-e2e="e2e-IF-invNewInventory-input">
<div class="form-group sci-input-container">
<label><%= t("repositories.index.modal_create.name_label") %> </label>
<%= f.text_field :name,
autofocus: true,
@ -18,8 +18,8 @@
</div>
</div>
<div class="modal-footer">
<button type="button" data-e2e="e2e-BT-invNewInventory-cancel" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
<%= f.submit t("repositories.index.modal_create.submit"), class: "btn btn-success", 'data-e2e':"e2e-BT-invNewInventory-create" %>
<button type="button" data-e2e="e2e-BT-invNewInventoryMD-cancel" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
<%= f.submit t("repositories.index.modal_create.submit"), class: "btn btn-success", 'data-e2e':"e2e-BT-invNewInventoryMD-create" %>
</div>
</div>
</div>

View file

@ -1,16 +1,16 @@
<div class="modal fade" id="deleteRepositoryRecord" tabindex="-1" role="dialog" aria-labelledby="deleteRepositoryRecordLabel">
<div class="modal-dialog" role="document">
<div class="modal-dialog" role="document" data-e2e="e2e-MD-invInventoryDeleteAT">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<button type="button" data-e2e="e2e-BT-invInventoryDeleteMD-close" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<h4 class="modal-title"><%= t("repositories.modal_delete_record.title") %></h4>
</div>
<div class="modal-body">
<%= t("repositories.modal_delete_record.notice") %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t("general.cancel")%></button>
<button type="button" class="btn btn-danger delete-record-modal-button" data-dismiss="modal">
<button type="button" data-e2e="e2e-BT-invInventoryDeleteMD-cancel" class="btn btn-secondary" data-dismiss="modal"><%= t("general.cancel")%></button>
<button type="button" data-e2e="e2e-BT-invInventoryDeleteMD-delete" class="btn btn-danger delete-record-modal-button" data-dismiss="modal">
<%= t("repositories.modal_delete_record.delete") %>
</button>
</div>

View file

@ -6,10 +6,10 @@
<%= form_with(url: export_repository_team_path(repository),
html: { id: 'form-repository-rows-export' },
data: { remote: true }) do |f| %>
<div class="modal-dialog" role="document">
<div class="modal-dialog" role="document" data-e2e="e2e-MD-invInventoryExportAT">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<button type="button" data-e2e="e2e-BT-invInventoryExportMD-close" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<h4 class="modal-title"><%=t 'zip_export.repositories_modal_label' %></h4>
</div>
<div class="modal-body">
@ -18,7 +18,7 @@
<div><%=t 'zip_export.repository_footer_html' %></div>
</div>
<div class="modal-footer">
<button type='button' class='btn btn-secondary' data-dismiss='modal' id='close-modal-export-repository-rows'><%= t('general.cancel')%></button>
<button type='button' data-e2e='e2e-BT-invInventoryExportMD-cancel' class='btn btn-secondary' data-dismiss='modal' id='close-modal-export-repository-rows'><%= t('general.cancel')%></button>
<%= f.submit t('my_modules.repository.export'), id: "export-repository-rows", class: "btn btn-success" %>
</div>
</div>

View file

@ -1,8 +1,8 @@
<div class="modal fade" id="modal-info-repository-row" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content" data-e2e="e2e-MD-invInventoryItem">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close" data-e2e="e2e-BT-invInventoryItem-cancel"></i></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
<h4 class="modal-title">
<%= t('repository_row.modal_info.head_title', repository_row: @repository_row.name) %>
<%= @repository_row.archived? ? I18n.t('atwho.res.archived') : '' %>
@ -138,8 +138,8 @@
</button>
</span>
<% else %>
<button data-e2e="e2e-BT-invInventoryItem-close" type="button" class="btn btn-secondary" data-dismiss="modal"><%= t('general.close')%></button>
<button data-e2e="e2e-BT-invInventoryItem-print" type="button" class="btn btn-primary print-label-button" data-rows="[<%= @repository_row.id %>]"><%= t('repository_row.modal_print_label.print_label') %></button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t('general.close')%></button>
<button type="button" class="btn btn-primary print-label-button" data-rows="[<%= @repository_row.id %>]"><%= t('repository_row.modal_print_label.print_label') %></button>
<% end %>
</div>
</div>

View file

@ -27,7 +27,7 @@
data-snapshot-provisioning="<%= @snapshot_provisioning %>"
data-status-url="<%= repository_status_path(@repository) %>">
<thead>
<tr class="repository-table-head-<%= repository.id %> hidden" data-e2e="e2e-RT-invInventory-tableHeadRow-<%= repository.id %>">
<tr class="repository-table-head-<%= repository.id %> hidden" data-e2e="e2e-TR-invInventory-headRow-<%= repository.id %>">
<th id="checkbox" data-unmanageable="true">
<div class="sci-checkbox-container">
<input name="select_all" value="1" type="checkbox" class="sci-checkbox">

View file

@ -88,7 +88,7 @@
<button class="btn btn-light btn-black icon-btn manage-repo-column-index" title="<%= t("libraries.manange_modal_column.button_tooltip") %>"
data-modal-url="<%= repository_repository_columns_path(@repository) %>"
data-action="new">
<span class="sn-icon sn-icon sn-icon-reports">
<span class="sn-icon sn-icon sn-icon-reports" data-e2e="e2e-BT-invInventoryRT-manageColumns">
</button>
</div>
</div>

View file

@ -78,7 +78,6 @@
<%= render partial: 'repository_filters' %>
<%= render partial: 'save_repository_filter_modal' %>
<%= render partial: 'shared/manage_stock_value_modal' %>
<%= javascript_include_tag 'vue_components_action_toolbar' %>

View file

@ -46,7 +46,9 @@
<div class="task-notes">
<div class="task-notes-content">
<% if @my_module.description.present? %>
<%= smart_annotation_text(@my_module.shareable_tinymce_render(:description)) %>
<div class="rtf-view w-full">
<%= smart_annotation_text(@my_module.shareable_tinymce_render(:description)) %>
</div>
<% else %>
<span class="no-description"><%= t('my_modules.notes.no_description') %></span>
<% end %>

View file

@ -42,7 +42,7 @@
<div>
<div id="protocol-description-container" >
<% if protocol.description.present? %>
<div>
<div class="rtf-view w-full">
<%= smart_annotation_text(protocol.shareable_tinymce_render(:description)) %>
</div>
<% else %>

View file

@ -8,7 +8,7 @@
</div>
<% end %>
<% if element.text.present? %>
<div class="rounded min-h-[2.25rem] mb-4 relative group/text_container content__text-body">
<div class="rtf-view w-full rounded min-h-[2.25rem] mb-4 relative group/text_container content__text-body">
<%= smart_annotation_text(element.shareable_tinymce_render(:text)) %>
</div>
<% else %>

View file

@ -1,16 +1,16 @@
<div class="dropdown view-switch" >
<div href="#" class="btn btn-light btn-black view-switch-button prevent-shrink <%= "disabled" if disabled %>" id="viewSwitchButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="state-view-switch-btn-name"><%= archived ? t('toolbar.archived_state') : t('toolbar.active_state') %></span>
<span class="state-view-switch-btn-name" data-e2e="e2e-TX-invInventoryViewSwitchRT-selected"><%= archived ? t('toolbar.archived_state') : t('toolbar.active_state') %></span>
<span class="sn-icon sn-icon-down"></span>
</div>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="viewSwitchButton">
<% if switchable %>
<li class="view-switch-active">
<li class="view-switch-active" data-e2e="e2e-BT-invInventoryViewSwitchRT-active">
<%= link_to active_url, class: "#{ 'form-dropdown-state-item prevent-shrink' unless archived }" do %>
<%= t('toolbar.active_state') %>
<% end %>
</li>
<li class="view-switch-archived">
<li class="view-switch-archived" data-e2e="e2e-BT-invInventoryViewSwitchRT-archived">
<%= link_to archived_url, class: "#{ 'form-dropdown-state-item prevent-shrink' if archived }" do %>
<%= t('toolbar.archived_state') %>
<% end %>

View file

@ -111,7 +111,7 @@
</div>
</div>
<div class="edit-webhook-container hidden">
<%= form_with model: webhook, url: users_settings_webhook_path(webhook, filter_id: filter.id, sort: @current_sort), class: 'webhook-form', method: :patch do |f| %>
<%= form_with model: webhook, url: users_settings_webhook_path(webhook, filter_id: filter.id, sort: @current_sort), class: 'webhook-form', method: :patch, data: { remote: true } do |f| %>
<%= render partial: 'webhook_form', locals: {f: f} %>
<% end %>
</div>

View file

@ -10,4 +10,6 @@ Rails.application.configure do
config.x.core_api_rate_limit = ENV['CORE_API_RATE_LIMIT'] ? ENV['CORE_API_RATE_LIMIT'].to_i : 1000
config.x.core_api_v1_enabled = ENV['CORE_API_V1_ENABLED'] || false
config.x.core_api_v2_enabled = ENV['CORE_API_V2_ENABLED'] || false
end

View file

@ -112,6 +112,7 @@ Rails.application.config.assets.precompile += %w(reports/template_helpers.js)
Rails.application.config.assets.precompile += %w(shareable_links/my_module_protocol_show.js)
Rails.application.config.assets.precompile += %w(shareable_links/repositories.js)
Rails.application.config.assets.precompile += %w(shareable_links/date_formatting.js)
Rails.application.config.assets.precompile += %w(shareable_links/handson_table_wraping.js)
Rails.application.config.assets.precompile += %w(shareable_links/my_module_results_show.js)
# Libraries needed for Handsontable formulas

View file

@ -220,7 +220,7 @@ en:
configuration:
disabled: 'Webhooks are disabled'
url:
not_valid: 'Not valid URL'
not_valid: 'not valid URL'
result_text:
attributes:
text:
@ -1297,7 +1297,8 @@ en:
16_x_24: '384 (16 x 24)'
8_x_12: '96 (8 x 12)'
6_x_8: '48 (6 x 8 )'
6_x_4: '24 (6 x 4)'
4_x_6: '24 (4 x 6)'
3_x_4: '12 (3 x 4)'
2_x_3: '6 (2 x 3)'
text:
placeholder: "Enter result text"
@ -3310,7 +3311,8 @@ en:
16_x_24: '384 (16 x 24)'
8_x_12: '96 (8 x 12)'
6_x_8: '48 (6 x 8 )'
6_x_4: '24 (6 x 4)'
4_x_6: '24 (4 x 6)'
3_x_4: '12 (3 x 4)'
2_x_3: '6 (2 x 3)'
table:
default_name: 'Table %{position}'

View file

@ -7,10 +7,6 @@ Rails.application.routes.draw do
# Addons
def draw(routes_name)
instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
end
constraints UserSubdomain do
devise_for :users, controllers: { registrations: 'users/registrations',
sessions: 'users/sessions',
@ -960,6 +956,8 @@ Rails.application.routes.draw do
end
end
end
draw(:api_v2) if Rails.configuration.x.core_api_v2_enabled
end
end

View file

@ -81,6 +81,7 @@ namespace :v2, module: 'v1' do
resources :step_texts, only: %i(index show create update destroy), path: 'step_texts'
end
end
get 'activities', to: 'tasks#activities'
end
end
@ -112,6 +113,19 @@ namespace :v2 do
resources :result_tables, only: %i(index show create update destroy), path: 'tables'
resources :result_texts, only: %i(index show create update destroy)
end
resources :protocols, only: :show do
resources :steps, except: %i(new edit) do
scope module: 'step_elements' do
resources :assets, except: %i(new edit)
resources :checklists, except: %i(new edit) do
resources :checklist_items, except: %i(new edit), as: :items
end
resources :tables, except: %i(new edit)
resources :texts, except: %i(new edit)
end
end
end
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
FactoryBot.define do
factory :result_orderable_element do
result
sequence(:position) { |n| n }
trait :result_text do
after(:build) do |result_orderable_element|
result_orderable_element.orderable ||= build(:result_text, result: result_orderable_element.result)
end
end
trait :result_table do
after(:build) do |result_orderable_element|
result_orderable_element.orderable ||= build(:result_table, result: result_orderable_element.result)
end
end
end
end

View file

@ -2,6 +2,7 @@
FactoryBot.define do
factory :result_text do
name { Faker::Name.unique.name }
text { Faker::Lorem.paragraph }
result
end

View file

@ -4,6 +4,6 @@ FactoryBot.define do
factory :step_orderable_element do
orderable { create :step_text, step: step }
step
position { step ? step.step_orderable_elements.count : Faker::Number.between(from: 1, to: 10) }
sequence(:position) { |n| n }
end
end

View file

@ -3,6 +3,6 @@
FactoryBot.define do
factory :table do
name { Faker::Name.unique.name }
contents { { data: [%w(A B C), %w(D E F), %w(G H I)] } }
contents { { data: [%w(A B C), %w(D E F), %w(G H I)] }.to_json }
end
end

View file

@ -7,6 +7,7 @@ require_relative 'support/controller_macros'
ENV['RAILS_ENV'] = 'test'
ENV['CORE_API_V1_ENABLED'] = 'true'
ENV['CORE_API_V2_ENABLED'] = 'true'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production

View file

@ -0,0 +1,195 @@
# frozen_string_literal: true
# rubocop:disable Metrics/BlockLength
require 'rails_helper'
RSpec.describe 'Api::V2::ResultAssetsController', type: :request do
let(:user) { create(:user) }
let(:team) { create(:team, created_by: user) }
let(:project) { create(:project, team: team, created_by: user) }
let(:experiment) { create(:experiment, :with_tasks, project: project, created_by: user) }
let(:task) { experiment.my_modules.first }
let(:result) { create(:result, user: user, my_module: task) }
let(:result_archived) { create(:result, :archived, user: user, my_module: task) }
let(:valid_headers) { { Authorization: "Bearer #{generate_token(user.id)}", 'Content-Type': 'application/json' } }
let(:api_path) do
api_v2_team_project_experiment_task_result_result_assets_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id
)
end
describe 'GET result_assets, #index' do
let(:result_asset) { create(:result_asset, result: result) }
context 'when has valid params' do
it 'renders 200' do
get api_path, headers: valid_headers
expect(response).to have_http_status(200)
hash_body = nil
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match_array(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(result.result_assets, each_serializer: Api::V2::ResultAssetSerializer)
.to_json
)['data']
)
end
end
context 'when result is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_result_result_assets_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: -1
), headers: valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET result_asset, #show' do
let(:result_asset) { create(:result_asset, result: result) }
context 'when has valid params' do
it 'renders 200' do
hash_body = nil
get api_v2_team_project_experiment_task_result_result_asset_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_asset.asset.id
), headers: valid_headers
expect(response).to have_http_status(200)
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(result_asset, serializer: Api::V2::ResultAssetSerializer)
.to_json
)['data']
)
end
end
end
describe 'POST result_asset, #create' do
let(:action) do
post(api_path, params: request_body.to_json, headers: valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'attachments',
attributes: {
file_data: "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lEQVQIHWP8//8/
AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg=='\''",
file_type: 'image/jpeg',
file_name: 'test.jpg'
}
}
}
end
it 'creates new result_asset' do
expect { action }.to change { ResultAsset.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status(201)
end
it 'returns well-formatted response' do
hash_body = nil
action
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(ResultAsset.last, serializer: Api::V2::ResultAssetSerializer)
.to_json
)['data']
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'attachments',
attributes: {}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'DELETE result_asset, #destroy' do
let(:result_asset) { create(:result_asset, result: result) }
let(:result_asset_archived) { create(:result_asset, result: result_archived) }
let(:action) do
delete(api_v2_team_project_experiment_task_result_result_asset_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_asset.asset.id
), headers: valid_headers)
end
let(:action_archived) do
delete(api_v2_team_project_experiment_task_result_result_asset_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result_archived.id,
id: result_asset_archived.asset.id
), headers: valid_headers)
end
it 'deletes result_asset' do
action
expect(response).to have_http_status(200)
expect(ResultAsset.where(id: result_asset.id)).to_not exist
expect(Asset.where(id: result_asset.asset.id)).to_not exist
end
it 'does not delete result_asset of archived result' do
action_archived
expect(response).to have_http_status(403)
expect(ResultAsset.where(id: result_asset_archived.id)).to exist
expect(Asset.where(id: result_asset_archived.asset.id)).to exist
end
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -0,0 +1,289 @@
# frozen_string_literal: true
# rubocop:disable Metrics/BlockLength
require 'rails_helper'
RSpec.describe 'Api::V2::ResultTablesController', type: :request do
let(:user) { create(:user) }
let(:team) { create(:team, created_by: user) }
let(:project) { create(:project, team: team, created_by: user) }
let(:experiment) { create(:experiment, :with_tasks, project: project, created_by: user) }
let(:task) { experiment.my_modules.first }
let(:result) { create(:result, user: user, my_module: task) }
let(:result_archived) { create(:result, :archived, user: user, my_module: task) }
let(:valid_headers) { { Authorization: "Bearer #{generate_token(user.id)}", 'Content-Type': 'application/json' } }
let(:api_path) do
api_v2_team_project_experiment_task_result_result_tables_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id
)
end
describe 'GET result_tables, #index' do
let!(:result_orderable_element) { create(:result_orderable_element, :result_table, result: result) }
context 'when has valid params' do
it 'renders 200' do
get api_path, headers: valid_headers
expect(response).to have_http_status(200)
hash_body = nil
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match_array(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(result.result_tables, each_serializer: Api::V2::ResultTableSerializer)
.to_json
)['data']
)
end
end
context 'when result is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_result_result_tables_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: -1
), headers: valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET result_table, #show' do
let(:result_table) { create(:result_table, result: result) }
context 'when has valid params' do
it 'renders 200' do
hash_body = nil
get api_v2_team_project_experiment_task_result_result_table_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_table.table.id
), headers: valid_headers
expect(response).to have_http_status(200)
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(result_table, serializer: Api::V2::ResultTableSerializer)
.to_json
)['data']
)
end
end
end
describe 'POST result_table, #create' do
let(:action) do
post(api_path, params: request_body.to_json, headers: valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'tables',
attributes: {
name: 'Result table',
contents: '{"data": [["group/time", "1 dpi", "6 dpi", "", ""], ["PVYNTN", "1", "1", "", ""]]}'
}
}
}
end
it 'creates new result_table' do
expect { action }.to change { ResultTable.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well-formatted response' do
hash_body = nil
action
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(ResultTable.last, serializer: Api::V2::ResultTableSerializer)
.to_json
)['data']
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'result_tables',
attributes: {}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'PATCH result_table, #update' do
let(:result_table) { create(:result_table, result: result) }
let(:result_table_archived) { create(:result_table, result: result_archived) }
let(:action) do
patch(api_v2_team_project_experiment_task_result_result_table_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_table.table.id
), params: request_body.to_json, headers: valid_headers)
end
let(:action_archived) do
patch(api_v2_team_project_experiment_task_result_result_table_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result_archived.id,
id: result_table_archived.table.id
), params: request_body.to_json, headers: valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'tables',
attributes: {
name: 'Result table',
contents: '{"data": [["group/time", "1 dpi", "6 dpi", "", ""], ["PVYNTN", "1", "1", "", ""]]}'
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well-formatted response' do
hash_body = nil
action
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(ResultTable.last, serializer: Api::V2::ResultTableSerializer)
.to_json
)['data']
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'result_tables',
attributes: {}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
context 'when result is archived' do
let(:request_body) do
{
data: {
type: 'result_tables',
attributes: {
name: 'Result table',
contents: '{"data": [["group/time", "1 dpi", "6 dpi", "", ""], ["PVYNTN", "1", "1", "", ""]]}'
}
}
}
end
it 'renders 403' do
action_archived
expect(response).to have_http_status(403)
end
end
end
describe 'DELETE result_table, #destroy' do
let(:result_table) { create(:result_table, result: result) }
let(:result_table_archived) { create(:result_table, result: result_archived) }
let(:delete_action) do
delete(api_v2_team_project_experiment_task_result_result_table_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_table.table.id
), headers: valid_headers)
end
let(:delete_action_archived) do
delete(api_v2_team_project_experiment_task_result_result_table_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result_archived.id,
id: result_table_archived.table.id
), headers: valid_headers)
end
it 'deletes result_table' do
delete_action
expect(response).to have_http_status(200)
expect(ResultTable.where(id: result_table.id)).to_not exist
expect(Table.where(id: result_table.table.id)).to_not exist
end
it 'does not delete result_table of archived result' do
delete_action_archived
expect(response).to have_http_status(403)
expect(ResultTable.where(id: result_table_archived.id)).to exist
expect(Table.where(id: result_table_archived.table.id)).to exist
end
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -0,0 +1,373 @@
# frozen_string_literal: true
# rubocop:disable Metrics/BlockLength
require 'rails_helper'
RSpec.describe 'Api::V2::ResultTextsController', type: :request do
let(:user) { create(:user) }
let(:team) { create(:team, created_by: user) }
let(:project) { create(:project, team: team, created_by: user) }
let(:experiment) { create(:experiment, :with_tasks, project: project, created_by: user) }
let(:task) { experiment.my_modules.first }
let(:result) { create(:result, user: user, my_module: task) }
let(:result_archived) { create(:result, :archived, user: user, my_module: task) }
let(:valid_headers) { { Authorization: "Bearer #{generate_token(user.id)}", 'Content-Type': 'application/json' } }
let(:api_path) do
api_v2_team_project_experiment_task_result_result_texts_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id
)
end
describe 'GET result_texts, #index' do
let!(:result_orderable_element) { create(:result_orderable_element, :result_text, result: result) }
context 'when has valid params' do
it 'renders 200' do
get api_path, headers: valid_headers
expect(response).to have_http_status(200)
hash_body = nil
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match_array(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(result.result_texts, each_serializer: Api::V2::ResultTextSerializer)
.to_json
)['data']
)
end
end
context 'when result is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_result_result_texts_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: -1
), headers: valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET result_text, #show' do
let(:result_text) { create(:result_text, result: result) }
context 'when has valid params' do
it 'renders 200' do
hash_body = nil
get api_v2_team_project_experiment_task_result_result_text_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_text.id
), headers: valid_headers
expect(response).to have_http_status(200)
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(result_text, serializer: Api::V2::ResultTextSerializer)
.to_json
)['data']
)
end
end
end
describe 'POST result_text, #create' do
let(:action) do
post(api_path, params: request_body.to_json, headers: valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'result_texts',
attributes: {
name: 'Result text',
text: '<p>Hello!</p>'
}
}
}
end
it 'creates new result_text' do
expect { action }.to change { ResultText.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status(201)
end
it 'returns well-formatted response' do
hash_body = nil
action
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(ResultText.last, serializer: Api::V2::ResultTextSerializer)
.to_json
)['data']
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'result_texts',
attributes: {}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
context 'when include tinymce' do
let(:request_body) do
{
data: {
type: 'result_texts',
attributes: {
name: 'Result text',
text: "Result text 1 <img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAA" \
"AACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lE" \
"QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg=='\" data-mce-token=\"1\">"
}
},
included: [
{
type: 'tiny_mce_assets',
attributes: {
file_data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAA'\
'AACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lE'\
'QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg==',
file_token: '1',
file_name: 'test.png'
}
}
]
}
end
it 'Response with correct text result and TinyMCE images' do
hash_body = nil
action
expect(response).to have_http_status 201
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(ResultText.last, serializer: Api::V2::ResultTextSerializer)
.to_json
)['data']
)
expect(ResultText.last.text).to include "data-mce-token=\"#{Base62.encode(TinyMceAsset.last.id)}\""
end
end
end
describe 'PATCH result_text, #update' do
let(:result_text) { create(:result_text, result: result) }
let(:result_text_archived) { create(:result_text, result: result_archived) }
let(:action) do
patch(api_v2_team_project_experiment_task_result_result_text_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_text.id
), params: request_body.to_json, headers: valid_headers)
end
let(:action_archived) do
patch(api_v2_team_project_experiment_task_result_result_text_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result_archived.id,
id: result_text_archived.id
), params: request_body.to_json, headers: valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'result_texts',
attributes: {
name: 'Result text',
text: '<h1>Hello!</h1>'
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well-formatted response' do
hash_body = nil
action
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(ResultText.last, serializer: Api::V2::ResultTextSerializer)
.to_json
)['data']
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'result_texts',
attributes: {}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
context 'when result is archived' do
let(:request_body) do
{
data: {
type: 'result_texts',
attributes: {
name: 'Result text',
text: '<h1>Hello!</h1>'
}
}
}
end
it 'renders 403' do
action_archived
expect(response).to have_http_status(403)
end
end
context 'when include tinymce' do
let(:request_body) do
{
data: {
type: 'result_texts',
attributes: {
name: 'Result text',
text: 'Result text 1 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAA'\
'AACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lE'\
'QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJgg==" data-mce-token="1">'
}
},
included: [
{
type: 'tiny_mce_assets',
attributes: {
file_data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAA'\
'AACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lE'\
'QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg==',
file_token: '1',
file_name: 'test.png'
}
}
]
}
end
it 'Response with correct text result and TinyMCE images' do
hash_body = nil
action
expect(response).to have_http_status 200
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(ResultText.last, serializer: Api::V2::ResultTextSerializer)
.to_json
)['data']
)
expect(ResultText.last.text).to include "data-mce-token=\"#{Base62.encode(TinyMceAsset.last.id)}\""
end
end
end
describe 'DELETE result_text, #destroy' do
let(:result_text) { create(:result_text, result: result) }
let(:result_text_archived) { create(:result_text, result: result_archived) }
let(:action) do
delete(api_v2_team_project_experiment_task_result_result_text_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result.id,
id: result_text.id
), headers: valid_headers)
end
let(:action_archived) do
delete(api_v2_team_project_experiment_task_result_result_text_path(
team_id: team.id,
project_id: project.id,
experiment_id: experiment.id,
task_id: task.id,
result_id: result_archived.id,
id: result_text_archived.id
), headers: valid_headers)
end
it 'deletes result_text' do
action
expect(response).to have_http_status(200)
expect(ResultText.where(id: result_text.id)).to_not exist
end
it 'does not delete result_text of archived result' do
action_archived
expect(response).to have_http_status(403)
expect(ResultText.where(id: result_text_archived.id)).to exist
end
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -0,0 +1,369 @@
# frozen_string_literal: true
# rubocop:disable Metrics/BlockLength
require 'rails_helper'
RSpec.describe 'Api::V2::ResultsController', type: :request do
let(:user) { create(:user) }
let(:another_user) { create(:user) }
let(:team1) { create(:team, created_by: user) }
let(:team2) { create(:team, created_by: another_user) }
let(:valid_project) { create(:project, name: Faker::Name.unique.name, created_by: user, team: team1) }
let(:unaccessible_project) { create(:project, name: Faker::Name.unique.name, created_by: user, team: team2) }
let(:valid_experiment) { create(:experiment, created_by: user, last_modified_by: user, project: valid_project) }
let(:unaccessible_experiment) { create(:experiment, created_by: user, last_modified_by: user, project: unaccessible_project) }
let(:valid_task) { create(:my_module, created_by: user, last_modified_by: user, experiment: valid_experiment) }
let(:unaccessible_task) { create(:my_module, created_by: user, last_modified_by: user, experiment: unaccessible_experiment) }
let!(:results) { create_list(:result, 3, user: user, last_modified_by: user, my_module: valid_task) }
let!(:unaccessible_result) { create(:result, user: user, last_modified_by: user, my_module: unaccessible_task) }
let(:valid_hash_body) do
{
data: {
type: 'results',
attributes: {
name: Faker::Name.unique.name
}
}
}
end
let(:valid_headers) do
{
Authorization: "Bearer #{generate_token(user.id)}",
'Content-Type': 'application/json'
}
end
describe 'GET results, #index' do
let(:api_path) do
api_v2_team_project_experiment_task_results_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task
)
end
it 'Response with correct results' do
hash_body = nil
get api_path, headers: valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(valid_task.results, each_serializer: Api::V2::ResultSerializer)
.to_json
)['data']
)
end
it 'When invalid request, task from another experiment' do
hash_body = nil
get api_v2_team_project_experiment_task_results_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: unaccessible_task
), headers: valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include(status: 404)
end
it 'When invalid request, user is not a member of the team' do
hash_body = nil
get api_v2_team_project_experiment_task_results_path(
team_id: team2.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task
), 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)
end
it 'When invalid request, non-existing task' do
hash_body = nil
get api_v2_team_project_experiment_task_results_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: -1
), headers: valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include(status: 404)
end
end
describe 'GET result, #show' do
let(:result_valid) { valid_task.results.first }
let(:api_path) do
api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: result_valid.id
)
end
it 'When valid request, user can read result' do
hash_body = nil
get api_path, headers: valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(result_valid, serializer: Api::V2::ResultSerializer)
.to_json
)['data']
)
end
it 'When invalid request, user is not a member of the team' do
hash_body = nil
get api_v2_team_project_experiment_task_result_path(
team_id: team2.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: result_valid.id
), 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)
end
it 'When invalid request, non-existing result' do
hash_body = nil
get api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: -1
), headers: valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include(status: 404)
end
it 'When invalid request, result from an unaccessible task' do
hash_body = nil
get api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: unaccessible_task,
id: unaccessible_result.id
), headers: valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include(status: 404)
end
end
describe 'POST result, #create' do
let(:api_path) do
api_v2_team_project_experiment_task_results_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task
)
end
it 'Response with correct result' do
hash_body = nil
post api_path, params: valid_hash_body.to_json, headers: valid_headers
expect(response).to have_http_status 200
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
JSON.parse(
ActiveModelSerializers::SerializableResource
.new(Result.last, serializer: Api::V2::ResultSerializer)
.to_json
)['data']
)
end
it 'When invalid request, non-existing task' do
hash_body = nil
post api_v2_team_project_experiment_task_results_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: -1
), params: valid_hash_body.to_json, headers: valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include(status: 404)
end
it 'When invalid request, user is not a member of the team' do
hash_body = nil
post api_v2_team_project_experiment_task_results_path(
team_id: team2.id,
project_id: unaccessible_project,
experiment_id: unaccessible_experiment,
task_id: unaccessible_task
), params: valid_hash_body.to_json, 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)
end
it 'When invalid request, task from another experiment' do
hash_body = nil
post api_v2_team_project_experiment_task_results_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: unaccessible_task
), params: valid_hash_body.to_json, headers: valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include(status: 404)
end
end
describe 'PUT result, #update' do
let(:result_valid) { valid_task.results.first }
let(:request_body) do
{
data: {
type: 'results',
attributes: {
name: 'my result'
}
}
}
end
let(:action) do
put(api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: result_valid.id
), params: request_body.to_json, headers: valid_headers)
end
context 'when has attributes for update' do
it 'updates tasks name' do
action
expect(result_valid.reload.name).to eq('my result')
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
end
context 'when there is nothing to update' do
let(:request_body_with_same_name) do
{
data: {
type: 'results',
attributes: {
name: result_valid.reload.name
}
}
}
end
it 'returns 204' do
put(api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: result_valid.id
), params: request_body_with_same_name.to_json, headers: valid_headers)
expect(response).to have_http_status 204
end
end
context 'when result is archived' do
let(:result_archived) do
create(:result, :archived, user: user, last_modified_by: user, my_module: valid_task)
end
let(:request_with_archived_result) do
{
data: {
type: 'results',
attributes: {
name: 'Result archived'
}
}
}
end
it 'returns 403' do
put(api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: result_archived.id
), params: request_with_archived_result.to_json, headers: valid_headers)
expect(response).to have_http_status 403
end
end
end
describe 'DELETE result, #destroy' do
let(:result_archived) do
create(:result, :archived, user: user, last_modified_by: user, my_module: valid_task)
end
let(:result_valid) { valid_task.results.first }
let(:action) do
delete(api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: result_valid.id
), headers: valid_headers)
end
let(:action_archived) do
delete(api_v2_team_project_experiment_task_result_path(
team_id: team1.id,
project_id: valid_project,
experiment_id: valid_experiment,
task_id: valid_task,
id: result_archived.id
), headers: valid_headers)
end
it 'deletes result' do
action_archived
expect(response).to have_http_status(200)
expect(Result.where(id: result_archived.id)).to_not exist
end
it 'does not delete archived result' do
action
expect(response).to have_http_status(403)
expect(Result.where(id: result_valid.id)).to exist
end
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -0,0 +1,198 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V2::AssetsController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team)
@experiment = create(:experiment, :with_tasks, project: @project)
@task = @experiment.my_modules.first
@protocol = create(:protocol, my_module: @task)
@step = create(:step, protocol: @protocol)
create_user_assignment(@task, UserRole.find_by(name: I18n.t('user_roles.predefined.owner')), @user)
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
let(:asset) { create :asset, step: @step }
describe 'GET steps, #index' do
context 'when has valid params' do
it 'renders 200' do
create(:step_asset, step: @step, asset: asset)
get(api_v2_team_project_experiment_task_protocol_step_assets_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), headers: @valid_headers)
expect(response).to have_http_status(200)
end
end
context 'when protocol is not found' do
it 'renders 404' do
get(api_v2_team_project_experiment_task_protocol_step_assets_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: -1,
step_id: @step.id
), headers: @valid_headers)
expect(response).to have_http_status(404)
end
end
end
describe 'GET step, #show' do
context 'when has valid params' do
it 'renders 200' do
create(:step_asset, step: @step, asset: asset)
get(api_v2_team_project_experiment_task_protocol_step_asset_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: asset.id
), headers: @valid_headers)
expect(response).to have_http_status(200)
end
end
context 'when experiment is not found' do
it 'renders 404' do
get(api_v2_team_project_experiment_task_protocol_step_asset_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: -1,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: asset.id
), headers: @valid_headers)
expect(response).to have_http_status(404)
end
end
context 'when asset is not found' do
it 'renders 404' do
get(api_v2_team_project_experiment_task_protocol_step_asset_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: -1
), headers: @valid_headers)
expect(response).to have_http_status(404)
end
end
end
describe 'POST step, #create' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
end
before :each do
allow_any_instance_of(Asset).to receive(:post_process_file)
end
let(:action) do
post(api_v2_team_project_experiment_task_protocol_step_assets_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
),
params: request_body,
headers: @valid_headers)
end
let(:request_body) do
{
data: {
type: 'attachments',
attributes: attributes
}
}
end
context 'when has valid params' do
context 'when multipart form' do
let(:file) { Rack::Test::UploadedFile.new(file_fixture('test.jpg').open) }
let(:attributes) { { file: file } }
it 'creates new asset' do
expect { action }.to change { Asset.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'calls post_process_file function for text extraction' do
expect_any_instance_of(Asset).to receive(:post_process_file)
action
end
end
context 'when base64 with json' do
let(:filedata_base64) do
'iVBORw0KGgoAAAANSUhEUgAAAAIAA'\
'AACCAIAAAD91JpzAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAE0lE'\
'QVQIHWP8//8/AwMDExADAQAkBgMBOOSShwAAAABJRU5ErkJggg=='
end
let(:attributes) { { file_data: filedata_base64, file_name: 'file.png', file_type: 'image/png' } }
let(:request_body) { super().to_json }
it 'creates new asset' do
expect { action }.to change { Asset.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'calls post_process_file function for text extraction' do
expect_any_instance_of(Asset).to receive(:post_process_file)
action
end
end
end
context 'when has missing param' do
let(:request_body) { { data: { type: 'attachments', attributes: {} } } }
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
end

View file

@ -0,0 +1,234 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V2::ChecklistsController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team, created_by: @user)
@experiment = create(:experiment, :with_tasks, project: @project)
@task = @experiment.my_modules.first
@protocol = create(:protocol, my_module: @task)
@step = create(:step, protocol: @protocol)
@checklist = create(:checklist, step: @step)
@valid_headers = {
'Authorization': 'Bearer ' + generate_token(@user.id),
'Content-Type': 'application/json'
}
end
describe 'GET checklist_items, #index' do
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_checklist_items_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
checklist_id: @checklist.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when checklist is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_protocol_step_checklist_items_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
checklist_id: -1
), headers: @valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET checklist_item, #show' do
let(:checklist_item) { create(:checklist_item, checklist: @checklist) }
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_checklist_item_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
checklist_id: @checklist.id,
id: checklist_item.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
end
describe 'POST checklist_item, #create' do
let(:action) do
post(api_v2_team_project_experiment_task_protocol_step_checklist_items_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
checklist_id: @checklist.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'checklist_items',
attributes: {
text: 'New checklist_item'
}
}
}
end
it 'creates new checklist_item' do
expect { action }.to change { ChecklistItem.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'checklist_items',
attributes: hash_including(text: 'New checklist_item')
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'checklist_items',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'PATCH checklist_item, #update' do
let(:checklist_item) { create(:checklist_item, checklist: @checklist) }
let(:action) do
patch(api_v2_team_project_experiment_task_protocol_step_checklist_item_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
checklist_id: @checklist.id,
id: checklist_item.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'checklist_items',
attributes: {
text: 'New checklist_item name'
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'checklist_items',
attributes: hash_including(
text: 'New checklist_item name'
)
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'checklist_items',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'DELETE checklist_item, #destroy' do
let(:checklist_item) { create(:checklist_item, checklist: @checklist) }
let(:action) do
delete(api_v2_team_project_experiment_task_protocol_step_checklist_item_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
checklist_id: @checklist.id,
id: checklist_item.id
), headers: @valid_headers)
end
it 'deletes checklist_item' do
action
expect(response).to have_http_status(200)
expect(ChecklistItem.where(id: checklist_item.id)).to_not exist
end
end
end

View file

@ -0,0 +1,226 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V2::ChecklistsController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team, created_by: @user)
@experiment = create(:experiment, :with_tasks, project: @project)
@task = @experiment.my_modules.first
@protocol = create(:protocol, my_module: @task)
@step = create(:step, protocol: @protocol)
@valid_headers = {
'Authorization': 'Bearer ' + generate_token(@user.id),
'Content-Type': 'application/json'
}
end
describe 'GET checklists, #index' do
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_checklists_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when step is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_protocol_step_checklists_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: -1
), headers: @valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET checklist, #show' do
let(:checklist) { create(:checklist, step: @step) }
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_checklist_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: checklist.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
end
describe 'POST checklist, #create' do
let(:action) do
post(api_v2_team_project_experiment_task_protocol_step_checklists_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'checklists',
attributes: {
name: 'New checklist'
}
}
}
end
it 'creates new checklist' do
expect { action }.to change { Checklist.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'checklists',
attributes: hash_including(name: 'New checklist')
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'checklists',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'PATCH checklist, #update' do
let(:checklist) { create(:checklist, step: @step) }
let(:action) do
patch(api_v2_team_project_experiment_task_protocol_step_checklist_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: checklist.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'checklists',
attributes: {
name: 'New checklist name'
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'checklists',
attributes: hash_including(
name: 'New checklist name'
)
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'checklists',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'DELETE checklist, #destroy' do
let(:checklist) { create(:checklist, step: @step) }
let(:action) do
delete(api_v2_team_project_experiment_task_protocol_step_checklist_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: checklist.id
), headers: @valid_headers)
end
it 'deletes checklist' do
action
expect(response).to have_http_status(200)
expect(Checklist.where(id: checklist.id)).to_not exist
end
end
end

View file

@ -0,0 +1,230 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V2::StepElements::TablesController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team, created_by: @user)
@experiment = create(:experiment, :with_tasks, project: @project, created_by: @user)
@task = @experiment.my_modules.first
@protocol = create(:protocol, my_module: @task)
@step = create(:step, protocol: @protocol)
@valid_headers = {
'Authorization': 'Bearer ' + generate_token(@user.id),
'Content-Type': 'application/json'
}
end
describe 'GET tables, #index' do
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_tables_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when step is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_protocol_step_tables_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: -1
), headers: @valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET table, #show' do
let(:table) { create(:table, step: @step) }
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_table_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: table.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
end
describe 'POST table, #create' do
let(:action) do
post(api_v2_team_project_experiment_task_protocol_step_tables_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'tables',
attributes: {
name: 'New table',
contents: '{"data": [["group/time", "1 dpi", "6 dpi", "", ""], ["PVYNTN", "1", "1", "", ""]]}'
}
}
}
end
it 'creates new table' do
expect { action }.to change { Table.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'tables',
attributes: hash_including(name: 'New table')
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'tables',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'PATCH table, #update' do
let(:table) { create(:table, step: @step) }
let(:action) do
patch(api_v2_team_project_experiment_task_protocol_step_table_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: table.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'tables',
attributes: {
name: 'New table name',
contents: '{"data": [["group/time", "2 dpi", "7 dpi", "", ""], ["PVYNTN", "2", "2", "", ""]]}'
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'tables',
attributes: hash_including(
name: 'New table name',
contents: '{"data": [["group/time", "2 dpi", "7 dpi", "", ""], ["PVYNTN", "2", "2", "", ""]]}'
)
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'tables',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'DELETE table, #destroy' do
let(:table) { create(:table, step: @step) }
let(:action) do
delete(api_v2_team_project_experiment_task_protocol_step_table_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: table.id
), headers: @valid_headers)
end
it 'deletes table' do
action
expect(response).to have_http_status(200)
expect(Table.where(id: table.id)).to_not exist
end
end
end

View file

@ -0,0 +1,227 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V2::StepElements::TextsController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team, created_by: @user)
@experiment = create(:experiment, :with_tasks, project: @project, created_by: @user)
@task = @experiment.my_modules.first
@protocol = create(:protocol, my_module: @task)
@step = create(:step, protocol: @protocol)
@valid_headers = {
'Authorization': 'Bearer ' + generate_token(@user.id),
'Content-Type': 'application/json'
}
end
describe 'GET texts, #index' do
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_texts_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when step is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_protocol_step_texts_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: -1
), headers: @valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET step_text, #show' do
let(:step_text) { create(:step_text, step: @step) }
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_text_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: step_text.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
end
describe 'POST step_text, #create' do
let(:action) do
post(api_v2_team_project_experiment_task_protocol_step_texts_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'step_texts',
attributes: {
text: "<p>Hello!</p>"
}
}
}
end
it 'creates new step_text' do
expect { action }.to change { StepText.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'step_texts',
attributes: hash_including(text: '<p>Hello!</p>')
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'step_texts',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'PATCH step_text, #update' do
let(:step_text) { create(:step_text, step: @step) }
let(:action) do
patch(api_v2_team_project_experiment_task_protocol_step_text_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: step_text.id
), params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'step_texts',
attributes: {
text: '<h1>Hello!</h1>'
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'step_texts',
attributes: hash_including(
text: '<h1>Hello!</h1>'
)
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'step_texts',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'DELETE step_text, #destroy' do
let(:step_text) { create(:step_text, step: @step) }
let(:action) do
delete(api_v2_team_project_experiment_task_protocol_step_text_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: step_text.id
), headers: @valid_headers)
end
it 'deletes step_text' do
action
expect(response).to have_http_status(200)
expect(StepText.where(id: step_text.id)).to_not exist
end
end
end

View file

@ -0,0 +1,280 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V2::StepsController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team, created_by: @user)
@experiment = create(:experiment, :with_tasks, project: @project, created_by: @user)
@task = @experiment.my_modules.first
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
let(:protocol) { create :protocol, my_module: @task }
let(:steps) { create_list(:step, 3, protocol: protocol) }
let(:step) { steps.first }
describe 'GET steps, #index' do
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_steps_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when has valid params, with rendered RTE field' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_steps_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id
), params: { render_rte: true }, headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when protocol is not found' do
it 'renders 404' do
get api_v2_team_project_experiment_task_protocol_steps_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: -1
), headers: @valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET step, #show' do
context 'when has valid params' do
it 'renders 200' do
get api_v2_team_project_experiment_task_protocol_step_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id,
id: steps.first.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
end
describe 'POST step, #create' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
end
let(:action) do
post(api_v2_team_project_experiment_task_protocol_steps_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id
),
params: request_body.to_json,
headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'steps',
attributes: {
name: 'Step name',
description: 'Description about step'
}
}
}
end
it 'creates new step' do
expect { action }.to change { Step.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'steps',
attributes: hash_including(name: 'Step name')
)
)
)
expect(json).to match(
hash_including(
data: hash_including(
type: 'steps',
attributes: hash_not_including(:description)
)
)
)
end
it 'sets the last_changed_by' do
action
expect(Step.find(json['data']['id']).last_modified_by_id).to be @user.id
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'steps',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'PATCH step, #update' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
end
let(:action) do
patch(
api_v2_team_project_experiment_task_protocol_step_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id,
id: step.id
),
params: request_body.to_json,
headers: @valid_headers
)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'steps',
attributes: {
name: 'New step name',
description: 'New description about step'
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'steps',
attributes: hash_including(name: 'New step name')
)
)
)
expect(json).to match(
hash_including(
data: hash_including(
type: 'steps',
attributes: hash_not_including(:description)
)
)
)
end
it 'sets the last_changed_by' do
action
expect(Step.find(json['data']['id']).last_modified_by_id).to be @user.id
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'steps',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
describe 'DELETE step, #destroy' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
end
let(:action) do
delete(
api_v2_team_project_experiment_task_protocol_step_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id,
id: step.id,
data: {attributes: {name: 'Test'}, type: "steps"}
),
headers: @valid_headers
)
end
it 'deletes step' do
action
expect(response).to have_http_status(200)
expect(Step.where(id: step.id)).to_not exist
end
end
end