Mergre latest core-api [SCI-2515]

This commit is contained in:
Oleksii Kriuchykhin 2018-09-14 10:24:05 +02:00
commit 49e788a9ad
71 changed files with 1913 additions and 126 deletions

View file

@ -54,7 +54,6 @@ gem 'rgl' # Graph framework for project diagram calculations
gem 'nested_form_fields'
gem 'ajax-datatables-rails', '~> 0.3.1'
gem 'commit_param_routing' # Enables different submit actions in the same form to route to different actions in controller
gem 'kaminari'
gem 'i18n-js', '~> 3.0' # Localization in javascript files
gem 'roo', '~> 2.7.1' # Spreadsheet parser
gem 'wicked_pdf', '~> 1.1.0'

View file

@ -115,6 +115,7 @@
RepositoryItemEditForm.prototype.parseToFormObject = function(tableID, selectedRecord) {
var formData = this.formData;
var formDataObj = new FormData();
var removeFileColumns = [];
formDataObj.append('request_url', $(tableID).data('current-uri'));
formDataObj.append('repository_row_id', $(selectedRecord).attr('id'));
@ -125,18 +126,25 @@
} else {
var colId = element.replace('colId-', '');
var $el = $('#' + element);
// don't save anything if element is not visible
if($el.length == 0) {
return true;
}
if($el.attr('type') === 'file') {
// don't save anything if element is deleted
// handle deleting of element
if($el.attr('remove') === "true") {
return true;
removeFileColumns.push(colId);
formDataObj.append('repository_cells[' + colId + ']', null);
} else {
formDataObj.append('repository_cells[' + colId + ']',
getFileValue($el));
}
formDataObj.append('repository_cells[' + colId + ']',
getFileValue($el));
} else if(value.length > 0) {
} else if(value.length >= 0) {
formDataObj.append('repository_cells[' + colId + ']', value);
}
}
});
formDataObj.append('remove_file_columns', JSON.stringify(removeFileColumns));
return formDataObj;
}
/**

View file

@ -270,7 +270,7 @@
function _uploadedAssetPreview(asset, i) {
var html = '<div class="panel panel-default panel-step-attachment-new">';
html += '<div class="panel-heading">';
html += '<span class="fas fa-file"></span>';
html += '<span class="fas fa-paperclip"></span>';
html += '<%= I18n.t 'assets.drag_n_drop.file_label' %>';
html += '<div class="pull-right">';
html += '<a data-item-id="' + i + '" href="#">';
@ -442,7 +442,7 @@
function _uploadedAssetPreview(asset, i) {
var html = '<div class="panel panel-default panel-result-attachment-new">';
html += '<div class="panel-heading">';
html += '<span class="fas fa-file"></span>';
html += '<span class="fas fa-paperclip"></span>';
html += '<%= I18n.t 'assets.drag_n_drop.file_label' %>';
html += '<div class="pull-right">';
html += '<a data-item-id="' + i + '" href="#">';

View file

@ -164,8 +164,10 @@
}
function initializeModalsToggle() {
$("[data-trigger='invite-users']").on('click', function() {
$("[data-trigger='invite-users']").on('click', function(event) {
var id = $(this).attr('data-modal-id');
event.preventDefault();
event.stopPropagation();
$('[data-role=invite-users-modal][data-id=' + id + ']')
.modal('show');
});

View file

@ -1,5 +1,3 @@
//= require users/settings/teams/invite_users_modal
(function() {
'use strict';

View file

@ -23,7 +23,7 @@ class ActivitiesController < ApplicationController
activities = current_user.last_activities
.page(page)
.per(Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
unless activities.last_page?
unless activities.blank? || activities.last_page?
more_url = url_for(
activities_url(
format: :json,

View file

@ -1,14 +1,16 @@
# frozen_string_literal: true
module Api
module V1
class BaseController < ApiController
rescue_from ActionController::ParameterMissing do |e|
logger.error e.message
render json: {}, status: :bad_request
render jsonapi: {}, status: :bad_request
end
rescue_from ActiveRecord::RecordNotFound do |e|
logger.error e.message
render json: {}, status: :not_found
render jsonapi: {}, status: :not_found
end
end
end

View file

@ -0,0 +1,59 @@
# frozen_string_literal: true
module Api
module V1
class ConnectionsController < BaseController
before_action :load_team
before_action :load_project
before_action :load_experiment
before_action :load_connections
before_action :load_connection, only: :show
def index
@connections = @connections.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: @connections, each_serializer: ConnectionSerializer
end
def show
render jsonapi: @connection, serializer: ConnectionSerializer
end
private
def load_team
@team = Team.find(params.require(:team_id))
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
def load_project
@project = @team.projects.find(params.require(:project_id))
render jsonapi: {}, status: :forbidden unless can_read_project?(
@project
)
end
def load_experiment
@experiment = @project.experiments.find(params.require(:experiment_id))
render jsonapi: {}, status: :forbidden unless can_read_experiment?(
@experiment
)
end
def load_connections
@connections = Connection.joins(
'LEFT JOIN my_modules AS inputs ON input_id = inputs.id'
).joins(
'LEFT JOIN my_modules AS outputs ON output_id = outputs.id'
).where(
'inputs.experiment_id = ? OR outputs.experiment_id = ?',
@experiment.id, @experiment.id
)
end
def load_connection
@connection = @connections.find(params.require(:id))
end
end
end
end

View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
module Api
module V1
class ExperimentsController < BaseController
before_action :load_team
before_action :load_project
before_action :load_experiment, only: :show
def index
experiments = @project.experiments
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: experiments, each_serializer: ExperimentSerializer
end
def show
render jsonapi: @experiment, serializer: ExperimentSerializer
end
private
def load_team
@team = Team.find(params.require(:team_id))
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
def load_project
@project = @team.projects.find(params.require(:project_id))
render jsonapi: {}, status: :forbidden unless can_read_project?(
@project
)
end
def load_experiment
@experiment = @project.experiments.find(params.require(:id))
render jsonapi: {}, status: :forbidden unless can_read_experiment?(
@experiment
)
end
end
end
end

View file

@ -0,0 +1,77 @@
# frozen_string_literal: true
module Api
module V1
class InventoriesController < BaseController
before_action :load_team
before_action :load_inventory, only: %i(show update destroy)
before_action :check_manage_permissions, only: %i(create update destroy)
def index
inventories = @team.repositories
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: inventories, each_serializer: InventorySerializer
end
def create
inventory = @team.repositories.create!(inventory_params)
render jsonapi: inventory,
serializer: InventorySerializer,
status: :created
end
def show
render jsonapi: @inventory, serializer: InventorySerializer
end
def update
@inventory.attributes = update_inventory_params
if @inventory.changed? && @inventory.save!
render jsonapi: @inventory, serializer: InventorySerializer
else
render body: nil
end
end
def destroy
@inventory.destroy!
render body: nil
end
private
def load_team
@team = Team.find(params.require(:team_id))
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
def load_inventory
@inventory = @team.repositories.find(params.require(:id))
end
def check_manage_permissions
unless can_manage_repository?(@inventory)
render body: nil, status: :forbidden
end
end
def inventory_params
unless params.require(:data).require(:type) == 'inventories'
raise ActionController::BadRequest,
'Wrong object type within parameters'
end
params.require(:data).require(:attributes)
params.permit(data: { attributes: %i(name) })[:data]
end
def update_inventory_params
unless params.require(:data).require(:id).to_i == params[:id].to_i
raise ActionController::BadRequest,
'Object ID mismatch in URL and request body'
end
inventory_params[:attributes]
end
end
end
end

View file

@ -0,0 +1,89 @@
# frozen_string_literal: true
module Api
module V1
class InventoryColumnsController < BaseController
before_action :load_team
before_action :load_inventory
before_action :load_inventory_column, only: %i(show update destroy)
before_action :check_manage_permissions, only: %i(update destroy)
def index
columns = @inventory.repository_columns
.includes(:repository_list_items)
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: columns, each_serializer: InventoryColumnSerializer,
include: :inventory_list_items
end
def create
inventory_column =
@inventory.repository_columns.create!(inventory_column_params)
render jsonapi: inventory_column,
serializer: InventoryColumnSerializer,
status: :created
end
def show
render jsonapi: @inventory_column, serializer: InventoryColumnSerializer
end
def update
@inventory_column.attributes = update_inventory_column_params
if @inventory_column.changed? && @inventory_column.save!
render jsonapi: @inventory_column,
serializer: InventoryColumnSerializer
else
render body: nil
end
end
def destroy
@inventory_column.destroy!
render body: nil
end
private
def load_team
@team = Team.find(params.require(:team_id))
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
def load_inventory
@inventory = @team.repositories.find(params.require(:inventory_id))
end
def load_inventory_column
@inventory_column = @inventory.repository_columns
.find(params.require(:id))
end
def check_manage_permissions
unless can_manage_repository_column?(@inventory_column)
render body: nil, status: :forbidden
end
end
def inventory_column_params
unless params.require(:data).require(:type) == 'inventory_columns'
raise ActionController::BadRequest,
'Wrong object type within parameters'
end
params.require(:data).require(:attributes)
params
.permit(data: { attributes: %i(name data_type) })[:data]
.merge(created_by: @current_user)
end
def update_inventory_column_params
unless params.require(:data).require(:id).to_i == params[:id].to_i
raise ActionController::BadRequest,
'Object ID mismatch in URL and request body'
end
inventory_column_params[:attributes]
end
end
end
end

View file

@ -0,0 +1,139 @@
# frozen_string_literal: true
module Api
module V1
class InventoryItemsController < BaseController
before_action :load_team
before_action :load_inventory
before_action :load_inventory_item, only: %i(show update destroy)
before_action :check_manage_permissions, only: %i(create update destroy)
def index
items =
@inventory.repository_rows
.includes(repository_cells: :repository_column)
.includes(
repository_cells: Extends::REPOSITORY_SEARCH_INCLUDES
).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: items,
each_serializer: InventoryItemSerializer,
include: :inventory_cells
end
def create
attributes = inventory_item_params.merge(
created_by: current_user,
last_modified_by: current_user
)
if inventory_cells_params.present?
inventory_cells_params
.each { |p| p.require(:attributes).require(%i(column_id value)) }
item = @inventory.repository_rows.new(attributes)
item.transaction do
item.save!
inventory_cells_params.each do |cell_params|
cell_attributes = cell_params[:attributes]
column =
@inventory.repository_columns.find(cell_attributes[:column_id])
RepositoryCell.create_with_value!(
item, column, cell_attributes[:value], current_user
)
end
end
else
item = @inventory.repository_rows.create!(attributes)
end
render jsonapi: item,
serializer: InventoryItemSerializer,
include: :inventory_cells,
status: :created
end
def show
render jsonapi: @inventory_item,
serializer: InventoryItemSerializer,
include: :inventory_cells
end
def update
item_changed = false
if inventory_cells_params.present?
inventory_cells_params.each do |p|
p.require(%i(id attributes))
p.require(:attributes).require(:value)
end
@inventory_item.transaction do
inventory_cells_params.each do |cell_params|
cell = @inventory_item.repository_cells.find(cell_params[:id])
cell_value = cell_params.dig(:attributes, :value)
next unless cell.value.data_changed?(cell_value)
cell.value.update_data!(cell_value, current_user)
item_changed = true
end
end
end
@inventory_item.attributes = update_inventory_item_params
item_changed = true if @inventory_item.changed?
if item_changed
@inventory_item.last_modified_by = current_user
@inventory_item.save!
render jsonapi: @inventory_item,
serializer: InventoryItemSerializer,
include: :inventory_cells
else
render body: nil
end
end
def destroy
@inventory_item.destroy!
render body: nil
end
private
def load_team
@team = Team.find(params.require(:team_id))
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
def load_inventory
@inventory = @team.repositories.find(params.require(:inventory_id))
end
def load_inventory_item
@inventory_item = @inventory.repository_rows.find(params[:id].to_i)
end
def check_manage_permissions
unless can_manage_repository_rows?(@team)
render body: nil, status: :forbidden
end
end
def inventory_item_params
unless params.require(:data).require(:type) == 'inventory_items'
raise ActionController::BadRequest,
'Wrong object type within parameters'
end
params.require(:data).require(:attributes)
params.permit(data: { attributes: %i(name uid) })[:data]
end
def update_inventory_item_params
unless params.require(:data).require(:id).to_i == params[:id].to_i
raise ActionController::BadRequest,
'Object ID mismatch in URL and request body'
end
inventory_item_params[:attributes]
end
# Partially implement sideposting draft
# https://github.com/json-api/json-api/pull/1197
def inventory_cells_params
params[:included]&.select { |el| el[:type] == 'inventory_cells' }
end
end
end
end

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
module Api
module V1
class MyModuleGroupsController < BaseController
before_action :load_team
before_action :load_project
before_action :load_experiment
before_action :load_task_group, only: :show
def index
my_module_groups = @experiment.my_module_groups
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: my_module_groups,
each_serializer: MyModuleGroupSerializer
end
def show
render jsonapi: @my_module_group, serializer: MyModuleGroupSerializer
end
private
def load_team
@team = Team.find(params.require(:team_id))
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
def load_project
@project = @team.projects.find(params.require(:project_id))
render jsonapi: {}, status: :forbidden unless can_read_project?(
@project
)
end
def load_experiment
@experiment = @project.experiments.find(params.require(:experiment_id))
render jsonapi: {}, status: :forbidden unless can_read_experiment?(
@experiment
)
end
def load_task_group
@my_module_group = @experiment.my_module_groups.find(
params.require(:id)
)
end
end
end
end

View file

@ -0,0 +1,50 @@
# frozen_string_literal: true
module Api
module V1
class MyModulesController < BaseController
before_action :load_team
before_action :load_project
before_action :load_experiment
before_action :load_task, only: :show
def index
tasks = @experiment.my_modules
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: tasks, each_serializer: MyModuleSerializer
end
def show
render jsonapi: @my_module, serializer: MyModuleSerializer
end
private
def load_team
@team = Team.find(params.require(:team_id))
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
def load_project
@project = @team.projects.find(params.require(:project_id))
render jsonapi: {}, status: :forbidden unless can_read_project?(
@project
)
end
def load_experiment
@experiment = @project.experiments.find(params.require(:experiment_id))
render jsonapi: {}, status: :forbidden unless can_read_experiment?(
@experiment
)
end
def load_task
@my_module = @experiment.my_modules.find(params.require(:id))
render jsonapi: {}, status: :not_found if @my_module.nil?
end
end
end
end

View file

@ -1,23 +1,26 @@
# frozen_string_literal: true
module Api
module V1
class TeamsController < BaseController
before_action :load_team, only: :show
def index
teams = current_user.teams.page(params[:page]).per(params[:page_size])
render json: teams, each_serializer: TeamSerializer
teams = current_user.teams
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: teams, each_serializer: TeamSerializer
end
def show
render json: @team, serializer: TeamSerializer
render jsonapi: @team, serializer: TeamSerializer
end
private
def load_team
@team = Team.find(params.require(:id))
return render json: {}, status: :not_found unless @team
return render json: {}, status: :forbidden unless can_read_team?(@team)
render jsonapi: {}, status: :forbidden unless can_read_team?(@team)
end
end
end

View file

@ -0,0 +1,70 @@
# frozen_string_literal: true
module Api
module V1
class UserIdentitiesController < BaseController
before_action :load_user
before_action :load_user_identity, only: %i(show update destroy)
def index
identities = @user.user_identities
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: identities, each_serializer: UserIdentitySerializer
end
def create
identity = @user.user_identities.create!(user_identity_params)
render jsonapi: identity,
serializer: UserIdentitySerializer,
status: :created
end
def show
render jsonapi: @identity, serializer: UserIdentitySerializer
end
def update
@identity.attributes = update_user_identity_params
if @identity.changed? && @identity.save!
render jsonapi: @identity, serializer: UserIdentitySerializer
else
render body: nil
end
end
def destroy
@identity.destroy!
render body: nil
end
private
def load_user
@user = current_user if current_user.id == params[:user_id].to_i
return render body: nil, status: :forbidden unless @user
end
def load_user_identity
@identity = @user.user_identities.find(params[:id].to_i)
end
def user_identity_params
unless params.require(:data).require(:type) == 'user_identities'
raise ActionController::BadRequest,
'Wrong object type within parameters'
end
params.require(:data).require(:attributes)
params.permit(data: { attributes: %i(provider uid) })[:data]
end
def update_user_identity_params
unless params.require(:data).require(:id).to_i == params[:id].to_i
raise ActionController::BadRequest,
'Object ID mismatch in URL and request body'
end
user_identity_params
end
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Api
module V1
class UsersController < BaseController
before_action :load_user, only: :show
def show
render jsonapi: @user, serializer: UserSerializer
end
private
def load_user
@user = User.joins(:user_teams)
.where('user_teams.team': current_user.teams)
.find_by_id(params[:id])
render jsonapi: {}, status: :forbidden unless @user
end
end
end
end

View file

@ -134,7 +134,7 @@ class RepositoryRowsController < ApplicationController
existing.delete
end
elsif existing.value_type == 'RepositoryAssetValue'
next if value.blank?
existing.value.destroy && next if remove_file_columns_params.include?(key)
if existing.value.asset.update(file: value)
existing.value.asset.created_by = current_user
existing.value.asset.last_modified_by = current_user
@ -145,6 +145,7 @@ class RepositoryRowsController < ApplicationController
}
end
else
existing.value.destroy && next if value == ''
existing.value.data = value
if existing.value.save
record_annotation_notification(@record, existing)
@ -156,17 +157,12 @@ class RepositoryRowsController < ApplicationController
end
end
else
next if value == ''
# Looks like it is a new cell, so we need to create new value, cell
# will be created automatically
next if create_cell_value(@record, key, value, errors).nil?
end
end
# Clean up empty cells, not present in updated record
@record.repository_cells.each do |cell|
next if cell.value_type == 'RepositoryListValue'
cell.value.destroy unless cell_params
.key?(cell.repository_column_id.to_s)
end
else
@record.repository_cells.each { |c| c.value.destroy }
end
@ -369,6 +365,10 @@ class RepositoryRowsController < ApplicationController
params.permit(repository_cells: {}).to_h[:repository_cells]
end
def remove_file_columns_params
JSON.parse(params.fetch(:remove_file_columns) { '[]' })
end
def selected_params
params.permit(selected_rows: []).to_h[:selected_rows]
end

View file

@ -22,4 +22,32 @@ class RepositoryAssetValue < ApplicationRecord
def data
asset.file_file_name
end
def data_changed?(_new_data)
true
end
def update_data!(new_data, user)
file = Paperclip.io_adapters.for(new_data[:file_data])
file.original_filename = new_data[:file_name]
asset.file = file
asset.last_modified_by = user
self.last_modified_by = user
asset.save! && save!
end
def self.new_with_payload(payload, attributes)
value = new(attributes)
team = value.repository_cell.repository_column.repository.team
file = Paperclip.io_adapters.for(payload[:file_data])
file.original_filename = payload[:file_name]
value.asset = Asset.create!(
file: file,
created_by: value.created_by,
last_modified_by: value.created_by,
team: team
)
value.asset.post_process_file(team)
value
end
end

View file

@ -8,31 +8,51 @@ class RepositoryCell < ActiveRecord::Base
dependent: :destroy
belongs_to :repository_text_value,
(lambda do
where(repository_cells: { value_type: 'RepositoryTextValue' })
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryTextValue' })
end),
optional: true, foreign_key: :value_id
belongs_to :repository_date_value,
(lambda do
where(repository_cells: { value_type: 'RepositoryDateValue' })
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryDateValue' })
end),
optional: true, foreign_key: :value_id
belongs_to :repository_list_value,
(lambda do
where(repository_cells: { value_type: 'RepositoryListValue' })
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryListValue' })
end),
optional: true, foreign_key: :value_id
belongs_to :repository_asset_value,
(lambda do
where(repository_cells: { value_type: 'RepositoryAssetValue' })
includes(:repository_cell)
.where(repository_cells: { value_type: 'RepositoryAssetValue' })
end),
optional: true, foreign_key: :value_id
validates_inclusion_of :repository_column,
in: (lambda do |cell|
cell.repository_row.repository.repository_columns
end)
validates :repository_column, presence: true
validate :repository_column_data_type
validates :repository_row,
uniqueness: { scope: :repository_column },
unless: :importing
def self.create_with_value!(row, column, data, user)
cell = new(repository_row: row, repository_column: column)
cell.transaction do
value_klass = column.data_type.constantize
value = value_klass.new_with_payload(data, repository_cell: cell,
created_by: user,
last_modified_by: user)
cell.value = value
value.save!
end
end
private
def repository_column_data_type

View file

@ -17,4 +17,20 @@ class RepositoryDateValue < ApplicationRecord
def formatted
data
end
def data_changed?(new_data)
new_data != data
end
def update_data!(new_data, user)
self.data = new_data
self.last_modified_by = user
save!
end
def self.new_with_payload(payload, attributes)
value = new(attributes)
value.data = payload
value
end
end

View file

@ -1,6 +1,5 @@
class RepositoryListValue < ApplicationRecord
belongs_to :repository_list_item,
optional: true
belongs_to :repository_list_item
belongs_to :created_by,
foreign_key: :created_by_id,
class_name: 'User'
@ -11,6 +10,12 @@ class RepositoryListValue < ApplicationRecord
accepts_nested_attributes_for :repository_cell
validates :repository_cell, presence: true
validates_inclusion_of :repository_list_item,
in: (lambda do |list_value|
list_value.repository_cell
.repository_column
.repository_list_items
end)
def formatted
data.to_s
@ -20,4 +25,23 @@ class RepositoryListValue < ApplicationRecord
return nil unless repository_list_item
repository_list_item.data
end
def data_changed?(new_data)
new_data.to_i != repository_list_item_id
end
def update_data!(new_data, user)
self.repository_list_item_id = new_data.to_i
self.last_modified_by = user
save!
end
def self.new_with_payload(payload, attributes)
value = new(attributes)
value.repository_list_item = value.repository_cell
.repository_column
.repository_list_items
.find(payload)
value
end
end

View file

@ -10,7 +10,7 @@ class RepositoryRow < ApplicationRecord
foreign_key: :last_modified_by_id,
class_name: 'User',
optional: true
has_many :repository_cells, dependent: :destroy
has_many :repository_cells, -> { order(:id) }, dependent: :destroy
has_many :repository_columns, through: :repository_cells
has_many :my_module_repository_rows,
inverse_of: :repository_row, dependent: :destroy

View file

@ -18,4 +18,20 @@ class RepositoryTextValue < ApplicationRecord
def formatted
data
end
def data_changed?(new_data)
new_data != data
end
def update_data!(new_data, user)
self.data = new_data
self.last_modified_by = user
save!
end
def self.new_with_payload(payload, attributes)
value = new(attributes)
value.data = payload
value
end
end

View file

@ -1,5 +1,5 @@
class UserIdentity < ActiveRecord::Base
belongs_to :user
validates :provider, uniqueness: { scope: :user_id }
validates :uid, uniqueness: { scope: :provider }
validates :provider, presence: true, uniqueness: { scope: :user_id }
validates :uid, presence: true, uniqueness: { scope: :provider }
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Api
module V1
class ConnectionSerializer < ActiveModel::Serializer
type :connections
attributes :id, :input_id, :output_id
has_one :input_task, serializer: MyModuleSerializer
has_one :output_task, serializer: MyModuleSerializer
def input_task
MyModule.find(object.input_id)
end
def output_task
MyModule.find(object.output_id)
end
end
end
end

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Api
module V1
class ExperimentSerializer < ActiveModel::Serializer
type :experiments
attributes :id, :name, :description, :archived
end
end
end

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Api
module V1
class InventoryCellSerializer < ActiveModel::Serializer
type :inventory_cells
attributes :id, :value_type, :value
attribute :repository_column_id, key: :column_id
def value_type
type_id = RepositoryColumn
.data_types[object.repository_column.data_type]
I18n.t("api.v1.inventory_data_types.t#{type_id}")
end
def value
value =
case object.value_type
when 'RepositoryTextValue'
object.repository_text_value
when 'RepositoryDateValue'
object.repository_date_value
when 'RepositoryListValue'
object.repository_list_value
when 'RepositoryAssetValue'
object.repository_asset_value
end
ActiveModelSerializers::SerializableResource.new(
value,
namespace: Api::V1,
adapter: :attribute
).as_json
end
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Api
module V1
class InventoryColumnSerializer < ActiveModel::Serializer
type :inventory_columns
attributes :name, :data_type
has_many :repository_list_items, key: :inventory_list_items,
serializer: InventoryListItemSerializer,
class_name: 'RepositoryListItem'
def data_type
type_id = RepositoryColumn
.data_types[object.data_type]
I18n.t("api.v1.inventory_data_types.t#{type_id}")
end
end
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Api
module V1
class InventoryItemSerializer < ActiveModel::Serializer
type :inventory_items
attributes :name
has_many :repository_cells, key: :inventory_cells,
serializer: InventoryCellSerializer,
class_name: 'RepositoryCell'
end
end
end

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Api
module V1
class InventoryListItemSerializer < ActiveModel::Serializer
type :inventory_list_items
attribute :data
end
end
end

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Api
module V1
class InventorySerializer < ActiveModel::Serializer
type :inventories
attributes :id, :name
end
end
end

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Api
module V1
class MyModuleGroupSerializer < ActiveModel::Serializer
type :task_groups
attributes :id, :experiment_id
belongs_to :experiment, serializer: ExperimentSerializer
end
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Api
module V1
class MyModuleSerializer < ActiveModel::Serializer
type :tasks
attributes :id, :name, :due_date, :description, :state
attribute :my_module_group_id, key: :task_group_id
belongs_to :experiment, serializer: ExperimentSerializer
end
end
end

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Api
module V1
class RepositoryAssetValueSerializer < ActiveModel::Serializer
attributes :file_id, :file_name, :file_size, :url
def file_id
object.asset&.id
end
def file_name
object.asset&.file_file_name
end
def file_size
object.asset&.file_file_size
end
def url
if !object.asset&.file_present
nil
elsif object.asset&.file&.is_stored_on_s3?
object.asset.presigned_url(download: true)
else
# separate api endpoint for local files download is needed
download_asset_path(object.asset.id)
end
end
end
end
end

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Api
module V1
class RepositoryListValueSerializer < ActiveModel::Serializer
attribute :repository_list_item_id, key: :inventory_list_item_id
attribute :formatted, key: :inventory_list_item_name
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Api
module V1
class RepositoryTextValueSerializer < ActiveModel::Serializer
attribute :formatted, key: :text
end
end
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Api
module V1
class TeamSerializer < ActiveModel::Serializer

View file

@ -0,0 +1,7 @@
module Api
module V1
class UserIdentitySerializer < ActiveModel::Serializer
attributes :provider, :uid
end
end
end

View file

@ -0,0 +1,13 @@
module Api
module V1
class UserSerializer < ActiveModel::Serializer
attributes :full_name, :initials, :email
attribute :avatar_file_name,
if: -> { object.avatar.present? } { object.avatar_file_name }
attribute :avatar_file_size,
if: -> { object.avatar.present? } { object.avatar.size }
attribute :avatar_url,
if: -> { object.avatar.present? } { object.avatar.url(:icon) }
end
end
end

View file

@ -16,12 +16,14 @@ module Api
attr_accessor :core_api_token_ttl
attr_accessor :core_api_token_iss
attr_accessor :azure_ad_apps
attr_accessor :core_api_v1_preview
def initialize
@core_api_sign_alg = 'HS256'
@core_api_token_ttl = 30.minutes
@core_api_token_iss = 'SciNote'
@azure_ad_apps = {}
@core_api_v1_preview = false
end
end
end

View file

@ -0,0 +1,151 @@
class UserDataDeletion
def self.delete_team_data(team)
ActiveRecord::Base.logger = Logger.new(STDOUT)
Step.skip_callback(:destroy, :after, :cascade_after_destroy)
team.transaction do
# Destroy tiny_mce_assets
if team.tiny_mce_assets.present?
team.tiny_mce_assets.each do |tiny_mce_asset|
paperclip_file_destroy(tiny_mce_asset) if tiny_mce_asset.image.exists?
end
end
team.repositories.each do |repository|
repository.repository_rows.find_each do |repository_row|
# Destroy repository_cell assets
repository_row
.repository_cells
.where(value_type: 'RepositoryAssetValue').each do |repository_cell|
next unless repository_cell.value.asset.file.exists?
paperclip_file_destroy(repository_cell.value.asset)
end
end
repository.destroy
end
team.projects.each do |project|
project.reports.destroy_all
project.activities.destroy_all
project.experiments.each do |experiment|
experiment.my_modules.each do |my_module|
# Destroy result assets
my_module.results.each do |result|
result.result_table.delete if result.result_table.present?
result.table.delete if result.table.present?
next unless result.asset && result.asset.file.exists?
paperclip_file_destroy(result.asset)
end
my_module.activities.destroy_all
my_module.inputs.destroy_all
my_module.outputs.destroy_all
my_module.results.destroy_all
my_module.my_module_tags.destroy_all
my_module.task_comments.destroy_all
my_module.my_module_repository_rows.destroy_all
my_module.user_my_modules.destroy_all
my_module.report_elements.destroy_all
my_module.sample_my_modules.destroy_all
my_module.protocols.each { |p| p.update_attributes(parent_id: nil) }
my_module.protocols.each do |protocol|
destroy_protocol(protocol)
end
my_module.delete
end
# Destroy workflow image
if experiment.workflowimg.exists?
experiment.workflowimg.clear(:original)
end
experiment.activities.destroy_all
experiment.report_elements.destroy_all
experiment.my_module_groups.delete_all
experiment.delete
end
project.user_projects.destroy_all
project.tags.destroy_all
project.project_comments.destroy_all
project.report_elements.destroy_all
project.delete
end
team.protocols.each { |p| p.update_attributes(parent_id: nil) }
team.protocols.where(my_module: nil).each do |protocol|
destroy_protocol(protocol)
end
team.samples.destroy_all
team.samples_tables.destroy_all
team.sample_groups.destroy_all
team.sample_types.destroy_all
team.custom_fields.destroy_all
team.protocol_keywords.destroy_all
team.user_teams.delete_all
User.where(current_team_id: team).each do |user|
user.update(current_team_id: nil)
end
team.reports.destroy_all
team.destroy!
# raise ActiveRecord::Rollback
end
Step.set_callback(:destroy, :after, :cascade_after_destroy)
end
def self.destroy_protocol(protocol)
protocol.steps.each do |step|
# Destroy step assets
if step.assets.present?
step.assets.each do |asset|
next unless asset.file.present?
paperclip_file_destroy(asset)
end
end
# Destroy step
step.tables.destroy_all
step.step_tables.delete_all
step.report_elements.destroy_all
step.step_comments.destroy_all
step.step_assets.destroy_all
step.checklists.destroy_all
step.assets.destroy_all
step.tiny_mce_assets.destroy_all
step.delete
end
# Destroy protocol
protocol.protocol_protocol_keywords.destroy_all
protocol.protocol_keywords.destroy_all
protocol.delete
end
def self.destroy_notifications(user)
# Find all notifications where user is the only reference
# on the notification, and destroy all such notifications
# (user_notifications are destroyed when notification is
# destroyed). We try to do this efficiently (hence in_groups_of).
nids_all = user.notifications.pluck(:id)
nids_all.in_groups_of(1000, false) do |nids|
Notification
.where(id: nids)
.joins(:user_notifications)
.group('notifications.id')
.having('count(notification_id) <= 1')
.destroy_all
end
# Now, simply destroy all user notification relations left
user.user_notifications.destroy_all
end
# Workaround for Paperclip preserve_files option, to delete files anyway;
# if you call #clear with a list of styles to delete,
# it'll call #queue_some_for_delete, which doesn't check :preserve_files.
def self.paperclip_file_destroy(asset)
if asset.class.name == 'TinyMceAsset'
all_styles = asset.image.styles.keys.map do |key|
asset.image.styles[key].name
end << :original
asset.image.clear(*all_styles)
else
all_styles = asset.file.styles.keys.map do |key|
asset.file.styles[key].name
end << :original
asset.file.clear(*all_styles)
end
asset.save!
asset.destroy!
end
end

View file

@ -48,11 +48,10 @@ module RepositoryImportParser
if @column_list_items_size >= Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN
return
end
item = RepositoryListItem.new(data: value,
created_by: @user,
last_modified_by: @user,
repository_column: @column,
repository: @repository)
item = @column.repository_list_items.new(data: value,
created_by: @user,
last_modified_by: @user,
repository: @repository)
if item.save
@column_list_items_size += 1
return item

View file

@ -39,7 +39,7 @@
<% if result.asset.is_image? %>
<span class="fas fa-image"></span>
<% else %>
<span class="fas fa-file"></span>
<span class="fas fa-paperclip"></span>
<% end %>
<% elsif result.is_text %>
<span class="fas fa-asterisk"></span>

View file

@ -17,12 +17,12 @@
<% if can_manage_protocol_in_module?(@protocol) %>
<li>
<a class="btn-open-file" href="#" data-action="load-from-file" data-import-url="<%= load_from_file_protocol_path(@protocol, format: :json) %>">
<span class="fas fa-file"></span>&nbsp;<%= t("my_modules.protocols.buttons.load_protocol_from_file") %>
<span class="fas fa-paperclip"></span>&nbsp;<%= t("my_modules.protocols.buttons.load_protocol_from_file") %>
<input type="file" value="" accept=".eln" data-turbolinks="false">
</a>
</li>
<% else %>
<li class="disabled"><a href="#"><span class="fas fa-file"></span>&nbsp;<%= t("my_modules.protocols.buttons.load_protocol_from_file") %></a></li>
<li class="disabled"><a href="#"><span class="fas fa-paperclip"></span>&nbsp;<%= t("my_modules.protocols.buttons.load_protocol_from_file") %></a></li>
<% end %>
</ul>
</div>

View file

@ -61,7 +61,7 @@
<ul class="dropdown-menu">
<li>
<a class="btn-link-alt btn-default-link btn-open-file" <%= can_create_protocols_in_repository?(@current_team) ? 'data-action="import"' : 'disabled="disabled"' %>>
<span class="fas fa-file"></span>
<span class="fas fa-paperclip"></span>
<span class="hidden-xs"><%= t("protocols.index.import_alt") %></span>
<input type="file" value="" accept=".eln" data-role="import-file-input"
data-team-id="<%= @current_team.id %>"
@ -70,8 +70,8 @@
</a>
</li>
<li>
<%= link_to "#modal-import-json-protocol", data: { toggle: 'modal', remote: true } do %>
<span class="fas fa-file"></span>
<%= link_to "#modal-import-json-protocol", data: { toggle: 'modal' } do %>
<span class="fas fa-paperclip"></span>
<span class="hidden-xs"><%= t("protocols.index.import_json") %></span>
<% end %>
</li>
@ -85,7 +85,7 @@
<div class="btn-group" role="group">
<a class="btn btn-default" data-action="archive" data-url="<%= archive_protocols_path %>">
<span class="fas fa-briefcase"></span>
<span class="fas fa-archive"></span>
<span class="hidden-xs">&nbsp;<%= t("protocols.index.archive_action") %></span>
</a>
</div>

View file

@ -8,8 +8,8 @@
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a href="#" data-sort="desc"><%= t('projects.reports.new.nav_sort_desc') %></a></li>
<li><a href="#" data-sort="asc"><%= t('projects.reports.new.nav_sort_asc') %></a></li>
<li><a href="#" data-sort="desc" data-turbolinks="false"><%= t('projects.reports.new.nav_sort_desc') %></a></li>
<li><a href="#" data-sort="asc" data-turbolinks="false"><%= t('projects.reports.new.nav_sort_asc') %></a></li>
</ul>
</div>
@ -21,7 +21,7 @@
<div class="form-group">
<%= hidden_field_tag "html", "" %>
<%= link_to "", class: "btn btn-default", remote: true, id: "get-report-pdf" do %>
<span class="fas fa-file-download"></span>
<span class="fas fa-upload"></span>
<span class="hidden-xs"><%=t "projects.reports.new.nav_pdf" %></span>
<% end %>
</div>
@ -30,6 +30,8 @@
<button
onclick="$('#savePDFtoInventory').modal('show')"
class="btn btn-default">
<span class="fas fa-save">
</span>
<%=t 'projects.reports.new.save_PDF_to_inventory'%>
</button>

View file

@ -182,7 +182,7 @@
match_case: @search_case, utf8: '✓',
search_id: @search_id}.to_query %>">
<span class="badge pull-right"><%= @asset_search_count %></span>
<span class="fas fa-file"></span>
<span class="fas fa-paperclip"></span>
<%= t'Assets' %>
</a>
</li>

View file

@ -6,7 +6,7 @@
<% if wopi_file?(asset) %>
<%= file_extension_icon(asset) %>
<% else %>
<span class="fas fa-file"></span>
<span class="fas fa-paperclip"></span>
<% end %>
<% end %>
<%= render partial: "search/results/partials/asset_text.html.erb", locals: { asset: asset, query: search_query } %>

View file

@ -8,7 +8,7 @@
<% if result.asset.is_image? %>
<span class="fas fa-image"></span>
<% else %>
<span class="fas fa-file"></span>
<span class="fas fa-paperclip"></span>
<% end %>
<% end %>
<%= render partial: "search/results/partials/result_text.html.erb", locals: { result: result, query: search_query, target: nil } %>

View file

@ -14,7 +14,7 @@
id="new-step-assets-tab"
onClick="dragNdropAssetsInit('steps')">
<a href="#new-step-assets" data-toggle="tab" data-no-turbolink="true">
<span class="fas fa-file"></span>
<span class="fas fa-paperclip"></span>
<%= t("protocols.steps.new.tab_assets") %>
</a>
</li>

View file

@ -1,6 +1,6 @@
<div class="panel panel-default panel-step-attachment">
<div class="panel-heading">
<span class="fas fa-file"></span>
<span class="fas fa-paperclip"></span>
<%= t("protocols.steps.new.asset_panel_title") %>
<div class="pull-right">
<% unless ff.object.file.exists? && ff.object.locked? %>

View file

@ -93,7 +93,7 @@
<div class="panel-body">
<div class="col-xs-24 col-sm-12">
<a href="#" class="btn btn-primary pull-right row" data-trigger="invite-users"
data-modal-id="team-invite-users-modal" data-remote="true">
data-modal-id="team-invite-users-modal">
<span class="fas fa-plus"></span>
<%= t("users.settings.teams.edit.add_user") %>
</a>
@ -137,4 +137,5 @@
<%= render partial: 'users/settings/user_teams/destroy_user_team_modal.html.erb' %>
<%= stylesheet_link_tag 'datatables' %>
<%= javascript_include_tag 'users/settings/teams/show' %>
<%= javascript_include_tag 'users/settings/teams/invite_users_modal' %>
<span data-hook="team-bottom"></span>

View file

@ -1,2 +1,6 @@
# frozen_string_literal: true
require 'active_model_serializers/register_jsonapi_renderer'
ActiveModelSerializers.config.adapter = :json_api
ActiveModelSerializers.config.key_transform = :unaltered

View file

@ -9,6 +9,10 @@ Api.configure do |config|
config.core_api_token_iss = ENV['CORE_API_TOKEN_ISS']
end
config.core_api_v1_preview = true if ENV['CORE_API_V1_PREVIEW']
Paperclip::DataUriAdapter.register if ENV['CORE_API_V1_PREVIEW']
vars = ENV.select { |name, _| name =~ /^[[:alnum:]]*_AZURE_AD_APP_ID/ }
vars.each do |name, value|
app_name = name.sub('_AZURE_AD_APP_ID', '')

View file

@ -28,6 +28,8 @@ Rails.application.config.assets.precompile +=
Rails.application.config.assets.precompile +=
%w(users/settings/teams/add_user_modal.js)
Rails.application.config.assets.precompile += %w(users/settings/teams/show.js)
Rails.application.config.assets.precompile +=
%w(users/settings/teams/invite_users_modal.js)
Rails.application.config.assets.precompile += %w(my_modules/activities.js)
Rails.application.config.assets.precompile += %w(my_modules/protocols.js)
Rails.application.config.assets.precompile +=

View file

@ -1943,6 +1943,12 @@ en:
status_ok: "Ok"
expired_token: "Token is expired"
invalid_token: "Token is invalid"
v1:
inventory_data_types:
t0: "text"
t1: "date"
t2: "list"
t3: "file"
Add: "Add"
Asset: "File"

View file

@ -542,8 +542,41 @@ Rails.application.routes.draw do
namespace :api, defaults: { format: 'json' } do
get 'health', to: 'api#health'
get 'status', to: 'api#status'
namespace :v1 do
resources :teams, only: %i(index show) do
if Api.configuration.core_api_v1_preview
namespace :v1 do
resources :teams, only: %i(index show) do
resources :inventories,
only: %i(index create show update destroy) do
resources :inventory_columns,
only: %i(index create show update destroy),
path: 'columns',
as: :columns
resources :inventory_items,
only: %i(index create show update destroy),
path: 'items',
as: :items
end
resources :projects, only: %i(index show) do
resources :experiments, only: %i(index show) do
resources :my_modules,
only: %i(index show),
path: 'tasks',
as: :tasks
resources :my_module_groups,
only: %i(index show),
path: 'task_groups',
as: :task_groups
resources :connections,
only: %i(index show),
path: 'connections',
as: :connections
end
end
end
resources :users, only: %i(show) do
resources :user_identities,
only: %i(index create show update destroy)
end
end
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
class ChangePrimaryKeyToBigintOnRepositories < ActiveRecord::Migration[5.1]
def up
change_column :repository_rows, :id, :bigint
change_column :repository_columns, :id, :bigint
change_column :repository_cells, :id, :bigint
change_column :repository_cells, :repository_row_id, :bigint
change_column :repository_cells, :value_id, :bigint
change_column :repository_text_values, :id, :bigint
change_column :repository_date_values, :id, :bigint
change_column :my_module_repository_rows, :repository_row_id, :bigint
end
def down
change_column :repository_rows, :id, :integer
change_column :repository_columns, :id, :integer
change_column :repository_cells, :id, :integer
change_column :repository_cells, :repository_row_id, :integer
change_column :repository_cells, :value_id, :integer
change_column :repository_text_values, :id, :integer
change_column :repository_date_values, :id, :integer
change_column :my_module_repository_rows, :repository_row_id, :integer
end
end

View file

@ -181,7 +181,7 @@ ActiveRecord::Schema.define(version: 20180813120338) do
end
create_table "my_module_repository_rows", id: :serial, force: :cascade do |t|
t.integer "repository_row_id", null: false
t.bigint "repository_row_id", null: false
t.integer "my_module_id"
t.integer "assigned_by_id", null: false
t.datetime "created_at"
@ -420,11 +420,11 @@ ActiveRecord::Schema.define(version: 20180813120338) do
t.index ["last_modified_by_id"], name: "index_repository_asset_values_on_last_modified_by_id"
end
create_table "repository_cells", id: :serial, force: :cascade do |t|
t.integer "repository_row_id"
create_table "repository_cells", force: :cascade do |t|
t.bigint "repository_row_id"
t.integer "repository_column_id"
t.string "value_type"
t.integer "value_id"
t.bigint "value_id"
t.datetime "created_at"
t.datetime "updated_at"
t.index ["repository_column_id"], name: "index_repository_cells_on_repository_column_id"
@ -432,7 +432,7 @@ ActiveRecord::Schema.define(version: 20180813120338) do
t.index ["value_type", "value_id"], name: "index_repository_cells_on_value_type_and_value_id"
end
create_table "repository_columns", id: :serial, force: :cascade do |t|
create_table "repository_columns", force: :cascade do |t|
t.integer "repository_id"
t.integer "created_by_id", null: false
t.string "name"
@ -442,7 +442,7 @@ ActiveRecord::Schema.define(version: 20180813120338) do
t.index ["repository_id"], name: "index_repository_columns_on_repository_id"
end
create_table "repository_date_values", id: :serial, force: :cascade do |t|
create_table "repository_date_values", force: :cascade do |t|
t.datetime "data"
t.datetime "created_at"
t.datetime "updated_at"
@ -476,7 +476,7 @@ ActiveRecord::Schema.define(version: 20180813120338) do
t.index ["repository_list_item_id"], name: "index_repository_list_values_on_repository_list_item_id"
end
create_table "repository_rows", id: :serial, force: :cascade do |t|
create_table "repository_rows", force: :cascade do |t|
t.integer "repository_id"
t.integer "created_by_id", null: false
t.integer "last_modified_by_id", null: false
@ -497,7 +497,7 @@ ActiveRecord::Schema.define(version: 20180813120338) do
t.index ["user_id"], name: "index_repository_table_states_on_user_id"
end
create_table "repository_text_values", id: :serial, force: :cascade do |t|
create_table "repository_text_values", force: :cascade do |t|
t.string "data"
t.datetime "created_at"
t.datetime "updated_at"

View file

@ -2,7 +2,7 @@ version: '2'
services:
db:
container_name: scinote_db_production
image: postgres:9.4
image: postgres:9.6
volumes:
- scinote_production_postgres:/var/lib/postgresql/data

View file

@ -96,4 +96,14 @@ namespace :data do
)
TeamImporter.new.import_from_dir(args[:dir_path])
end
desc 'Delete team and all data inside the team'
task :team_delete, [:team_id] => [:environment] do |_, args|
Rails.logger.info(
"Deleting team with ID:#{args[:team_id]} and all data inside the team"
)
team = Team.find_by_id(args[:team_id])
raise StandardError, 'Can not load team' unless team
UserDataDeletion.delete_team_data(team) if team
end
end

View file

@ -2,6 +2,7 @@ FactoryBot.define do
factory :repository_row do
name 'Custom row'
created_by { User.first || association(:user) }
repository { Repository.first || create(:repository) }
last_modified_by { User.first || association(:user) }
end
end

View file

@ -0,0 +1,6 @@
FactoryBot.define do
factory :user_identity do
uid Faker::Crypto.unique.sha1
provider Faker::App.unique.name
end
end

View file

@ -20,13 +20,13 @@ RSpec.describe RepositoryListValue, type: :model do
describe '#formatted' do
let!(:repository) { create :repository }
let!(:repository_column) { create :repository_column, name: 'My column' }
let!(:repository_column) do
create :repository_column, data_type: :RepositoryListValue
create :repository_column, name: 'My column',
data_type: :RepositoryListValue
end
let!(:repository_row) { create :repository_row, name: 'My row' }
let!(:repository_list_value) do
create :repository_list_value, repository_cell_attributes: {
build :repository_list_value, repository_cell_attributes: {
repository_column: repository_column,
repository_row: repository_row
}
@ -38,49 +38,20 @@ RSpec.describe RepositoryListValue, type: :model do
repository: repository,
repository_column: repository_column
repository_list_value.repository_list_item = list_item
repository_list_value.save
repository_list_value.save!
expect(repository_list_value.reload.formatted).to eq 'my item'
end
it 'retuns only the the item related to the list' do
repository_row_two = create :repository_row, name: 'New row'
repository_list_value_two =
create :repository_list_value, repository_cell_attributes: {
repository_column: repository_column,
repository_row: repository_row_two
}
list_item = create :repository_list_item,
data: 'new item',
repository: repository,
repository_column: repository_column
repository_list_value.repository_list_item = list_item
expect(repository_list_value.reload.formatted).to_not eq 'my item'
expect(repository_list_value.formatted).to eq ''
end
it 'returns an empty string if no item selected' do
list_item = create :repository_list_item,
data: 'my item',
repository: repository,
repository_column: repository_column
expect(repository_list_value.reload.formatted).to eq ''
end
it 'returns an empty string if item does not exists' do
repository_list_value.repository_list_item = nil
expect(repository_list_value.reload.formatted).to eq ''
end
end
describe '#data' do
let!(:repository) { create :repository }
let!(:repository_column) { create :repository_column, name: 'My column' }
let!(:repository_column) do
create :repository_column, data_type: :RepositoryListValue
create :repository_column, name: 'My column',
data_type: :RepositoryListValue
end
let!(:repository_row) { create :repository_row, name: 'My row' }
let!(:repository_list_value) do
create :repository_list_value, repository_cell_attributes: {
build :repository_list_value, repository_cell_attributes: {
repository_column: repository_column,
repository_row: repository_row
}
@ -92,36 +63,24 @@ RSpec.describe RepositoryListValue, type: :model do
repository: repository,
repository_column: repository_column
repository_list_value.repository_list_item = list_item
repository_list_value.save
repository_list_value.save!
expect(repository_list_value.reload.data).to eq 'my item'
end
it 'retuns only the the item related to the list' do
repository_row_two = create :repository_row, name: 'New row'
create :repository_list_value,
repository_cell_attributes: {
repository_column: repository_column,
repository_row: repository_row_two
}
repository_column_two = create :repository_column, name: 'New column'
list_item = create :repository_list_item,
data: 'new item',
repository: repository,
repository_column: repository_column
repository_list_value.repository_list_item = list_item
expect(repository_list_value.reload.data).to_not eq 'my item'
expect(repository_list_value.data).to be_nil
end
it 'returns an empty string if no item selected' do
create :repository_list_item, data: 'my item',
repository: repository,
repository_column: repository_column
expect(repository_list_value.reload.data).to be_nil
end
it 'returns an empty string if item does not exists' do
repository_list_value.repository_list_item = nil
expect(repository_list_value.reload.data).to be_nil
repository_column: repository_column_two
repository_list_value_two = build :repository_list_value,
repository_cell_attributes: {
repository_column: repository_column,
repository_row: repository_row
}
repository_list_value_two.repository_list_item = list_item
saved = repository_list_value_two.save
expect(saved).to eq false
end
end
end

View file

@ -5,6 +5,9 @@ require 'database_cleaner'
require 'devise'
require_relative 'support/controller_macros'
ENV['RAILS_ENV'] = 'test'
ENV['CORE_API_V1_PREVIEW'] = 'true'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?

View file

@ -0,0 +1,95 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe "Api::V1::InventoriesController", type: :request do
before :all do
@user = create(:user)
@teams = create_list(:team, 2, created_by: @user)
create(:user_team, user: @user, team: @teams.first, role: 2)
# valid_inventories
create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.first)
create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.first)
# unaccessable_inventories
create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.second)
create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.second)
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
describe 'GET inventories, #index' do
it 'Response with correct inventories' do
hash_body = nil
get api_v1_team_inventories_path(team_id: @teams.first.id),
headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@teams.first.repositories,
each_serializer: Api::V1::InventorySerializer)
.as_json[:data]
)
end
it 'When invalid request, user in not member of the team' do
hash_body = nil
get api_v1_team_inventories_path(team_id: @teams.second.id),
headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
end
describe 'GET inventory, #show' do
it 'When valid request, user is member of the team' do
hash_body = nil
get api_v1_team_inventory_path(team_id: @teams.first.id,
id: @teams.first.repositories.first.id),
headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@teams.first.repositories.first,
serializer: Api::V1::InventorySerializer)
.as_json[:data]
)
end
it 'When invalid request, user in not member of the team' do
hash_body = nil
get api_v1_team_inventory_path(team_id: @teams.second.id,
id: @teams.second.repositories.first.id),
headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, non existing inventory' do
hash_body = nil
get api_v1_team_inventory_path(team_id: @teams.first.id, id: 123),
headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, repository from another team' do
hash_body = nil
get api_v1_team_inventory_path(team_id: @teams.first.id,
id: @teams.second.repositories.first.id),
headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
end
end

View file

@ -0,0 +1,217 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::InventoryColumnsController', type: :request do
before :all do
@user = create(:user)
@teams = create_list(:team, 2, created_by: @user)
create(:user_team, user: @user, team: @teams.first, role: 2)
# valid_inventory
@valid_inventory = create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.first)
# unaccessable_inventory
create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.second)
create(:repository_column, name: Faker::Name.unique.name,
repository: @valid_inventory, data_type: :RepositoryTextValue)
list_column = create(:repository_column, name: Faker::Name.unique.name,
repository: @valid_inventory, data_type: :RepositoryListValue)
create(:repository_list_item, repository: @valid_inventory,
repository_column: list_column, data: Faker::Name.unique.name)
create(:repository_column, name: Faker::Name.unique.name,
repository: @valid_inventory, data_type: :RepositoryAssetValue)
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
describe 'GET inventory_columns, #index' do
it 'Response with correct inventory items, default per page' do
hash_body = nil
get api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@valid_inventory.repository_columns.limit(10),
each_serializer: Api::V1::InventoryColumnSerializer,
include: :inventory_columns)
.as_json[:data]
)
expect(hash_body[:included]).to match(
ActiveModelSerializers::SerializableResource
.new(@valid_inventory.repository_columns.limit(10),
each_serializer: Api::V1::InventoryColumnSerializer,
include: :inventory_list_items)
.as_json[:included]
)
end
it 'When invalid request, user in not member of the team' do
hash_body = nil
get api_v1_team_inventory_columns_path(
team_id: @teams.second.id,
inventory_id: @teams.second.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, non existing inventory' do
hash_body = nil
get api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: 123
), headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, repository from another team' do
hash_body = nil
get api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.second.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
end
describe 'POST inventory_column, #create' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
@request_body = { data:
{ type: 'inventory_columns',
attributes: {
name: Faker::Name.unique.name,
data_type:
RepositoryColumn.data_types.values.first
} } }
end
it 'Response with correct inventory column' do
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), params: @request_body.to_json, headers: @valid_headers
expect(response).to have_http_status 201
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(RepositoryColumn.last,
serializer: Api::V1::InventoryColumnSerializer,
include: :inventory_cells)
.as_json[:data]
)
end
it 'When invalid request, user in not member of the team' do
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.second.id,
inventory_id: @teams.first.repositories.first.id
), params: @request_body.to_json, headers: @valid_headers
expect(response).to have_http_status 403
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, non existing inventory' do
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: 123
), params: @request_body.to_json, headers: @valid_headers
expect(response).to have_http_status 404
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, repository from another team' do
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.second.repositories.first.id
), params: @request_body.to_json, headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, incorrect type' do
invalid_request_body = @request_body.deep_dup
invalid_request_body[:data][:type] = 'repository_rows'
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), params: invalid_request_body.to_json, headers: @valid_headers
expect(response).to have_http_status(400)
expect { hash_body = json }.to_not raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, missing data param' do
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), params: {}, headers: @valid_headers
expect(response).to have_http_status(400)
expect { hash_body = json }.to_not raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, missing attributes param' do
invalid_request_body = @request_body.deep_dup
invalid_request_body[:data].delete(:attributes)
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), params: invalid_request_body.to_json, headers: @valid_headers
expect(response).to have_http_status(400)
expect { hash_body = json }.to_not raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, missing type param' do
invalid_request_body = @request_body.deep_dup
invalid_request_body[:data].delete(:type)
hash_body = nil
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), params: invalid_request_body.to_json, headers: @valid_headers
expect(response).to have_http_status(400)
expect { hash_body = json }.to_not raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, missing attributes values' do
hash_body = nil
%i(name data_type).each do |attr|
invalid_request_body = @request_body.deep_dup
invalid_request_body[:data][:attributes].delete(attr)
post api_v1_team_inventory_columns_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), params: invalid_request_body.to_json, headers: @valid_headers
expect(response).to have_http_status(400)
expect { hash_body = json }.to_not raise_exception
expect(hash_body).to match({})
end
end
end
end

View file

@ -0,0 +1,175 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::InventoryItemsController', type: :request do
before :all do
@user = create(:user)
@teams = create_list(:team, 2, created_by: @user)
create(:user_team, user: @user, team: @teams.first, role: 2)
# valid_inventory
@valid_inventory = create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.first)
# unaccessable_inventory
create(:repository, name: Faker::Name.unique.name,
created_by: @user, team: @teams.second)
text_column = create(:repository_column, name: Faker::Name.unique.name,
repository: @valid_inventory, data_type: :RepositoryTextValue)
list_column = create(:repository_column, name: Faker::Name.unique.name,
repository: @valid_inventory, data_type: :RepositoryListValue)
list_item =
create(:repository_list_item, repository: @valid_inventory,
repository_column: list_column, data: Faker::Name.unique.name)
file_column = create(:repository_column, name: Faker::Name.unique.name,
repository: @valid_inventory, data_type: :RepositoryAssetValue)
asset = create(:asset)
create_list(:repository_row, 100, repository: @valid_inventory)
@valid_inventory.repository_rows.each do |row|
create(:repository_text_value,
data: Faker::Name.name,
repository_cell_attributes:
{ repository_row: row, repository_column: text_column })
create(:repository_list_value, repository_list_item: list_item,
repository_cell_attributes:
{ repository_row: row, repository_column: list_column })
create(:repository_asset_value, asset: asset,
repository_cell_attributes:
{ repository_row: row, repository_column: file_column })
end
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
describe 'GET inventory_items, #index' do
it 'Response with correct inventory items, default per page' do
hash_body = nil
get api_v1_team_inventory_items_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@valid_inventory.repository_rows.limit(10),
each_serializer: Api::V1::InventoryItemSerializer,
include: :inventory_cells)
.as_json[:data]
)
expect(hash_body[:included]).to match(
ActiveModelSerializers::SerializableResource
.new(@valid_inventory.repository_rows.limit(10),
each_serializer: Api::V1::InventoryItemSerializer,
include: :inventory_cells)
.as_json[:included]
)
end
it 'Response with correct inventory items, 100 per page' do
hash_body = nil
get api_v1_team_inventory_items_path(
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), params: { page: { size: 100 } }, headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@valid_inventory.repository_rows.limit(100),
each_serializer: Api::V1::InventoryItemSerializer,
include: :inventory_cells)
.as_json[:data]
)
expect(hash_body[:included]).to match(
ActiveModelSerializers::SerializableResource
.new(@valid_inventory.repository_rows.limit(100),
each_serializer: Api::V1::InventoryItemSerializer,
include: :inventory_cells)
.as_json[:included]
)
end
it 'When invalid request, user in not member of the team' do
hash_body = nil
get api_v1_team_inventory_items_path(
team_id: @teams.second.id,
inventory_id: @teams.second.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, non existing inventory' do
hash_body = nil
get api_v1_team_inventory_items_path(
team_id: @teams.first.id,
inventory_id: 123
), headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, repository from another team' do
hash_body = nil
get api_v1_team_inventory_items_path(
team_id: @teams.first.id,
inventory_id: @teams.second.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
end
describe 'DELETE inventory_items, #destroy' do
it 'Destroys inventory item' do
deleted_id = @teams.first.repositories.first.repository_rows.last.id
delete api_v1_team_inventory_item_path(
id: deleted_id,
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(200)
expect(RepositoryRow.where(id: deleted_id)).to_not exist
expect(RepositoryCell.where(repository_row: deleted_id).count).to equal 0
end
it 'Invalid request, non existing inventory item' do
deleted_id = RepositoryRow.last.id + 1
delete api_v1_team_inventory_item_path(
id: deleted_id,
team_id: @teams.first.id,
inventory_id: @teams.first.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(404)
end
it 'When invalid request, incorrect repository' do
deleted_id = @teams.first.repositories.first.repository_rows.last.id
delete api_v1_team_inventory_item_path(
id: deleted_id,
team_id: @teams.first.id,
inventory_id: @teams.second.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(404)
expect(RepositoryRow.where(id: deleted_id)).to exist
end
it 'When invalid request, repository from another team' do
deleted_id = @teams.first.repositories.first.repository_rows.last.id
delete api_v1_team_inventory_item_path(
id: deleted_id,
team_id: @teams.second.id,
inventory_id: @teams.first.repositories.first.id
), headers: @valid_headers
expect(response).to have_http_status(403)
expect(RepositoryRow.where(id: deleted_id)).to exist
end
end
end

View file

@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::UsersController', type: :request do
before :all do
@user1 = create(:user, email: Faker::Internet.unique.email)
@user2 = create(:user, email: Faker::Internet.unique.email)
@user3 = create(:user, email: Faker::Internet.unique.email)
@team1 = create(:team, created_by: @user1)
@team2 = create(:team, created_by: @user2)
@team3 = create(:team, created_by: @user3)
create(:user_team, user: @user1, team: @team1, role: 2)
create(:user_team, user: @user2, team: @team1, role: 2)
create(:user_team, user: @user2, team: @team2, role: 2)
create(:user_team, user: @user3, team: @team3, role: 2)
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user1.id) }
end
describe 'GET user, #show' do
it 'When valid request, requested user is member of the same teams' do
hash_body = nil
get api_v1_user_path(id: @user2.id), headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@user2, serializer: Api::V1::UserSerializer)
.as_json[:data]
)
end
it 'When invalid request, requested user is not member of the same teams' do
hash_body = nil
get api_v1_user_path(id: @user3.id), headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
it 'When invalid request, non existing user' do
hash_body = nil
get api_v1_user_path(id: 123), headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body).to match({})
end
end
end

View file

@ -0,0 +1,113 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::UsersIdentitiesController', type: :request do
before :all do
@user1 = create(:user, email: Faker::Internet.unique.email)
@user2 = create(:user, email: Faker::Internet.unique.email)
create(:user_identity, user: @user1,
uid: Faker::Crypto.unique.sha1, provider: Faker::App.unique.name)
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user1.id) }
end
describe 'GET user_identities, #index' do
it 'When valid request, requested identities for current user' do
hash_body = nil
get api_v1_user_user_identities_path(user_id: @user1.id),
headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@user1.user_identities,
each_serializer: Api::V1::UserIdentitySerializer)
.as_json[:data]
)
end
it 'When invalid request, requested user is not signed in user' do
hash_body = nil
get api_v1_user_user_identities_path(user_id: @user2.id),
headers: @valid_headers
expect(response).to have_http_status(403)
expect(hash_body).to match(nil)
end
it 'When invalid request, non existing user' do
hash_body = nil
get api_v1_user_user_identities_path(user_id: 123),
headers: @valid_headers
expect(response).to have_http_status(403)
expect(hash_body).to match(nil)
end
end
describe 'POST user_identities, #create' do
it 'When valid request, create new identity for current user' do
hash_body = nil
post api_v1_user_user_identities_path(user_id: @user1.id),
params: {
data: { type: 'user_identities',
attributes: { uid: Faker::Crypto.unique.sha1,
provider: Faker::App.unique.name } }
},
headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@user1.user_identities.order(:id).last,
serializer: Api::V1::UserIdentitySerializer)
.as_json[:data]
)
end
end
describe 'GET user_identity, #show' do
it 'When valid request, requested specific identity for current user' do
hash_body = nil
get api_v1_user_user_identity_path(
user_id: @user1.id, id: @user1.user_identities.order(:id).last
), headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@user1.user_identities.order(:id).last,
serializer: Api::V1::UserIdentitySerializer)
.as_json[:data]
)
end
end
describe 'PUT user_identities, #update' do
it 'When valid request, update identity for current user' do
hash_body = nil
put api_v1_user_user_identity_path(user_id: @user1.id,
id: @user1.user_identities.order(:id).last),
params: {
data: { id: @user1.user_identities.order(:id).last.id,
type: 'user_identities',
attributes: { uid: Faker::Crypto.unique.sha1 } }
},
headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@user1.user_identities.order(:id).last,
serializer: Api::V1::UserIdentitySerializer)
.as_json[:data]
)
end
end
describe 'DELETE user_identity, #destroy' do
it 'When valid request, destroy specified identity for current user' do
identity = @user1.user_identities.order(:id).last
delete api_v1_user_user_identity_path(user_id: @user1.id, id: identity),
headers: @valid_headers
expect(response).to have_http_status(200)
expect(response.body).to eq('')
expect(UserIdentity.find_by_id(identity.id)).to eq(nil)
end
end
end