mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-21 15:36:22 +08:00
Merge pull request #5912 from artoscinote/ma_SCI_8947
Basic results component and structure [SCI-8947]
This commit is contained in:
commit
a8bf290fc7
|
@ -33,10 +33,6 @@ html {
|
|||
|
||||
/* Results index page */
|
||||
|
||||
#results {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.navigation-results-counter {
|
||||
background-color: $color-concrete;
|
||||
border-radius: $border-radius-circle;
|
||||
|
|
46
app/controllers/result_oderable_elements_controller.rb
Normal file
46
app/controllers/result_oderable_elements_controller.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ResultOrderableElementsController < ApplicationController
|
||||
before_action :load_vars_nested
|
||||
before_action :check_manage_permissions
|
||||
|
||||
def reorder
|
||||
@result.with_lock do
|
||||
params[:result_orderable_element_positions].each do |id, position|
|
||||
@result.result_orderable_elements.find(id).update_column(:position, position)
|
||||
end
|
||||
|
||||
log_activity(:result_content_rearranged, @my_module.experiment.project, my_module: @my_module.id)
|
||||
|
||||
@result.touch
|
||||
end
|
||||
|
||||
render json: params[:result_orderable_element_positions], status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars_nested
|
||||
@result = Result.find_by(id: params[:result_id])
|
||||
return render_404 unless @result
|
||||
|
||||
@my_module = @result.my_module
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 unless can_manage_result?(@result)
|
||||
end
|
||||
|
||||
def log_activity(type_of, project = nil, message_items = {})
|
||||
default_items = { result: @result.id }
|
||||
message_items = default_items.merge(message_items)
|
||||
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @result,
|
||||
team: @@my_module.team,
|
||||
project: project,
|
||||
message_items: message_items)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ResultsController < ApplicationController
|
||||
before_action :load_vars
|
||||
before_action :check_destroy_permissions
|
||||
skip_before_action :verify_authenticity_token, only: %i(create destroy)
|
||||
|
||||
before_action :load_my_module
|
||||
before_action :load_vars, only: %i(destroy elements assets)
|
||||
before_action :check_destroy_permissions, only: :destroy
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
# API endpoint
|
||||
render(
|
||||
json: apply_sort(@my_module.results),
|
||||
formats: :json
|
||||
)
|
||||
end
|
||||
|
||||
format.html do
|
||||
# Main view
|
||||
@experiment = @my_module.experiment
|
||||
@project = @experiment.project
|
||||
render(:index, formats: :html)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
result = @my_module.results.create!(user: current_user)
|
||||
|
||||
render json: result
|
||||
end
|
||||
|
||||
def elements
|
||||
render json: @result.result_orderable_elements.order(:position),
|
||||
each_serializer: ResultOrderableElementSerializer,
|
||||
user: current_user
|
||||
end
|
||||
|
||||
def assets
|
||||
render json: @result.assets,
|
||||
each_serializer: AssetSerializer,
|
||||
user: current_user
|
||||
end
|
||||
|
||||
def destroy
|
||||
result_type = if @result.is_text
|
||||
|
@ -27,9 +69,32 @@ class ResultsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def apply_sort(results)
|
||||
case params[:sort]
|
||||
when 'updated_at_asc'
|
||||
results.order(updated_at: :asc)
|
||||
when 'updated_at_desc'
|
||||
results.order(updated_at: :desc)
|
||||
when 'created_at_asc'
|
||||
results.order(created_at: :asc)
|
||||
when 'created_at_desc'
|
||||
results.order(created_at: :desc)
|
||||
when 'name_asc'
|
||||
results.order(name: :asc)
|
||||
when 'name_desc'
|
||||
results.order(name: :desc)
|
||||
end
|
||||
end
|
||||
|
||||
def load_my_module
|
||||
@my_module = MyModule.readable_by_user(current_user).find(params[:my_module_id])
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@result = Result.find_by_id(params[:id])
|
||||
@result = @my_module.results.find(params[:id])
|
||||
|
||||
return render_403 unless @result
|
||||
|
||||
@my_module = @result.my_module
|
||||
end
|
||||
|
||||
|
|
13
app/javascript/packs/vue/results.js
Normal file
13
app/javascript/packs/vue/results.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import TurbolinksAdapter from 'vue-turbolinks';
|
||||
import Vue from 'vue/dist/vue.esm';
|
||||
import Results from '../../vue/results/results.vue';
|
||||
|
||||
Vue.use(TurbolinksAdapter);
|
||||
Vue.prototype.i18n = window.I18n;
|
||||
|
||||
new Vue({
|
||||
el: '#results',
|
||||
components: {
|
||||
Results
|
||||
}
|
||||
});
|
19
app/javascript/vue/results/result.vue
Normal file
19
app/javascript/vue/results/result.vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div class="result-wrapper">
|
||||
{{ result.id }}
|
||||
{{ result.attributes.name }}
|
||||
<hr>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Result from './result.vue';
|
||||
|
||||
export default {
|
||||
name: 'Results',
|
||||
components: { Result },
|
||||
props: {
|
||||
result: { type: Object, required: true }
|
||||
}
|
||||
}
|
||||
</script>
|
69
app/javascript/vue/results/results.vue
Normal file
69
app/javascript/vue/results/results.vue
Normal file
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div class="results-wrapper">
|
||||
<ResultsToolbar :sort="sort" @setSort="setSort" @newResult="createResult" @expandAll="expandAll" @collapseAll="collapseAll" lass="mb-3" />
|
||||
<div class="results-list">
|
||||
<Result v-for="result in results" :key="result.id" :result="result" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import ResultsToolbar from './results_toolbar.vue';
|
||||
import Result from './result.vue';
|
||||
|
||||
export default {
|
||||
name: 'Results',
|
||||
components: { ResultsToolbar, Result },
|
||||
props: {
|
||||
url: { type: String, required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
sort: 'created_at_desc'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadResults();
|
||||
},
|
||||
methods: {
|
||||
loadResults() {
|
||||
axios.get(
|
||||
`${this.url}?sort=${this.sort}`,
|
||||
{
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
).then((response) => this.results = response.data.data);
|
||||
},
|
||||
setSort(sort) {
|
||||
this.sort = sort;
|
||||
this.loadResults();
|
||||
},
|
||||
createResult() {
|
||||
axios.post(
|
||||
`${this.url}`,
|
||||
{
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
).then(
|
||||
(response) => {
|
||||
this.results.unshift(response.data.data)
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
},
|
||||
expandAll() {
|
||||
$('.result-wrapper .collapse').collapse('show')
|
||||
},
|
||||
collapseAll() {
|
||||
$('.result-wrapper .collapse').collapse('hide')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
61
app/javascript/vue/results/results_toolbar.vue
Normal file
61
app/javascript/vue/results/results_toolbar.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="result-toolbar p-3 flex justify-between rounded-md bg-sn-white">
|
||||
<div class="result-toolbar__left">
|
||||
<button class="btn btn-secondary" @click="$emit('newResult')">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
{{ i18n.t('my_modules.results.add_label') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="result-toolbar__right flex items-center" @click="$emit('expandAll')">
|
||||
<button class="btn btn-secondary mr-3">
|
||||
{{ i18n.t('my_modules.results.expand_label') }}
|
||||
</button>
|
||||
<button class="btn btn-secondary mr-3" @click="$emit('collapseAll')">
|
||||
{{ i18n.t('my_modules.results.collapse_label') }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-light icon-btn mr-3">
|
||||
<i class="sn-icon sn-icon-filter"></i>
|
||||
</button>
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="dropdown-toggle btn btn-light icon-btn mr-3" id="sortDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="sn-icon sn-icon-sort"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="sortDropdown">
|
||||
<li v-for="sort in sorts" :key="sort">
|
||||
<a class="cursor-pointer" @click="setSort(sort)">
|
||||
{{ i18n.t(`my_modules.results.sorts.${sort}`)}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const SORTS = [
|
||||
'updated_at_asc',
|
||||
'updated_at_desc',
|
||||
'created_at_asc',
|
||||
'created_at_desc',
|
||||
'name_asc',
|
||||
'name_desc'
|
||||
];
|
||||
|
||||
export default {
|
||||
name: 'ResultsToolbar',
|
||||
props: {
|
||||
sort: { type: String, required: true }
|
||||
},
|
||||
created() {
|
||||
this.sorts = SORTS;
|
||||
},
|
||||
methods: {
|
||||
setSort(sort) {
|
||||
this.$emit('setSort', sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -107,15 +107,16 @@ module Reports
|
|||
|
||||
results = my_module_element.my_module.results
|
||||
order_results_for_report(results, report.settings.dig(:task, :result_order)).each do |result|
|
||||
next unless result.is_asset && PREVIEW_EXTENSIONS.include?(result.asset.file.blob.filename.extension)
|
||||
result.assets.each do |asset|
|
||||
next unless PREVIEW_EXTENSIONS.include?(asset.file.blob.filename.extension)
|
||||
|
||||
asset = result.asset
|
||||
if !asset.file_pdf_preview.attached? || (asset.file.created_at > asset.file_pdf_preview.created_at)
|
||||
PdfPreviewJob.perform_now(asset.id)
|
||||
asset.reload
|
||||
end
|
||||
asset.file_pdf_preview.open(tmpdir: tmp_dir) do |file|
|
||||
report_file = merge_pdf_files(file, report_file)
|
||||
if !asset.file_pdf_preview.attached? || (asset.file.created_at > asset.file_pdf_preview.created_at)
|
||||
PdfPreviewJob.perform_now(asset.id)
|
||||
asset.reload
|
||||
end
|
||||
asset.file_pdf_preview.open(tmpdir: tmp_dir) do |file|
|
||||
report_file = merge_pdf_files(file, report_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,25 +6,25 @@ class Result < ApplicationRecord
|
|||
include SearchableByNameModel
|
||||
|
||||
auto_strip_attributes :name, nullify: false
|
||||
validates :user, :my_module, presence: true
|
||||
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
|
||||
|
||||
belongs_to :user, inverse_of: :results
|
||||
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User', optional: true
|
||||
belongs_to :archived_by, foreign_key: 'archived_by_id', class_name: 'User', optional: true
|
||||
belongs_to :restored_by, foreign_key: 'restored_by_id', class_name: 'User', optional: true
|
||||
belongs_to :last_modified_by, class_name: 'User', optional: true
|
||||
belongs_to :archived_by, class_name: 'User', optional: true
|
||||
belongs_to :restored_by, class_name: 'User', optional: true
|
||||
belongs_to :my_module, inverse_of: :results
|
||||
has_one :result_asset, inverse_of: :result, dependent: :destroy
|
||||
has_one :asset, through: :result_asset
|
||||
has_one :result_table, inverse_of: :result, dependent: :destroy
|
||||
has_one :table, through: :result_table
|
||||
has_one :result_text, inverse_of: :result, dependent: :destroy
|
||||
has_many :result_comments, foreign_key: :associated_id, dependent: :destroy
|
||||
has_many :result_orderable_elements, inverse_of: :result, dependent: :destroy
|
||||
has_many :result_assets, inverse_of: :result, dependent: :destroy
|
||||
has_many :assets, through: :result_assets
|
||||
has_many :result_tables, inverse_of: :result, dependent: :destroy
|
||||
has_many :tables, through: :result_tables
|
||||
has_many :result_texts, inverse_of: :result, dependent: :destroy
|
||||
has_many :result_comments, inverse_of: :result, foreign_key: :associated_id, dependent: :destroy
|
||||
has_many :report_elements, inverse_of: :result, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :result_text
|
||||
accepts_nested_attributes_for :asset
|
||||
accepts_nested_attributes_for :table
|
||||
accepts_nested_attributes_for :result_texts
|
||||
accepts_nested_attributes_for :assets
|
||||
accepts_nested_attributes_for :tables
|
||||
|
||||
def self.search(user,
|
||||
include_archived,
|
||||
|
@ -37,7 +37,7 @@ class Result < ApplicationRecord
|
|||
new_query =
|
||||
Result
|
||||
.distinct
|
||||
.joins('LEFT JOIN result_texts ON results.id = result_texts.result_id')
|
||||
.left_outer_joins(:result_texts)
|
||||
.where(results: { my_module_id: module_ids })
|
||||
.where_attributes_like(['results.name', 'result_texts.text'], query, options)
|
||||
|
||||
|
@ -66,7 +66,7 @@ class Result < ApplicationRecord
|
|||
end
|
||||
|
||||
def space_taken
|
||||
is_asset ? result_asset.space_taken : 0
|
||||
result_assets.joins(asset: { file_attachment: :blob }).sum('active_storage_blobs.byte_size')
|
||||
end
|
||||
|
||||
def last_comments(last_id = 1, per_page = Constants::COMMENTS_SEARCH_LIMIT)
|
||||
|
@ -80,29 +80,19 @@ class Result < ApplicationRecord
|
|||
end
|
||||
|
||||
def is_text
|
||||
result_text.present?
|
||||
raise StandardError, 'Deprecated method, needs to be replaced!'
|
||||
end
|
||||
|
||||
def is_table
|
||||
table.present?
|
||||
raise StandardError, 'Deprecated method, needs to be replaced!'
|
||||
end
|
||||
|
||||
def is_asset
|
||||
asset.present?
|
||||
raise StandardError, 'Deprecated method, needs to be replaced!'
|
||||
end
|
||||
|
||||
def unlocked?(result)
|
||||
if result.is_asset
|
||||
!result.asset.locked?
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def editable?
|
||||
return false if is_asset && asset.file.metadata['asset_type'] == 'marvinjs'
|
||||
|
||||
true
|
||||
result.assets.none?(&:locked?)
|
||||
end
|
||||
|
||||
def comments
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ResultAsset < ApplicationRecord
|
||||
validates :result, :asset, presence: true
|
||||
|
||||
belongs_to :result, inverse_of: :result_asset, touch: true
|
||||
belongs_to :result, inverse_of: :result_assets, touch: true
|
||||
belongs_to :asset, inverse_of: :result_asset, dependent: :destroy
|
||||
|
||||
def space_taken
|
||||
|
|
35
app/models/result_orderable_element.rb
Normal file
35
app/models/result_orderable_element.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ResultOrderableElement < ApplicationRecord
|
||||
validates :position, uniqueness: { scope: :step }
|
||||
validate :check_result_relations
|
||||
|
||||
around_destroy :decrement_following_elements_positions
|
||||
|
||||
belongs_to :result, inverse_of: :result_orderable_elements, touch: true
|
||||
belongs_to :orderable, polymorphic: true, inverse_of: :result_orderable_element
|
||||
|
||||
private
|
||||
|
||||
def check_step_relations
|
||||
if step != orderable.step
|
||||
errors.add(
|
||||
:step_orderable_element,
|
||||
I18n.t('activerecord.errors.models.result_orderable_element.attributes.result.wrong_result')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def decrement_following_elements_positions
|
||||
result.with_lock do
|
||||
yield
|
||||
result.result_orderable_elements
|
||||
.where('position > ?', position)
|
||||
.order(position: :asc).each do |result_orderable_element|
|
||||
# find_each ignore any ordering
|
||||
result_orderable_element.position -= 1
|
||||
result_orderable_element.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ResultTable < ApplicationRecord
|
||||
validates :result, :table, presence: true
|
||||
|
||||
belongs_to :result, inverse_of: :result_table
|
||||
belongs_to :result, inverse_of: :result_tables
|
||||
belongs_to :table, inverse_of: :result_table, dependent: :destroy, touch: true
|
||||
has_one :result_orderable_element, as: :orderable, dependent: :destroy
|
||||
end
|
||||
|
|
|
@ -4,8 +4,8 @@ class ResultText < ApplicationRecord
|
|||
include TinyMceImages
|
||||
|
||||
auto_strip_attributes :text, nullify: false
|
||||
validates :result, :text, presence: true
|
||||
validates :text, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
|
||||
|
||||
belongs_to :result, inverse_of: :result_text, touch: true
|
||||
belongs_to :result, inverse_of: :result_texts, touch: true
|
||||
has_one :result_orderable_element, as: :orderable, dependent: :destroy
|
||||
end
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StepAsset < ApplicationRecord
|
||||
validates :step, :asset, presence: true
|
||||
|
||||
belongs_to :step, inverse_of: :step_assets, touch: true
|
||||
belongs_to :asset, inverse_of: :step_asset, dependent: :destroy
|
||||
end
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StepTable < ApplicationRecord
|
||||
validates :step, :table, presence: true
|
||||
|
||||
belongs_to :step, inverse_of: :step_tables, touch: true
|
||||
belongs_to :table, inverse_of: :step_table
|
||||
has_one :step_orderable_element, as: :orderable, dependent: :destroy
|
||||
|
|
36
app/serializers/result_serializer.rb
Normal file
36
app/serializers/result_serializer.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ResultSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
include ApplicationHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
include InputSanitizeHelper
|
||||
|
||||
attributes :name, :id, :urls, :updated_at, :created_at_formatted, :updated_at_formatted, :user
|
||||
|
||||
def updated_at
|
||||
object.updated_at.to_i
|
||||
end
|
||||
|
||||
def user
|
||||
{
|
||||
avatar: object.user&.avatar_url(:icon_small),
|
||||
name: object.user&.full_name
|
||||
}
|
||||
end
|
||||
|
||||
def created_at_formatted
|
||||
I18n.l(object.created_at, format: :full)
|
||||
end
|
||||
|
||||
def updated_at_formatted
|
||||
I18n.l(object.updated_at, format: :full)
|
||||
end
|
||||
|
||||
def urls
|
||||
{
|
||||
|
||||
}
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="panel-options pull-right">
|
||||
<% if result.editable? && can_manage_my_module?(result.my_module) %>
|
||||
<% if can_manage_my_module?(result.my_module) %>
|
||||
<a class="btn btn-light icon-btn edit-result-button <%= edit_result_button_class(result) %>" id="<%= result.id %>_edit" href="<%= edit_result_link(result) %>" data-remote="true" title="<%= t'my_modules.results.options.edit_title' %>">
|
||||
<span class="sn-icon sn-icon-edit"></span>
|
||||
</a>
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
<% provide(:head_title, t("my_modules.results.head_title", project: h(@project.name), module: h(@my_module.name)).html_safe) %>
|
||||
<% provide(:sidebar_title, t("sidebar.my_module.sidebar_title")) %>
|
||||
|
||||
<%= content_for :sidebar do %>
|
||||
<%= render partial: "shared/sidebar/#{@my_module.archived_branch? ? 'archived_my_module' : 'my_module'}",
|
||||
locals: {
|
||||
my_modules: @experiment_my_modules,
|
||||
experiment: @my_module.experiment,
|
||||
current_my_module: @my_module
|
||||
}
|
||||
%>
|
||||
<% end %>
|
||||
<%= render partial: 'shared/drag_n_drop_overlay' %>
|
||||
<%= render partial: 'assets/wopi/create_wopi_file_modal' %>
|
||||
<% provide(:container_class, 'no-second-nav-container') %>
|
||||
|
||||
<div class="content-pane flexible">
|
||||
<%= render partial: 'my_modules/header' %>
|
||||
<%= render partial: 'my_modules/header_actions' %>
|
||||
<div class="mt-5" id="results-toolbar">
|
||||
<% if can_manage_my_module?(@my_module) %>
|
||||
<div class="add-result-dropdown dropdown sci-dropdown">
|
||||
<a href="#" id="add-result-button" class="btn btn-secondary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span class="sn-icon sn-icon-new-task"></span>
|
||||
<span><%= t("my_modules.results.add_label") %></span>
|
||||
<span class="sn-icon sn-icon-down"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu add-result-dropdown" aria-labelledby="add-result-button">
|
||||
<li id="new-result-text" data-href="<%= new_my_module_result_text_path(my_module_id: @my_module.id, page: params[:page], order: params[:order]) %>">
|
||||
<i class="sn-icon sn-icon-result-text"></i><%= t("my_modules.results.new_text_result") %>
|
||||
</li>
|
||||
<li id="new-result-table" data-href="<%= new_my_module_result_table_path(my_module_id: @my_module.id, page: params[:page], order: params[:order]) %>">
|
||||
<i class="sn-icon sn-icon-tables"></i><%= t("my_modules.results.new_table_result") %>
|
||||
</li>
|
||||
<li id="new-result-asset" data-href="<%= new_my_module_result_asset_path(my_module_id: @my_module.id, page: params[:page], order: params[:order]) %>">
|
||||
<i class="sn-icon sn-icon-files"></i><%= t("my_modules.results.new_asset_result") %>
|
||||
</li>
|
||||
<%= render partial: '/assets/marvinjs/create_marvin_sketch_li',
|
||||
locals: { element_id: @my_module.id, element_type: 'Result', sketch_container: "#results[data-module-id=#{@my_module.id}]" } %>
|
||||
<%= render partial: "assets/wopi/create_wopi_file_li",
|
||||
locals: { element_id: @my_module.id, element_type: 'Result' } %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="sci-btn-group collapse-expand-result">
|
||||
<button class="btn btn-light" id="results-collapse-btn">
|
||||
<span class="sn-icon sn-icon-up"></span>
|
||||
<span class="hidden-xs-custom"><%= t'my_modules.results.collapse_label' %></span>
|
||||
</button>
|
||||
<button class="btn btn-light" id="results-expand-btn">
|
||||
<span class="sn-icon sn-icon-down"></span>
|
||||
<span class="hidden-xs-custom"><%= t'my_modules.results.expand_label' %></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="sort-result-dropdown dropdown">
|
||||
<button id="sort-result-button" class="btn btn-light icon-btn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="sn-icon sn-icon-sort-up"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="sort-result-button">
|
||||
<li><%= link_to t('general.sort_new.atoz'), results_my_module_path(@my_module, page: params[:page], order: 'atoz'), class: (@results_order == 'atoz' ? 'selected' : '') %></li>
|
||||
<li><%= link_to t('general.sort_new.ztoa'), results_my_module_path(@my_module, page: params[:page], order: 'ztoa'), class: (@results_order == 'ztoa' ? 'selected' : '') %></li>
|
||||
<li><%= link_to t('general.sort_new.old'), results_my_module_path(@my_module, page: params[:page], order: 'old'), class: (@results_order == 'old' ? 'selected' : '') %></li>
|
||||
<li><%= link_to t('general.sort_new.new'), results_my_module_path(@my_module, page: params[:page], order: 'new'), class: (@results_order == 'new' ? 'selected' : '') %></li>
|
||||
<li><%= link_to t('general.sort_new.old_updated'), results_my_module_path(@my_module, page: params[:page], order: 'old_updated'), class: (@results_order == 'old_updated' ? 'selected' : '') %></li>
|
||||
<li><%= link_to t('general.sort_new.new_updated'), results_my_module_path(@my_module, page: params[:page], order: 'new_updated'), class: (@results_order == 'new_updated' ? 'selected' : '') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height: 15px;"></div>
|
||||
|
||||
<div id="results" data-module-id="<%= @my_module.id %>" data-task-id="<%= @my_module.id %>">
|
||||
<% @results.each do |result| %>
|
||||
<%= render partial: "result", locals: { result: result } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="kaminari-pagination">
|
||||
<%= paginate @results, outer_window: 1, window: 1 %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag "handsontable.full" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<%= render partial: "shared/formulas_libraries" %>
|
||||
|
||||
<%= javascript_include_tag "assets/wopi/create_wopi_file" %>
|
||||
<%= javascript_include_tag "my_modules/results" %>
|
||||
<%= javascript_include_tag "results/result_texts" %>
|
||||
<%= javascript_include_tag "results/result_tables" %>
|
||||
<%= javascript_include_tag "results/result_assets" %>
|
||||
<%= javascript_include_tag 'pdf_js' %>
|
||||
<%= stylesheet_link_tag 'pdf_js_styles' %>
|
||||
|
||||
<%= render 'shared/tiny_mce_packs' %>
|
21
app/views/results/index.html.erb
Normal file
21
app/views/results/index.html.erb
Normal file
|
@ -0,0 +1,21 @@
|
|||
<% provide(:head_title, t("my_modules.results.head_title", project: h(@project.name), module: h(@my_module.name)).html_safe) %>
|
||||
<% provide(:sidebar_title, t("sidebar.my_module.sidebar_title")) %>
|
||||
|
||||
<%= content_for :sidebar do %>
|
||||
<%= render partial: "shared/sidebar/#{@my_module.archived_branch? ? 'archived_my_module' : 'my_module'}",
|
||||
locals: {
|
||||
my_modules: @experiment.my_modules,
|
||||
experiment: @experiment,
|
||||
current_my_module: @my_module
|
||||
}
|
||||
%>
|
||||
<% end %>
|
||||
<%= render partial: 'shared/drag_n_drop_overlay' %>
|
||||
<%= render partial: 'assets/wopi/create_wopi_file_modal' %>
|
||||
<% provide(:container_class, 'no-second-nav-container') %>
|
||||
|
||||
<div id="results" data-behaviour="vue">
|
||||
<results url="<%= my_module_results_url(@my_module) %>">
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag 'vue_results' %>
|
|
@ -214,7 +214,7 @@ class Extends
|
|||
|
||||
ACTIVITY_MESSAGE_ITEMS_TYPES =
|
||||
ACTIVITY_SUBJECT_TYPES + %w(
|
||||
User Tag RepositoryColumn RepositoryRow Step Asset TinyMceAsset
|
||||
User Tag RepositoryColumn RepositoryRow Step Result Asset TinyMceAsset
|
||||
Repository MyModuleStatus RepositorySnapshot
|
||||
).freeze
|
||||
|
||||
|
@ -448,7 +448,8 @@ class Extends
|
|||
team_sharing_tasks_disabled: 245,
|
||||
task_link_sharing_enabled: 246,
|
||||
task_link_sharing_disabled: 247,
|
||||
shared_task_message_edited: 248
|
||||
shared_task_message_edited: 248,
|
||||
result_content_rearranged: 244
|
||||
}
|
||||
|
||||
ACTIVITY_GROUPS = {
|
||||
|
|
|
@ -192,6 +192,10 @@ en:
|
|||
attributes:
|
||||
step:
|
||||
wrong_step: "connected to the wrong step"
|
||||
result_orderable_element:
|
||||
attributes:
|
||||
result:
|
||||
wrong_result: "connected to the wrong result"
|
||||
repository_snapshot:
|
||||
attributes:
|
||||
selected:
|
||||
|
@ -1208,7 +1212,7 @@ en:
|
|||
load_from_file_protocol_general_error: "Failed to load the protocol from file. It is likely that certain fields (protocol and individual step titles and names) contain too many or too few characters.(max is %{max} and min is %{min})"
|
||||
results:
|
||||
head_title: "%{project} | %{module} | Results"
|
||||
add_label: "Add new result"
|
||||
add_label: "New result"
|
||||
new_text_result: "Text"
|
||||
new_table_result: "Table"
|
||||
new_asset_result: "File"
|
||||
|
@ -1224,6 +1228,13 @@ en:
|
|||
info_tab: "Info"
|
||||
comments_tab: "Comments"
|
||||
comment_title: "%{user} at %{time}:"
|
||||
sorts:
|
||||
updated_at_asc: "Modified first"
|
||||
updated_at_desc: "Modified last"
|
||||
created_at_asc: "Created first"
|
||||
created_at_desc: "Created last"
|
||||
name_asc: "Name A to Z"
|
||||
name_desc: "Name Z to A"
|
||||
options:
|
||||
comment_title: "Comments"
|
||||
no_comments: "No comments"
|
||||
|
|
|
@ -214,6 +214,7 @@ en:
|
|||
task_inventory_item_stock_consumed_html: "%{user} changed consumption of inventory item %{repository_row} from %{stock_consumption_was} %{unit} to %{stock_consumption} %{unit} on task %{my_module} \n%{comment}"
|
||||
task_steps_rearranged_html: "%{user} rearranged protocol's steps on task %{my_module}"
|
||||
task_step_content_rearranged_html: "%{user} rearranged content of protocol's step %{step_position} <strong>%{step}</strong> on task %{my_module}"
|
||||
result_content_rearranged_html: "%{user} rearranged result's content on task %{my_module}"
|
||||
task_step_file_added_html: "%{user} added file <strong>%{file}</strong> to protocol's step %{step_position} <strong>%{step}</strong> on task %{my_module}"
|
||||
task_step_file_deleted_html: "%{user} deleted file <strong>%{file}</strong> in protocol's step %{step_position} <strong>%{step}</strong> on task %{my_module}"
|
||||
protocol_steps_rearranged_html: "%{user} rearranged protocol's steps in protocol %{protocol} in Protocol repository"
|
||||
|
|
|
@ -317,6 +317,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
resources :my_modules, only: %i(show) do
|
||||
|
||||
member do
|
||||
get :tree
|
||||
end
|
||||
|
@ -525,7 +526,6 @@ Rails.application.routes.draw do
|
|||
get 'protocols' # Protocols view for single module
|
||||
get 'protocol', to: 'my_modules#protocol', as: 'protocol'
|
||||
patch 'protocol', to: 'my_modules#update_protocol', as: 'update_protocol'
|
||||
get 'results' # Results view for single module
|
||||
get 'archive' # Archive view for single module
|
||||
end
|
||||
|
||||
|
@ -533,6 +533,11 @@ Rails.application.routes.draw do
|
|||
# to preserve original id parameters in URL.
|
||||
get 'tags/edit', to: 'my_module_tags#index_edit'
|
||||
get 'users/edit', to: 'user_my_modules#index_edit'
|
||||
|
||||
resources :results, only: %i(index show create update destroy) do
|
||||
get :elements
|
||||
get :assets
|
||||
end
|
||||
end
|
||||
|
||||
resources :steps, only: %i(index update destroy show) do
|
||||
|
|
|
@ -29,6 +29,7 @@ const entryList = {
|
|||
pdf_js_worker: './app/javascript/packs/pdfjs/pdf_js_worker.js',
|
||||
vue_label_template: './app/javascript/packs/vue/label_template.js',
|
||||
vue_protocol: './app/javascript/packs/vue/protocol.js',
|
||||
vue_results: './app/javascript/packs/vue/results.js',
|
||||
vue_repository_filter: './app/javascript/packs/vue/repository_filter.js',
|
||||
vue_repository_search: './app/javascript/packs/vue/repository_search.js',
|
||||
vue_repository_print_modal: './app/javascript/packs/vue/repository_print_modal.js',
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateResultOrderableElements < ActiveRecord::Migration[7.0]
|
||||
def up
|
||||
create_table :result_orderable_elements do |t|
|
||||
t.references :result, null: false, index: false, foreign_key: true
|
||||
t.integer :position, null: false
|
||||
t.references :orderable, polymorphic: true
|
||||
|
||||
t.timestamps
|
||||
|
||||
t.index %i(result_id position), unique: true
|
||||
end
|
||||
|
||||
ActiveRecord::Base.connection.execute(
|
||||
'INSERT INTO ' \
|
||||
'result_orderable_elements(result_id, position, orderable_type, orderable_id, created_at, updated_at) ' \
|
||||
'SELECT result_id, 0, \'ResultText\', id, NOW(), NOW() FROM result_texts;'
|
||||
)
|
||||
|
||||
ActiveRecord::Base.connection.execute(
|
||||
'INSERT INTO ' \
|
||||
'result_orderable_elements(result_id, position, orderable_type, orderable_id, created_at, updated_at) ' \
|
||||
'SELECT result_id, 0, \'ResultTable\', id, NOW(), NOW() FROM result_tables;'
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
ResultOrderableElement.delete_all
|
||||
drop_table :result_orderable_elements
|
||||
end
|
||||
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_07_20_070830) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_07_25_120831) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "btree_gist"
|
||||
enable_extension "pg_trgm"
|
||||
|
@ -903,6 +903,17 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_20_070830) do
|
|||
t.index ["result_id", "asset_id"], name: "index_result_assets_on_result_id_and_asset_id"
|
||||
end
|
||||
|
||||
create_table "result_orderable_elements", force: :cascade do |t|
|
||||
t.bigint "result_id", null: false
|
||||
t.integer "position", null: false
|
||||
t.string "orderable_type"
|
||||
t.bigint "orderable_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["orderable_type", "orderable_id"], name: "index_result_orderable_elements_on_orderable"
|
||||
t.index ["result_id", "position"], name: "index_result_orderable_elements_on_result_id_and_position", unique: true
|
||||
end
|
||||
|
||||
create_table "result_tables", force: :cascade do |t|
|
||||
t.bigint "result_id", null: false
|
||||
t.bigint "table_id", null: false
|
||||
|
@ -1429,6 +1440,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_20_070830) do
|
|||
add_foreign_key "repository_text_values", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "result_assets", "assets"
|
||||
add_foreign_key "result_assets", "results"
|
||||
add_foreign_key "result_orderable_elements", "results"
|
||||
add_foreign_key "result_tables", "results"
|
||||
add_foreign_key "result_tables", "tables"
|
||||
add_foreign_key "result_texts", "results"
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
"@joeattardi/emoji-button": "^4.6.2",
|
||||
"ajv": "6.12.6",
|
||||
"autoprefixer": "10.4.14",
|
||||
"axios": "^1.4.0",
|
||||
"babel-loader": "^8.2.5",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"bootstrap": "^3.4.1",
|
||||
|
|
28
yarn.lock
28
yarn.lock
|
@ -1900,6 +1900,15 @@ available-typed-arrays@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||
|
||||
axios@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
|
||||
integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
axobject-query@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0"
|
||||
|
@ -3606,6 +3615,11 @@ focus-trap@^5.1.0:
|
|||
tabbable "^4.0.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
|
||||
for-each@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
|
||||
|
@ -3622,6 +3636,15 @@ form-data@^3.0.0:
|
|||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fraction.js@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||
|
@ -5805,6 +5828,11 @@ prop-types@^15.8.1:
|
|||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
pseudomap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
|
|
Loading…
Reference in a new issue