Skip to content

Commit 7d4ac60

Browse files
committed
fix: cellline_materials add index and created_by column #2353
Squashed commit of the following: commit 44b54e8 Author: Adrian Herrmann <adrian.herrmann@kit.edu> Date: Tue Mar 11 08:13:08 2025 +0000 review findings commit a427fc3 Author: Adrian Herrmann <adrian.herrmann@kit.edu> Date: Fri Feb 28 09:28:10 2025 +0000 refactor: rubocop & typo commit 5d32e53 Author: Adrian Herrmann <adrian.herrmann@kit.edu> Date: Wed Feb 26 14:46:10 2025 +0000 feat: added column created_by and set access commit faf855e Author: Adrian Herrmann <adrian.herrmann@kit.edu> Date: Wed Feb 26 10:04:43 2025 +0000 fix: tests because of index commit 03b432c Author: Adrian Herrmann <adrian.herrmann@kit.edu> Date: Fri Feb 21 11:37:14 2025 +0000 added index on (name, source) in cellline_materials
1 parent eb501eb commit 7d4ac60

File tree

14 files changed

+100
-48
lines changed

14 files changed

+100
-48
lines changed

app/javascript/src/apps/mydb/elements/details/cellLines/propertiesTab/CellLineName.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default class CellLineName extends React.Component {
1818

1919
componentDidMount() {
2020
CellLinesFetcher.getAllCellLineNames()
21-
.then((data) => data.map((x) => ({ value: x.id, label: x.name, name: x.name })))
21+
.then((data) => data.map((x) => ({ value: x.id, label: `${x.name} - ${x.source}`, name: x.name })))
2222
.then((data) => {
2323
this.setState({ nameSuggestions: data });
2424
});

app/javascript/src/apps/mydb/elements/details/cellLines/propertiesTab/GeneralProperties.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,28 @@ import PropTypes from 'prop-types';
99
import CellLineName from 'src/apps/mydb/elements/details/cellLines/propertiesTab/CellLineName';
1010
import Amount from 'src/apps/mydb/elements/details/cellLines/propertiesTab/Amount';
1111
import InvalidPropertyWarning from 'src/apps/mydb/elements/details/cellLines/propertiesTab/InvalidPropertyWarning';
12+
import UserStore from 'src/stores/alt/stores/UserStore';
1213

1314
class GeneralProperties extends React.Component {
1415
// eslint-disable-next-line react/static-property-placement
1516
static contextType = StoreContext;
1617

18+
checkPermission(attributeName) {
19+
const readonlyAttributes = [
20+
'Disease', 'Organism', 'Tissue', 'Growth medium', 'Mutation', 'Variant', 'Biosafety level',
21+
'Cryopreservation medium', 'Opt. growth temperature', 'Gender', 'Cell type', 'Material Description'
22+
];
23+
const { item } = this.props;
24+
const { currentUser } = UserStore.getState();
25+
if (item.created_by == null) {
26+
const { cellLineDetailsStore } = this.context;
27+
const cellLine = cellLineDetailsStore.cellLines(item.id);
28+
return readonlyAttributes.includes(attributeName)
29+
&& cellLine.created_by !== '' && cellLine.created_by !== currentUser.id.toString();
30+
}
31+
return readonlyAttributes.includes(attributeName) && item.created_by !== currentUser.id;
32+
}
33+
1734
renderOptionalAttribute(attributeName, defaultValue, onChangeCallBack) {
1835
return this.renderAttribute(attributeName, defaultValue, onChangeCallBack, true);
1936
}
@@ -43,7 +60,7 @@ class GeneralProperties extends React.Component {
4360
<Form.Label column sm={3}>{attributeName}</Form.Label>
4461
<Col sm={9}>
4562
<Form.Control
46-
disabled={readOnly}
63+
disabled={readOnly || this.checkPermission(attributeName)}
4764
className={styleClass}
4865
type="text"
4966
value={defaultValue}
@@ -69,10 +86,10 @@ class GeneralProperties extends React.Component {
6986
<Form.Label column sm={3}>Biosafety level</Form.Label>
7087
<Col sm={9}>
7188
<Select
72-
isDisabled={readOnly}
89+
isDisabled={readOnly || this.checkPermission('Biosafety level')}
7390
options={options}
7491
isClearable={false}
75-
value={options.find(({value}) => value === item.bioSafetyLevel)}
92+
value={options.find(({ value }) => value === item.bioSafetyLevel)}
7693
onChange={(e) => { cellLineDetailsStore.changeBioSafetyLevel(item.id, e.value); }}
7794
/>
7895
</Col>
@@ -117,7 +134,7 @@ class GeneralProperties extends React.Component {
117134
<Amount
118135
cellLineId={item.id}
119136
initialValue={item.amount}
120-
readOnly={readOnly}
137+
readOnly={readOnly || this.checkPermission('Amount')}
121138
/>
122139
</Col>
123140
<Col sm={3} className="amount-unit">
@@ -187,7 +204,7 @@ class GeneralProperties extends React.Component {
187204
{this.renderOptionalAttribute('Cell type', cellLineItem.cellType, (e) => {
188205
cellLineDetailsStore.changeCellType(cellLineId, e.target.value);
189206
})}
190-
{this.renderOptionalAttribute('Description', cellLineItem.materialDescription, (e) => {
207+
{this.renderOptionalAttribute('Material Description', cellLineItem.materialDescription, (e) => {
191208
cellLineDetailsStore.changeMaterialDescription(cellLineId, e.target.value);
192209
})}
193210
</Accordion.Body>
@@ -209,7 +226,7 @@ class GeneralProperties extends React.Component {
209226
{this.renderOptionalAttribute('Name of specific probe', cellLineItem.itemName, (e) => {
210227
cellLineDetailsStore.changeItemName(cellLineId, e.target.value);
211228
})}
212-
{this.renderOptionalAttribute('Description', cellLineItem.itemDescription, (e) => {
229+
{this.renderOptionalAttribute('Sample Description', cellLineItem.itemDescription, (e) => {
213230
cellLineDetailsStore.changeItemDescription(cellLineId, e.target.value);
214231
})}
215232
</Accordion.Body>
@@ -224,6 +241,8 @@ export default observer(GeneralProperties);
224241
GeneralProperties.propTypes = {
225242
readOnly: PropTypes.bool.isRequired,
226243
item: PropTypes.shape({
227-
id: PropTypes.string.isRequired
244+
id: PropTypes.string.isRequired,
245+
can_update: PropTypes.bool,
246+
created_by: PropTypes.number
228247
}).isRequired
229248
};

app/javascript/src/models/cellLine/CellLine.js

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default class CellLine extends Element {
4545
cellLine.cellType = response.cellline_material.cell_type;
4646
cellLine.bioSafetyLevel = response.cellline_material.biosafety_level;
4747
cellLine.cryopreservationMedium = response.cellline_material.cryo_pres_medium;
48+
cellLine.created_by = response.cellline_material.created_by;
4849
cellLine.is_new = false;
4950

5051
cellLine.container = response.container;
@@ -67,6 +68,7 @@ export default class CellLine extends Element {
6768
this.gender = cellLineItem.gender;
6869
this.materialDescription = cellLineItem.materialDescription;
6970
this.source = cellLineItem.source;
71+
this.created_by = cellLineItem.created_by;
7072
}
7173

7274
adoptPropsFromMobXModel(mobx) {
@@ -91,5 +93,6 @@ export default class CellLine extends Element {
9193
this.bioSafetyLevel = mobx.bioSafetyLevel;
9294
this.cellType = mobx.cellType;
9395
this.cryopreservationMedium = mobx.cryopreservationMedium;
96+
this.created_by = mobx.created_by;
9497
}
9598
}

app/javascript/src/stores/mobx/CellLineDetailsStore.jsx

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const CellLineItem = types
4444
growthMedium: '',
4545
itemDescription: '',
4646
itemName: '',
47+
created_by: '',
4748
changed: false
4849
}).views((self) => ({
4950
isAmountValid() {
@@ -165,6 +166,7 @@ export const CellLineDetailsStore = types
165166
item.mutation = properties.mutation;
166167
item.source = properties.source;
167168
item.growthMedium = properties.growth_medium;
169+
item.created_by = properties.created_by?.toString();
168170
},
169171
convertJsModelToMobxModel(container) {
170172
return CellLineAnalysis.create({
@@ -203,6 +205,7 @@ export const CellLineDetailsStore = types
203205
growthMedium: jsCellLineModel.growthMedium,
204206
itemName: jsCellLineModel.itemName,
205207
shortLabel: jsCellLineModel.short_label,
208+
created_by: jsCellLineModel.created_by?.toString(),
206209
}));
207210
}
208211
}))

app/models/cellline_material.rb

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# deleted_at :datetime
2323
# created_at :datetime not null
2424
# updated_at :datetime not null
25+
# created_by :integer
2526
#
2627
class CelllineMaterial < ApplicationRecord
2728
acts_as_paranoid

app/usecases/cell_lines/create.rb

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def create_cellline_material
4545
cryo_pres_medium: @params[:cryo_pres_medium],
4646
gender: @params[:gender],
4747
description: @params[:material_description],
48+
created_by: @current_user.id,
4849
)
4950
end
5051

app/usecases/cell_lines/update.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ def execute!
1616

1717
@cell_line_sample.cellline_material = find_material || create_new_material
1818
update_sample_properties
19-
update_material_properties(@cell_line_sample.cellline_material)
19+
if @cell_line_sample.cellline_material.created_by.nil? ||
20+
@cell_line_sample.cellline_material.created_by == @current_user.id
21+
update_material_properties(@cell_line_sample.cellline_material)
22+
end
2023
@cell_line_sample.save
2124
@cell_line_sample
2225
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
class AddColumnToCelllineMaterial < ActiveRecord::Migration[6.1]
4+
def change
5+
add_index :cellline_materials, %i[name source], unique: true
6+
add_column :cellline_materials, :created_by, :integer, null: true
7+
end
8+
end

db/schema.rb

+2
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@
132132
t.datetime "deleted_at"
133133
t.datetime "created_at", precision: 6, null: false
134134
t.datetime "updated_at", precision: 6, null: false
135+
t.integer "created_by"
136+
t.index ["name", "source"], name: "index_cellline_materials_on_name_and_source", unique: true
135137
end
136138

137139
create_table "cellline_samples", force: :cascade do |t|

spec/api/chemotion/cell_line_api_spec.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
describe 'GET /api/v1/cell_lines/' do
1111
let(:collection) { create(:collection) }
1212
let!(:user) { create(:user, collections: [collection]) }
13-
let!(:cell_line) { create(:cellline_sample, collections: [collection]) }
13+
let(:material) { create(:cellline_material) }
14+
let!(:cell_line) { create(:cellline_sample, collections: [collection], cellline_material: material) }
1415
let!(:cell_line2) do
1516
create(:cellline_sample,
1617
collections: [collection],
18+
cellline_material: material,
1719
created_at: DateTime.parse('2000-01-01'),
1820
updated_at: DateTime.parse('2010-01-01'))
1921
end

spec/api/chemotion/suggestion_api_spec.rb

+25-22
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
describe Chemotion::SuggestionAPI do
77
let!(:user) { create(:person, first_name: 'tam', last_name: 'M') }
8+
let(:material) { create(:cellline_material) }
89
let(:collection) { create(:collection, user: user, is_shared: true, permission_level: 1, sample_detail_level: 10) }
910
let(:query) { 'query' }
10-
let(:json_repsonse) { JSON.parse(response.body) }
11-
let(:json_response) { response.body }
11+
let(:json_response) { JSON.parse(response.body) }
1212
let(:params) do
1313
{
1414
collection_id: collection.id,
@@ -19,9 +19,11 @@
1919

2020
describe 'GET /api/v1/cell_lines/suggestions/cell_lines' do
2121
include_context 'api request authorization context'
22-
let!(:cell_line) { create(:cellline_sample, collections: [collection]) }
23-
let!(:cell_line2) { create(:cellline_sample, name: 'search-example', collections: [collection]) }
24-
let!(:cell_line_without_col) { create(:cellline_sample, name: 'search-example') }
22+
let!(:cell_line) { create(:cellline_sample, collections: [collection], cellline_material: material) }
23+
let!(:cell_line2) do
24+
create(:cellline_sample, name: 'search-example', collections: [collection], cellline_material: material)
25+
end
26+
let!(:cell_line_without_col) { create(:cellline_sample, name: 'search-example', cellline_material: material) }
2527
let!(:sample) { create(:sample, name: 'search-example', collections: [collection]) }
2628

2729
before do
@@ -36,9 +38,9 @@
3638
end
3739

3840
it 'suggestions should be returned' do
39-
expect(json_repsonse['suggestions'].length).to be 1
40-
expect(json_repsonse['suggestions'].first['name']).to eq 'name-001'
41-
expect(json_repsonse['suggestions'].first['search_by_method']).to eq 'cell_line_material_name'
41+
expect(json_response['suggestions'].length).to be 1
42+
expect(json_response['suggestions'].first['name']).to eq 'name-001'
43+
expect(json_response['suggestions'].first['search_by_method']).to eq 'cell_line_material_name'
4244
end
4345
end
4446

@@ -50,17 +52,19 @@
5052
end
5153

5254
it 'suggestions should be returned' do
53-
expect(json_repsonse['suggestions'].length).to be 1
54-
expect(json_repsonse['suggestions'].first['name']).to eq 'search-example'
55-
expect(json_repsonse['suggestions'].first['search_by_method']).to eq 'cell_line_sample_name'
55+
expect(json_response['suggestions'].length).to be 1
56+
expect(json_response['suggestions'].first['name']).to eq 'search-example'
57+
expect(json_response['suggestions'].first['search_by_method']).to eq 'cell_line_sample_name'
5658
end
5759
end
5860
end
5961

6062
describe 'GET /api/v1/cell_lines/suggestions/all' do
6163
include_context 'api request authorization context'
62-
let!(:cell_line) { create(:cellline_sample, collections: [collection]) }
63-
let!(:cell_line2) { create(:cellline_sample, name: 'search-example', collections: [collection]) }
64+
let!(:cell_line) { create(:cellline_sample, collections: [collection], cellline_material: material) }
65+
let!(:cell_line2) do
66+
create(:cellline_sample, name: 'search-example', collections: [collection], cellline_material: material)
67+
end
6468
let!(:sample) { create(:sample, name: 'search-example', collections: [collection]) }
6569

6670
before do
@@ -75,9 +79,9 @@
7579
end
7680

7781
it 'suggestions should be returned' do
78-
expect(json_repsonse['suggestions'].length).to be 1
79-
expect(json_repsonse['suggestions'].first['name']).to eq 'name-001'
80-
expect(json_repsonse['suggestions'].first['search_by_method']).to eq 'cell_line_material_name'
82+
expect(json_response['suggestions'].length).to be 1
83+
expect(json_response['suggestions'].first['name']).to eq 'name-001'
84+
expect(json_response['suggestions'].first['search_by_method']).to eq 'cell_line_material_name'
8185
end
8286
end
8387

@@ -89,17 +93,17 @@
8993
end
9094

9195
it 'two suggestions were found' do
92-
expect(json_repsonse['suggestions'].length).to be 2
96+
expect(json_response['suggestions'].length).to be 2
9397
end
9498

9599
it 'first suggestion from sample' do
96-
expect(json_repsonse['suggestions'].first['name']).to eq 'search-example'
97-
expect(json_repsonse['suggestions'].first['search_by_method']).to eq 'sample_name'
100+
expect(json_response['suggestions'].first['name']).to eq 'search-example'
101+
expect(json_response['suggestions'].first['search_by_method']).to eq 'sample_name'
98102
end
99103

100104
it 'second suggestion from cell line' do
101-
expect(json_repsonse['suggestions'].second['name']).to eq 'search-example'
102-
expect(json_repsonse['suggestions'].second['search_by_method']).to eq 'cell_line_sample_name'
105+
expect(json_response['suggestions'].second['name']).to eq 'search-example'
106+
expect(json_response['suggestions'].second['search_by_method']).to eq 'cell_line_sample_name'
103107
end
104108
end
105109
end
@@ -114,7 +118,6 @@
114118
is_sync: false,
115119
}
116120
expect(response).to have_http_status(:success)
117-
json_response = JSON.parse(response.body)
118121
expect(json_response.keys).to contain_exactly('suggestions')
119122
suggestions = json_response['suggestions']
120123
expect(suggestions).to be_an(Array)

spec/api/collection_api_spec.rb

+13-10
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
create(:sync_collections_user, collection_id: c_sync_w.id, user_id: user.id, permission_level: 1,
4949
shared_by_id: owner.id, fake_ancestry: c_sync_ancestry.id.to_s)
5050
end
51+
let(:material) { create(:cellline_material) }
5152

5253
before do
5354
allow_any_instance_of(WardenAuthentication).to receive(:current_user).and_return(user)
@@ -184,7 +185,7 @@
184185
JSON.parse(response.body)['collections'].map do |root|
185186
root['children'].pluck('id')
186187
end.flatten,
187-
).to match_array [c4.id, c6.id]
188+
).to contain_exactly(c4.id, c6.id)
188189
end
189190
end
190191
end
@@ -303,9 +304,9 @@
303304
# c_target.reload2
304305
# end
305306
describe 'PUT /api/v1/collections/elements' do
306-
let!(:cell_line_1) { create(:cellline_sample, collections: [c_source]) }
307-
let!(:cell_line_2) { create(:cellline_sample, collections: [c_source]) }
308-
let!(:cell_line_3) { create(:cellline_sample, collections: [c_source]) }
307+
let!(:cell_line_1) { create(:cellline_sample, collections: [c_source], cellline_material: material) }
308+
let!(:cell_line_2) { create(:cellline_sample, collections: [c_source], cellline_material: material) }
309+
let!(:cell_line_3) { create(:cellline_sample, collections: [c_source], cellline_material: material) }
309310
let(:cell_line_ids) { [] }
310311
let!(:ui_state) do
311312
{
@@ -354,7 +355,9 @@
354355
end
355356

356357
context 'when try to move cell line element into collection where it already exists' do
357-
let!(:cellline_in_two_colls) { create(:cellline_sample, collections: [c_source, c_target]) }
358+
let!(:cellline_in_two_colls) do
359+
create(:cellline_sample, collections: [c_source, c_target], cellline_material: material)
360+
end
358361
let(:target_collection_id) { c_target.id }
359362
let(:cell_line_ids) { [cellline_in_two_colls.id] }
360363

@@ -392,7 +395,7 @@
392395
end
393396

394397
describe 'POST /api/v1/collections/elements' do
395-
let!(:cell_line_sample) { create(:cellline_sample, collections: [c_source]) }
398+
let!(:cell_line_sample) { create(:cellline_sample, collections: [c_source], cellline_material: material) }
396399
let!(:ui_state) do
397400
{
398401
cell_line: {
@@ -686,10 +689,10 @@
686689
c = Collection.where(is_shared: true, user_id: u2.id, shared_by_id: user.id)
687690
.where("label LIKE 'My project with%'").first
688691
expect(c).not_to be_nil
689-
expect(c.samples).to match_array [s1, s3]
690-
expect(c.reactions).to match_array [r1]
691-
expect(c.wellplates).to match_array [w1]
692-
expect(c.screens).to match_array []
692+
expect(c.samples).to contain_exactly(s1, s3)
693+
expect(c.reactions).to contain_exactly(r1)
694+
expect(c.wellplates).to contain_exactly(w1)
695+
expect(c.screens).to be_empty
693696
end
694697
end
695698

0 commit comments

Comments
 (0)