mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-08 14:15:35 +08:00
Add step/results linking [SCI-11883][SCI-11884]
This commit is contained in:
parent
60ae88f64e
commit
dbf76acc51
14 changed files with 497 additions and 40 deletions
|
@ -40,6 +40,12 @@ class ResultsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def list
|
||||
@results = @my_module.results.active
|
||||
|
||||
update_and_apply_user_sort_preference!
|
||||
end
|
||||
|
||||
def create
|
||||
result = @my_module.results.create!(user: current_user)
|
||||
log_activity(:add_result, { result: result })
|
||||
|
|
|
@ -1,53 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StepResultsController < ApplicationController
|
||||
before_action :load_step, only: :create
|
||||
before_action :load_result, only: :create
|
||||
before_action :load_step_result, only: :destroy
|
||||
before_action :check_manage_permissions, only: %i(create destroy)
|
||||
before_action :load_steps
|
||||
before_action :load_results
|
||||
before_action :load_step_results
|
||||
before_action :check_manage_permissions
|
||||
|
||||
def create
|
||||
def link_results
|
||||
ActiveRecord::Base.transaction do
|
||||
@step_result = StepResult.create!(step: @step, result: @result, created_by: current_user)
|
||||
render json: { step_result: { id: @step_result.id } }
|
||||
@step_results.where.not(result: @results).destroy_all
|
||||
@results.where.not(id: @step_results.select(:result_id)).each do |result|
|
||||
StepResult.create!(step: @steps.first, result: result, created_by: current_user)
|
||||
end
|
||||
render json: { results: @steps.first.results.map { |r| { id: r.id, name: r.name } } }, status: :created
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
render json: { errors: @step_result.errors.full_messages }, status: :unprocessable_entity
|
||||
render json: { message: :error }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
def link_steps
|
||||
ActiveRecord::Base.transaction do
|
||||
if @step_result.destroy
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
render json: { errors: @step_result.errors.full_messages }, status: :unprocessable_entity
|
||||
@step_results.where.not(step: @steps).destroy_all
|
||||
@steps.where.not(id: @step_results.select(:step_id)).each do |step|
|
||||
StepResult.create!(step: step, result: @results.first, created_by: current_user)
|
||||
end
|
||||
render json: { steps: @results.first.steps.map { |s| { id: s.id, name: s.name } } }, status: :created
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
render json: { message: :error }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
@step_result.destroy!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_step
|
||||
@step = Step.find_by(id: params[:step_id])
|
||||
render_404 unless @step
|
||||
def load_steps
|
||||
@steps = Step.where(id: params[:step_ids])
|
||||
|
||||
render_404 and return if (action_name == 'link_results') && @steps.size != 1
|
||||
|
||||
render_403 and return if @steps.pluck(:protocol_id).uniq.size > 1
|
||||
end
|
||||
|
||||
def load_result
|
||||
@result = Result.find_by(id: params[:result_id])
|
||||
render_404 unless @result
|
||||
def load_results
|
||||
@results = Result.where(id: params[:result_ids])
|
||||
|
||||
render_404 and return if (action_name == 'link_steps') && @results.size != 1
|
||||
|
||||
render_403 and return if @results.pluck(:my_module_id).uniq.size > 1
|
||||
end
|
||||
|
||||
def load_step_result
|
||||
@step_result = StepResult.find_by(id: params[:id])
|
||||
render_404 unless @step_result
|
||||
|
||||
@step = @step_result.step
|
||||
@result = @step_result.result
|
||||
def load_step_results
|
||||
@step_results = StepResult.where(step: @steps) if action_name == 'link_results'
|
||||
@step_results = StepResult.where(result: @results) if action_name == 'link_steps'
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 unless @step.my_module == @result.my_module && can_manage_my_module?(@step.my_module)
|
||||
case action_name
|
||||
when 'link_results'
|
||||
render_403 and return unless can_manage_my_module?(@steps.first.my_module)
|
||||
when 'link_steps'
|
||||
render_403 and return unless can_manage_my_module?(@results.first.my_module)
|
||||
end
|
||||
|
||||
render_403 and return unless (@results + @steps).map(&:my_module).uniq.one?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,11 +7,11 @@ class StepsController < ApplicationController
|
|||
before_action :load_vars, only: %i(update destroy show toggle_step_state update_view_state
|
||||
update_asset_view_mode elements
|
||||
attachments upload_attachment duplicate)
|
||||
before_action :load_vars_nested, only: %i(create index reorder list_protocol_steps add_protocol_steps)
|
||||
before_action :load_vars_nested, only: %i(create index list reorder list_protocol_steps add_protocol_steps)
|
||||
before_action :convert_table_contents_to_utf8, only: %i(create update)
|
||||
|
||||
before_action :check_protocol_manage_permissions, only: %i(reorder add_protocol_steps)
|
||||
before_action :check_view_permissions, only: %i(show index attachments elements list_protocol_steps)
|
||||
before_action :check_view_permissions, only: %i(show index list attachments elements list_protocol_steps)
|
||||
before_action :check_create_permissions, only: %i(create)
|
||||
before_action :check_manage_permissions, only: %i(update destroy
|
||||
update_view_state update_asset_view_mode upload_attachment)
|
||||
|
@ -21,6 +21,10 @@ class StepsController < ApplicationController
|
|||
render json: @protocol.steps.in_order, each_serializer: StepSerializer, user: current_user
|
||||
end
|
||||
|
||||
def list
|
||||
@steps = @protocol.steps.in_order
|
||||
end
|
||||
|
||||
def elements
|
||||
render json: @step.step_orderable_elements.order(:position),
|
||||
each_serializer: StepOrderableElementSerializer,
|
||||
|
|
132
app/javascript/vue/protocol/modals/link_results.vue
Normal file
132
app/javascript/vue/protocol/modals/link_results.vue
Normal file
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block">
|
||||
{{ i18n.t('protocols.steps.modals.link_results.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="results.length > 0" class="modal-body">
|
||||
<p>
|
||||
{{ i18n.t('protocols.steps.modals.link_results.description') }}
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<label class="sci-label">{{ i18n.t('protocols.steps.modals.link_results.result_label') }}</label>
|
||||
<SelectDropdown
|
||||
:options="results"
|
||||
:value="selectedResults"
|
||||
@change="changeResults"
|
||||
:multiple="true"
|
||||
:withCheckboxes="true"
|
||||
:placeholder="i18n.t('protocols.steps.modals.link_results.placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="modal-body">
|
||||
<p>
|
||||
{{ i18n.t('protocols.steps.modals.link_results.empty_description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
<template v-if="results.length > 0">
|
||||
<button v-if="step.attributes.results.length == 0" type="submit" class="btn btn-primary" @click="linkResults">
|
||||
{{ i18n.t('protocols.steps.modals.link_results.link_results') }}
|
||||
</button>
|
||||
<button v-else type="submit" class="btn btn-primary" @click="linkResults">
|
||||
{{ i18n.t('general.save') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a :href="resultsPageUrl" class="btn btn-primary">
|
||||
{{ i18n.t('protocols.steps.modals.link_results.go_to_results') }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule I18n */
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin.js';
|
||||
import {
|
||||
list_my_module_results_path,
|
||||
my_module_results_path,
|
||||
link_results_step_results_path
|
||||
} from '../../../routes.js';
|
||||
|
||||
export default {
|
||||
name: 'LinksResultsModal',
|
||||
props: {
|
||||
step: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
created() {
|
||||
this.selectedResults = this.step.attributes.results.map((result) => result.id);
|
||||
this.loadResults();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
selectedResults: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
resultsListUrl() {
|
||||
return list_my_module_results_path({ my_module_id: this.step.attributes.my_module_id });
|
||||
},
|
||||
resultsPageUrl() {
|
||||
return my_module_results_path({ my_module_id: this.step.attributes.my_module_id });
|
||||
},
|
||||
resultsLinkUrl() {
|
||||
return link_results_step_results_path({ format: 'json' });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeResults(value) {
|
||||
this.selectedResults = value;
|
||||
},
|
||||
linkResults() {
|
||||
axios.post(
|
||||
this.resultsLinkUrl,
|
||||
{
|
||||
step_ids: this.step.id,
|
||||
result_ids: this.selectedResults
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
this.$emit('close');
|
||||
this.$emit('updateStep', response.data.results);
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.steps.modals.link_results.success'), 'success');
|
||||
}).catch(() => {
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.steps.modals.link_results.error'), 'danger');
|
||||
this.$emit('close');
|
||||
});
|
||||
},
|
||||
loadResults() {
|
||||
axios.get(this.resultsListUrl)
|
||||
.then((response) => {
|
||||
this.results = response.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -83,8 +83,35 @@
|
|||
:data-object-type="step.attributes.type"
|
||||
tabindex="0"
|
||||
></span> <!-- Hidden element to support legacy code -->
|
||||
|
||||
<a href="#"
|
||||
<button v-if="step.attributes.results.length == 0" class="btn btn-light icon-btn" @click="this.openLinkResultsModal = true">
|
||||
<i class="sn-icon sn-icon-results"></i>
|
||||
</button>
|
||||
<GeneralDropdown v-else ref="linkedResultsDropdown" position="right">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-light icon-btn">
|
||||
<i class="sn-icon sn-icon-results"></i>
|
||||
<span class="absolute top-1 right-1 h-4 min-w-4 bg-sn-science-blue text-white flex items-center justify-center rounded-full text-[10px]">
|
||||
{{ step.attributes.results.length }}
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<a v-for="result in step.attributes.results"
|
||||
:key="result.id"
|
||||
:title="result.name"
|
||||
:href="resultUrl(result.id)"
|
||||
class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer block hover:no-underline text-sn-blue truncate"
|
||||
>
|
||||
{{ result.name }}
|
||||
</a>
|
||||
<hr class="my-0">
|
||||
<div class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer text-sn-blue"
|
||||
@click="this.openLinkResultsModal = true; $refs.linkedResultsDropdown.closeMenu()">
|
||||
{{ i18n.t('protocols.steps.manage_links') }}
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
<a href=" #"
|
||||
v-if="!inRepository"
|
||||
ref="comments"
|
||||
class="open-comments-sidebar btn icon-btn btn-light"
|
||||
|
@ -181,6 +208,12 @@
|
|||
@close="openFormSelectModal = false"
|
||||
@submit="createElement('form_response', null, null, $event); openFormSelectModal = false"
|
||||
/>
|
||||
<LinkResultsModal
|
||||
v-if="openLinkResultsModal"
|
||||
:step="step"
|
||||
@updateStep="updateLinkedResults"
|
||||
@close="openLinkResultsModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -197,9 +230,11 @@
|
|||
import Checklist from '../shared/content/checklist.vue'
|
||||
import FormResponse from '../shared/content/form_response.vue'
|
||||
import deleteStepModal from './modals/delete_step.vue'
|
||||
import LinkResultsModal from './modals/link_results.vue'
|
||||
import Attachments from '../shared/content/attachments.vue'
|
||||
import ReorderableItemsModal from '../shared/reorderable_items_modal.vue'
|
||||
import MenuDropdown from '../shared/menu_dropdown.vue'
|
||||
import GeneralDropdown from '../shared/general_dropdown.vue'
|
||||
import ContentToolbar from '../shared/content/content_toolbar.vue'
|
||||
import CustomWellPlateModal from '../shared/content/modal/custom_well_plate_modal.vue'
|
||||
|
||||
|
@ -211,6 +246,10 @@
|
|||
import StorageUsage from '../shared/content/attachments/storage_usage.vue'
|
||||
import axios from '../../packs/custom_axios';
|
||||
|
||||
import {
|
||||
my_module_results_path,
|
||||
} from '../../routes.js';
|
||||
|
||||
export default {
|
||||
name: 'StepContainer',
|
||||
props: {
|
||||
|
@ -256,6 +295,7 @@
|
|||
inlineEditError: null,
|
||||
customWellPlate: false,
|
||||
openFormSelectModal: false,
|
||||
openLinkResultsModal: false,
|
||||
wellPlateOptions: [
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.custom'),
|
||||
emit: 'create:custom_well_plate',
|
||||
|
@ -305,7 +345,9 @@
|
|||
ContentToolbar,
|
||||
CustomWellPlateModal,
|
||||
SelectFormModal,
|
||||
FormResponse
|
||||
FormResponse,
|
||||
LinkResultsModal,
|
||||
GeneralDropdown
|
||||
},
|
||||
created() {
|
||||
this.loadAttachments();
|
||||
|
@ -751,7 +793,16 @@
|
|||
}).fail(() => {
|
||||
HelperModule.flashAlertMsg(this.i18n.t('protocols.steps.step_duplication_failed'), 'danger');
|
||||
});
|
||||
}
|
||||
},
|
||||
updateLinkedResults(results) {
|
||||
this.$emit('step:update', {
|
||||
results: results,
|
||||
position: this.step.attributes.position
|
||||
})
|
||||
},
|
||||
resultUrl(result_id) {
|
||||
return my_module_results_path({my_module_id: this.step.attributes.my_module_id, result_id: result_id})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
136
app/javascript/vue/results/modals/link_steps.vue
Normal file
136
app/javascript/vue/results/modals/link_steps.vue
Normal file
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block">
|
||||
{{ i18n.t('my_modules.results.modals.link_steps.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="steps.length > 0" class="modal-body">
|
||||
<p>
|
||||
{{ i18n.t('my_modules.results.modals.link_steps.description') }}
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<label class="sci-label">{{ i18n.t('my_modules.results.modals.link_steps.steps_label') }}</label>
|
||||
<SelectDropdown
|
||||
:options="steps"
|
||||
:value="selectedSteps"
|
||||
@change="changeSteps"
|
||||
:multiple="true"
|
||||
:withCheckboxes="true"
|
||||
:placeholder="i18n.t('my_modules.results.modals.link_steps.placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="modal-body">
|
||||
<p>
|
||||
{{ i18n.t('my_modules.results.modals.link_steps.empty_description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
<template v-if="steps.length > 0">
|
||||
<button v-if="result.attributes.steps.length == 0" type="submit" class="btn btn-primary" @click="linkSteps">
|
||||
{{ i18n.t('my_modules.results.modals.link_steps.link_steps') }}
|
||||
</button>
|
||||
<button v-else type="submit" class="btn btn-primary" @click="linkSteps">
|
||||
{{ i18n.t('general.save') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a :href="protocolPageUrl" class="btn btn-primary">
|
||||
{{ i18n.t('my_modules.results.modals.link_steps.go_to_protocol') }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule I18n */
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin.js';
|
||||
import {
|
||||
list_steps_path,
|
||||
protocols_my_module_path,
|
||||
link_steps_step_results_path
|
||||
} from '../../../routes.js';
|
||||
|
||||
export default {
|
||||
name: 'LinkStepsModal',
|
||||
props: {
|
||||
result: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
protocolId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
created() {
|
||||
this.selectedSteps = this.result.attributes.steps.map((step) => step.id);
|
||||
this.loadSteps();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
steps: [],
|
||||
selectedSteps: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stepsListUrl() {
|
||||
return list_steps_path({ protocol_id: this.protocolId });
|
||||
},
|
||||
protocolPageUrl() {
|
||||
return protocols_my_module_path({ id: this.result.attributes.my_module_id });
|
||||
},
|
||||
stepsLinkUrl() {
|
||||
return link_steps_step_results_path({ format: 'json' });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeSteps(value) {
|
||||
this.selectedSteps = value;
|
||||
},
|
||||
linkSteps() {
|
||||
axios.post(
|
||||
this.stepsLinkUrl,
|
||||
{
|
||||
step_ids: this.selectedSteps,
|
||||
result_ids: this.result.id
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
this.$emit('close');
|
||||
this.$emit('updateResult', response.data.steps);
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.steps.modals.link_results.success'), 'success');
|
||||
}).catch(() => {
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.steps.modals.link_results.error'), 'danger');
|
||||
this.$emit('close');
|
||||
});
|
||||
},
|
||||
loadSteps() {
|
||||
axios.get(this.stepsListUrl)
|
||||
.then((response) => {
|
||||
this.steps = response.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -65,7 +65,34 @@
|
|||
:data-object-type="result.attributes.type"
|
||||
tabindex="0"
|
||||
></span> <!-- Hidden element to support legacy code -->
|
||||
|
||||
<button v-if="result.attributes.steps.length == 0" class="btn btn-light icon-btn" @click="this.openLinkStepsModal = true">
|
||||
{{ i18n.t('my_modules.results.link_to_step') }}
|
||||
</button>
|
||||
<GeneralDropdown v-else ref="linkedStepsDropdown" position="right">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-light icon-btn">
|
||||
{{ i18n.t('my_modules.results.link_to_step') }}
|
||||
<span class="absolute top-1 -right-1 h-4 min-w-4 bg-sn-science-blue text-white flex items-center justify-center rounded-full text-[10px]">
|
||||
{{ result.attributes.steps.length }}
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<a v-for="step in result.attributes.steps"
|
||||
:key="step.id"
|
||||
:title="step.name"
|
||||
:href="protocolUrl(step.id)"
|
||||
class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer block hover:no-underline text-sn-blue truncate"
|
||||
>
|
||||
{{ step.name }}
|
||||
</a>
|
||||
<hr class="my-0">
|
||||
<div class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer text-sn-blue"
|
||||
@click="this.openLinkStepsModal = true; $refs.linkedStepsDropdown.closeMenu()">
|
||||
{{ i18n.t('protocols.steps.manage_links') }}
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
<a href="#"
|
||||
ref="comments"
|
||||
class="open-comments-sidebar btn icon-btn btn-light"
|
||||
|
@ -148,6 +175,13 @@
|
|||
@cancel="closeCustomWellPlateModal"
|
||||
@create:table="(...args) => this.createElement('table', ...args)"
|
||||
/>
|
||||
<LinkStepsModal
|
||||
v-if="openLinkStepsModal"
|
||||
:result="result"
|
||||
:protocolId="protocolId"
|
||||
@updateResult="updateLinkedSteps"
|
||||
@close="openLinkStepsModal = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -161,7 +195,9 @@ import ResultText from '../shared/content/text.vue';
|
|||
import Attachments from '../shared/content/attachments.vue';
|
||||
import InlineEdit from '../shared/inline_edit.vue';
|
||||
import MenuDropdown from '../shared/menu_dropdown.vue';
|
||||
import GeneralDropdown from '../shared/general_dropdown.vue';
|
||||
import deleteResultModal from './delete_result.vue';
|
||||
import LinkStepsModal from './modals/link_steps.vue'
|
||||
import ContentToolbar from '../shared/content/content_toolbar';
|
||||
import CustomWellPlateModal from '../shared/content/modal/custom_well_plate_modal.vue'
|
||||
|
||||
|
@ -170,12 +206,16 @@ import WopiFileModal from '../shared/content/attachments/mixins/wopi_file_modal.
|
|||
import OveMixin from '../shared/content/attachments/mixins/ove.js';
|
||||
import UtilsMixin from '../mixins/utils.js';
|
||||
import StorageUsage from '../shared/content/attachments/storage_usage.vue';
|
||||
import {
|
||||
protocols_my_module_path,
|
||||
} from '../../routes.js';
|
||||
|
||||
export default {
|
||||
name: 'Results',
|
||||
props: {
|
||||
result: { type: Object, required: true },
|
||||
resultToReload: { type: Number, required: false },
|
||||
protocolId: { type: Number, required: false },
|
||||
activeDragResult: {
|
||||
required: false
|
||||
},
|
||||
|
@ -193,6 +233,7 @@ export default {
|
|||
showFileModal: false,
|
||||
dragingFile: false,
|
||||
customWellPlate: false,
|
||||
openLinkStepsModal: false,
|
||||
wellPlateOptions: [
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.custom'), emit: 'create:custom_well_plate'},
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.32_x_48'), emit: 'create:table', params: [32, 48] },
|
||||
|
@ -219,7 +260,9 @@ export default {
|
|||
deleteResultModal,
|
||||
StorageUsage,
|
||||
ContentToolbar,
|
||||
CustomWellPlateModal
|
||||
CustomWellPlateModal,
|
||||
LinkStepsModal,
|
||||
GeneralDropdown
|
||||
},
|
||||
watch: {
|
||||
resultToReload() {
|
||||
|
@ -576,7 +619,15 @@ export default {
|
|||
axios.patch(this.urls.update_url, { result: { name } }).then((_) => {
|
||||
this.$emit('updated');
|
||||
});
|
||||
}
|
||||
},
|
||||
updateLinkedSteps(steps) {
|
||||
this.$emit('result:update', this.result.id,{
|
||||
steps: steps
|
||||
})
|
||||
},
|
||||
protocolUrl(step_id) {
|
||||
return protocols_my_module_path({ id: this.result.attributes.my_module_id }, { step_id: step_id })
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
:resultToReload="resultToReload"
|
||||
:activeDragResult="activeDragResult"
|
||||
:userSettingsUrl="userSettingsUrl"
|
||||
:protocolId="protocolId"
|
||||
@result:update="updateResult"
|
||||
@result:elements:loaded="resultToReload = null; elementsLoaded++"
|
||||
@result:move_element="reloadResult"
|
||||
@result:attachments:loaded="resultToReload = null; attachmentsLoaded++"
|
||||
|
@ -74,7 +76,8 @@ export default {
|
|||
archived: { type: String, required: true },
|
||||
active_url: { type: String, required: true },
|
||||
archived_url: { type: String, required: true },
|
||||
userSettingsUrl: { type: String, required: false }
|
||||
userSettingsUrl: { type: String, required: false },
|
||||
protocolId: { type: Number, required: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -238,6 +241,13 @@ export default {
|
|||
|
||||
axios.put(this.userSettingsUrl, { settings: [settings] });
|
||||
},
|
||||
updateResult(id, attributes) {
|
||||
const resultIndex = this.results.findIndex((result) => result.id === id);
|
||||
this.results[resultIndex].attributes = {
|
||||
...this.results[resultIndex].attributes,
|
||||
...attributes
|
||||
};
|
||||
},
|
||||
removeResult(result_id) {
|
||||
this.results = this.results.filter((r) => r.id != result_id);
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ class StepSerializer < ActiveModel::Serializer
|
|||
attributes :name, :position, :completed, :attachments_manageble, :urls, :assets_view_mode,
|
||||
:marvinjs_enabled, :marvinjs_context, :created_by, :created_at, :assets_order,
|
||||
:wopi_enabled, :wopi_context, :comments_count, :unseen_comments, :storage_limit,
|
||||
:type, :open_vector_editor_context, :collapsed, :results
|
||||
:type, :open_vector_editor_context, :collapsed, :my_module_id, :results
|
||||
|
||||
def collapsed
|
||||
step_states = @instance_options[:user].settings.fetch('task_step_states', {})
|
||||
|
@ -27,6 +27,10 @@ class StepSerializer < ActiveModel::Serializer
|
|||
end
|
||||
end
|
||||
|
||||
def my_module_id
|
||||
object.my_module&.id
|
||||
end
|
||||
|
||||
def type
|
||||
'Step'
|
||||
end
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<div id="results" data-behaviour="vue">
|
||||
<results url="<%= my_module_results_url(@my_module, view_mode: params[:view_mode]) %>"
|
||||
active_url="<%= my_module_results_url(@my_module) %>"
|
||||
protocol-id="<%= @my_module.protocol.id %>"
|
||||
archived_url="<%= my_module_results_url(@my_module, view_mode: :archived) %>"
|
||||
can-create=<%= can_create_results?(@my_module) && !(params[:view_mode] == 'archived') %>
|
||||
archived=<%= params[:view_mode] == 'archived' %>>
|
||||
|
|
5
app/views/results/list.json.jbuilder
Normal file
5
app/views/results/list.json.jbuilder
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
json.array! @results do |r|
|
||||
json.array! [r.id, r.name]
|
||||
end
|
11
app/views/steps/list.json.jbuilder
Normal file
11
app/views/steps/list.json.jbuilder
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
json.array! @steps do |r|
|
||||
json.array! [
|
||||
r.id,
|
||||
r.name,
|
||||
{
|
||||
position: r.position
|
||||
}
|
||||
]
|
||||
end
|
|
@ -1603,6 +1603,17 @@ en:
|
|||
info_tab: "Info"
|
||||
comments_tab: "Comments"
|
||||
comment_title: "%{user} at %{time}:"
|
||||
link_to_step: "Link to step"
|
||||
modals:
|
||||
link_steps:
|
||||
title: 'Link result to protocol steps'
|
||||
description: 'You can link result to multipple steps'
|
||||
empty_description: 'No step found. Add a step first to enable linking with a result.'
|
||||
steps_label: 'Protocol steps'
|
||||
placeholder: 'Select steps'
|
||||
link_steps: 'Link steps'
|
||||
go_to_protocol: 'Go to Protocol'
|
||||
step_label: "Step %{position} - %{name}"
|
||||
insert:
|
||||
button: 'Insert content'
|
||||
title: 'Insert result content'
|
||||
|
@ -3980,6 +3991,7 @@ en:
|
|||
component_duplication_failed: "Step content could not be duplicated, try again later."
|
||||
timestamp: "Created on %{date} by %{user}"
|
||||
timestamp_iso_html: "Created on <span class='iso-formatted-date'>%{date}</span> by %{user}"
|
||||
manage_links: "Manage links"
|
||||
status:
|
||||
complete: "Mark as done"
|
||||
uncomplete: "Unmark as done"
|
||||
|
@ -4067,6 +4079,16 @@ en:
|
|||
confirm: 'Yes, delete them'
|
||||
reorder_elements:
|
||||
title: 'Rearrange step %{step_position} content'
|
||||
link_results:
|
||||
title: 'Link results to this step'
|
||||
description: 'You can link multiple results to a step'
|
||||
empty_description: 'No results found. Add a result first to enable linking with a step.'
|
||||
result_label: 'Result'
|
||||
placeholder: 'Select results'
|
||||
link_results: 'Link results'
|
||||
go_to_results: 'Go to Results'
|
||||
success: 'Step and result links successfully updated.'
|
||||
error: 'There was a problem with linking result to the step.'
|
||||
custom_well_plate:
|
||||
title: 'Insert custom well plate'
|
||||
name_label: 'Well plate name'
|
||||
|
|
|
@ -460,9 +460,10 @@ Rails.application.routes.draw do
|
|||
get 'experiments/:experiment_id/table', to: 'my_modules#index'
|
||||
get 'experiments/:experiment_id/modules', to: 'my_modules#index', as: :my_modules
|
||||
|
||||
resources :step_results, only: %i(destroy) do
|
||||
resources :step_results, only: [] do
|
||||
collection do
|
||||
post :create
|
||||
post :link_results
|
||||
post :link_steps
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -564,6 +565,9 @@ Rails.application.routes.draw do
|
|||
get 'users/edit', to: 'user_my_modules#index_edit'
|
||||
|
||||
resources :results, only: %i(index show create update destroy) do
|
||||
collection do
|
||||
get :list
|
||||
end
|
||||
member do
|
||||
get :elements
|
||||
get :assets
|
||||
|
@ -642,6 +646,9 @@ Rails.application.routes.draw do
|
|||
post 'update_asset_view_mode'
|
||||
post 'duplicate'
|
||||
end
|
||||
collection do
|
||||
get :list
|
||||
end
|
||||
end
|
||||
|
||||
# tinyMCE image uploader endpoint
|
||||
|
|
Loading…
Add table
Reference in a new issue