Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LIMS-1188: Create web-based CSV importer #714

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
LIMS-1188: Create web-based CSV importer
  • Loading branch information
Mark Williams committed Jan 19, 2024
commit 8d56b5f0b33d0b063c18a929a35fee35a2975950
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -57,6 +57,7 @@ This file should be copied to create a `client/src/js/config.json` file and edit
| site_name | Site Name to display in footer |
| site_link | URL to site home page |
| data_catalogue | Object that includes name and url property for a link to a data catalogue - displayed on the landing page |
| csv_profile | The csv profile for importing shipments, currently diamond or imca, as client/src/js/csv/<csv_profile>.js |

Site Image can be customised via the tailwind.config.js header-site-logo and footer-site-logo values.

21 changes: 16 additions & 5 deletions api/src/Page/Sample.php
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ class Sample extends Page
'CRYSTALID' => '\d+',
'CONTAINERID' => '\d+',
'LOCATION' => '\d+',
'SUBLOCATION' => '\d+',
'CODE' => '(\w|\s|\-)+|^$', // Change validation to work for dashes as well as numbers
'ACRONYM' => '([\w\-])+',
'SEQUENCE' => '[\s\w\(\)\.>\|;\n]+',
@@ -102,7 +103,7 @@ class Sample extends Page

'EXPERIMENTKIND' => '[\w|\s]+',
'CENTRINGMETHOD' => '\w+',
'RADIATIONSENSITIVITY' => '\w+',
'RADIATIONSENSITIVITY' => '\d+(.\d+)?',
'USERPATH' => '(?=.{0,40}$)(\w|-)+\/?(\w|-)+', // Up to two folders as a path, 40 characters maximum
'EXPOSURETIME' => '\d+(.\d+)?',
'PREFERREDBEAMSIZEX' => '\d+(.\d+)?',
@@ -138,6 +139,7 @@ class Sample extends Page
'TYPE' => '\w+',
'BLSAMPLEGROUPSAMPLEID' => '\d+-\d+',
'PLANORDER' => '\d',
'SHIPPINGID' => '\d+',

'SAMPLEGROUPID' => '\d+',
'SCREENINGMETHOD' => '\w+',
@@ -146,6 +148,7 @@ class Sample extends Page
'INITIALSAMPLEGROUP' => '\d+',
'STRATEGYOPTION' => '',
'MINIMUMRESOLUTION' => '\d+(.\d+)?',
'OBSERVEDRESOLUTION' => '\d+(.\d+)?',
'groupSamplesType' => '.*' // query parameter to query sample groups by sample types. Should be comma separated values like so: groupSamplesType=container,capillary
);

@@ -1053,6 +1056,12 @@ function _samples()
array_push($args, $this->arg('BLSAMPLEGROUPID'));
}

# For a specific shipment
if ($this->has_arg('SHIPPINGID')) {
$where .= ' AND d.shippingid=:'.(sizeof($args)+1);
array_push($args, $this->arg('SHIPPINGID'));
}

# For a specific container
if ($this->has_arg('cid')) {
$where .= ' AND c.containerid=:' . (sizeof($args) + 1);
@@ -1455,13 +1464,15 @@ function _prepare_sample_args($s = null)
'COLOR',
'THEORETICALDENSITY',
'LOOPTYPE',
'SUBLOCATION',
'ENERGY',
'USERPATH',
'SCREENINGMETHOD',
'SCREENINGCOLLECTVALUE',
'SAMPLEGROUP',
'STRATEGYOPTION',
'MINIMUMRESOLUTION',
'OBSERVEDRESOLUTION',
'INITIALSAMPLEGROUP'
) as $f) {
if ($s)
@@ -1479,8 +1490,8 @@ function _do_add_sample($s)
$a = $this->_prepare_strategy_option_for_sample($s);

$this->db->pq(
"INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath, strategyoption, minimalresolution) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7, :8, :9) RETURNING diffractionplanid INTO :id",
array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['STRATEGYOPTION'], $a['MINIMUMRESOLUTION'])
"INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath, strategyoption, minimalresolution, observedresolution) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10) RETURNING diffractionplanid INTO :id",
array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['STRATEGYOPTION'], $a['MINIMUMRESOLUTION'], $a['OBSERVEDRESOLUTION'])
);
$did = $this->db->id();

@@ -1513,8 +1524,8 @@ function _do_add_sample($s)
}

$this->db->pq(
"INSERT INTO blsample (blsampleid,crystalid,diffractionplanid,containerid,location,comments,name,code,blsubsampleid,screencomponentgroupid,volume,packingfraction,dimension1,dimension2,dimension3,shape,looptype) VALUES (s_blsample.nextval,:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16) RETURNING blsampleid INTO :id",
array($crysid, $did, $a['CONTAINERID'], $a['LOCATION'], $a['COMMENTS'], $a['NAME'], $a['CODE'], $a['BLSUBSAMPLEID'], $a['SCREENCOMPONENTGROUPID'], $a['VOLUME'], $a['PACKINGFRACTION'], $a['DIMENSION1'], $a['DIMENSION2'], $a['DIMENSION3'], $a['SHAPE'], $a['LOOPTYPE'])
"INSERT INTO blsample (blsampleid,crystalid,diffractionplanid,containerid,location,comments,name,code,blsubsampleid,screencomponentgroupid,volume,packingfraction,dimension1,dimension2,dimension3,shape,looptype,sublocation) VALUES (s_blsample.nextval,:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:17) RETURNING blsampleid INTO :id",
array($crysid, $did, $a['CONTAINERID'], $a['LOCATION'], $a['COMMENTS'], $a['NAME'], $a['CODE'], $a['BLSUBSAMPLEID'], $a['SCREENCOMPONENTGROUPID'], $a['VOLUME'], $a['PACKINGFRACTION'], $a['DIMENSION1'], $a['DIMENSION2'], $a['DIMENSION3'], $a['SHAPE'], $a['LOOPTYPE'], $a['SUBLOCATION'])
);
$sid = $this->db->id();

2 changes: 1 addition & 1 deletion api/src/Page/Shipment.php
Original file line number Diff line number Diff line change
@@ -2247,7 +2247,7 @@ function _container_registry()
}

$rows = $this->db->paginate("SELECT r.containerregistryid, r.barcode, GROUP_CONCAT(distinct CONCAT(p.proposalcode,p.proposalnumber) SEPARATOR ', ') as proposals, count(distinct c.containerid) as instances, TO_CHAR(r.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp,
TO_CHAR(max(c.bltimestamp),'DD-MM-YYYY') as lastuse, max(CONCAT(p.proposalcode,p.proposalnumber)) as prop, r.comments, COUNT(distinct cr.containerreportid) as reports
TO_CHAR(max(c.bltimestamp),'DD-MM-YYYY') as lastuse, max(CONCAT(p.proposalcode,p.proposalnumber)) as prop, r.comments, COUNT(distinct cr.containerreportid) as reports, c.code as lastname
FROM containerregistry r
LEFT OUTER JOIN containerregistry_has_proposal rhp on rhp.containerregistryid = r.containerregistryid
LEFT OUTER JOIN proposal p ON p.proposalid = rhp.proposalid
15 changes: 15 additions & 0 deletions client/src/css/partials/_content.scss
Original file line number Diff line number Diff line change
@@ -2090,5 +2090,20 @@ ul.messages {
background: color(#00ff00 tint(80%));
}
}
}


.dropimage {
color: $content-search-background;
padding: 20px;
border: 2px dashed $content-search-background;
margin: 2% 0;
text-align: center;
border-radius: 5px;

&.active {
color: $content-header-color;
background: $content-dark-background;
text-decoration: italic;
}
}
3 changes: 3 additions & 0 deletions client/src/js/config_sample.json
Original file line number Diff line number Diff line change
@@ -20,6 +20,9 @@
"maintenance_message": "This is the maintenance message",
"maintenance": false,

"csv_profile": "diamond",
"csv_message": "This CSV uploader is in Beta mode, use at your own risk.",

"ga_ident": "",

"_data_catalogue_comment": " Remove the data_catalogue object if you don't want a link on the landing page",
60 changes: 60 additions & 0 deletions client/src/js/csv/diamond.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
define([], function() {

return {

// The csv column names
headers: ['Proposal Code', 'Proposal Number', 'Visit Number', 'Shipping Name', 'Dewar Code', 'Puck', 'preObsResolution', 'minimalResolution', 'Oscillation Range', 'Protein Acronym', 'Protein Name', 'Space Group',
'Barcode', 'Sample Name', 'Location', 'Comments', 'Cell A', 'Cell B', 'Cell C', 'Cell Alpha', 'Cell Beta', 'Cell Gamma', 'Sublocation', 'Loop Type', 'Required Resolution', 'Centring Method', 'Experiment Kind',
'Radiation Sensitivity', 'Energy', 'User Path', 'Screen and Collect Recipe', 'S&C N value', 'Sample Group'],

// ... and their ISPyB table mapping
mapping: ['PROPOSALCODE', 'PROPOSALNUMBER', 'VISITNUMBER', 'SHIPPINGNAME', 'FACILITYCODE', 'CONTAINER', 'OBSERVEDRESOLUTION', 'MINIMUMRESOLUTION', 'AXISRANGE', 'ACRONYM', 'PROTEINNAME', 'SPACEGROUP',
'CODE', 'NAME', 'LOCATION', 'COMMENTS', 'CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA', 'SUBLOCATION', 'LOOPTYPE', 'REQUIREDRESOLUTION', 'CENTRINGMETHOD', 'EXPERIMENTKIND',
'RADIATIONSENSITIVITY', 'ENERGY', 'USERPATH', 'SCREENINGMETHOD', 'SCREENINGCOLLECTVALUE', 'SAMPLEGROUPNAME'],

// Columns to show on the import page
columns: {
LOCATION: 'Location',
ACRONYM: 'Protein Acronym',
NAME: 'Name',
SAMPLEGROUPNAME: 'Sample Group',
CODE: 'Barcode',
COMMENTS: 'Comment',
USERPATH: 'User Path',
SPACEGROUP: 'Spacegroup',
CELL: 'Cell',
CENTRINGMETHOD: 'Centring Method',
EXPERIMENTKIND: 'Experiment Kind',
ENERGY: 'Energy (eV)',
SCREENINGMETHOD: 'Screening Method',
REQUIREDRESOLUTION: 'Required Res',
MINIMUMRESOLUTION: 'Minimum Res',
SCREENINGCOLLECTVALUE: 'Number to collect',
},

// Import transforms
transforms: {
SPACEGROUP: function(v, m) {
m.SPACEGROUP = v.replace(/[\(\)]/g, '').toUpperCase()

Check failure on line 38 in client/src/js/csv/diamond.js

GitHub Actions / JavaScript build, test and lint

Unnecessary escape character: \(

Check failure on line 38 in client/src/js/csv/diamond.js

GitHub Actions / JavaScript build, test and lint

Unnecessary escape character: \)
}
},

exampleCSV: `cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0221,TestInsulin-x00021,1,Z1992316315,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00022,2,Z1787158625,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00023,3,Z1275599911,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0765,TestInsulin-x00024,4,Z3201466300,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00025,5,Z8187272620,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E1412,TestInsulin-x00026,6,Z1454840342,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00027,7,Z1563512128,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00028,8,Z5567190000,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00029,9,Z1650868495,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0472,TestInsulin-x00030,10,Z1741785925,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0413,TestInsulin-x00031,11,Z2510259379,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0797,TestInsulin-x00032,12,Z2856434779,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00033,13,Z2856434839,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0888,TestInsulin-x00034,14,Z1432018343,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0553,TestInsulin-x00035,15,Z4884759400,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00036,16,Z1315161580,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry`
}

})
92 changes: 92 additions & 0 deletions client/src/js/csv/imca.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
define([], function() {

return {
// The csv column names
headers: ['Puck', 'Pin', 'Project', 'Priority', 'Mode', 'Notes to Staff', 'Collection strategy', 'Contact person', 'Expected space group', 'Expected Cell Dimensions', 'Expected Resolution', 'Minimum Resolution Required to Collect', 'Recipe', 'Exposure time', 'Image Width', 'Phi', 'Attenuation', 'Aperture', 'Detector Distance', 'Prefix for frames', 'Observed Resolution', 'Comments From Staff', 'Status'],

// ... and their ISPyB table mapping
mapping: ['CONTAINER', 'LOCATION', 'ACRONYM', 'PRIORITY', 'COLLECTIONMODE', 'COMMENTS', 'COMMENTS', 'OWNER', 'SPACEGROUP', 'CELL', 'AIMEDRESOLUTION', 'REQUIREDRESOLUTION', 'RECIPE', 'EXPOSURETIME', 'AXISRANGE', 'AXISROTATION', 'TRANSMISSION', 'PREFERREDBEAMSIZEX', 'DETECTORDISTANCE', 'PREFIX', 'DCRESOLUTION', 'STAFFCOMMENTS', 'STATUS'],

// Columns to show on the import page
columns: {
LOCATION: 'Location',
PROTEINID: 'Protein',
NAME: 'Sample',
PRIORITY: 'Priority',
COLLECTIONMODE: 'Mode',
COMMENTS: 'Comments',
SPACEGROUP: 'Spacegroup',
CELL: 'Cell',
AIMEDRESOLUTION: 'Aimed Res',
REQUIREDRESOLUTION: 'Required Res',
EXPOSURETIME: 'Exposure (s)',
AXISRANGE: 'Axis Osc',
NUMBEROFIMAGES: 'No. Images',
TRANSMISSION: 'Transmission',
PREFERREDBEAMSIZEX: 'Beamsize',
},

// Import transforms
transforms: {
CELL: function(v, m) {
var comps = v.split(/\s+/)
_.each(['CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA'], function(ax, i) {
if (comps.length > i) m[ax] = comps[i].replace(',', '')
})
},
AXISROTATION: function(v, m) {
if (m.AXISRANGE) m.NUMBEROFIMAGES = m.AXISROTATION / m.AXISRANGE
},
SPACEGROUP: function(v, m) {
m.SPACEGROUP = v.replace(/[\(\)]/g, '')

Check failure on line 41 in client/src/js/csv/imca.js

GitHub Actions / JavaScript build, test and lint

Unnecessary escape character: \(

Check failure on line 41 in client/src/js/csv/imca.js

GitHub Actions / JavaScript build, test and lint

Unnecessary escape character: \)
},
LOCATION: function(v, m) {
if (!this.xcount) this.xcount = 1
m.NAME = 'x'+(this.xcount++)
},
COLLECTIONMODE: function(v, m) {
m.COLLECTIONMODE = v.toLowerCase()
}
},

// Export transforms
export: {
CELL: function(m) {
return `${m.CELL_A}, ${m.CELL_B}, ${m.CELL_C}, ${m.CELL_ALPHA}, ${m.CELL_BETA}, ${m.CELL_GAMMA}`.trim()
},

STATUS: function(m) {
var status = 'skipped'
if (m.QUEUEDTIMESTAMP) status = 'queued';
if (m.R > 0) status = 'received'
if (m.DC > 0) status = 'collected'

return status
},

AXISROTATION: function(m) {
return m.AXISRANGE * m.NUMBEROFIMAGES
},

COMMENTS: function(m, h) {
var comments = m.COMMENTS.split(' | ')
return comments.length > 1 && h == 'Collection strategy' ? comments[1] : comments[0]
}
},

exampleCSV: `Puck,Pin,Project,Priority,Mode,Notes to Staff,Collection strategy,Contact person,Expected space group,Expected Cell Dimensions,Expected Resolution,Minimum Resolution Required to Collect,Recipe,Exposure time,Image Width,Phi,Attenuation,Aperture,Detector Distance,Prefix for frames,Observed Resolution,Comments From Staff,Status
Blue53,1,a,1,Manual,Tricky,Do best you can,Luke,C2,"143.734, 67.095, 76.899, 90, 110.45, 90",1.9-3.5,4,luke-360.rcp,,,,,,,,,,
Blue53,2,a,1,Manual,Very tricky,New crystals,Luke,C2,140 65 75 90 110 90,1.8-2.4,3.5,,0.1,0.25,,95,5,250,image_,,,
Blue53,2,a,1,Manual,Very tricky,New crystals,Luke,C2,140 65 75 90 110 90,1.8-2.4,3.5,,0.1,0.25,,95,5,250,image_,,,
Blue53,3,b,3,Auto,Routine,SeMet,Luke,P2,52.4 39.8 65.0 108.5,1.5,1.7,,0.04,0.25,360,,10,300,,,,
Blue53,4,c,3,Auto,Rods,Native,Luke,P21,39 69.2 60 90 105.3,1.5,1.7,,0.04,0.25,360,95,20,,image_,,,
Blue53,5,d,8,,Plates,,Luke,C222,280 45 112 102 90,1.5,1.7,,0.04,0.25,360,95,50,300,image_,,,
Blue54,1,e,,Auto,,,,P212121,67 82 276,2.1,2.5,,,0.25,180,,10,350,image_,,,
Blue54,2,e,4,,,,Luke,P2(1)2(1)2(1),67 82 276,,1.7,luke-180.rcp,,,,,,,,,,
Blue54,3,f,,Auto,,,,P222,,2.1,,,0.04,,180,95,,350,image_,,,
Blue54,4,g,4,Auto,,,Luke,,,2.1,2.5,,0.04,0.25,180,75,,350,image_,,,
Blue54,5,h,99,Auto,,,Luke,P222,,2.2,2.5,,0.04,0.25,180,95,,400,image_,,,
`
}

})
20 changes: 17 additions & 3 deletions client/src/js/models/sample.js
Original file line number Diff line number Diff line change
@@ -204,12 +204,26 @@ define(['backbone', 'collections/components',
pattern: 'twopath',
maxLength: 40,
},
ENERGY: {
required: false,
pattern: 'number',
},
SCREENINGMETHOD: {
required: false,
pattern: 'word'
oneOf: ['none', 'all', 'best']
},
SCREENINGCOLLECTVALUE: {
required: false,
required: function() {
return this.get('SCREENINGMETHOD') == 'best'
},
pattern: 'digits',
min: 1,
max: 5
},
MINIMUMRESOLUTION: {
required: function() {
return this.get('SCREENINGMETHOD') == 'all'
},
pattern: 'number'
},
SAMPLEGROUP: {
@@ -218,7 +232,7 @@ define(['backbone', 'collections/components',
},
EXPERIMENTKIND: {
required: false,
pattern: 'word'
pattern: 'wwsdash'
},

COMPONENTAMOUNTS: function(from_ui, attr, all_values) {
Loading
Loading