diff --git a/app/api/chemotion/reaction_api.rb b/app/api/chemotion/reaction_api.rb
index da6a7096e3..5c70814359 100644
--- a/app/api/chemotion/reaction_api.rb
+++ b/app/api/chemotion/reaction_api.rb
@@ -91,6 +91,7 @@ class ReactionAPI < Grape::API
get do
reaction = Reaction.find(params[:id])
+ class_name = reaction&.class&.name
{
reaction: Entities::ReactionEntity.represent(
@@ -98,7 +99,7 @@ class ReactionAPI < Grape::API
policy: @element_policy,
detail_levels: ElementDetailLevelCalculator.new(user: current_user, element: reaction).detail_levels,
),
- literatures: Entities::LiteratureEntity.represent(citation_for_elements(params[:id], 'Reaction')),
+ literatures: Entities::LiteratureEntity.represent(citation_for_elements(params[:id], class_name)),
}
end
end
diff --git a/app/api/chemotion/research_plan_api.rb b/app/api/chemotion/research_plan_api.rb
index 9609f0b592..740482bb3f 100644
--- a/app/api/chemotion/research_plan_api.rb
+++ b/app/api/chemotion/research_plan_api.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Chemotion
# rubocop: disable Metrics/ClassLength
@@ -20,8 +22,8 @@ class ResearchPlanAPI < Grape::API
get do
scope = if params[:collection_id]
begin
- Collection.belongs_to_or_shared_by(current_user.id,current_user.group_ids).
- find(params[:collection_id]).research_plans
+ Collection.belongs_to_or_shared_by(current_user.id, current_user.group_ids)
+ .find(params[:collection_id]).research_plans
rescue ActiveRecord::RecordNotFound
ResearchPlan.none
end
@@ -138,6 +140,17 @@ class ResearchPlanAPI < Grape::API
end
end
+ desc 'Return element linked to research plan'
+ params do
+ requires :id, type: Integer, desc: 'Research plan id'
+ requires :element, type: String, desc: 'Sample or Reaction'
+ end
+
+ get 'linked' do
+ type = "#{params[:element]}_id"
+ ResearchPlan.where('body @> ?', [{ value: { type => params[:id] } }].to_json).select(:id, :name)
+ end
+
desc 'Return serialized research plan by id'
params do
requires :id, type: Integer, desc: 'Research plan id'
diff --git a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js
index 307dd2c504..58dc6edb61 100644
--- a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js
+++ b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js
@@ -7,6 +7,7 @@ import {
import SvgFileZoomPan from 'react-svg-file-zoom-pan-latest';
import { findIndex } from 'lodash';
import ElementCollectionLabels from 'src/apps/mydb/elements/labels/ElementCollectionLabels';
+import ElementResearchPlanLabels from 'src/apps/mydb/elements/labels/ElementResearchPlanLabels';
import ElementAnalysesLabels from 'src/apps/mydb/elements/labels/ElementAnalysesLabels';
import ElementActions from 'src/stores/alt/actions/ElementActions';
import DetailActions from 'src/stores/alt/actions/DetailActions';
@@ -279,6 +280,10 @@ export default class ReactionDetails extends Component {
);
+ const rsPlanLabel = (reaction.isNew || _.isEmpty(reaction.research_plans)) ? null : (
+
+ );
+
return (
{titleTooltip}}>
@@ -344,6 +349,7 @@ export default class ReactionDetails extends Component {
{colLabel}
+ {rsPlanLabel}
@@ -474,7 +480,6 @@ export default class ReactionDetails extends Component {
green_chemistry: 'Green Chemistry'
}
-
addSegmentTabs(reaction, this.handleSegmentsChange, tabContentsMap);
const tabContents = [];
diff --git a/app/packs/src/apps/mydb/elements/details/researchPlans/researchPlanTab/ResearchPlanDetailsField.js b/app/packs/src/apps/mydb/elements/details/researchPlans/researchPlanTab/ResearchPlanDetailsField.js
index 2ce0b7fc90..f63bb6668e 100644
--- a/app/packs/src/apps/mydb/elements/details/researchPlans/researchPlanTab/ResearchPlanDetailsField.js
+++ b/app/packs/src/apps/mydb/elements/details/researchPlans/researchPlanTab/ResearchPlanDetailsField.js
@@ -10,7 +10,6 @@ import ResearchPlanDetailsFieldImage from 'src/apps/mydb/elements/details/resear
import ResearchPlanDetailsFieldTable from 'src/apps/mydb/elements/details/researchPlans/researchPlanTab/ResearchPlanDetailsFieldTable';
import ResearchPlanDetailsFieldSample from 'src/apps/mydb/elements/details/researchPlans/researchPlanTab/ResearchPlanDetailsFieldSample';
import ResearchPlanDetailsFieldReaction from 'src/apps/mydb/elements/details/researchPlans/researchPlanTab/ResearchPlanDetailsFieldReaction';
-import AttachmentFetcher from 'src/fetchers/AttachmentFetcher';
export default class ResearchPlanDetailsField extends Component {
render() {
diff --git a/app/packs/src/apps/mydb/elements/labels/ElementResearchPlanLabels.js b/app/packs/src/apps/mydb/elements/labels/ElementResearchPlanLabels.js
new file mode 100644
index 0000000000..b0a1356202
--- /dev/null
+++ b/app/packs/src/apps/mydb/elements/labels/ElementResearchPlanLabels.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import { Label, Button, OverlayTrigger, Popover } from 'react-bootstrap';
+
+export default class ElementResearchPlanLabels extends React.Component {
+ constructor(props) {
+
+ super(props);
+ this.state = {
+ research_plans: props.plans
+ };
+
+ this.handleOnClick = this.handleOnClick.bind(this);
+ this.preventOnClick = this.preventOnClick.bind(this);
+ }
+
+ handleOnClick(label, e) {
+ e.stopPropagation();
+
+ let url = "/research_plan/" + label.id;
+ Aviator.navigate(url);
+ }
+
+ preventOnClick(e) {
+ e.stopPropagation();
+ }
+
+ formatLabels(labels) {
+ return labels.map((label, index) => {
+ return (
+
+
+
+
+ );
+ });
+ }
+
+ renderCollectionsLabels(research_plans) {
+ if (research_plans == undefined) return
;
+
+ return (
+
+
Research Plans
+
+ {this.formatLabels(research_plans)}
+
+
+ );
+ }
+
+ render() {
+ const { research_plans } = this.state;
+
+ let placement = 'right';
+ let researchPlanOverlay = (
+
+ {this.renderCollectionsLabels(research_plans)}
+
+ );
+
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/app/packs/src/fetchers/ReactionsFetcher.js b/app/packs/src/fetchers/ReactionsFetcher.js
index ac1fb5455f..42b0b3fc2c 100644
--- a/app/packs/src/fetchers/ReactionsFetcher.js
+++ b/app/packs/src/fetchers/ReactionsFetcher.js
@@ -6,32 +6,43 @@ import Reaction from 'src/models/Reaction';
import AttachmentFetcher from 'src/fetchers/AttachmentFetcher';
import Literature from 'src/models/Literature';
import GenericElsFetcher from 'src/fetchers/GenericElsFetcher';
+import ResearchPlansFetcher from './ResearchPlansFetcher';
// TODO: Extract common base functionality into BaseFetcher
export default class ReactionsFetcher {
- static fetchById(id) {
- return fetch(`/api/v1/reactions/${id}.json`, {
- credentials: 'same-origin'
- }).then(response => response.json())
- .then((json) => {
- if (json.hasOwnProperty("reaction")) {
- const reaction = new Reaction(json.reaction);
- if (json.literatures && json.literatures.length > 0) {
- const tliteratures = json.literatures.map(literature => new Literature(literature));
- const lits = tliteratures.reduce((acc, l) => acc.set(l.literal_id, l), new Immutable.Map());
- reaction.literatures = lits;
+ static async fetchById(id) {
+ try {
+ const response = await fetch(`/api/v1/reactions/${id}.json`, {
+ credentials: 'same-origin'
+ }).then(response => response.json())
+ .then((json) => {
+ if (json.hasOwnProperty("reaction")) {
+ const reaction = new Reaction(json.reaction);
+ if (json.literatures && json.literatures.length > 0) {
+ const tliteratures = json.literatures.map(literature => new Literature(literature));
+ const lits = tliteratures.reduce((acc, l) => acc.set(l.literal_id, l), new Immutable.Map());
+ reaction.literatures = lits;
+ }
+ if (json.research_plans && json.research_plans.length > 0) {
+ reaction.research_plans = json.research_plans;
+ }
+ reaction.updateMaxAmountOfProducts();
+ return reaction;
}
- reaction.updateMaxAmountOfProducts();
- return reaction;
- }
- const rReaction = new Reaction(json.reaction);
- if (json.error) {
- rReaction.id = `${id}:error:Reaction ${id} is not accessible!`;
- }
- return rReaction;
- }).catch((errorMessage) => {
- console.log(errorMessage);
- });
+ const rReaction = new Reaction(json.reaction);
+ if (json.error) {
+ rReaction.id = `${id}:error:Reaction ${id} is not accessible!`;
+ }
+ return rReaction;
+ }).catch((errorMessage) => {
+ console.log(errorMessage);
+ });
+ const researchPlans = await ResearchPlansFetcher.fetchResearchPlansForElements(id, response.type);
+ response['research_plans'] = researchPlans;
+ return response;
+ } catch (error) {
+ console.error(error);
+ }
}
static fetchByCollectionId(id, queryParams = {}, isSync = false) {
@@ -78,10 +89,10 @@ export default class ReactionsFetcher {
return Promise.all(tasks).then(() => {
return promise();
});
- }
-
+ }
+
return promise();
- }
+ }
static updateAnnotationsInReaction(reaction){
const tasks=[];
@@ -89,7 +100,7 @@ export default class ReactionsFetcher {
return Promise.all(tasks);
}
- static update(reaction) {
+ static update(reaction) {
return ReactionsFetcher.create(reaction, 'put');
}
}
diff --git a/app/packs/src/fetchers/ResearchPlansFetcher.js b/app/packs/src/fetchers/ResearchPlansFetcher.js
index 768ea17057..752826049b 100644
--- a/app/packs/src/fetchers/ResearchPlansFetcher.js
+++ b/app/packs/src/fetchers/ResearchPlansFetcher.js
@@ -295,4 +295,17 @@ export default class ResearchPlansFetcher {
return Promise.all(updateTasks);
}
+
+ static fetchResearchPlansForElements(id, element) {
+ return fetch(`/api/v1/research_plans/linked?id=${id}&element=${element}`, {
+ credentials: 'same-origin',
+ method: 'get',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ }
+ }).then((response) => response.json())
+ .then((json) => json)
+ .catch((errorMessage) => { console.log(errorMessage); });
+ }
}
diff --git a/app/packs/src/models/Reaction.js b/app/packs/src/models/Reaction.js
index 917e44991c..b82e1225aa 100644
--- a/app/packs/src/models/Reaction.js
+++ b/app/packs/src/models/Reaction.js
@@ -114,6 +114,7 @@ export default class Reaction extends Element {
duration: '',
durationDisplay: DurationDefault,
literatures: {},
+ research_plans: {},
name: '',
observation: Reaction.quillDefault(),
products: [],
@@ -175,6 +176,7 @@ export default class Reaction extends Element {
durationCalc: this.durationCalc(),
id: this.id,
literatures: this.literatures,
+ research_plans: this.research_plans,
materials: {
starting_materials: this.starting_materials.map(s => s.serializeMaterial()),
reactants: this.reactants.map(s => s.serializeMaterial()),
@@ -864,10 +866,18 @@ export default class Reaction extends Element {
return this._literatures || {};
}
+ get research_plans() {
+ return this._research_plans || {};
+ }
+
set literatures(literatures) {
this._literatures = literatures;
}
+ set research_plans(research_plans) {
+ this._research_plans = research_plans;
+ }
+
get totalVolume() {
let totalVolume = 0.0;
const materials = [...this.starting_materials,
diff --git a/spec/api/research_plan_api_spec.rb b/spec/api/research_plan_api_spec.rb
index f37c410af8..9361870357 100644
--- a/spec/api/research_plan_api_spec.rb
+++ b/spec/api/research_plan_api_spec.rb
@@ -31,31 +31,6 @@
end
end
- describe 'GET /api/v1/research_plans' do
- let!(:c) { create(:collection, label: 'C1', user: user, is_shared: false) }
- let(:rp) { create(:research_plan) }
- let!(:research_plan_metadata) { create(:research_plan_metadata) }
-
- before do
- rp.research_plan_metadata = research_plan_metadata
- CollectionsResearchPlan.create!(research_plan: rp, collection: c)
- end
-
- it 'returns serialized research_plans of logged in user' do
- get '/api/v1/research_plans'
- first_rp = JSON.parse(response.body)['research_plans'].first
- expect(response.status).to eq 200
- expect(first_rp).to include(
- 'type' => 'research_plan',
- 'name' => rp.name
- )
- expect(first_rp['research_plan_metadata']).to include(
- 'id' => research_plan_metadata.id,
- 'doi' => research_plan_metadata.doi
- )
- end
- end
-
describe 'POST /api/v1/research_plans' do
context 'with valid parameters' do
let(:params) do
@@ -135,5 +110,23 @@
expect(first_row['readout_1_unit']).to eq first_readout['unit']
end
end
+
+ describe 'GET /api/v1/research_plans/linked' do
+ let!(:c) { create(:collection, label: 'C1', user: user) }
+ let!(:research_plan) { create(:research_plan, :with_linked) }
+
+ before do
+ get '/api/v1/research_plans/linked', params: { id: 100, element: 'reaction' }
+ end
+
+ it 'returns 200 status code' do
+ expect(response.status).to eq 200
+ end
+
+ it 'returns research_plans linked to an element' do
+ response_body = JSON.parse(response.body)
+ expect(response_body[0]['name']).to eq research_plan.name
+ end
+ end
end
end
diff --git a/spec/factories/research_plans.rb b/spec/factories/research_plans.rb
index c7f64c6bbc..8e2287277d 100644
--- a/spec/factories/research_plans.rb
+++ b/spec/factories/research_plans.rb
@@ -7,7 +7,7 @@
{ "id"=>SecureRandom.uuid,
"type"=>"richtext",
"value"=>{ "ops"=>[{ "insert"=>"some text here\n" }] } },
-
+
]
end
@@ -24,6 +24,20 @@
end
end
+ trait :with_linked do
+ name { 'Linked Research Plan' }
+
+ body do
+ [
+ {
+ 'id' => SecureRandom.uuid,
+ 'type' => 'reaction',
+ 'value' => { 'reaction_id' => 100 },
+ },
+ ]
+ end
+ end
+
callback(:before_create) do |research_plan|
research_plan.creator = FactoryBot.build(:user) unless research_plan.creator
end