Commit 654099ec authored by pierre.tremouilhac's avatar pierre.tremouilhac
Browse files

Merge branch '219-molecule-name-as-hash-samples-molecule_name-column' into 'development'

Sample molecule name

Closes #219

See merge request !406
parents 25c11d91 5eec6d85
......@@ -42,6 +42,26 @@ module Chemotion
molecule.load_cas if molecule
molecule
end
desc 'return names of the molecule'
params do
requires :inchikey, type: String, desc: 'Molecule inchikey'
optional :new_name, type: String, desc: 'New molecule_name'
end
get :names do
inchikey = params[:inchikey]
new_name = params[:new_name]
mol = Molecule.find_by(inchikey: inchikey)
return [] if mol.blank?
user_id = current_user.id
mol.create_molecule_name_by_user(new_name, user_id) if new_name.present?
mol.molecule_names.map do |mn|
{ id: mn.id, name: mn.name }
end
end
end
end
end
......@@ -247,6 +247,7 @@ module Chemotion
optional :residues, type: Array
optional :elemental_compositions, type: Array
optional :xref, type: Hash
optional :molecule_name_id, type: Integer
requires :container, type: Hash
#use :root_container_params
end
......@@ -316,6 +317,7 @@ module Chemotion
optional :residues, type: Array
optional :elemental_compositions, type: Array
optional :xref, type: Hash
optional :molecule_name_id, type: Integer
requires :container, type: Hash
end
post do
......@@ -343,7 +345,8 @@ module Chemotion
residues: params[:residues],
elemental_compositions: params[:elemental_compositions],
created_by: current_user.id,
xref: params[:xref]
xref: params[:xref],
molecule_name_id: params[:molecule_name_id]
}
# otherwise ActiveRecord::UnknownAttributeError appears
......
......@@ -2,7 +2,7 @@ import React from 'react';
import { Button, Checkbox, FormGroup, FormControl, InputGroup, ControlLabel,
Table, Glyphicon } from 'react-bootstrap';
import Select from 'react-select';
import DetailActions from './actions/DetailActions';
import NumeralInputWithUnitsCompo from './NumeralInputWithUnitsCompo';
import { solventOptions } from './staticDropdownOptions/options';
......@@ -13,9 +13,16 @@ export default class SampleForm extends React.Component {
this.state = {
sample: props.sample,
molarityBlocked: (props.sample.molarity_value || 0) <= 0,
isMolNameLoading: false,
};
this.handleFieldChanged = this.handleFieldChanged.bind(this);
this.updateMolName = this.updateMolName.bind(this);
this.addMolName = this.addMolName.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({ isMolNameLoading: false });
}
handleAmountChanged(amount) {
......@@ -74,18 +81,53 @@ export default class SampleForm extends React.Component {
return (<span />);
}
openMolName(moleculeNames) {
const { sample } = this.state;
if (moleculeNames.length <= 1) {
this.setState({ isMolNameLoading: true });
DetailActions.updateMoleculeNames(sample);
}
}
addMolName(moleculeName) {
const { sample } = this.state;
this.setState({ isMolNameLoading: true });
DetailActions.updateMoleculeNames(sample, moleculeName.label);
}
updateMolName(e) {
const { sample } = this.state;
sample.molecule_name = e;
this.props.parent.setState({ sample });
}
moleculeInput(sample) {
const mnos = sample.molecule_names;
const mno = sample.molecule_name;
let moleculeNames = mno ? [mno] : [];
if (sample && mnos) {
moleculeNames = mnos.map(n => (
Object.assign({ label: n.name, value: n.id })
));
}
const onOpenMolName = () => this.openMolName(moleculeNames);
return (
<FormGroup style={{ width: '100%' }}>
<ControlLabel>Molecule</ControlLabel>
<InputGroup>
<FormControl
type="text"
ref="moleculeInput"
value={sample.molecule_name}
<Select.Creatable
name="moleculeName"
multi={false}
disabled={!sample.can_update}
readOnly={!sample.can_update}
onChange={(e) => this.handleFieldChanged(sample, 'molecule_iupac_name', e.target.value)}
options={moleculeNames}
onOpen={onOpenMolName}
onChange={this.updateMolName}
isLoading={this.state.isMolNameLoading}
value={mno && mno.value}
onNewOptionClick={this.addMolName}
clearable={false}
/>
<InputGroup.Button>
{this.structureEditorButton(!sample.can_update)}
......
......@@ -29,6 +29,23 @@ class DetailActions {
})
}
}
updateMoleculeNames(sample, newMolName = '') {
const inchikey = sample.molecule.inchikey;
if (!inchikey) { return null; }
return (dispatch) => {
MoleculesFetcher
.updateNames(inchikey, newMolName)
.then((result) => {
const mn = result.find(r => r.name === newMolName);
if (mn) sample.molecule_name = { label: mn.name, value: mn.id };
sample.molecule_names = result;
dispatch(sample);
})
.catch(errorMessage => console.log(errorMessage));
};
}
}
export default alt.createActions(DetailActions)
import React, {Component} from 'react';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import Formula from './Formula'
//var _ = require('lodash');
import React from 'react';
import Formula from './Formula';
export default class SampleName extends React.Component {
render() {
let {sample} = this.props;
const sumFormula = sf => <Formula formula={sf} />;
let molecule_name = sample._molecule.iupac_name
let sum_formular = (<Formula formula={sample._molecule.sum_formular}/>)
if(sample.contains_residues) {
let polymer_name = sample.polymer_type.charAt(0).toUpperCase()
+ sample.polymer_type.slice(1);
return (
<div>
<p>
{polymer_name.replace('_', '-') + ' - '}
{sum_formular}
</p>
<p>{molecule_name}</p>
</div>
)
} else {
return (
<div>
<p>{sum_formular}</p>
<p>{molecule_name}</p>
</div>
)
}
const sampleNameWithResidues = (polymer_type, sumFormulaCom, moleculeName) => {
const polymerName = (polymer_type.charAt(0).toUpperCase()
+ polymer_type.slice(1)).replace('_', '-') + ' - ';
return (
<div>
<p>
{polymerName}
{sumFormulaCom}
</p>
<p>{moleculeName}</p>
</div>
);
};
const SampleName = ({ sample }) => {
const { sum_formular, iupac_name } = sample._molecule;
const { contains_residues, polymer_type } = sample;
const mnl = sample.molecule_name_label;
const moleculeName = mnl && mnl !== sum_formular ? mnl : iupac_name;
const sumFormulaCom = sumFormula(sum_formular);
if (contains_residues) {
return sampleNameWithResidues(polymer_type, sumFormulaCom, moleculeName);
}
}
return (
<div>
<p>{sumFormulaCom}</p>
<p>{moleculeName}</p>
</div>
);
};
export default SampleName;
......@@ -38,4 +38,15 @@ export default class MoleculesFetcher {
return promise
}
static updateNames(inchikey, newMolName = '') {
const promise = fetch(`/api/v1/molecules/names?inchikey=${inchikey}` +
`&new_name=${newMolName}`, {
credentials: 'same-origin',
}).then(response => response.json())
.then(json => json.molecules)
.catch(errorMessage => console.log(errorMessage));
return promise;
}
}
......@@ -27,7 +27,8 @@ export default class Element {
checksum(fieldsToOmit = []) {
return sha256(JSON.stringify(_.omit(_.omit(this,
['_checksum', 'belongTo', 'matGroup', ...fieldsToOmit]), _.isEmpty)));
['_checksum', 'belongTo', 'matGroup', 'molecule_names', ...fieldsToOmit],
), _.isEmpty)));
}
get getChecksum() {
......
......@@ -201,6 +201,7 @@ export default class Sample extends Element {
location: this.location,
molfile: this.molfile,
molecule: this.molecule && this.molecule.serialize(),
molecule_name_id: this.molecule_name && this.molecule_name.value,
sample_svg_file: this.sample_svg_file,
is_top_secret: this.is_top_secret || false,
parent_id: this.parent_id,
......@@ -288,16 +289,16 @@ export default class Sample extends Element {
return cssClass;
}
get molecule_name(){
if (this.contains_residues) {
let polymer_name = this.polymer_type.charAt(0).toUpperCase()
+ this.polymer_type.slice(1);
let val = polymer_name.replace('_', '-') + ' - ';
val += this.molecule.sum_formular;
return val;
}
else
return this.molecule && (this.molecule.iupac_name || this.molecule.sum_formular || '')
get molecule_name_label() {
return this.molecule_name_hash && this.molecule_name_hash.label;
}
get molecule_name() {
return this.molecule_name_hash;
}
set molecule_name(mno) {
this.molecule_name_hash = mno;
}
get name() {
......
......@@ -19,6 +19,7 @@ class DetailStore {
handleConfirmDelete: DetailActions.confirmDelete,
handleChangeCurrentElement: DetailActions.changeCurrentElement,
handleGetMoleculeCas: DetailActions.getMoleculeCas,
handleUpdateMoleculeNames: DetailActions.updateMoleculeNames,
})
}
......@@ -74,6 +75,15 @@ class DetailStore {
this.setState({ selecteds: newSelecteds })
}
handleUpdateMoleculeNames(updatedSample) {
if (updatedSample) {
const selecteds = this.selecteds;
const index = this.elementIndex(selecteds, updatedSample);
const newSelecteds = this.updateElement(updatedSample, index);
this.setState({ selecteds: newSelecteds });
}
}
synchronizeElements(close, open) {
let associatedSampleFromReaction = (
close instanceof Reaction && open instanceof Sample &&
......
......@@ -8,8 +8,10 @@ class Molecule < ActiveRecord::Base
has_many :samples
has_many :collections, through: :samples
has_many :molecule_names
before_save :sanitize_molfile
after_create :create_molecule_names
skip_callback :save, before: :sanitize_molfile, if: :skip_sanitize_molfile
validates_uniqueness_of :inchikey, scope: :is_partial
......@@ -222,6 +224,26 @@ class Molecule < ActiveRecord::Base
end
end
def create_molecule_names
if names.present?
names.each do |nm|
molecule_names.create(name: nm, description: 'iupac_name')
end
end
molecule_names.create(name: sum_formular, description: 'sum_formular')
end
def create_molecule_name_by_user(new_name, user_id)
return unless unique_molecule_name(new_name)
molecule_names
.create(name: new_name, description: "defined by user #{user_id}")
end
def unique_molecule_name(new_name)
mns = molecule_names.map(&:name)
!mns.include?(new_name)
end
private
# TODO: check that molecules are OK and remove this method. fix is in editor
......
class MoleculeName < ActiveRecord::Base
acts_as_paranoid
belongs_to :user
belongs_to :molecule
has_many :samples
end
......@@ -105,6 +105,7 @@ class Sample < ActiveRecord::Base
belongs_to :molecule
belongs_to :fingerprints
belongs_to :user
belongs_to :molecule_name
has_one :container, :as => :containable
......@@ -141,8 +142,9 @@ class Sample < ActiveRecord::Base
after_save :update_data_for_reactions
before_create :check_short_label
after_create :update_counter
before_create :check_molecule_name
after_create :update_counter
after_create :create_root_container
def molecule_sum_formular
......@@ -359,6 +361,11 @@ class Sample < ActiveRecord::Base
end
end
def molecule_name_hash
mn = molecule_name
mn ? { label: mn.name, value: mn.id } : {}
end
private
def has_collections
......@@ -464,4 +471,12 @@ private
self.container = Container.create_root_container
end
end
def check_molecule_name
if molecule_name_id.blank?
target = molecule_iupac_name || molecule_sum_formular
mn = molecule.molecule_names.find_by(name: target)
self.molecule_name_id = mn.id
end
end
end
......@@ -4,11 +4,11 @@ class DetailLevels::Sample
:id, :type, :name, :short_label, :description, :created_at,
:target_amount_value, :target_amount_unit, :real_amount_value, :location,
:real_amount_unit, :molfile, :solvent, :molarity_value, :molarity_unit,
:is_top_secret, :is_restricted, :external_label, :analyses, :purity,
:is_top_secret, :is_restricted, :external_label, :analyses, :purity,
:children_count, :parent_id, :imported_readout, :_contains_residues,
:sample_svg_file, :density, :boiling_point, :melting_point,
:reaction_description, :container, :pubchem_tag, :xref, :code_log,
:can_update, :can_publish
:can_update, :can_publish, :molecule_name_hash
]
end
......@@ -41,7 +41,7 @@ class DetailLevels::Sample
:real_amount_unit, :purity, :solvent, :molarity_value, :molarity_unit,
:children_count, :parent_id, :imported_readout, :location,
:boiling_point, :melting_point, :reaction_description,
:can_update, :can_publish
:can_update, :can_publish, :molecule_name_hash
]
end
......
......@@ -21,6 +21,8 @@ module SampleLevelListSerializable
[]
when :_contains_residues
false
when :molecule_name_hash
{}
else
nil
end
......
......@@ -34,6 +34,8 @@ module SampleLevelReportSerializable
false
when :container, :molecule_iupac_name, :get_svg_path
nil
when :molecule_name_hash
{}
else
'***'
end
......
......@@ -35,6 +35,8 @@ module SampleLevelSerializable
false
when :container
nil
when :molecule_name_hash
{}
else
'***'
end
......
class CreateMoleculeNames < ActiveRecord::Migration
def change
create_table :molecule_names do |t|
t.belongs_to :molecule, index: true
t.belongs_to :user, index: true
t.text :description
t.string :name, null: false, index: true
t.datetime :deleted_at, index: true
t.timestamps null: false
end
add_index :molecule_names, [:user_id, :molecule_id]
add_column :samples, :molecule_name_id, :integer
add_index :samples, :molecule_name_id
end
end
......@@ -372,6 +372,22 @@ ActiveRecord::Schema.define(version: 20170908105401) do
add_index "literatures", ["deleted_at"], name: "index_literatures_on_deleted_at", using: :btree
add_index "literatures", ["reaction_id"], name: "index_literatures_on_reaction_id", using: :btree
create_table "molecule_names", force: :cascade do |t|
t.integer "molecule_id"
t.integer "user_id"
t.text "description"
t.string "name", null: false
t.datetime "deleted_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "molecule_names", ["deleted_at"], name: "index_molecule_names_on_deleted_at", using: :btree
add_index "molecule_names", ["molecule_id"], name: "index_molecule_names_on_molecule_id", using: :btree
add_index "molecule_names", ["name"], name: "index_molecule_names_on_name", using: :btree
add_index "molecule_names", ["user_id", "molecule_id"], name: "index_molecule_names_on_user_id_and_molecule_id", using: :btree
add_index "molecule_names", ["user_id"], name: "index_molecule_names_on_user_id", using: :btree
create_table "molecules", force: :cascade do |t|
t.string "inchikey"
t.string "inchistring"
......@@ -579,11 +595,13 @@ ActiveRecord::Schema.define(version: 20170908105401) do
t.jsonb "xref", default: {}
t.float "molarity_value", default: 0.0
t.string "molarity_unit", default: "M"
t.integer "molecule_name_id"
end
add_index "samples", ["deleted_at"], name: "index_samples_on_deleted_at", using: :btree
add_index "samples", ["identifier"], name: "index_samples_on_identifier", using: :btree
add_index "samples", ["molecule_id"], name: "index_samples_on_sample_id", using: :btree
add_index "samples", ["molecule_name_id"], name: "index_samples_on_molecule_name_id", using: :btree
add_index "samples", ["user_id"], name: "index_samples_on_user_id", using: :btree
create_table "screens", force: :cascade do |t|
......
namespace :data do
desc 'create molecule_name'
task ver_20170906105933_molecule_name: :environment do
Molecule.find_each(&:create_molecule_names)
Sample.with_deleted.find_each do |s|
m = s.molecule
mns = m.molecule_names
origin = m.iupac_name || m.sum_formular
if origin && mns.present?
mn = mns.where(name: origin).first
s.update_columns(molecule_name_id: mn.id) if mn
else
Rails.logger.warn(
"Warning: No molecule_names for Sample id #{s.id}"
)
end
end
end
end
......@@ -80,5 +80,16 @@ M END"
.to eq ["123-456-789", "987-654-321", "558440-22-5"]
end
end
describe 'Get /api/v1/molecules/names' do
let!(:m) { create(:molecule) }
let!(:nn) { "this_is_a_new_name" }
it 'returns molecule_names hash' do
get "/api/v1/molecules/names?inchikey=#{m.inchikey}"
mns = JSON.parse(response.body)["molecules"].map { |m| m["name"] }
expect(mns).to include(m.sum_formular)
end
end
end
end
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment