Merge branch 'features/team-automations' into develop

This commit is contained in:
Martin Artnik 2025-09-08 10:35:19 +02:00
commit 7c4041cad1
71 changed files with 915 additions and 1788 deletions

File diff suppressed because it is too large Load diff

View file

@ -11,8 +11,6 @@
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:00.553Z",
"updated_at": "2019-02-07T14:56:54.966Z",
"uuid": "1a3d39cf-ea51-48a8-b622-d7a9492fd1e7"
},
"my_modules": [
@ -25,8 +23,6 @@
"x": 0,
"y": 0,
"my_module_group_id": 1182,
"created_at": "2018-11-08T10:28:01.009Z",
"updated_at": "2018-12-19T11:33:27.356Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -70,13 +66,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:01.077Z",
"updated_at": "2019-02-20T07:40:35.070Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -92,8 +85,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:01.386Z",
"updated_at": "2018-12-21T13:40:38.295Z",
"last_modified_by_id": 202,
"protocol_id": 3391
},
@ -120,8 +111,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-15T13:25:17.662Z",
"updated_at": "2019-02-20T07:41:30.052Z",
"last_modified_by_id": 202,
"protocol_id": 3391
},
@ -132,8 +121,6 @@
{
"asset": {
"id": 3756,
"created_at": "2019-02-20T07:40:34.833Z",
"updated_at": "2019-02-20T07:41:30.043Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 15033,
@ -165,8 +152,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-15T12:25:48.867Z",
"updated_at": "2019-02-20T07:40:29.591Z",
"last_modified_by_id": 202,
"protocol_id": 3391
},
@ -177,8 +162,6 @@
{
"asset": {
"id": 3755,
"created_at": "2019-02-20T07:40:21.348Z",
"updated_at": "2019-02-20T07:40:29.583Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 15690,
@ -218,8 +201,6 @@
"x": 35,
"y": 0,
"my_module_group_id": 1182,
"created_at": "2018-11-08T10:28:01.476Z",
"updated_at": "2018-12-19T11:33:27.359Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -263,13 +244,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:01.537Z",
"updated_at": "2019-02-20T07:53:32.436Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -285,8 +263,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-15T14:54:15.366Z",
"updated_at": "2019-02-20T07:53:32.379Z",
"last_modified_by_id": 202,
"protocol_id": 3393
},
@ -306,8 +282,6 @@
{
"table": {
"id": 579,
"created_at": "2018-11-15T14:54:15.369Z",
"updated_at": "2019-02-20T07:53:32.362Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Mixture",
@ -327,8 +301,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-15T14:38:34.247Z",
"updated_at": "2019-02-20T07:53:25.630Z",
"last_modified_by_id": 202,
"protocol_id": 3393
},
@ -355,8 +327,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-15T14:00:37.365Z",
"updated_at": "2019-02-07T13:01:07.241Z",
"last_modified_by_id": 202,
"protocol_id": 3393
},
@ -376,8 +346,6 @@
{
"table": {
"id": 577,
"created_at": "2018-11-15T14:16:48.703Z",
"updated_at": "2019-02-07T13:01:07.192Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Restriction digest",
@ -390,8 +358,6 @@
{
"table": {
"id": 677,
"created_at": "2018-12-19T10:48:05.316Z",
"updated_at": "2019-02-07T13:01:07.218Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Ligation",
@ -411,8 +377,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-15T13:57:43.072Z",
"updated_at": "2019-02-19T14:53:00.254Z",
"last_modified_by_id": 202,
"protocol_id": 3393
},
@ -447,8 +411,6 @@
"x": 69,
"y": 0,
"my_module_group_id": 1182,
"created_at": "2018-11-16T10:48:03.813Z",
"updated_at": "2018-12-19T11:33:27.361Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -492,13 +454,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-16T10:48:03.821Z",
"updated_at": "2019-02-19T14:55:34.883Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -514,8 +473,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-16T12:34:00.600Z",
"updated_at": "2019-02-19T14:54:57.484Z",
"last_modified_by_id": 202,
"protocol_id": 3583
},
@ -542,8 +499,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-16T11:50:40.125Z",
"updated_at": "2019-02-19T14:54:39.448Z",
"last_modified_by_id": 202,
"protocol_id": 3583
},
@ -563,8 +518,6 @@
{
"table": {
"id": 583,
"created_at": "2018-11-16T12:13:30.368Z",
"updated_at": "2019-02-19T14:54:39.422Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Mixture",
@ -584,8 +537,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-16T12:53:26.057Z",
"updated_at": "2019-02-19T14:55:34.834Z",
"last_modified_by_id": 202,
"protocol_id": 3583
},
@ -612,8 +563,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-16T10:52:10.517Z",
"updated_at": "2019-02-19T14:53:39.075Z",
"last_modified_by_id": 202,
"protocol_id": 3583
},
@ -633,8 +582,6 @@
{
"table": {
"id": 582,
"created_at": "2018-11-16T10:52:10.520Z",
"updated_at": "2019-02-19T14:53:39.061Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Digestion mixture",
@ -654,8 +601,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-16T12:46:27.019Z",
"updated_at": "2019-02-19T14:55:10.602Z",
"last_modified_by_id": 202,
"protocol_id": 3583
},
@ -675,8 +620,6 @@
{
"table": {
"id": 584,
"created_at": "2018-11-16T12:46:27.024Z",
"updated_at": "2019-02-19T14:55:10.586Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Table 1",
@ -704,8 +647,6 @@
"x": 69,
"y": 18,
"my_module_group_id": 1182,
"created_at": "2018-11-08T10:28:01.704Z",
"updated_at": "2018-12-19T11:33:27.364Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -749,13 +690,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:01.760Z",
"updated_at": "2019-02-20T07:53:56.919Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -771,8 +709,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:01.867Z",
"updated_at": "2019-02-19T14:56:03.660Z",
"last_modified_by_id": 202,
"protocol_id": 3395
},
@ -799,8 +735,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-16T13:23:32.534Z",
"updated_at": "2019-02-20T07:53:56.784Z",
"last_modified_by_id": 202,
"protocol_id": 3395
},
@ -820,8 +754,6 @@
{
"table": {
"id": 585,
"created_at": "2018-11-16T14:34:51.901Z",
"updated_at": "2019-02-20T07:53:56.770Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "PCR mixture",
@ -837,8 +769,6 @@
"id": 971,
"name": "Guideline:",
"step_id": 4626,
"created_at": "2018-12-21T14:16:54.698Z",
"updated_at": "2018-12-21T14:16:54.698Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -848,8 +778,6 @@
"text": "In order to verify colony, pick half of a colony with a pipette tip and suspend cells in a 1.5 mL microcentrifuge tube containing 100 μL ddH2O.",
"checked": false,
"checklist_id": 971,
"created_at": "2018-12-21T14:16:54.700Z",
"updated_at": "2018-12-21T14:16:54.700Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -859,8 +787,6 @@
"text": "Heat to 98 °C for 10 min and use 2 μL as DNA template for the PCR.",
"checked": false,
"checklist_id": 971,
"created_at": "2018-12-21T14:16:54.707Z",
"updated_at": "2018-12-21T14:16:54.707Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -870,8 +796,6 @@
"text": "Streak the other half of the colony on a fresh YEP agar plate supplemented with Rif/Gent/Kan.",
"checked": false,
"checklist_id": 971,
"created_at": "2018-12-21T14:16:54.715Z",
"updated_at": "2018-12-21T14:16:54.715Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -881,8 +805,6 @@
"text": "Grow cells at 30 °C for one day and then store at 4 °C.",
"checked": false,
"checklist_id": 971,
"created_at": "2018-12-21T14:16:54.721Z",
"updated_at": "2018-12-21T14:16:54.721Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -901,8 +823,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-16T14:41:33.494Z",
"updated_at": "2019-02-20T07:41:30.530Z",
"last_modified_by_id": 202,
"protocol_id": 3395
},
@ -913,8 +833,6 @@
{
"asset": {
"id": 3757,
"created_at": "2019-02-20T07:40:57.546Z",
"updated_at": "2019-02-20T07:41:30.518Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 18526,
@ -954,8 +872,6 @@
"x": 35,
"y": 18,
"my_module_group_id": 1182,
"created_at": "2018-11-08T10:28:02.085Z",
"updated_at": "2018-12-19T11:33:27.366Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -995,13 +911,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:02.138Z",
"updated_at": "2019-02-20T07:41:16.226Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -1017,8 +930,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-19T11:28:44.151Z",
"updated_at": "2018-12-19T11:29:48.670Z",
"last_modified_by_id": 202,
"protocol_id": 3399
},
@ -1045,8 +956,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:02.155Z",
"updated_at": "2019-02-20T07:41:31.063Z",
"last_modified_by_id": 202,
"protocol_id": 3399
},
@ -1057,8 +966,6 @@
{
"asset": {
"id": 3758,
"created_at": "2019-02-20T07:41:15.986Z",
"updated_at": "2019-02-20T07:41:31.054Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 35622,
@ -1093,10 +1000,8 @@
"my_module_groups": [
{
"id": 1182,
"created_at": "2018-12-19T11:33:27.354Z",
"updated_at": "2018-12-19T11:33:27.354Z",
"created_by_id": 202,
"experiment_id": 422
}
]
}
}

View file

@ -11,8 +11,6 @@
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:48.389Z",
"updated_at": "2018-12-21T13:26:16.207Z",
"uuid": "37198411-2ad0-4198-b680-47f3b455ce0d"
},
"my_modules": [
@ -25,8 +23,6 @@
"x": 72,
"y": 0,
"my_module_group_id": 1190,
"created_at": "2018-11-08T10:28:49.322Z",
"updated_at": "2018-12-21T13:25:20.391Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -70,13 +66,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:49.390Z",
"updated_at": "2019-02-20T07:39:44.494Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -92,8 +85,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:49.408Z",
"updated_at": "2018-12-21T13:31:50.804Z",
"last_modified_by_id": 202,
"protocol_id": 3407
},
@ -120,8 +111,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:49.496Z",
"updated_at": "2019-02-07T10:46:31.811Z",
"last_modified_by_id": 202,
"protocol_id": 3407
},
@ -141,8 +130,6 @@
{
"table": {
"id": 555,
"created_at": "2018-11-08T10:28:49.522Z",
"updated_at": "2019-02-07T10:46:31.797Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Separation options",
@ -162,8 +149,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-19T07:24:14.116Z",
"updated_at": "2019-02-20T07:40:29.217Z",
"last_modified_by_id": 202,
"protocol_id": 3407
},
@ -174,8 +159,6 @@
{
"asset": {
"id": 3754,
"created_at": "2019-02-20T07:39:44.265Z",
"updated_at": "2019-02-20T07:40:29.203Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 12331,
@ -215,8 +198,6 @@
"x": 36,
"y": 0,
"my_module_group_id": 1190,
"created_at": "2018-11-08T10:28:48.971Z",
"updated_at": "2018-12-21T13:25:20.382Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -260,13 +241,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:49.025Z",
"updated_at": "2019-02-19T14:49:30.738Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -282,8 +260,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:49.261Z",
"updated_at": "2018-12-24T09:19:05.983Z",
"last_modified_by_id": 202,
"protocol_id": 3405
},
@ -306,8 +282,6 @@
"id": 968,
"name": "Guideline:",
"step_id": 4286,
"created_at": "2018-12-21T13:27:09.411Z",
"updated_at": "2018-12-21T13:27:09.411Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -317,8 +291,6 @@
"text": "Equilibrate all materials to the temperature at which the separation will be performed.",
"checked": false,
"checklist_id": 968,
"created_at": "2018-12-21T13:27:09.413Z",
"updated_at": "2018-12-21T13:27:09.413Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -328,8 +300,6 @@
"text": "Eliminate air by flushing column end pieces with buffer. Ensure no air is trapped under the column net. Close column outlet leaving 12 cm of the buffer in the column.",
"checked": false,
"checklist_id": 968,
"created_at": "2018-12-21T13:27:09.421Z",
"updated_at": "2018-12-21T13:27:09.421Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -339,8 +309,6 @@
"text": "Gently resuspend the medium. For media not supplied in suspension, use a medium: buffer ratio of approximately 1:2 to produce a suspension for mixing during rehydration. Avoid using magnetic stirrers since they may damage the matrix.",
"checked": false,
"checklist_id": 968,
"created_at": "2018-12-21T13:27:09.427Z",
"updated_at": "2018-12-21T13:27:09.427Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -359,8 +327,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:49.069Z",
"updated_at": "2018-12-21T13:29:08.411Z",
"last_modified_by_id": 202,
"protocol_id": 3405
},
@ -387,8 +353,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:49.179Z",
"updated_at": "2019-02-19T14:49:30.685Z",
"last_modified_by_id": 202,
"protocol_id": 3405
},
@ -423,8 +387,6 @@
"x": 0,
"y": 0,
"my_module_group_id": 1190,
"created_at": "2018-11-08T10:28:48.686Z",
"updated_at": "2018-12-21T13:25:20.385Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -468,13 +430,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:48.743Z",
"updated_at": "2019-02-20T07:39:23.923Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -490,8 +449,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-08T10:28:48.761Z",
"updated_at": "2019-02-20T07:39:28.712Z",
"last_modified_by_id": 202,
"protocol_id": 3403
},
@ -502,8 +459,6 @@
{
"asset": {
"id": 3753,
"created_at": "2019-02-20T07:39:23.642Z",
"updated_at": "2019-02-20T07:39:28.703Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 29576,
@ -528,8 +483,6 @@
{
"table": {
"id": 554,
"created_at": "2018-11-08T10:28:48.787Z",
"updated_at": "2018-12-20T13:33:54.646Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Table 1",
@ -549,8 +502,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-18T14:10:46.319Z",
"updated_at": "2018-12-24T09:16:18.589Z",
"last_modified_by_id": 202,
"protocol_id": 3403
},
@ -573,8 +524,6 @@
"id": 934,
"name": "Before use:",
"step_id": 5209,
"created_at": "2018-12-18T14:10:46.321Z",
"updated_at": "2018-12-18T14:10:46.321Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -584,8 +533,6 @@
"text": "Check the inside of the centrifuge and the rotors to ensure that everything is dry.",
"checked": false,
"checklist_id": 934,
"created_at": "2018-12-18T14:10:46.323Z",
"updated_at": "2018-12-21T12:47:12.263Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -595,8 +542,6 @@
"text": "Check that shock-absorbing pads are in the bottom of the centrifuge buckets.",
"checked": false,
"checklist_id": 934,
"created_at": "2018-12-18T14:10:46.331Z",
"updated_at": "2018-12-18T14:10:46.331Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -606,8 +551,6 @@
"text": "Balance the opposing buckets by weighing them with their tubes on an open two-pan balance.",
"checked": false,
"checklist_id": 934,
"created_at": "2018-12-18T14:10:46.337Z",
"updated_at": "2018-12-18T14:10:46.337Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -617,8 +560,6 @@
"text": "Never fill centrifuge tubes to more than three-quarters capacity.",
"checked": false,
"checklist_id": 934,
"created_at": "2018-12-18T14:10:46.344Z",
"updated_at": "2018-12-18T14:10:46.344Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -628,8 +569,6 @@
"text": "Symmetrically distribute balanced tubes in opposing buckets. Always operate the centrifuge with all buckets in place, even if two opposing buckets are empty.",
"checked": false,
"checklist_id": 934,
"created_at": "2018-12-18T14:10:46.354Z",
"updated_at": "2018-12-18T14:10:46.354Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -648,8 +587,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-18T14:12:05.603Z",
"updated_at": "2019-02-07T10:41:20.533Z",
"last_modified_by_id": 202,
"protocol_id": 3403
},
@ -684,8 +621,6 @@
"x": 105,
"y": 0,
"my_module_group_id": 1190,
"created_at": "2018-11-08T10:28:49.665Z",
"updated_at": "2018-12-21T13:25:20.388Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -725,13 +660,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-08T10:28:49.719Z",
"updated_at": "2018-12-24T09:22:25.677Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -747,8 +679,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-19T08:41:05.893Z",
"updated_at": "2018-12-24T09:22:25.629Z",
"last_modified_by_id": 202,
"protocol_id": 3409
},
@ -771,8 +701,6 @@
"id": 970,
"name": "Guideline:",
"step_id": 5226,
"created_at": "2018-12-21T13:36:26.307Z",
"updated_at": "2018-12-21T13:36:26.307Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -782,8 +710,6 @@
"text": "Add 2 µL of 6X SDS loading buffer to 510 µL of supernatant from crude extracts, cell lysates or purified fractions as appropriate.",
"checked": false,
"checklist_id": 970,
"created_at": "2018-12-21T13:36:26.308Z",
"updated_at": "2018-12-21T13:36:26.308Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -793,8 +719,6 @@
"text": "Vortex briefly and heat for 5 minutes at +90 to +100 °C.",
"checked": false,
"checklist_id": 970,
"created_at": "2018-12-21T13:36:26.316Z",
"updated_at": "2018-12-21T13:36:26.316Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -804,8 +728,6 @@
"text": "Load the samples onto an SDS-polyacrylamide gel.",
"checked": false,
"checklist_id": 970,
"created_at": "2018-12-21T13:36:26.323Z",
"updated_at": "2018-12-21T13:36:26.323Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -815,8 +737,6 @@
"text": "Run the gel and stain with Coomassie Blue or silver.",
"checked": false,
"checklist_id": 970,
"created_at": "2018-12-21T13:36:26.329Z",
"updated_at": "2018-12-21T13:36:26.329Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -835,8 +755,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-19T09:32:37.616Z",
"updated_at": "2018-12-21T13:38:06.144Z",
"last_modified_by_id": 202,
"protocol_id": 3409
},
@ -856,8 +774,6 @@
{
"table": {
"id": 676,
"created_at": "2018-12-19T09:32:37.619Z",
"updated_at": "2018-12-21T13:38:06.147Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Commonly used mass spectrometers and their characteristics",
@ -877,8 +793,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-19T08:02:56.820Z",
"updated_at": "2018-12-24T09:20:53.287Z",
"last_modified_by_id": 202,
"protocol_id": 3409
},
@ -901,8 +815,6 @@
"id": 969,
"name": "Guideline:",
"step_id": 4643,
"created_at": "2018-12-21T13:35:09.660Z",
"updated_at": "2018-12-21T13:35:09.660Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -912,8 +824,6 @@
"text": "Prepare five to eight dilutions of BSA standard with a range of 5 to 100 µg protein.",
"checked": false,
"checklist_id": 969,
"created_at": "2018-12-21T13:35:09.662Z",
"updated_at": "2018-12-21T13:35:09.662Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -923,8 +833,6 @@
"text": "Dilute protein samples to obtain 5-100 µg protein/30 µL.",
"checked": false,
"checklist_id": 969,
"created_at": "2018-12-21T13:35:09.669Z",
"updated_at": "2018-12-21T13:35:09.669Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -934,8 +842,6 @@
"text": "Set two blank tubes. For the standard curve, add 30 µL of water instead of the standard solution. For the unknown protein samples, add 30 µL protein preparation buffer instead. Protein solutions are normally assayed in duplicate or triplicate.",
"checked": false,
"checklist_id": 969,
"created_at": "2018-12-21T13:35:09.675Z",
"updated_at": "2018-12-21T13:35:09.675Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -945,8 +851,6 @@
"text": "Add 1.5 mL of Bradford reagent to each tube and mix well.",
"checked": false,
"checklist_id": 969,
"created_at": "2018-12-21T13:35:09.682Z",
"updated_at": "2018-12-21T13:35:09.682Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -956,8 +860,6 @@
"text": "Incubate at room temperature for at least 5 min.",
"checked": false,
"checklist_id": 969,
"created_at": "2018-12-21T13:35:09.688Z",
"updated_at": "2018-12-21T13:35:09.688Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -967,8 +869,6 @@
"text": "Absorbance will increase over time; samples should incubate at RT for no more than 1 h.",
"checked": false,
"checklist_id": 969,
"created_at": "2018-12-21T13:35:09.703Z",
"updated_at": "2018-12-21T13:35:09.703Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 5
@ -978,8 +878,6 @@
"text": "Measure absorbance at 595 nm.",
"checked": false,
"checklist_id": 969,
"created_at": "2018-12-21T13:35:09.710Z",
"updated_at": "2018-12-21T13:35:09.710Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 6
@ -1001,10 +899,8 @@
"my_module_groups": [
{
"id": 1190,
"created_at": "2018-12-21T13:25:20.378Z",
"updated_at": "2018-12-21T13:25:20.378Z",
"created_by_id": 202,
"experiment_id": 423
}
]
}
}

View file

@ -11,8 +11,6 @@
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T08:38:03.789Z",
"updated_at": "2019-02-07T14:55:25.517Z",
"uuid": "f2949887-1f9e-4eb8-b645-ebbdd8808caf"
},
"my_modules": [
@ -25,8 +23,6 @@
"x": 106,
"y": 26,
"my_module_group_id": null,
"created_at": "2018-11-09T14:43:02.935Z",
"updated_at": "2018-11-09T14:43:31.822Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -66,13 +62,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T14:43:02.943Z",
"updated_at": "2019-02-07T13:04:03.800Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -88,8 +81,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T14:44:17.342Z",
"updated_at": "2019-02-07T13:04:03.745Z",
"last_modified_by_id": 202,
"protocol_id": 3461
},
@ -109,8 +100,6 @@
{
"table": {
"id": 600,
"created_at": "2018-11-22T09:34:27.270Z",
"updated_at": "2019-02-07T13:04:03.738Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Table 1",
@ -138,8 +127,6 @@
"x": 41,
"y": 0,
"my_module_group_id": null,
"created_at": "2018-11-09T13:28:22.716Z",
"updated_at": "2018-12-19T13:28:00.727Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -179,13 +166,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T13:28:22.724Z",
"updated_at": "2019-02-19T14:40:36.402Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -201,8 +185,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T13:29:23.180Z",
"updated_at": "2018-12-21T12:05:41.321Z",
"last_modified_by_id": 202,
"protocol_id": 3455
},
@ -225,8 +207,6 @@
"id": 967,
"name": "Guidelines:",
"step_id": 4375,
"created_at": "2018-12-21T12:05:41.323Z",
"updated_at": "2018-12-21T12:05:41.323Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -236,8 +216,6 @@
"text": "Dilute this suspension 1:100 by pipetting 10 µL into sterile capped Eppendorf tubes containing 990 µL nutrient-rich broth or sterile saline solution (10-2)",
"checked": false,
"checklist_id": 967,
"created_at": "2018-12-21T12:05:41.325Z",
"updated_at": "2018-12-21T12:05:41.325Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -247,8 +225,6 @@
"text": "Dilute this solution sequentially 1:10 three times until you reach a dilution of 105.",
"checked": false,
"checklist_id": 967,
"created_at": "2018-12-21T12:05:41.338Z",
"updated_at": "2018-12-21T12:05:41.338Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -258,8 +234,6 @@
"text": "Plate 100 mL of the last two 1:10 dilutions (104 to 105) evenly onto antibiotic-free nutrient-rich agar plates using a sterile cell spreader.",
"checked": false,
"checklist_id": 967,
"created_at": "2018-12-21T12:05:41.345Z",
"updated_at": "2018-12-21T12:05:41.345Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -278,8 +252,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T13:44:18.697Z",
"updated_at": "2019-02-07T10:32:30.551Z",
"last_modified_by_id": 202,
"protocol_id": 3455
},
@ -306,8 +278,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T13:41:55.993Z",
"updated_at": "2019-02-19T14:40:36.350Z",
"last_modified_by_id": 202,
"protocol_id": 3455
},
@ -334,8 +304,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T13:46:56.839Z",
"updated_at": "2019-02-07T10:33:29.603Z",
"last_modified_by_id": 202,
"protocol_id": 3455
},
@ -370,8 +338,6 @@
"x": 70,
"y": 26,
"my_module_group_id": 1185,
"created_at": "2018-11-09T14:35:06.102Z",
"updated_at": "2018-12-19T13:28:00.795Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -411,13 +377,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T14:35:06.105Z",
"updated_at": "2019-02-20T07:38:52.274Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -433,8 +396,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T14:42:15.692Z",
"updated_at": "2019-02-20T07:39:28.160Z",
"last_modified_by_id": 202,
"protocol_id": 3460
},
@ -445,8 +406,6 @@
{
"asset": {
"id": 3752,
"created_at": "2019-02-20T07:38:52.075Z",
"updated_at": "2019-02-20T07:39:28.152Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 62584,
@ -486,8 +445,6 @@
"x": 34,
"y": 26,
"my_module_group_id": 1185,
"created_at": "2018-11-09T14:20:10.098Z",
"updated_at": "2018-12-19T13:28:00.798Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -531,13 +488,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T14:20:10.107Z",
"updated_at": "2019-02-20T07:38:33.613Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -553,8 +507,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T14:27:56.093Z",
"updated_at": "2019-02-20T07:39:27.591Z",
"last_modified_by_id": 202,
"protocol_id": 3458
},
@ -565,8 +517,6 @@
{
"asset": {
"id": 3751,
"created_at": "2019-02-20T07:38:33.374Z",
"updated_at": "2019-02-20T07:39:27.582Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 12769,
@ -598,8 +548,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T14:06:59.583Z",
"updated_at": "2019-02-19T14:45:31.219Z",
"last_modified_by_id": 202,
"protocol_id": 3458
},
@ -634,8 +582,6 @@
"x": 0,
"y": 35,
"my_module_group_id": 1185,
"created_at": "2018-11-09T14:09:52.226Z",
"updated_at": "2018-12-19T13:28:00.800Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -679,13 +625,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T14:09:52.235Z",
"updated_at": "2019-02-19T14:41:27.828Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -701,8 +644,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T14:16:11.568Z",
"updated_at": "2018-12-24T09:07:19.921Z",
"last_modified_by_id": 202,
"protocol_id": 3457
},
@ -722,8 +663,6 @@
{
"table": {
"id": 683,
"created_at": "2018-12-21T11:38:45.382Z",
"updated_at": "2018-12-21T11:48:45.258Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Table 1",
@ -739,8 +678,6 @@
"id": 965,
"name": "Guideline:",
"step_id": 4382,
"created_at": "2018-12-21T11:38:45.338Z",
"updated_at": "2018-12-21T11:38:45.338Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -750,8 +687,6 @@
"text": "Prepare antibiotic dilutions in sterile MHB in sterile test tubes according to table.",
"checked": false,
"checklist_id": 965,
"created_at": "2018-12-21T11:38:45.340Z",
"updated_at": "2018-12-21T11:38:45.340Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -761,8 +696,6 @@
"text": "As the antibiotic solution is later inoculated with an equal amount of bacteria in broth, the dilutions are prepared at a concentration twice the desired final concentration.",
"checked": false,
"checklist_id": 965,
"created_at": "2018-12-21T11:38:45.347Z",
"updated_at": "2018-12-21T11:38:45.347Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -772,8 +705,6 @@
"text": "Start by dispensing sterile broth into twelve sterile 13x100 mm tubes closed with metal caps. A single 10 mL pipette can be used to pipette the 9 mL and 3 mL volumes of broth into the respective tubes. Use a single 1 mL pipette for pipetting the 1 mL of broth in stages 2, 5, 8 and 11.",
"checked": false,
"checklist_id": 965,
"created_at": "2018-12-21T11:38:45.354Z",
"updated_at": "2018-12-21T11:38:45.354Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -783,8 +714,6 @@
"text": "It is possible to use the same pipette for pipetting 1 mL of the antibiotic stock solution into the first test tube. Mix thoroughly using a vortex mixer.",
"checked": false,
"checklist_id": 965,
"created_at": "2018-12-21T11:38:45.361Z",
"updated_at": "2018-12-21T11:38:45.361Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -794,8 +723,6 @@
"text": "Use separate pipettes/pipette tips when preparing each of the other antibiotic solutions. Mix thoroughly using a vortex mixer.",
"checked": false,
"checklist_id": 965,
"created_at": "2018-12-21T11:38:45.371Z",
"updated_at": "2018-12-21T11:38:45.371Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -814,8 +741,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T14:17:30.554Z",
"updated_at": "2019-02-19T14:41:27.778Z",
"last_modified_by_id": 202,
"protocol_id": 3457
},
@ -850,8 +775,6 @@
"x": 0,
"y": 50,
"my_module_group_id": 1185,
"created_at": "2018-11-09T10:47:34.880Z",
"updated_at": "2018-12-19T13:28:00.803Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -895,13 +818,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T10:47:34.945Z",
"updated_at": "2019-02-19T14:43:41.236Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -917,8 +837,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T12:10:09.672Z",
"updated_at": "2019-02-19T14:43:41.186Z",
"last_modified_by_id": 202,
"protocol_id": 3453
},
@ -945,8 +863,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T12:22:03.852Z",
"updated_at": "2018-12-24T09:08:20.489Z",
"last_modified_by_id": 202,
"protocol_id": 3453
},
@ -966,8 +882,6 @@
{
"table": {
"id": 682,
"created_at": "2018-12-20T13:07:47.021Z",
"updated_at": "2018-12-24T09:08:20.452Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Table 1",
@ -983,8 +897,6 @@
"id": 966,
"name": "Guidelines:",
"step_id": 4370,
"created_at": "2018-12-21T11:52:37.302Z",
"updated_at": "2018-12-21T11:52:37.302Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -994,8 +906,6 @@
"text": "Dispense appropriate amounts of antibiotic solution into the respective containers. Follow table steps.",
"checked": false,
"checklist_id": 966,
"created_at": "2018-12-21T11:52:37.304Z",
"updated_at": "2018-12-21T11:52:37.304Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -1005,8 +915,6 @@
"text": "For each agar plate, add 25 mL agar (now at a temperature of 50°C) into the container, mix well (avoid bubbles) and pour 25 ml into a petri dish labelled with the respective antibiotic concentration.",
"checked": false,
"checklist_id": 966,
"created_at": "2018-12-21T11:52:37.311Z",
"updated_at": "2018-12-21T11:52:37.311Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -1016,8 +924,6 @@
"text": "Pour a control agar plate without any antibiotic. Adjust the number if necessary.",
"checked": false,
"checklist_id": 966,
"created_at": "2018-12-21T11:52:37.317Z",
"updated_at": "2018-12-21T11:52:37.317Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -1027,8 +933,6 @@
"text": "Allow agar to set.",
"checked": false,
"checklist_id": 966,
"created_at": "2018-12-21T11:52:37.324Z",
"updated_at": "2018-12-21T11:52:37.324Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -1038,8 +942,6 @@
"text": "Dry the surface of the agar plates either in an incubator or in a laminar airflow hood for 30 min. Leave the lid ajar.",
"checked": false,
"checklist_id": 966,
"created_at": "2018-12-21T11:52:37.330Z",
"updated_at": "2018-12-21T11:52:37.330Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -1049,8 +951,6 @@
"text": "Mark the bottom of the agar plates to define an orientation.",
"checked": false,
"checklist_id": 966,
"created_at": "2018-12-21T11:52:37.336Z",
"updated_at": "2018-12-21T11:52:37.336Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 5
@ -1069,8 +969,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T14:00:24.509Z",
"updated_at": "2019-02-19T14:42:34.757Z",
"last_modified_by_id": 202,
"protocol_id": 3453
},
@ -1097,8 +995,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T11:07:28.842Z",
"updated_at": "2019-02-19T14:43:10.090Z",
"last_modified_by_id": 202,
"protocol_id": 3453
},
@ -1118,8 +1014,6 @@
{
"table": {
"id": 681,
"created_at": "2018-12-20T13:03:47.611Z",
"updated_at": "2019-02-19T14:43:10.075Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Table 2",
@ -1147,8 +1041,6 @@
"x": 0,
"y": 0,
"my_module_group_id": 1185,
"created_at": "2018-11-09T09:15:09.287Z",
"updated_at": "2018-12-19T13:28:00.805Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -1192,13 +1084,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T09:15:09.296Z",
"updated_at": "2019-02-20T07:38:05.434Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -1214,8 +1103,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T14:34:48.930Z",
"updated_at": "2018-12-17T14:34:48.930Z",
"last_modified_by_id": 202,
"protocol_id": 3450
},
@ -1242,8 +1129,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T09:26:34.638Z",
"updated_at": "2018-12-24T08:59:10.687Z",
"last_modified_by_id": 202,
"protocol_id": 3450
},
@ -1266,8 +1151,6 @@
"id": 962,
"name": "Guidelines",
"step_id": 4357,
"created_at": "2018-12-21T11:09:29.266Z",
"updated_at": "2018-12-21T11:09:29.266Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -1277,8 +1160,6 @@
"text": "For each isolate, select three to five morphologically similar colonies from the fresh agar plate from task",
"checked": false,
"checklist_id": 962,
"created_at": "2018-12-21T11:09:29.268Z",
"updated_at": "2018-12-21T11:09:29.268Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -1288,8 +1169,6 @@
"text": "Plate preparation",
"checked": false,
"checklist_id": 962,
"created_at": "2018-12-21T11:09:29.276Z",
"updated_at": "2018-12-21T11:09:29.276Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -1299,8 +1178,6 @@
"text": "Touch the top of each selected colony using a sterile loop or cotton swab.",
"checked": false,
"checklist_id": 962,
"created_at": "2018-12-21T11:09:29.287Z",
"updated_at": "2018-12-21T11:09:29.287Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -1310,8 +1187,6 @@
"text": "Transfer the growth into a sterile capped glass tube containing sterile broth or saline solution.",
"checked": false,
"checklist_id": 962,
"created_at": "2018-12-21T11:09:29.300Z",
"updated_at": "2019-02-07T09:10:36.760Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -1321,8 +1196,6 @@
"text": "Mix using a vortex mixer.",
"checked": false,
"checklist_id": 962,
"created_at": "2018-12-21T11:09:29.307Z",
"updated_at": "2018-12-21T11:09:29.307Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -1341,8 +1214,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T13:07:04.214Z",
"updated_at": "2019-02-20T07:38:27.031Z",
"last_modified_by_id": 202,
"protocol_id": 3450
},
@ -1353,8 +1224,6 @@
{
"asset": {
"id": 3750,
"created_at": "2019-02-20T07:38:05.206Z",
"updated_at": "2019-02-20T07:38:27.023Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 48768,
@ -1382,8 +1251,6 @@
"id": 961,
"name": "Guidelines:",
"step_id": 5195,
"created_at": "2018-12-21T10:50:36.576Z",
"updated_at": "2018-12-21T10:50:36.576Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -1393,8 +1260,6 @@
"text": "Prepare media (store at 4°C)",
"checked": false,
"checklist_id": 961,
"created_at": "2018-12-21T10:50:36.577Z",
"updated_at": "2018-12-21T10:50:36.577Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -1404,8 +1269,6 @@
"text": "Prepare antibiotic stock solutions",
"checked": false,
"checklist_id": 961,
"created_at": "2018-12-21T10:50:36.584Z",
"updated_at": "2018-12-21T10:50:36.584Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -1415,8 +1278,6 @@
"text": "Streak the bacterial isolates to be tested onto nutrient-rich agar plates without inhibitor to obtain single colonies",
"checked": false,
"checklist_id": 961,
"created_at": "2018-12-21T10:50:36.592Z",
"updated_at": "2018-12-21T10:50:36.592Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -1435,8 +1296,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T10:11:24.463Z",
"updated_at": "2019-02-07T10:31:21.634Z",
"last_modified_by_id": 202,
"protocol_id": 3450
},
@ -1463,8 +1322,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T10:13:32.396Z",
"updated_at": "2019-02-07T10:31:41.571Z",
"last_modified_by_id": 202,
"protocol_id": 3450
},
@ -1499,8 +1356,6 @@
"x": 0,
"y": 17,
"my_module_group_id": 1185,
"created_at": "2018-11-09T10:25:56.551Z",
"updated_at": "2018-12-19T13:28:00.808Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -1544,13 +1399,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-09T10:25:56.621Z",
"updated_at": "2019-02-20T07:37:48.403Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -1566,8 +1418,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T14:33:52.646Z",
"updated_at": "2018-12-17T14:33:52.646Z",
"last_modified_by_id": 202,
"protocol_id": 3452
},
@ -1594,8 +1444,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T10:25:56.635Z",
"updated_at": "2019-02-19T14:44:57.268Z",
"last_modified_by_id": 202,
"protocol_id": 3452
},
@ -1622,8 +1470,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T10:25:56.680Z",
"updated_at": "2018-12-24T09:01:33.668Z",
"last_modified_by_id": 202,
"protocol_id": 3452
},
@ -1646,8 +1492,6 @@
"id": 964,
"name": "Guidelines:",
"step_id": 4362,
"created_at": "2018-12-21T11:14:19.776Z",
"updated_at": "2018-12-21T11:14:19.776Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -1657,8 +1501,6 @@
"text": "For each isolate, select three to five morphologically similar colonies from the fresh agar plate from task .",
"checked": false,
"checklist_id": 964,
"created_at": "2018-12-21T11:14:19.778Z",
"updated_at": "2018-12-21T11:14:19.778Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -1668,8 +1510,6 @@
"text": "Plate preparation.",
"checked": false,
"checklist_id": 964,
"created_at": "2018-12-21T11:14:19.785Z",
"updated_at": "2018-12-21T11:14:19.785Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -1679,8 +1519,6 @@
"text": "Touch the top of each selected colony using a sterile loop or cotton swab.",
"checked": false,
"checklist_id": 964,
"created_at": "2018-12-21T11:14:19.791Z",
"updated_at": "2018-12-21T11:14:19.791Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -1690,8 +1528,6 @@
"text": "Transfer into a tube containing 34 mL of a suitable nutrient-rich medium.",
"checked": false,
"checklist_id": 964,
"created_at": "2018-12-21T11:14:19.798Z",
"updated_at": "2018-12-21T11:14:19.798Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -1701,8 +1537,6 @@
"text": "Mix using a vortex mixer.",
"checked": false,
"checklist_id": 964,
"created_at": "2018-12-21T11:14:19.805Z",
"updated_at": "2018-12-21T11:14:19.805Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -1721,8 +1555,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T10:25:56.700Z",
"updated_at": "2019-02-20T07:38:26.679Z",
"last_modified_by_id": 202,
"protocol_id": 3452
},
@ -1733,8 +1565,6 @@
{
"asset": {
"id": 3749,
"created_at": "2019-02-20T07:37:48.184Z",
"updated_at": "2019-02-20T07:38:26.671Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 48768,
@ -1762,8 +1592,6 @@
"id": 963,
"name": "Guidelines:",
"step_id": 4363,
"created_at": "2018-12-21T11:12:20.820Z",
"updated_at": "2018-12-21T11:12:20.820Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -1773,8 +1601,6 @@
"text": "Prepare media (store at 4°C).",
"checked": false,
"checklist_id": 963,
"created_at": "2018-12-21T11:12:20.822Z",
"updated_at": "2018-12-21T11:12:20.822Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -1784,8 +1610,6 @@
"text": "Prepare antibiotic stock solutions.",
"checked": false,
"checklist_id": 963,
"created_at": "2018-12-21T11:12:20.831Z",
"updated_at": "2018-12-21T11:12:20.831Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -1795,8 +1619,6 @@
"text": "Streak the bacterial isolates to be tested onto nutrient-rich agar plates without inhibitor to obtain single colonies",
"checked": false,
"checklist_id": 963,
"created_at": "2018-12-21T11:12:20.838Z",
"updated_at": "2018-12-21T11:12:20.838Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -1815,8 +1637,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T10:35:37.405Z",
"updated_at": "2019-02-19T14:43:59.634Z",
"last_modified_by_id": 202,
"protocol_id": 3452
},
@ -1843,8 +1663,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-09T10:25:56.657Z",
"updated_at": "2019-02-19T14:44:41.586Z",
"last_modified_by_id": 202,
"protocol_id": 3452
},
@ -1874,10 +1692,8 @@
"my_module_groups": [
{
"id": 1185,
"created_at": "2018-12-19T13:28:00.792Z",
"updated_at": "2018-12-19T13:28:00.792Z",
"created_by_id": 3,
"experiment_id": 430
}
]
}
}

View file

@ -11,8 +11,6 @@
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-12T08:12:09.669Z",
"updated_at": "2019-02-07T14:44:00.337Z",
"uuid": "de7f0d1b-f842-440b-a367-a0333a11430c"
},
"my_modules": [
@ -25,8 +23,6 @@
"x": 34,
"y": 0,
"my_module_group_id": 1167,
"created_at": "2018-11-12T08:39:44.221Z",
"updated_at": "2018-12-17T07:51:25.145Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -70,13 +66,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-12T08:39:44.225Z",
"updated_at": "2019-02-20T07:58:51.770Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -92,8 +85,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T07:34:16.845Z",
"updated_at": "2019-02-19T14:35:53.498Z",
"last_modified_by_id": 202,
"protocol_id": 3488
},
@ -113,8 +104,6 @@
{
"table": {
"id": 668,
"created_at": "2018-12-17T07:35:02.552Z",
"updated_at": "2019-02-19T14:35:53.484Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "PCR Mastermix",
@ -134,8 +123,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T14:41:18.099Z",
"updated_at": "2019-02-20T07:37:25.765Z",
"last_modified_by_id": 202,
"protocol_id": 3488
},
@ -146,8 +133,6 @@
{
"asset": {
"id": 3746,
"created_at": "2019-02-20T07:36:46.773Z",
"updated_at": "2019-02-20T07:37:25.383Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 19765,
@ -164,8 +149,6 @@
{
"asset": {
"id": 3747,
"created_at": "2019-02-20T07:36:46.946Z",
"updated_at": "2019-02-20T07:37:25.757Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 16540,
@ -193,8 +176,6 @@
"id": 960,
"name": "Guidelines:",
"step_id": 4422,
"created_at": "2018-12-21T10:22:55.963Z",
"updated_at": "2018-12-21T10:22:55.963Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -204,8 +185,6 @@
"text": "Size selection (100 to 800 bp) by agarose gel electrophoresis.",
"checked": false,
"checklist_id": 960,
"created_at": "2018-12-21T10:22:55.965Z",
"updated_at": "2018-12-21T10:22:55.965Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -215,8 +194,6 @@
"text": "Gel purification, end repair, dA overhang addition, P2 paired-end adapter ligation and library amplification.",
"checked": false,
"checklist_id": 960,
"created_at": "2018-12-21T10:22:55.972Z",
"updated_at": "2018-12-21T10:22:55.972Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -226,8 +203,6 @@
"text": "120 μL of each amplified library is size-selected (about 250 to 500 bp) by gel electrophoresis.",
"checked": false,
"checklist_id": 960,
"created_at": "2018-12-21T10:22:55.981Z",
"updated_at": "2018-12-21T10:22:55.981Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -237,8 +212,6 @@
"text": "Final libraries are put through quality control and high-throughput sequencing.",
"checked": false,
"checklist_id": 960,
"created_at": "2018-12-21T10:22:55.989Z",
"updated_at": "2018-12-21T10:23:18.774Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -257,8 +230,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T14:34:29.463Z",
"updated_at": "2019-02-20T07:58:51.732Z",
"last_modified_by_id": 202,
"protocol_id": 3488
},
@ -269,8 +240,6 @@
{
"asset": {
"id": 3745,
"created_at": "2019-02-20T07:36:16.089Z",
"updated_at": "2019-02-20T07:58:51.655Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"estimated_size": 16298,
@ -310,8 +279,6 @@
"x": 68,
"y": 0,
"my_module_group_id": 1167,
"created_at": "2018-11-12T08:39:44.243Z",
"updated_at": "2018-12-17T07:51:25.147Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -355,13 +322,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-12T08:39:44.247Z",
"updated_at": "2018-12-21T10:26:41.366Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -377,8 +341,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T14:59:18.147Z",
"updated_at": "2018-12-21T10:25:44.853Z",
"last_modified_by_id": 202,
"protocol_id": 3489
},
@ -405,8 +367,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-17T07:50:46.898Z",
"updated_at": "2018-12-21T10:26:41.233Z",
"last_modified_by_id": 202,
"protocol_id": 3489
},
@ -441,8 +401,6 @@
"x": 102,
"y": 0,
"my_module_group_id": 1167,
"created_at": "2018-11-12T08:39:44.312Z",
"updated_at": "2018-12-17T07:51:25.150Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -482,13 +440,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-12T08:39:44.325Z",
"updated_at": "2019-02-20T07:37:09.688Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -504,8 +459,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T15:22:18.606Z",
"updated_at": "2018-11-12T15:22:18.606Z",
"last_modified_by_id": 202,
"protocol_id": 3492
},
@ -532,8 +485,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T15:20:06.636Z",
"updated_at": "2019-02-20T07:37:26.227Z",
"last_modified_by_id": 202,
"protocol_id": 3492
},
@ -544,8 +495,6 @@
{
"asset": {
"id": 3748,
"created_at": "2019-02-20T07:37:09.362Z",
"updated_at": "2019-02-20T07:37:26.219Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 24074,
@ -570,8 +519,6 @@
{
"table": {
"id": 573,
"created_at": "2018-11-12T15:20:07.148Z",
"updated_at": "2019-02-20T07:37:09.513Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "Reaction mastermix",
@ -599,8 +546,6 @@
"x": 0,
"y": 0,
"my_module_group_id": 1167,
"created_at": "2018-11-12T08:39:44.191Z",
"updated_at": "2018-12-17T07:51:25.152Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -644,13 +589,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-12T08:39:44.202Z",
"updated_at": "2019-02-19T14:34:47.554Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -666,8 +608,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T12:00:57.856Z",
"updated_at": "2018-12-24T08:46:24.129Z",
"last_modified_by_id": 202,
"protocol_id": 3487
},
@ -690,8 +630,6 @@
"id": 958,
"name": "Guidelines:",
"step_id": 4416,
"created_at": "2018-12-21T09:50:59.139Z",
"updated_at": "2018-12-21T09:50:59.139Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -701,8 +639,6 @@
"text": "Add 1 volume of chloroform: Isoamyl alcohol to the solution and mix by inversion for 5 min. Centrifuge the sample for 10 min at 5000 × g and pipette the upper aqueous phase into a new Falcon tube.",
"checked": false,
"checklist_id": 958,
"created_at": "2018-12-21T09:50:59.141Z",
"updated_at": "2018-12-21T09:50:59.141Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -712,8 +648,6 @@
"text": "Add 5 μL of RNAse A to the solution and incubate at 37°C for 15 min with periodic, gentle mixing.",
"checked": false,
"checklist_id": 958,
"created_at": "2018-12-21T09:50:59.149Z",
"updated_at": "2018-12-21T09:50:59.149Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -723,8 +657,6 @@
"text": "After incubation, add 1 volume of chloroform: Isoamyl alcohol to the solution and mix by inversion for 5 min.",
"checked": false,
"checklist_id": 958,
"created_at": "2018-12-21T09:50:59.156Z",
"updated_at": "2018-12-21T09:50:59.156Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -734,8 +666,6 @@
"text": "Centrifuge the solution for 10 min at 5000 × g and pipette the aqueous phase into a new Falcon tube.",
"checked": false,
"checklist_id": 958,
"created_at": "2018-12-21T09:50:59.163Z",
"updated_at": "2018-12-21T09:50:59.163Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -754,8 +684,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T12:22:47.725Z",
"updated_at": "2019-02-19T14:34:47.503Z",
"last_modified_by_id": 202,
"protocol_id": 3487
},
@ -782,8 +710,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T11:06:48.312Z",
"updated_at": "2018-12-24T08:34:20.404Z",
"last_modified_by_id": 202,
"protocol_id": 3487
},
@ -806,8 +732,6 @@
"id": 957,
"name": "Guidelines",
"step_id": 4414,
"created_at": "2018-12-21T09:43:55.386Z",
"updated_at": "2018-12-21T09:43:55.386Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -817,8 +741,6 @@
"text": "Samples are stored in 95% ethanol solution at -20°C.",
"checked": false,
"checklist_id": 957,
"created_at": "2018-12-21T09:43:55.389Z",
"updated_at": "2018-12-21T09:43:55.389Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -828,8 +750,6 @@
"text": "Pre-heat water baths (65°C and 37°C).",
"checked": false,
"checklist_id": 957,
"created_at": "2018-12-21T09:43:55.396Z",
"updated_at": "2018-12-21T09:43:55.396Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -839,8 +759,6 @@
"text": "Prepare 10 mL (per 1 g of sample) extraction buffer by adding 0.3% (v/v) β-mercaptoethanol in a 50 mL Falcon tube, and pre-heat in the 65°C water bath.",
"checked": false,
"checklist_id": 957,
"created_at": "2018-12-21T09:43:55.403Z",
"updated_at": "2018-12-21T09:43:55.403Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -850,8 +768,6 @@
"text": "Put the sample into the 65°C water bath and mix by inversion every 10 min for 30-60 minutes.",
"checked": false,
"checklist_id": 957,
"created_at": "2018-12-21T09:43:55.412Z",
"updated_at": "2018-12-21T09:43:55.412Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -861,8 +777,6 @@
"text": "After incubation, centrifuge the sample tube for 5 min at 5000 × g and decant the supernatant into a new 50 mL Falcon tube.",
"checked": false,
"checklist_id": 957,
"created_at": "2018-12-21T09:43:55.419Z",
"updated_at": "2018-12-21T09:43:55.419Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -881,8 +795,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-12T12:05:42.004Z",
"updated_at": "2019-02-07T10:03:24.266Z",
"last_modified_by_id": 202,
"protocol_id": 3487
},
@ -905,8 +817,6 @@
"id": 959,
"name": "Guidelines:",
"step_id": 4417,
"created_at": "2018-12-21T09:54:21.494Z",
"updated_at": "2018-12-21T09:54:21.494Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -916,8 +826,6 @@
"text": "Add a ½ volume of 5 M NaCl to the sample and mix gently by inversion.",
"checked": false,
"checklist_id": 959,
"created_at": "2018-12-21T09:54:21.496Z",
"updated_at": "2018-12-21T09:54:21.496Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -927,8 +835,6 @@
"text": "Then, add 3 volumes of cold 95% ethanol and mix gently by inversion.",
"checked": false,
"checklist_id": 959,
"created_at": "2018-12-21T09:54:21.503Z",
"updated_at": "2018-12-21T09:54:21.503Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -938,8 +844,6 @@
"text": "Place the tubes into a -20°C freezer and incubate for 1 h.",
"checked": false,
"checklist_id": 959,
"created_at": "2018-12-21T09:54:21.509Z",
"updated_at": "2018-12-21T09:54:21.509Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -949,8 +853,6 @@
"text": "After incubation, centrifuge the Falcon tube for 10 min at 5000 × g to pellet the DNA.",
"checked": false,
"checklist_id": 959,
"created_at": "2018-12-21T09:54:21.516Z",
"updated_at": "2018-12-21T09:54:21.516Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -960,8 +862,6 @@
"text": "Carefully decant away the supernatant and wash the DNA pellet with 3 mL of 70% ethanol.",
"checked": false,
"checklist_id": 959,
"created_at": "2018-12-21T09:54:21.522Z",
"updated_at": "2018-12-21T09:54:21.522Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -971,8 +871,6 @@
"text": "Gently swirl the solution and centrifuge again for 10 min at 5000 × g.",
"checked": false,
"checklist_id": 959,
"created_at": "2018-12-21T09:54:21.529Z",
"updated_at": "2018-12-21T09:54:21.529Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 5
@ -982,8 +880,6 @@
"text": "Carefully decant the supernatant and air-dry DNA pellet for 15 min at room temperature. Once dried, suspend DNA in 200 μL of TE buffer.",
"checked": false,
"checklist_id": 959,
"created_at": "2018-12-21T09:54:21.535Z",
"updated_at": "2018-12-21T09:54:21.535Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 6
@ -1005,10 +901,8 @@
"my_module_groups": [
{
"id": 1167,
"created_at": "2018-12-17T07:51:25.142Z",
"updated_at": "2018-12-17T07:51:25.142Z",
"created_by_id": 202,
"experiment_id": 433
}
]
}
}

View file

@ -11,8 +11,6 @@
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-13T09:57:55.843Z",
"updated_at": "2019-02-07T14:41:26.829Z",
"uuid": "59b17955-fba6-405a-a6b8-6a78a05af66f"
},
"my_modules": [
@ -25,8 +23,6 @@
"x": 67,
"y": 7,
"my_module_group_id": 1165,
"created_at": "2018-11-13T15:51:41.812Z",
"updated_at": "2018-12-13T15:38:36.161Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -66,13 +62,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-13T15:51:41.820Z",
"updated_at": "2019-02-20T07:58:02.380Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -88,8 +81,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-13T15:04:47.190Z",
"updated_at": "2019-02-19T14:21:06.329Z",
"last_modified_by_id": 202,
"protocol_id": 3508
},
@ -116,8 +107,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-13T15:24:58.739Z",
"updated_at": "2019-02-20T07:58:02.245Z",
"last_modified_by_id": 202,
"protocol_id": 3508
},
@ -152,8 +141,6 @@
"x": 0,
"y": 0,
"my_module_group_id": 1165,
"created_at": "2018-11-13T10:11:52.799Z",
"updated_at": "2018-12-13T15:38:36.163Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -197,13 +184,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-13T10:11:52.850Z",
"updated_at": "2019-02-19T14:18:55.343Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -219,8 +203,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T10:35:19.862Z",
"updated_at": "2019-02-07T12:58:44.529Z",
"last_modified_by_id": 202,
"protocol_id": 3504
},
@ -247,8 +229,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T10:55:00.370Z",
"updated_at": "2018-12-24T08:25:52.273Z",
"last_modified_by_id": 202,
"protocol_id": 3504
},
@ -271,8 +251,6 @@
"id": 954,
"name": "Guidelines:",
"step_id": 4438,
"created_at": "2018-12-21T07:49:43.577Z",
"updated_at": "2018-12-21T07:49:43.577Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -282,8 +260,6 @@
"text": "Centrifuge samples for 10 min at 5000 r.p.m.",
"checked": false,
"checklist_id": 954,
"created_at": "2018-12-21T07:49:43.580Z",
"updated_at": "2018-12-21T07:49:43.580Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -293,8 +269,6 @@
"text": "Resuspension in a volume of cold 10% sterile glycerol equal to the original culture volume.",
"checked": false,
"checklist_id": 954,
"created_at": "2018-12-21T07:49:43.588Z",
"updated_at": "2018-12-21T07:49:43.588Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -304,8 +278,6 @@
"text": "Cells are collected by spinning for 10 min at 5000 r.p.m. at 4°C.",
"checked": false,
"checklist_id": 954,
"created_at": "2018-12-21T07:49:43.595Z",
"updated_at": "2018-12-21T07:49:43.595Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -315,8 +287,6 @@
"text": "After decanting the supernatant, cells are resuspended in the volume of glycerol remaining in the centrifiuge bottles and spun for 10 min at 7000 r.p.m. in a centrifuge.",
"checked": false,
"checklist_id": 954,
"created_at": "2018-12-21T07:49:43.604Z",
"updated_at": "2018-12-21T07:49:43.604Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -326,8 +296,6 @@
"text": "After decanting the supernatant, cells are resuspended in 10% glycerol, using a volume of between 2 and 2.25 m/L initial culture.",
"checked": false,
"checklist_id": 954,
"created_at": "2018-12-21T07:49:43.611Z",
"updated_at": "2018-12-21T07:49:43.611Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -346,8 +314,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T10:44:55.407Z",
"updated_at": "2019-02-19T14:18:55.293Z",
"last_modified_by_id": 202,
"protocol_id": 3504
},
@ -374,8 +340,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T10:23:13.538Z",
"updated_at": "2019-02-19T14:18:20.039Z",
"last_modified_by_id": 202,
"protocol_id": 3504
},
@ -410,8 +374,6 @@
"x": 33,
"y": 7,
"my_module_group_id": 1165,
"created_at": "2018-11-13T12:54:50.584Z",
"updated_at": "2018-12-13T15:38:36.166Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -455,13 +417,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-13T12:54:50.592Z",
"updated_at": "2019-02-20T07:35:25.312Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -477,8 +436,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T12:56:40.301Z",
"updated_at": "2019-02-07T09:34:28.051Z",
"last_modified_by_id": 202,
"protocol_id": 3507
},
@ -505,8 +462,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T15:48:41.251Z",
"updated_at": "2019-02-19T14:20:45.852Z",
"last_modified_by_id": 202,
"protocol_id": 3507
},
@ -533,8 +488,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T15:19:26.546Z",
"updated_at": "2019-02-20T07:36:24.474Z",
"last_modified_by_id": 202,
"protocol_id": 3507
},
@ -545,8 +498,6 @@
{
"asset": {
"id": 3744,
"created_at": "2019-02-20T07:35:25.074Z",
"updated_at": "2019-02-20T07:36:24.466Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 46253,
@ -586,8 +537,6 @@
"x": 0,
"y": 16,
"my_module_group_id": 1165,
"created_at": "2018-11-13T11:54:32.038Z",
"updated_at": "2018-12-13T15:38:36.168Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -631,13 +580,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-11-13T11:54:32.112Z",
"updated_at": "2019-02-19T14:17:49.489Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -653,8 +599,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-11-13T12:30:26.870Z",
"updated_at": "2019-02-07T09:32:54.981Z",
"last_modified_by_id": 202,
"protocol_id": 3505
},
@ -681,8 +625,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-13T09:33:57.218Z",
"updated_at": "2019-02-07T12:58:08.799Z",
"last_modified_by_id": 202,
"protocol_id": 3505
},
@ -705,8 +647,6 @@
"id": 955,
"name": "Guidelines:",
"step_id": 5186,
"created_at": "2018-12-21T09:14:37.947Z",
"updated_at": "2018-12-21T09:14:37.947Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -716,8 +656,6 @@
"text": "Selected clones are grown in 2.5 ml of L-broth containing 100 ug/ml ampicillin in 6-ml vials.",
"checked": false,
"checklist_id": 955,
"created_at": "2018-12-21T09:14:37.950Z",
"updated_at": "2018-12-21T09:14:37.950Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -727,8 +665,6 @@
"text": "After 18 h incubation, 0.5 ml of culture is transferred to a 1.5 ml Eppendorf tube for plasmid extraction.",
"checked": false,
"checklist_id": 955,
"created_at": "2018-12-21T09:14:37.957Z",
"updated_at": "2018-12-21T09:14:37.957Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -738,8 +674,6 @@
"text": "The remainder is stored at -20°C after the addition of glycerol to 40%.",
"checked": false,
"checklist_id": 955,
"created_at": "2018-12-21T09:14:37.964Z",
"updated_at": "2018-12-21T09:14:37.964Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -758,8 +692,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-13T10:48:53.179Z",
"updated_at": "2019-02-19T14:17:49.435Z",
"last_modified_by_id": 202,
"protocol_id": 3505
},
@ -782,8 +714,6 @@
"id": 956,
"name": "Guidelines:",
"step_id": 5187,
"created_at": "2018-12-21T09:22:56.932Z",
"updated_at": "2018-12-21T09:22:56.932Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -793,8 +723,6 @@
"text": "The tube is centrifuged for 15 seconds. The supernatant is carefully removed with a fine-tip aspirator and the cell pellet is thoroughly suspended in 100 ul of lysozyme solution.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.934Z",
"updated_at": "2018-12-21T09:22:56.934Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -804,8 +732,6 @@
"text": "After a 30 minute period of incubation at 0°C, 200 ml of alkaline SDS solution is added and the tube is gently vortexed. The suspension should become almost clear and slightly viscous.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.942Z",
"updated_at": "2018-12-21T09:22:56.942Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -815,8 +741,6 @@
"text": "The tube is maintained for 5 min at 0°C and then 150 ul of high-salt solution added. The contents of the tube are gently mixed by inversion for a few seconds during which time a clot of DNA forms.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.949Z",
"updated_at": "2018-12-21T09:22:56.949Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -826,8 +750,6 @@
"text": "The tube is maintained at 0°C for 60 min to allow most of the protein, high molecular weight RNA and chromosomal DNA to precipitate.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.957Z",
"updated_at": "2018-12-21T09:22:56.957Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -837,8 +759,6 @@
"text": "Centrifugation for 5 min yields an almost clear supernatant. Four-tenths of a ml of the supernatant is removed and transferred to a second centrifuge tube. Small amounts of floating material may be carried over at this time.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.964Z",
"updated_at": "2018-12-21T09:22:56.964Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -848,8 +768,6 @@
"text": "One ml of cold ethanol is added and the tube is held at -20°C for 30 min.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.971Z",
"updated_at": "2018-12-21T09:22:56.971Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 5
@ -859,8 +777,6 @@
"text": "The precipitate is collected by centrifugation for 2 min and the supernatant removed by aspiration.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.977Z",
"updated_at": "2018-12-21T09:22:56.977Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 6
@ -870,8 +786,6 @@
"text": "The pellet is dissolved in 100 ul of 0.1 M sodium acetate/0.05 M Tris-HCl (pH 8) and reprecipitated with 2 volumes of cold ethanol.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.984Z",
"updated_at": "2018-12-21T09:22:56.984Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 7
@ -881,8 +795,6 @@
"text": "Redissolve once more in 100 ul of 0.1 M sodium acetate/0.05 M Tris-HCl (pH 8) and reprecipitated with 2 volumes of cold ethanol.",
"checked": false,
"checklist_id": 956,
"created_at": "2018-12-21T09:22:56.992Z",
"updated_at": "2018-12-21T09:22:56.992Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 8
@ -904,10 +816,8 @@
"my_module_groups": [
{
"id": 1165,
"created_at": "2018-12-13T15:38:36.158Z",
"updated_at": "2018-12-13T15:38:36.158Z",
"created_by_id": 202,
"experiment_id": 436
}
]
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -11,8 +11,6 @@
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-12-24T10:56:55.416Z",
"updated_at": "2019-01-28T15:15:24.331Z",
"uuid": "3363b358-1478-402c-8597-57739d3fb2e3"
},
"my_modules": [
@ -25,8 +23,6 @@
"x": 99,
"y": 0,
"my_module_group_id": 1195,
"created_at": "2018-12-24T13:08:01.834Z",
"updated_at": "2018-12-24T13:43:31.275Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -66,13 +62,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-12-24T13:08:01.843Z",
"updated_at": "2019-02-20T07:31:00.167Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -88,8 +81,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T13:33:23.306Z",
"updated_at": "2018-12-24T13:33:53.668Z",
"last_modified_by_id": 202,
"protocol_id": 3905
},
@ -112,8 +103,6 @@
"id": 974,
"name": "Guidelines",
"step_id": 5264,
"created_at": "2018-12-24T13:33:23.308Z",
"updated_at": "2018-12-24T13:33:23.308Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -123,8 +112,6 @@
"text": "Place the membrane in blocking buffer and incubate at room temperature for 1 hour (or at 4°C overnight) with shaking.",
"checked": false,
"checklist_id": 974,
"created_at": "2018-12-24T13:33:23.310Z",
"updated_at": "2018-12-24T13:33:23.310Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -134,8 +121,6 @@
"text": "Wash the membrane with washing buffer 3 times (5 minutes each time).",
"checked": false,
"checklist_id": 974,
"created_at": "2018-12-24T13:33:23.316Z",
"updated_at": "2018-12-24T13:33:23.316Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -145,8 +130,6 @@
"text": "Place the membrane in primary antibody solution diluted in 1% BSA in Tween PBS, pH 7.2, and incubate at room temperature for 1 hour with shaking.",
"checked": false,
"checklist_id": 974,
"created_at": "2018-12-24T13:33:23.321Z",
"updated_at": "2018-12-24T13:33:23.321Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -156,8 +139,6 @@
"text": "Wash the membrane with washing buffer 3 times (10 minutes each time).",
"checked": false,
"checklist_id": 974,
"created_at": "2018-12-24T13:33:23.327Z",
"updated_at": "2018-12-24T13:33:23.327Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -167,8 +148,6 @@
"text": "Place the membrane in secondary antibody diluted in 1% BSA in Tween PBS, pH 7.2, and incubate at room temperature for 1 hour with shaking.",
"checked": false,
"checklist_id": 974,
"created_at": "2018-12-24T13:33:23.332Z",
"updated_at": "2018-12-24T13:33:23.332Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -178,8 +157,6 @@
"text": "Wash the membrane with washing buffer 3 times (10 minutes each time).",
"checked": false,
"checklist_id": 974,
"created_at": "2018-12-24T13:33:23.337Z",
"updated_at": "2018-12-24T13:33:23.337Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 5
@ -198,8 +175,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T13:17:10.122Z",
"updated_at": "2019-02-20T07:30:20.884Z",
"last_modified_by_id": 202,
"protocol_id": 3905
},
@ -210,8 +185,6 @@
{
"asset": {
"id": 3736,
"created_at": "2019-02-20T07:29:55.862Z",
"updated_at": "2019-02-20T07:30:20.876Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 18346,
@ -243,8 +216,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T13:24:09.108Z",
"updated_at": "2019-02-19T14:04:24.998Z",
"last_modified_by_id": 202,
"protocol_id": 3905
},
@ -271,8 +242,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T13:34:41.460Z",
"updated_at": "2019-02-20T07:31:21.254Z",
"last_modified_by_id": 202,
"protocol_id": 3905
},
@ -283,8 +252,6 @@
{
"asset": {
"id": 3737,
"created_at": "2019-02-20T07:30:59.882Z",
"updated_at": "2019-02-20T07:31:21.246Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 14173,
@ -324,8 +291,6 @@
"x": 99,
"y": 14,
"my_module_group_id": 1195,
"created_at": "2018-12-24T12:42:30.757Z",
"updated_at": "2018-12-24T13:43:31.281Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -365,13 +330,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-12-24T12:42:30.765Z",
"updated_at": "2019-02-19T14:04:53.999Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -387,8 +349,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:50:15.910Z",
"updated_at": "2019-01-29T10:20:35.719Z",
"last_modified_by_id": 202,
"protocol_id": 3904
},
@ -415,8 +375,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:47:54.498Z",
"updated_at": "2019-01-29T10:09:33.373Z",
"last_modified_by_id": 202,
"protocol_id": 3904
},
@ -443,8 +401,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:49:39.311Z",
"updated_at": "2019-01-29T10:19:56.594Z",
"last_modified_by_id": 202,
"protocol_id": 3904
},
@ -471,8 +427,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:45:54.303Z",
"updated_at": "2019-02-19T14:04:53.947Z",
"last_modified_by_id": 202,
"protocol_id": 3904
},
@ -499,8 +453,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:52:54.562Z",
"updated_at": "2019-01-29T10:21:07.061Z",
"last_modified_by_id": 202,
"protocol_id": 3904
},
@ -535,8 +487,6 @@
"x": 66,
"y": 7,
"my_module_group_id": 1195,
"created_at": "2018-12-24T12:27:59.858Z",
"updated_at": "2018-12-24T13:43:31.284Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -585,13 +535,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-12-24T12:27:59.867Z",
"updated_at": "2019-02-20T07:29:26.888Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -607,8 +554,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:31:41.933Z",
"updated_at": "2019-02-20T07:28:19.933Z",
"last_modified_by_id": 202,
"protocol_id": 3903
},
@ -619,8 +564,6 @@
{
"asset": {
"id": 3734,
"created_at": "2019-02-20T07:28:16.226Z",
"updated_at": "2019-02-20T07:28:19.924Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 12542,
@ -652,8 +595,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:39:15.168Z",
"updated_at": "2018-12-24T12:39:15.168Z",
"last_modified_by_id": 202,
"protocol_id": 3903
},
@ -680,8 +621,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T12:38:33.920Z",
"updated_at": "2019-02-20T07:30:20.395Z",
"last_modified_by_id": 202,
"protocol_id": 3903
},
@ -692,8 +631,6 @@
{
"asset": {
"id": 3735,
"created_at": "2019-02-20T07:29:26.573Z",
"updated_at": "2019-02-20T07:30:20.386Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 19250,
@ -733,8 +670,6 @@
"x": 0,
"y": 7,
"my_module_group_id": 1195,
"created_at": "2018-12-24T11:00:55.170Z",
"updated_at": "2018-12-24T13:43:31.287Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -778,13 +713,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-12-24T11:00:55.177Z",
"updated_at": "2019-02-20T07:25:44.713Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -800,8 +732,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T11:04:34.946Z",
"updated_at": "2019-01-29T10:02:37.258Z",
"last_modified_by_id": 202,
"protocol_id": 3902
},
@ -828,8 +758,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T11:12:28.609Z",
"updated_at": "2019-01-29T10:02:55.233Z",
"last_modified_by_id": 202,
"protocol_id": 3902
},
@ -849,8 +777,6 @@
{
"table": {
"id": 685,
"created_at": "2018-12-24T11:20:43.674Z",
"updated_at": "2019-01-29T10:02:55.216Z",
"created_by_id": 202,
"last_modified_by_id": 202,
"name": "For 10 mL separating gel:",
@ -866,8 +792,6 @@
"id": 972,
"name": "Guideline:",
"step_id": 5249,
"created_at": "2018-12-24T11:23:58.433Z",
"updated_at": "2018-12-24T11:23:58.433Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -877,8 +801,6 @@
"text": "Prepare the gel solution in a small beaker. AP and TEMED must be added right before each use.",
"checked": false,
"checklist_id": 972,
"created_at": "2018-12-24T11:23:58.434Z",
"updated_at": "2018-12-24T11:23:58.434Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -888,8 +810,6 @@
"text": "Swirl the solution gently but thoroughly.",
"checked": false,
"checklist_id": 972,
"created_at": "2018-12-24T11:23:58.442Z",
"updated_at": "2018-12-24T11:23:58.442Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -899,8 +819,6 @@
"text": "Pipette appropriate amount of separating gel solution (listed above) into the gap between the glass plates.",
"checked": false,
"checklist_id": 972,
"created_at": "2018-12-24T11:23:58.448Z",
"updated_at": "2018-12-24T11:23:58.448Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -910,8 +828,6 @@
"text": "To make the top of the separating gel be horizontal, fill in water (either isopropanol) into the gap until a overflow. Water also prevents contact with oxygen, which inhibits polymerization.",
"checked": false,
"checklist_id": 972,
"created_at": "2018-12-24T11:23:58.453Z",
"updated_at": "2018-12-24T11:26:09.996Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -921,8 +837,6 @@
"text": "Wait for 20-30min to let it gelate.",
"checked": false,
"checklist_id": 972,
"created_at": "2018-12-24T11:23:58.459Z",
"updated_at": "2018-12-24T11:23:58.459Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -941,8 +855,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T11:30:55.051Z",
"updated_at": "2019-02-20T07:26:19.503Z",
"last_modified_by_id": 202,
"protocol_id": 3902
},
@ -953,8 +865,6 @@
{
"asset": {
"id": 3733,
"created_at": "2019-02-20T07:25:44.445Z",
"updated_at": "2019-02-20T07:26:19.494Z",
"created_by_id": 202,
"last_modified_by_id": null,
"estimated_size": 48073,
@ -982,8 +892,6 @@
"id": 973,
"name": "Guideline",
"step_id": 5250,
"created_at": "2018-12-24T11:30:55.053Z",
"updated_at": "2018-12-24T11:30:55.053Z",
"created_by_id": null,
"last_modified_by_id": null
},
@ -993,8 +901,6 @@
"text": "Discard the water and you can see separating gel left.",
"checked": false,
"checklist_id": 973,
"created_at": "2018-12-24T11:30:55.054Z",
"updated_at": "2018-12-24T11:30:55.054Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 0
@ -1004,8 +910,6 @@
"text": "Pipette in stacking gel until an overflow.",
"checked": false,
"checklist_id": 973,
"created_at": "2018-12-24T11:30:55.060Z",
"updated_at": "2018-12-24T11:30:55.060Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 1
@ -1015,8 +919,6 @@
"text": "Insert the well-forming comb without trapping air under the teeth. Wait for 20-30min to let it gelate.",
"checked": false,
"checklist_id": 973,
"created_at": "2018-12-24T11:30:55.065Z",
"updated_at": "2018-12-24T11:30:55.065Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 2
@ -1026,8 +928,6 @@
"text": "Make sure a complete gelation of the stacking gel and take out the comb.",
"checked": false,
"checklist_id": 973,
"created_at": "2018-12-24T11:30:55.071Z",
"updated_at": "2018-12-24T11:30:55.071Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 3
@ -1037,8 +937,6 @@
"text": "Take the glass plates out of the casting frame and set them in the cell buffer dam.",
"checked": false,
"checklist_id": 973,
"created_at": "2018-12-24T11:30:55.076Z",
"updated_at": "2018-12-24T11:30:55.076Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 4
@ -1048,8 +946,6 @@
"text": "Pour the running buffer (electrophoresis buffer) into the inner chamber and keep pouring after overflow untill the buffer surface reaches the required level in the outer chamber.",
"checked": false,
"checklist_id": 973,
"created_at": "2018-12-24T11:30:55.081Z",
"updated_at": "2018-12-24T11:30:55.081Z",
"created_by_id": null,
"last_modified_by_id": null,
"position": 5
@ -1076,8 +972,6 @@
"x": 33,
"y": 7,
"my_module_group_id": 1195,
"created_at": "2018-12-24T10:57:17.160Z",
"updated_at": "2018-12-24T13:43:31.290Z",
"archived": false,
"archived_on": null,
"created_by_id": 202,
@ -1121,13 +1015,10 @@
"team_id": 1,
"protocol_type": "unlinked",
"parent_id": null,
"parent_updated_at": null,
"archived_by_id": null,
"archived_on": null,
"restored_by_id": null,
"restored_on": null,
"created_at": "2018-12-24T10:57:17.170Z",
"updated_at": "2019-02-19T14:03:57.520Z",
"published_on": null,
"nr_of_linked_children": 0
},
@ -1143,8 +1034,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T11:48:19.756Z",
"updated_at": "2019-02-19T14:03:57.459Z",
"last_modified_by_id": 202,
"protocol_id": 3901
},
@ -1171,8 +1060,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T11:47:05.158Z",
"updated_at": "2019-01-29T10:05:04.115Z",
"last_modified_by_id": 202,
"protocol_id": 3901
},
@ -1199,8 +1086,6 @@
"completed": false,
"completed_on": null,
"user_id": 202,
"created_at": "2018-12-24T11:44:32.382Z",
"updated_at": "2019-02-19T14:03:43.661Z",
"last_modified_by_id": 202,
"protocol_id": 3901
},
@ -1230,10 +1115,8 @@
"my_module_groups": [
{
"id": 1195,
"created_at": "2018-12-24T13:43:31.272Z",
"updated_at": "2018-12-24T13:43:31.272Z",
"created_by_id": 202,
"experiment_id": 497
}
]
}
}

View file

@ -4,7 +4,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception, prepend: true
before_action :authenticate_user!
helper_method :current_team
before_action :update_current_team, if: :user_signed_in?
before_action :set_current_team, if: :user_signed_in?
around_action :set_date_format, if: :user_signed_in?
around_action :set_time_zone, if: :current_user
layout 'main'
@ -29,11 +29,6 @@ class ApplicationController < ActionController::Base
controller_name == 'projects' && action_name == 'index'
end
# Sets current team for all controllers
def current_team
@current_team ||= current_user.teams.find_by(id: current_user.current_team_id)
end
def to_user_date_format
ts = I18n.l(Time.parse(params[:timestamp]),
format: params[:ts_format].to_sym)
@ -87,14 +82,18 @@ class ApplicationController < ActionController::Base
private
def update_current_team
return if current_team.present? && current_team.id == current_user.current_team_id
def current_team
@current_team ||= current_user.teams.find_by(id: current_user.current_team_id)
end
def set_current_team
if current_user.current_team_id
@current_team = current_user.teams.find_by(id: current_user.current_team_id)
elsif current_user.teams.any?
elsif current_user.teams.first.present?
@current_team = current_user.teams.first
current_user.update(current_team_id: current_user.teams.first.id)
end
Current.team = @current_team if @current_team.present?
end
# With this Devise callback user is redirected directly to sign in page instead

View file

@ -65,7 +65,8 @@ class ExternalProtocolsController < ApplicationController
partial: 'protocol_importers/import_form',
locals: { protocol: @protocol,
steps_json: service_call.serialized_steps,
steps_assets: service_call.steps_assets }
steps_assets: service_call.steps_assets,
source: new_params[:protocol_source] }
),
title: t('protocol_importers.new.modal_title', protocol_name: @protocol.name),
footer: render_to_string(
@ -88,6 +89,17 @@ class ExternalProtocolsController < ApplicationController
)
if service_call.succeed?
if params[:source] == 'protocolsio/v3'
protocol = service_call.protocol
Activities::CreateActivityService
.call(activity_type: :import_protocol_in_repository_from_protocols_io,
owner: current_user,
subject: protocol,
team: protocol.team,
message_items: {
protocol: protocol.id
})
end
message = t('protocols.index.protocolsio.import.success_flash', name: service_call.protocol.name)
render json: { protocol: service_call.protocol, message: message }
else

View file

@ -43,8 +43,6 @@ class MyModuleShareableLinksController < ApplicationController
@results_order = params[:order] || 'new'
@results = @my_module.results.active
@results = @results.page(params[:page]).per(Constants::RESULTS_PER_PAGE_LIMIT)
@results = case @results_order
when 'old' then @results.order(created_at: :asc)
when 'old_updated' then @results.order(updated_at: :asc)
@ -54,6 +52,15 @@ class MyModuleShareableLinksController < ApplicationController
else @results.order(created_at: :desc)
end
if params[:result_id].present?
page = @results.find_page_number(params[:result_id].to_i, Constants::RESULTS_PER_PAGE_LIMIT)
params[:result_id] = nil
else
page = params[:page]
end
@results = @results.page(page).per(Constants::RESULTS_PER_PAGE_LIMIT)
@gallery = @results.left_joins(:assets).pluck('assets.id').compact
@disable_smart_annotation_links = true

View file

@ -374,7 +374,7 @@ class MyModulesController < ApplicationController
def update_state
old_status_id = @my_module.my_module_status_id
@my_module.my_module_status_created_by = current_user
@my_module.status_changed_by = current_user
if @my_module.update(my_module_status_id: update_status_params[:status_id])
log_activity(:change_status_on_task_flow, @my_module, my_module_status_old: old_status_id,

View file

@ -60,8 +60,12 @@ class NavigationsController < ApplicationController
end
def settings_menu_links
links = [{ name: I18n.t('users.settings.sidebar.teams'), url: teams_path }]
links << { name: I18n.t('users.settings.sidebar.groups'), url: users_settings_team_user_groups_path(current_team) } if can_manage_team?(current_team)
if current_team && can_manage_team?(current_team)
links << { name: I18n.t('users.settings.sidebar.account_nav.automations'), url: automations_team_path(current_team) }
links << { name: I18n.t('users.settings.sidebar.groups'), url: users_settings_team_user_groups_path(current_team) }
end
links << { name: I18n.t('users.settings.sidebar.account_nav.addons'), url: addons_path }
private_methods.select { |i| i.to_s[/^settings_menu_links_[a-z]*_extension$/] }.each do |method|

View file

@ -7,10 +7,12 @@ class TeamsController < ApplicationController
helper_method :current_folder
before_action :load_vars, only: %i(sidebar export_projects export_projects_modal
disable_tasks_sharing_modal shared_tasks_toggle)
disable_tasks_sharing_modal shared_tasks_toggle
settings update_settings automations)
before_action :load_current_folder, only: :sidebar
before_action :check_read_permissions, except: %i(view_type visible_teams visible_users current_team_users)
before_action :check_export_projects_permissions, only: %i(export_projects_modal export_projects)
before_action :set_breadcrumbs_items, only: %i(automations)
def visible_teams
teams = current_user.teams.order(:name)
@ -99,8 +101,6 @@ class TeamsController < ApplicationController
end
def shared_tasks_toggle
return render_403 unless can_manage_team?(@team)
@team.toggle!(:shareable_links_enabled)
if @team.shareable_links_enabled?
@ -126,6 +126,28 @@ class TeamsController < ApplicationController
render json: { cards_view_type_class: cards_view_type_class(view_type_params) }, status: :ok
end
def automations
@active_tab = :automations
end
def settings
render json: {
teamName: @team.name,
teamAutomationGroups: Extends::TEAM_AUTOMATIONS_GROUPS,
teamSettings: @team.settings,
updateUrl: update_settings_team_path(@team)
}
end
def update_settings
@team.settings.merge!(update_settings_params)
if @team.save
render json: {}
else
render json: @team.errors, status: :unprocessable_entity
end
end
private
def load_vars
@ -145,6 +167,10 @@ class TeamsController < ApplicationController
render_403 unless can_read_team?(@team)
end
def check_manage_permissions
render_403 unless can_manage_team?(@team)
end
def load_current_folder
if current_team && params[:project_folder_id].present?
@current_folder = current_team.project_folders.find_by(id: params[:project_folder_id])
@ -168,6 +194,17 @@ class TeamsController < ApplicationController
end
end
def update_settings_params
params.require(:team).permit(team_automation_settings: {})
end
def set_breadcrumbs_items
@breadcrumbs_items = [
{ label: t('breadcrumbs.teams'), url: teams_path },
{ label: @team.name, url: team_path(@team) }
]
end
def log_activity(type_of, message_items = {})
Activities::CreateActivityService
.call(activity_type: type_of,

View file

@ -4,7 +4,7 @@ module TeamsHelper
if team != current_team && current_user.member_of_team?(team)
current_user.current_team_id = team.id
current_user.save
update_current_team
set_current_team
end
end

View file

@ -0,0 +1,10 @@
import { PerfectScrollbar } from 'vue3-perfect-scrollbar';
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import TeamAutomations from '../../vue/team_automations/container.vue';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
const app = createApp({});
app.component('TeamAutomations', TeamAutomations);
app.component('PerfectScrollbar', PerfectScrollbar);
app.config.globalProperties.i18n = window.I18n;
mountWithTurbolinks(app, '#team_automations');

View file

@ -141,7 +141,7 @@ export default {
this.assignedRepositories = response.data.data;
this.$nextTick(() => {
this.recalculateContainerSize();
this.$refs.assignedRepositories.forEach((repository) => {
this.$refs.assignedRepositories?.forEach((repository) => {
if (repository.sectionOpened) {
repository.getRows();
}

View file

@ -141,7 +141,7 @@ export default {
if (window.resetGridColumns) window.resetGridColumns(false);
},
onResizeStart() {
document.body.style.cursor = 'url(/images/icon_small/Resize.svg) 0 0, auto';
document.body.style.cursor = 'col-resize';
$('.sci--layout-navigation-navigator').addClass('!transition-none');
$('.sci--layout').addClass('!transition-none');
},

View file

@ -199,13 +199,26 @@
<span>{{ i18n.t("protocols.steps.new_step") }}</span>
</a>
<div v-if="steps.length > 0" class="flex justify-between items-center gap-4">
<button @click="collapseSteps" class="btn btn-secondary flex px-4" tabindex="0" data-e2e="e2e-BT-protocol-templateSteps-collapse">
<button
:title="i18n.t('protocols.steps.collapse_label')"
v-if="!stepCollapsed"
class="btn btn-secondary icon-btn xl:!px-4"
@click="collapseSteps"
tabindex="0"
data-e2e="e2e-BT-task-protocol-collapseAll"
>
<i class="sn-icon sn-icon-collapse-all"></i>
{{ i18n.t("protocols.steps.collapse_label") }}
<span class="tw-hidden xl:inline">{{ i18n.t("protocols.steps.collapse_label") }}</span>
</button>
<button @click="expandSteps" class="btn btn-secondary flex px-4" tabindex="0" data-e2e="e2e-BT-protocol-templateSteps-expand">
<button v-else
:title="i18n.t('protocols.steps.expand_label')"
class="btn btn-secondary icon-btn xl:!px-4"
@click="expandSteps"
tabindex="0"
data-e2e="e2e-BT-task-protocol-expandAll"
>
<i class="sn-icon sn-icon-expand-all"></i>
{{ i18n.t("protocols.steps.expand_label") }}
<span class="tw-hidden xl:inline">{{ i18n.t("protocols.steps.expand_label") }}</span>
</button>
<a v-if="steps.length > 0 && urls.reorder_steps_url"
class="btn btn-light icon-btn"

View file

@ -105,9 +105,12 @@
:key="result.id"
:title="result.name"
:href="resultUrl(result.id, result.archived)"
class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer block hover:no-underline text-sn-blue truncate"
class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer hover:no-underline text-sn-blue truncate flex items-center gap-2"
>
{{ result.name }}
<div v-if="result.archived" class="py-1 px-2 text-xs ml-auto text-white bg-sn-grey rounded-full">
{{ i18n.t('protocols.steps.archived_result') }}
</div>
</a>
</div>
<template v-if="urls.update_url">

View file

@ -179,6 +179,8 @@ export default {
this.nextPageUrl = response.data.links.next;
this.loadingPage = false;
this.infiniteScrollLoad();
if (this.anchorId) {
const result = this.results.find((e) => e.id === this.anchorId);
if (!result) {

View file

@ -0,0 +1,93 @@
<template>
<div class="text-sn-dark-grey mb-4 max-w-3xl">
<p>{{ i18n.t("team_automations.description") }}</p>
</div>
<div v-if="teamObject?.teamAutomationGroups">
<div class="flex flex-col gap-8 mb-4">
<template v-for="(_subGroups, group) in teamObject?.teamAutomationGroups" :key="group">
<div>
<h2 class="mt-0">{{ i18n.t(`team_automations.groups.${group}`) }}</h2>
<template v-for="(subGroupElements, subGroup) in teamObject?.teamAutomationGroups[group]" :key="subGroup">
<table class="bg-sn-white w-full">
<tbody>
<tr class="text-base">
<td colspan=2 class="pl-4"><h5>{{ i18n.t(`team_automations.sub_groups.${subGroup}`) }}</h5></td>
</tr>
<template v-for="(subGroupElement, i) in subGroupElements" :key="subGroupElement">
<tr class="text-base">
<td class="py-3 pl-6">{{ i18n.t(`team_automations.sub_group_element.${subGroupElement}`) }}</td>
<td class="p-3">
<div class="sci-toggle-checkbox-container">
<input v-model="teamAutomationSettings[group][subGroup][subGroupElement]" type="checkbox" class="sci-toggle-checkbox" @change="setTeamAutomationsSettings"/>
<label class="sci-toggle-checkbox-label"></label>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</template>
</div>
</template>
</div>
</div>
</template>
<script>
/* global HelperModule */
import axios from '../../packs/custom_axios.js';
export default {
name: 'TeamAutomationsSettingsContainer',
components: {},
props: {
teamUrl: String,
},
data() {
return {
teamObject: null,
teamAutomationSettings: null
};
},
created() {
this.loadTeam();
},
computed: {
emptySettings() {
const result = {};
for (const [group, subGroups] of Object.entries(this.teamObject.teamAutomationGroups)) {
result[group] = {};
for (const [subGroup, settingsArray] of Object.entries(subGroups)) {
result[group][subGroup] = {};
settingsArray.forEach(setting => {
result[group][subGroup][setting] = false;
});
}
}
return result;
}
},
methods: {
loadTeam() {
axios.get(this.teamUrl).then((response) => {
this.teamObject = response.data;
this.teamAutomationSettings = { ...this.emptySettings, ...this.teamObject.teamSettings?.team_automation_settings };
});
},
setTeamAutomationsSettings() {
axios.put(this.teamObject.updateUrl, {
team: { team_automation_settings: this.teamAutomationSettings }
});
}
}
};
</script>

View file

@ -8,6 +8,7 @@ class Asset < ApplicationRecord
include ActiveStorageConcerns
include ActiveStorageHelper
include VersionedAttachments
include ObservableModel
require 'tempfile'
# Lock duration set to 30 minutes

View file

@ -1,5 +1,6 @@
class Checklist < ApplicationRecord
include SearchableModel
include ObservableModel
SEARCHABLE_ATTRIBUTES = ['checklists.name'].freeze

View file

@ -1,4 +1,5 @@
class ChecklistItem < ApplicationRecord
include ObservableModel
include SearchableModel
SEARCHABLE_ATTRIBUTES = ['checklist_items.text'].freeze

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
module ObservableModel
extend ActiveSupport::Concern
included do
after_create :notify_observers_on_create
after_update :notify_observers_on_update
end
private
def changed_by
last_modified_by || created_by
end
def notify_observers_on_create
return if Current.team.blank?
Extends::TEAM_AUTOMATIONS_OBSERVERS_CONFIG[self.class.base_class.name].each { |observer| observer.constantize.on_create(self, changed_by) }
end
def notify_observers_on_update
return if Current.team.blank?
Extends::TEAM_AUTOMATIONS_OBSERVERS_CONFIG[self.class.base_class.name].each { |observer| observer.constantize.on_update(self, changed_by) }
end
end

View file

@ -3,6 +3,8 @@
module TimeTrackable
extend ActiveSupport::Concern
STATUS_ORDER = %i(not_started in_progress done).freeze
included do
scope :not_started, -> { where(started_at: nil).where(done_at: nil) }
scope :in_progress, -> { where.not(started_at: nil).where(done_at: nil) }
@ -25,13 +27,11 @@ module TimeTrackable
end
def status
if started_at.nil? && done_at.nil?
:not_started
elsif started_at && !done_at
:in_progress
else
:done
end
compute_status(started_at, done_at)
end
def status_was
compute_status(started_at_before_last_save, done_at_before_last_save)
end
def not_started?
@ -70,4 +70,22 @@ module TimeTrackable
def complete!
update!(done_at: DateTime.now)
end
def status_moved_forward?
return false unless started_at_previously_changed? || done_at_previously_changed?
STATUS_ORDER.index(status) > STATUS_ORDER.index(status_was)
end
private
def compute_status(started_at_value, done_at_value)
if started_at_value.nil? && done_at_value.nil?
:not_started
elsif started_at_value && !done_at_value
:in_progress
else
:done
end
end
end

5
app/models/current.rb Normal file
View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class Current < ActiveSupport::CurrentAttributes
attribute :user, :team
end

View file

@ -16,6 +16,7 @@ class Experiment < ApplicationRecord
include TimeTrackable
include Favoritable
include MetadataModel
include ObservableModel
before_save -> { report_elements.destroy_all }, if: -> { !new_record? && project_id_changed? }
before_save :reset_due_date_notification_sent, if: -> { due_date_changed? }

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class FormFieldValue < ApplicationRecord
include ObservableModel
belongs_to :form_response
belongs_to :form_field
belongs_to :created_by, class_name: 'User'
@ -44,4 +46,9 @@ class FormFieldValue < ApplicationRecord
errors.add(:value, :not_unique_latest)
end
# Override for ObservableModel
def changed_by
created_by
end
end

View file

@ -15,8 +15,9 @@ class MyModule < ApplicationRecord
include Cloneable
include Favoritable
include MetadataModel
include ObservableModel
attr_accessor :transition_error_rollback, :my_module_status_created_by
attr_accessor :transition_error_rollback, :status_changed_by
enum state: Extends::TASKS_STATES
enum provisioning_status: { done: 0, in_progress: 1, failed: 2 }
@ -566,7 +567,7 @@ class MyModule < ApplicationRecord
if status_changing_direction == :forward
my_module_status.my_module_status_consequences.each do |consequence|
consequence.before_forward_call(self, my_module_status_created_by)
consequence.before_forward_call(self, status_changed_by)
end
end

View file

@ -14,6 +14,7 @@ class Protocol < ApplicationRecord
include Assignable
include PermissionCheckableModel
include TinyMceImages
include ObservableModel
before_create -> { self.skip_user_assignments = true }, if: -> { in_module? }

View file

@ -141,7 +141,7 @@ class Repository < RepositoryBase
# Clone columns (only if new_repo was saved)
repository_columns.find_each do |col|
new_col = col.dup
new_col = col.deep_dup
new_col.repository = new_repo
new_col.created_by = created_by
new_col.save!

View file

@ -5,6 +5,7 @@ class Result < ApplicationRecord
include SearchableModel
include SearchableByNameModel
include ViewableModel
include ObservableModel
include Discard::Model
default_scope -> { kept }
@ -80,6 +81,11 @@ class Result < ApplicationRecord
)
end
def self.find_page_number(result_id, per_page = Kaminari.config.default_per_page)
position = pluck(:id).index(result_id)
(position.to_f / per_page).ceil
end
def duplicate(my_module, user, result_name: nil)
ActiveRecord::Base.transaction do
new_result = my_module.results.new(
@ -199,4 +205,11 @@ class Result < ApplicationRecord
def delete_step_results
step_results.destroy_all
end
private
# Override for ObservableModel
def changed_by
last_modified_by || user
end
end

View file

@ -3,6 +3,7 @@ class Step < ApplicationRecord
include SearchableByNameModel
include TinyMceImages
include ViewableModel
include ObservableModel
SEARCHABLE_ATTRIBUTES = ['steps.name'].freeze

View file

@ -1,12 +1,12 @@
# frozen_string_literal: true
class StepComment < Comment
include ObservableModel
before_create :fill_unseen_by
belongs_to :step, foreign_key: :associated_id, inverse_of: :step_comments
validates :step, presence: true
def commentable
step
end
@ -16,4 +16,9 @@ class StepComment < Comment
def fill_unseen_by
self.unseen_by += step.protocol.my_module.experiment.project.users.where.not(id: user.id).pluck(:id)
end
# Override for ObservableModel
def changed_by
last_modified_by || user
end
end

View file

@ -1,14 +1,16 @@
# frozen_string_literal: true
class StepOrderableElement < ApplicationRecord
include ObservableModel
validates :position, uniqueness: { scope: :step }
validate :check_step_relations
around_destroy :decrement_following_elements_positions
belongs_to :step, inverse_of: :step_orderable_elements, touch: true
belongs_to :orderable, polymorphic: true, inverse_of: :step_orderable_element
around_destroy :decrement_following_elements_positions
private
def check_step_relations
@ -26,4 +28,9 @@ class StepOrderableElement < ApplicationRecord
step.normalize_elements_position
end
end
# Override for ObservableModel
def changed_by
step.last_modified_by
end
end

View file

@ -2,6 +2,7 @@
class StepText < ApplicationRecord
include TinyMceImages
include ObservableModel
include SearchableModel
include ActionView::Helpers::TextHelper
@ -41,4 +42,11 @@ class StepText < ApplicationRecord
new_step_text
end
end
private
# Override for ObservableModel
def changed_by
step.last_modified_by
end
end

View file

@ -3,6 +3,7 @@
class Table < ApplicationRecord
include SearchableModel
include TableHelper
include ObservableModel
SEARCHABLE_ATTRIBUTES = ['tables.name', 'tables.data_vector'].freeze

View file

@ -12,6 +12,7 @@ class Team < ApplicationRecord
include ActionView::Helpers::NumberHelper
before_save -> { shareable_links.destroy_all }, if: -> { !shareable_links_enabled? }
before_create :init_default_settings
after_create :generate_template_project
after_create :create_default_label_templates
after_create :create_default_repository_templates
@ -205,4 +206,8 @@ class Team < ApplicationRecord
RepositoryTemplate.equipment.update(team: self)
RepositoryTemplate.chemicals_and_reagents.update(team: self)
end
def init_default_settings
self.settings = Extends::DEFAULT_TEAM_SETTINGS
end
end

View file

@ -16,7 +16,7 @@ class FormFieldValueSerializer < ActiveModel::Serializer
def value
if object.type == 'FormRepositoryRowsFieldValue'
object.value.map do |value|
object.value&.map do |value|
row_code = "#{RepositoryRow::ID_PREFIX}#{value['id']}"
repository = Repository.find_by(id: value['repository_id'])

View file

@ -82,9 +82,7 @@ module Lists
user_group_members: users_users_settings_team_user_groups_path(team_id: object.team.id)
}
if can_manage_project_users?(object.project)
urls_list[:update_access] = access_permissions_experiment_path(object)
end
urls_list[:update_access] = access_permissions_experiment_path(object) if can_manage_experiment_users?(object)
urls_list
end

View file

@ -72,9 +72,7 @@ module Lists
user_group_members: users_users_settings_team_user_groups_path(team_id: object.team.id)
}
if can_manage_project_users?(object.experiment.project)
urls_list[:update_access] = access_permissions_my_module_path(object)
end
urls_list[:update_access] = access_permissions_my_module_path(object) if can_manage_my_module_users?(object)
urls_list[:update_due_date] = my_module_path(object, user, format: :json) if can_update_my_module_due_date?(object)
urls_list[:update_start_date] = my_module_path(object, user, format: :json) if can_update_my_module_start_date?(object)

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
module AutomationObservers
class AllExperimentsDoneObserver < BaseObserver
def self.on_update(experiment, user)
return unless Current.team.settings.dig('team_automation_settings', 'projects', 'project_status_done', 'on_all_experiments_done')
return unless experiment.status_moved_forward?
project = experiment.project
return unless project.started?
return if project.experiments.active.where.not(id: project.experiments.active.done).exists?
project.update!(status: :done, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_project_status_changed,
owner: user,
team: project.team,
subject: project,
message_items: {
project: project.id,
project_status_old: I18n.t('experiments.table.column.status.in_progress'),
project_status_new: I18n.t("experiments.table.column.status.#{project.status}")
})
end
end
end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
module AutomationObservers
class AllStepsCompletionObserver < BaseObserver
def self.on_update(step, user)
return unless Current.team.settings.dig('team_automation_settings', 'tasks', 'task_status_completed', 'on_all_steps_completion')
return unless step.saved_change_to_completed? && step.completed
return unless step.protocol.in_module?
my_module = step.protocol.my_module
return unless my_module.my_module_status.previous_status == my_module.my_module_status_flow.initial_status && my_module.steps.where(completed: false).none?
previous_status_id = my_module.my_module_status.id
my_module.update!(my_module_status: my_module.my_module_status.next_status, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_task_status_changed,
owner: user,
team: my_module.team,
project: my_module.project,
subject: my_module,
message_items: {
my_module: my_module.id,
my_module_status_old: previous_status_id,
my_module_status_new: my_module.my_module_status.id
})
end
end
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
module AutomationObservers
class AllTasksDoneObserver < BaseObserver
def self.on_update(my_module, user)
return unless Current.team.settings.dig('team_automation_settings', 'experiments', 'experiment_status_done', 'on_all_tasks_done')
return unless my_module.saved_change_to_my_module_status_id?
return unless my_module.experiment.started?
return unless my_module.experiment.my_modules.active.joins(:my_module_status).where.not(my_module_status: MyModuleStatusFlow.first.final_status).none?
experiment = my_module.experiment
experiment.update!(status: :done, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_experiment_status_changed,
owner: user,
team: experiment.team,
project: experiment.project,
subject: experiment,
message_items: {
experiment: experiment.id,
experiment_status_old: I18n.t('experiments.table.column.status.in_progress'),
experiment_status_new: I18n.t("experiments.table.column.status.#{experiment.status}")
})
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module AutomationObservers
class BaseObserver
def self.on_create(object, user); end
def self.on_update(object, user); end
end
end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
module AutomationObservers
class ExperimentStatusChangeObserver < BaseObserver
def self.on_update(experiment, user)
return unless Current.team.settings.dig('team_automation_settings', 'projects', 'project_status_in_progress', 'on_experiment_in_progress')
return unless experiment.status_moved_forward? ||
experiment.saved_change_to_project_id ||
(experiment.saved_change_to_archived && !experiment.archived)
project = experiment.project
return unless project.not_started?
return if experiment.not_started?
project.update!(status: :in_progress, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_project_status_changed,
owner: user,
team: project.team,
subject: project,
message_items: {
project: project.id,
project_status_old: I18n.t('experiments.table.column.status.not_started'),
project_status_new: I18n.t("experiments.table.column.status.#{project.status}")
})
end
end
end

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
module AutomationObservers
class ResultCreateObserver < BaseObserver
def self.on_create(result, user)
return unless Current.team.settings.dig('team_automation_settings', 'tasks', 'task_status_in_progress', 'on_added_result')
return unless result.my_module.my_module_status.initial_status?
my_module = result.my_module
previous_status_id = my_module.my_module_status.id
my_module.update!(my_module_status: my_module.my_module_status.next_status, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_task_status_changed,
owner: user,
team: my_module.team,
project: my_module.project,
subject: my_module,
message_items: {
my_module: my_module.id,
my_module_status_old: previous_status_id,
my_module_status_new: my_module.my_module_status.id
})
end
end
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
module AutomationObservers
class StepCompletionObserver < BaseObserver
def self.on_update(step, user)
return unless Current.team.settings.dig('team_automation_settings', 'tasks', 'task_status_in_progress', 'on_step_completion')
return unless step.saved_change_to_completed? && step.completed
return unless step.protocol.in_module? && step.protocol.my_module.my_module_status.initial_status?
my_module = step.protocol.my_module
previous_status_id = my_module.my_module_status.id
my_module.update!(my_module_status: my_module.my_module_status.next_status, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_task_status_changed,
owner: user,
team: my_module.team,
project: my_module.project,
subject: my_module,
message_items: {
my_module: my_module.id,
my_module_status_old: previous_status_id,
my_module_status_new: my_module.my_module_status.id
})
end
end
end

View file

@ -0,0 +1,54 @@
# frozen_string_literal: true
module AutomationObservers
class TaskProtocolContentChangeObserver < BaseObserver
def self.on_create(element, user)
# Handle creation of an empty protocol alongside with a task
return if element.is_a?(Protocol)
on_update(element, user)
end
def self.on_update(element, user)
return unless Current.team.settings.dig('team_automation_settings', 'tasks', 'task_status_in_progress', 'on_protocol_content_change')
protocol = nil
case element.class.name
when 'Asset', 'Table'
return if element.step.blank?
protocol = element.step.protocol
when 'Checklist', 'StepText', 'StepComment', 'StepOrderableElement'
protocol = element.step.protocol
when 'ChecklistItem'
protocol = element.checklist.step.protocol
when 'FormFieldValue'
protocol = element.form_response.step.protocol
when 'Step'
protocol = element.protocol
when 'Protocol'
protocol = element
end
return if protocol.blank?
return unless protocol.in_module? && protocol.my_module.my_module_status.initial_status?
my_module = protocol.my_module
previous_status_id = my_module.my_module_status.id
my_module.update!(my_module_status: my_module.my_module_status.next_status, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_task_status_changed,
owner: user,
team: my_module.team,
project: my_module.project,
subject: my_module,
message_items: {
my_module: my_module.id,
my_module_status_old: previous_status_id,
my_module_status_new: my_module.my_module_status.id
})
end
end
end

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
module AutomationObservers
class TaskStatusChangeObserver < BaseObserver
def self.on_update(my_module, user)
return unless Current.team.settings.dig('team_automation_settings', 'experiments', 'experiment_status_in_progress', 'on_task_in_progress')
return unless my_module.saved_change_to_my_module_status_id? && !my_module.my_module_status.initial_status?
return unless my_module.experiment.not_started?
experiment = my_module.experiment
experiment.update!(status: :in_progress, last_modified_by: user)
Activities::CreateActivityService
.call(activity_type: :automation_experiment_status_changed,
owner: user,
team: experiment.team,
project: experiment.project,
subject: experiment,
message_items: {
experiment: experiment.id,
experiment_status_old: I18n.t('experiments.table.column.status.not_started'),
experiment_status_new: I18n.t("experiments.table.column.status.#{experiment.status}")
})
end
end
end

View file

@ -23,14 +23,14 @@ module Dashboard
all_activities = join_step_user_roles(all_activities)
team_activities = all_activities.where(subject_type: %w(Team RepositoryBase ProjectFolder))
project_activities = all_activities.where.not(project_user_roles: { id: nil })
report_activities = all_activities.where.not(report_project_user_roles: { id: nil })
experiment_activities = all_activities.where.not(experiment_user_roles: { id: nil })
my_module_activities = all_activities.where.not(my_module_user_roles: { id: nil })
result_activities = all_activities.where.not(result_my_module_user_roles: { id: nil })
protocol_activities = all_activities.where.not(protocol_my_module_user_roles: { id: nil })
project_activities = all_activities.where.not(project_subjects_user_roles: { id: nil })
report_activities = all_activities.where.not(report_projects_user_roles: { id: nil })
experiment_activities = all_activities.where.not(experiment_subjects_user_roles: { id: nil })
my_module_activities = all_activities.where.not(my_module_subjects_user_roles: { id: nil })
result_activities = all_activities.where.not(result_my_modules_user_roles: { id: nil })
protocol_activities = all_activities.where.not(protocol_my_modules_user_roles: { id: nil })
protocol_repository_activities = all_activities.where(project_id: nil, subject_type: 'Protocol')
step_activities = all_activities.where.not(step_my_module_user_roles: { id: nil })
step_activities = all_activities.where.not(step_my_modules_user_roles: { id: nil })
activities = team_activities.or(project_activities)
.or(report_activities)
@ -128,109 +128,128 @@ module Dashboard
private
def join_project_user_roles(activities)
activities.joins(
"LEFT OUTER JOIN projects project_subjects
ON project_subjects.id = activities.subject_id AND activities.subject_type='Project'
LEFT OUTER JOIN user_assignments project_user_assignments
ON project_user_assignments.assignable_type = 'Project'
AND project_user_assignments.assignable_id = project_subjects.id
AND project_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles project_user_roles
ON project_user_roles.id = project_user_assignments.user_role_id
AND project_user_roles.permissions @> ARRAY['#{ProjectPermissions::ACTIVITIES_READ}']::varchar[]"
)
def join_permitted_subjects(activities, join_alias, permission_model, permission)
activities.joins("
LEFT OUTER JOIN user_assignments #{join_alias}_user_assignments
ON #{join_alias}_user_assignments.assignable_type = '#{permission_model}'
AND #{join_alias}_user_assignments.assignable_id = #{join_alias}.id
AND #{join_alias}_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles #{join_alias}_user_roles
ON #{join_alias}_user_roles.id = #{join_alias}_user_assignments.user_role_id
AND #{join_alias}_user_roles.permissions @> ARRAY['#{permission}']::varchar[]
LEFT OUTER JOIN user_group_assignments #{join_alias}_user_group_assignments
ON #{join_alias}_user_group_assignments.assignable_type = '#{permission_model}'
AND #{join_alias}_user_group_assignments.assignable_id = #{join_alias}.id
LEFT OUTER JOIN user_group_memberships #{join_alias}_user_group_memberships
ON #{join_alias}_user_group_memberships.user_group_id = #{join_alias}_user_group_assignments.user_group_id
AND #{join_alias}_user_group_memberships.user_id = #{@user.id}
LEFT OUTER JOIN user_roles #{join_alias}_user_group_roles
ON #{join_alias}_user_group_roles.id = #{join_alias}_user_group_assignments.user_role_id
AND #{join_alias}_user_group_roles.permissions @> ARRAY['#{permission}']::varchar[]
LEFT OUTER JOIN team_assignments #{join_alias}_team_assignments
ON #{join_alias}_team_assignments.assignable_type = '#{permission_model}'
AND #{join_alias}_team_assignments.assignable_id = #{join_alias}.id
LEFT OUTER JOIN user_assignments #{join_alias}_team_user_assignments
ON #{join_alias}_team_user_assignments.assignable_type = 'Team'
AND #{join_alias}_team_user_assignments.assignable_id = #{join_alias}_team_assignments.team_id
AND #{join_alias}_team_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles #{join_alias}_team_user_roles
ON #{join_alias}_team_user_roles.id = #{join_alias}_team_user_assignments.user_role_id
AND #{join_alias}_team_user_roles.permissions @> ARRAY['#{permission}']::varchar[]
")
end
def join_report_project_user_roles(activities)
activities.joins(
"LEFT OUTER JOIN projects report_project_subjects
ON report_project_subjects.id = activities.project_id AND activities.subject_type='Report'
LEFT OUTER JOIN user_assignments report_project_user_assignments
ON report_project_user_assignments.assignable_type = 'Project'
AND report_project_user_assignments.assignable_id = report_project_subjects.id
AND report_project_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles report_project_user_roles
ON report_project_user_roles.id = report_project_user_assignments.user_role_id
AND report_project_user_roles.permissions @> ARRAY['#{ProjectPermissions::ACTIVITIES_READ}']::varchar[]"
def join_project_user_roles(activities)
join_permitted_subjects(
activities.joins(
"LEFT OUTER JOIN projects AS project_subjects
ON project_subjects.id = activities.subject_id AND activities.subject_type='Project'"
),
:project_subjects,
Project,
ProjectPermissions::ACTIVITIES_READ
)
end
def join_experiment_user_roles(activities)
activities.joins(
"LEFT OUTER JOIN experiments experiment_subjects
ON experiment_subjects.id = activities.subject_id AND activities.subject_type='Experiment'
LEFT OUTER JOIN user_assignments experiment_user_assignments
ON experiment_user_assignments.assignable_type = 'Experiment'
AND experiment_user_assignments.assignable_id = experiment_subjects.id
AND experiment_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles experiment_user_roles
ON experiment_user_roles.id = experiment_user_assignments.user_role_id
AND experiment_user_roles.permissions @> ARRAY['#{ExperimentPermissions::ACTIVITIES_READ}']::varchar[]"
join_permitted_subjects(
activities.joins(
"LEFT OUTER JOIN projects AS experiment_subjects
ON experiment_subjects.id = activities.subject_id AND activities.subject_type='Experiment'"
),
:experiment_subjects,
Experiment,
ExperimentPermissions::ACTIVITIES_READ
)
end
def join_my_module_user_roles(activities)
activities.joins(
"LEFT OUTER JOIN my_modules my_module_subjects
ON my_module_subjects.id = activities.subject_id AND activities.subject_type='MyModule'
LEFT OUTER JOIN user_assignments my_module_user_assignments
ON my_module_user_assignments.assignable_type = 'MyModule'
AND my_module_user_assignments.assignable_id = my_module_subjects.id
AND my_module_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles my_module_user_roles
ON my_module_user_roles.id = my_module_user_assignments.user_role_id
AND my_module_user_roles.permissions @> ARRAY['#{MyModulePermissions::ACTIVITIES_READ}']::varchar[]"
join_permitted_subjects(
activities.joins(
"LEFT OUTER JOIN my_modules AS my_module_subjects
ON my_module_subjects.id = activities.subject_id AND activities.subject_type='MyModule'"
),
:my_module_subjects,
MyModule,
MyModulePermissions::ACTIVITIES_READ
)
end
def join_report_project_user_roles(activities)
join_permitted_subjects(
activities.joins(
"LEFT OUTER JOIN projects report_projects
ON report_projects.id = activities.project_id AND activities.subject_type='Report'"
),
:report_projects,
Project,
ProjectPermissions::ACTIVITIES_READ
)
end
def join_result_user_roles(activities)
activities.joins(
"LEFT OUTER JOIN results result_subjects
ON result_subjects.id = activities.subject_id AND activities.subject_type='Result'
LEFT OUTER JOIN my_modules result_my_modules
ON result_subjects.my_module_id = result_my_modules.id
LEFT OUTER JOIN user_assignments result_my_module_user_assignments
ON result_my_module_user_assignments.assignable_type = 'MyModule'
AND result_my_module_user_assignments.assignable_id = result_my_modules.id
AND result_my_module_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles result_my_module_user_roles
ON result_my_module_user_roles.id = result_my_module_user_assignments.user_role_id
AND result_my_module_user_roles.permissions @> ARRAY['#{MyModulePermissions::ACTIVITIES_READ}']::varchar[]"
join_permitted_subjects(
activities.joins(
"LEFT OUTER JOIN results result_subjects
ON result_subjects.id = activities.subject_id AND activities.subject_type='Result'
LEFT OUTER JOIN my_modules result_my_modules
ON result_subjects.my_module_id = result_my_modules.id"
),
:result_my_modules,
MyModule,
MyModulePermissions::ACTIVITIES_READ
)
end
def join_protocol_user_roles(activities)
activities.joins(
"LEFT OUTER JOIN protocols protocol_subjects
ON protocol_subjects.id = activities.subject_id AND activities.subject_type='Protocol'
LEFT OUTER JOIN my_modules protocol_my_modules
ON protocol_subjects.my_module_id = protocol_my_modules.id
LEFT OUTER JOIN user_assignments protocol_my_module_user_assignments
ON protocol_my_module_user_assignments.assignable_type = 'MyModule'
AND protocol_my_module_user_assignments.assignable_id = protocol_my_modules.id
AND protocol_my_module_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles protocol_my_module_user_roles
ON protocol_my_module_user_roles.id = protocol_my_module_user_assignments.user_role_id
AND protocol_my_module_user_roles.permissions @> ARRAY['#{MyModulePermissions::ACTIVITIES_READ}']::varchar[]"
join_permitted_subjects(
activities.joins(
"LEFT OUTER JOIN protocols protocol_subjects
ON protocol_subjects.id = activities.subject_id AND activities.subject_type='Protocol'
LEFT OUTER JOIN my_modules protocol_my_modules
ON protocol_subjects.my_module_id = protocol_my_modules.id"
),
:protocol_my_modules,
MyModule,
MyModulePermissions::ACTIVITIES_READ
)
end
def join_step_user_roles(activities)
activities.joins(
"LEFT OUTER JOIN steps step_subjects
ON step_subjects.id = activities.subject_id AND activities.subject_type='Step'
LEFT OUTER JOIN protocols step_protocols
ON step_subjects.protocol_id = step_protocols.id
LEFT OUTER JOIN my_modules step_my_modules
ON step_protocols.my_module_id = step_my_modules.id
LEFT OUTER JOIN user_assignments step_my_module_user_assignments
ON step_my_module_user_assignments.assignable_type = 'MyModule'
AND step_my_module_user_assignments.assignable_id = step_my_modules.id
AND step_my_module_user_assignments.user_id = #{@user.id}
LEFT OUTER JOIN user_roles step_my_module_user_roles
ON step_my_module_user_roles.id = step_my_module_user_assignments.user_role_id
AND step_my_module_user_roles.permissions @> ARRAY['#{MyModulePermissions::ACTIVITIES_READ}']::varchar[]"
join_permitted_subjects(
activities.joins(
"LEFT OUTER JOIN steps step_subjects
ON step_subjects.id = activities.subject_id AND activities.subject_type='Step'
LEFT OUTER JOIN protocols step_protocols
ON step_subjects.protocol_id = step_protocols.id
LEFT OUTER JOIN my_modules step_my_modules
ON step_protocols.my_module_id = step_my_modules.id"
),
:step_my_modules,
MyModule,
MyModulePermissions::ACTIVITIES_READ
)
end

View file

@ -28,9 +28,11 @@ module SmartAnnotations
if type == 'rep_item'
repository_item(value[:name], user, team, type, object, preview_repository)
else
next unless object && SmartAnnotations::PermissionEval.check(user, type, object)
SmartAnnotations::HtmlPreview.html(nil, type, object)
if object && SmartAnnotations::PermissionEval.check(user, type, object)
SmartAnnotations::HtmlPreview.html(nil, type, object)
else
private_placeholder(object)
end
end
rescue ActiveRecord::RecordNotFound
next
@ -40,7 +42,7 @@ module SmartAnnotations
def repository_item(name, user, team, type, object, preview_repository)
if object&.repository
return unless SmartAnnotations::PermissionEval.check(user, type, object)
return private_placeholder(object) unless SmartAnnotations::PermissionEval.check(user, type, object)
return SmartAnnotations::HtmlPreview.html(nil, type, object, preview_repository)
end
@ -62,5 +64,22 @@ module SmartAnnotations
end
klass.find_by(id: id)
end
def private_placeholder(object = nil)
label = case object
when Project
I18n.t('smart_annotations.private.project')
when Experiment
I18n.t('smart_annotations.private.experiment')
when MyModule
I18n.t('smart_annotations.private.my_module')
when RepositoryRow
I18n.t('smart_annotations.private.repository_row')
else
I18n.t('smart_annotations.private.object')
end
"<span class=\"text-sn-grey\">#{label}</span>"
end
end
end

View file

@ -29,9 +29,11 @@ module SmartAnnotations
if type == 'rep_item'
repository_item(value[:name], user, team, type, object, is_shared_object)
else
next unless object && (is_shared_object || SmartAnnotations::PermissionEval.check(user, type, object))
SmartAnnotations::TextPreview.text(nil, type, object)
if object && (is_shared_object || SmartAnnotations::PermissionEval.check(user, type, object))
SmartAnnotations::TextPreview.text(nil, type, object)
else
private_placeholder(object)
end
end
rescue ActiveRecord::RecordNotFound
next
@ -56,7 +58,7 @@ module SmartAnnotations
def repository_item(name, user, team, type, object, is_shared_object)
if object
return unless is_shared_object || SmartAnnotations::PermissionEval.check(user, type, object)
return private_placeholder(object) unless is_shared_object || SmartAnnotations::PermissionEval.check(user, type, object)
return SmartAnnotations::TextPreview.text(nil, type, object)
end
@ -78,5 +80,20 @@ module SmartAnnotations
end
klass.find_by_id(id)
end
def private_placeholder(object = nil)
case object
when Project
I18n.t('smart_annotations.private.project')
when Experiment
I18n.t('smart_annotations.private.experiment')
when MyModule
I18n.t('smart_annotations.private.my_module')
when RepositoryRow
I18n.t('smart_annotations.private.repository_row')
else
I18n.t('smart_annotations.private.object')
end
end
end
end

View file

@ -35,7 +35,7 @@
<%= f.label :published_on_label,
t('protocols.import_export.import_modal.published_on_label'),
:"data-e2e" => "e2e-TX-protocolTemplates-previewProtocolsIo-publishedOnLabel" %>
<%= f.text_field :published_on_label,
<%= f.text_field :published_on_label,
value: I18n.l(protocol.published_on, format: :full),
class: 'form-control',
disabled: true,
@ -50,6 +50,7 @@
<%= f.hidden_field(:protocol_type, value: protocol.protocol_type) %>
<%= f.hidden_field(:visibility) %>
<%= f.hidden_field(:default_public_user_role_id) %>
<%= hidden_field_tag(:source, source) %>
<% end %>

View file

@ -38,7 +38,14 @@
<ul id="ResultsMenuDropdown<%= step.id %>" class="dropdown-menu dropdown-menu-right px-4 py-2">
<% step.results.each do |result| %>
<li class="dropdown-item">
<%= link_to result.name, shared_protocol_results_path(@shareable_link.uuid), class: "!py-2.5 !px-3 hover:!bg-sn-super-light-grey !cursor-pointer !block hover:!no-underline !text-sn-blue !truncate" %>
<%= link_to shared_protocol_results_path(@shareable_link.uuid, result_id: result.id, anchor: "resultContainer#{result.id}"), class: "!py-2.5 !px-3 hover:!bg-sn-super-light-grey !cursor-pointer hover:!no-underline !text-sn-blue !truncate !flex items-center gap-4 #{'disabled' if result.archived?}" do %>
<span class="leading-4"><%= result.name %></span>
<% if result.archived? %>
<div class="py-1 px-2 text-xs ml-auto text-white bg-sn-grey rounded-full ">
<%= I18n.t('protocols.steps.archived_result') %>
</div>
<% end %>
<% end %>
</li>
<% end %>
</ul>

View file

@ -1,4 +1,4 @@
<div class="result-container bg-white p-4 mb-4 rounded">
<div class="result-container bg-white p-4 mb-4 rounded" id="resultContainer<%= result.id %>">
<div class="result-header flex justify-between">
<div class="result-head-left flex items-start flex-grow gap-4">
<a class="result-collapse-link hover:no-underline focus:no-underline text-sn-black" href="#result-panel-<%= result.id %>" data-toggle="collapse">

View file

@ -0,0 +1,12 @@
<% provide(:head_title, t("users.settings.account.preferences.head_title")) %>
<div class="content-pane flexible with-grey-background">
<%= render partial: 'users/settings/teams/header' %>
<div id="team_automations" class="contents">
<team-automations
:team-url="'<%= settings_team_path(@team) %>'"
/>
</div>
</div>
<%= javascript_include_tag "vue_team_automations" %>

View file

@ -21,4 +21,5 @@
<%= link_to t("users.settings.teams.navigation.details"), team_path(@team), class: "p-2.5 hover:no-underline #{ 'disabled' unless can_read_team?(@team) } #{ @active_tab == :details ? "text-sn-blue" : "text-sn-grey" }"%>
<%= link_to t("users.settings.teams.navigation.members"), members_users_settings_team_path(@team), class: "p-2.5 hover:no-underline #{ @active_tab == :members ? "text-sn-blue" : "text-sn-grey" }"%>
<%= link_to t("users.settings.teams.navigation.groups"), users_settings_team_user_groups_path(@team), class: "p-2.5 hover:no-underline #{ 'disabled' unless can_manage_team?(@team) } #{ @active_tab == :user_groups ? "text-sn-blue" : "text-sn-grey" }"%>
<%= link_to t("users.settings.sidebar.account_nav.automations"), automations_team_path(@team), class: "p-2.5 hover:no-underline #{ 'disabled' unless can_manage_team?(@team) } #{ @active_tab == :automations ? "text-sn-blue" : "text-sn-grey" }"%>
</div>

View file

@ -620,26 +620,30 @@ class Extends
repository_access_granted_user_group: 403,
repository_access_changed_user_group: 404,
repository_access_revoked_user_group: 405,
experiment_access_changed_all_team_members: 406,
my_module_access_changed_all_team_members: 407
automation_task_status_changed: 406,
automation_experiment_status_changed: 407,
automation_project_status_changed: 408,
import_protocol_in_repository_from_protocols_io: 409,
experiment_access_changed_all_team_members: 410,
my_module_access_changed_all_team_members: 411
}
ACTIVITY_GROUPS = {
projects: [*0..7, 32, 33, 34, 95, 108, 65, 109, *158..162, 241, 242, 243, *370..378, *390..392],
projects: [*0..7, 32, 33, 34, 95, 108, 65, 109, *158..162, 241, 242, 243, *370..378, *390..392, 408],
task_results: [23, 26, 25, 42, 24, 40, 41, 99, 110, 122, 116, 128, *246..248, *257..273, *284..291, 301, 303, 306, 328],
task: [8, 58, 9, 59, *10..14, 35, 36, 37, 53, 54, *60..63, 138, 139, 140, 64, 66, 106, 126, 120, 132,
148, 166, 394, 395, 396, 407],
148, 166, 394, 395, 396, 406, 411],
task_protocol: [15, 22, 16, 18, 19, 20, 21, 17, 38, 39, 100, 111, 45, 46, 47, 121, 124, 115, 118, 127, 130, 137,
184, 185, 188, 189, *192..203, 221, 222, 224, 225, 226, 236, *249..252, *274..278, 299, 302, 305, 327, *347..352, 359],
task_inventory: [55, 56, 146, 147, 183],
experiment: [*27..31, 57, 141, 165, *363..369, 393, 406],
experiment: [*27..31, 57, 141, 165, *363..369, 393, 407, 410],
reports: [48, 50, 49, 163, 164],
inventories: [70, 71, 105, 144, 145, 72, 73, 74, 102, 142, 143, 75, 76, 77,
78, 96, 107, 113, 114, *133..136, 180, 181, 182, *292..298, 308, 329, *397..405],
protocol_repository: [80, 103, 89, 87, 79, 90, 91, 88, 85, 86, 84, 81, 82,
83, 101, 112, 123, 125, 117, 119, 129, 131, 187, 186,
190, 191, *204..215, 220, 223, 227, 228, 229, *230..235,
*237..240, *253..256, *279..283, 300, 304, 307, 330, *353..355, 360, *387..389],
*237..240, *253..256, *279..283, 300, 304, 307, 330, *353..355, 360, *387..389, 409],
team: [92, 94, 93, 97, 104, 244, 245, *379..383],
label_templates: [*216..219],
storage_locations: [*309..315, 361],
@ -785,6 +789,7 @@ class Extends
teams/members
user_groups/index
user_groups/show
teams/automations
)
DEFAULT_USER_NOTIFICATION_SETTINGS = {
@ -802,6 +807,53 @@ class Extends
}
}
TEAM_AUTOMATIONS_GROUPS = {
tasks: {
task_status_in_progress: %I[
on_protocol_content_change
on_step_completion
on_added_result
],
task_status_completed: %I[
on_all_steps_completion
]
},
experiments: {
experiment_status_in_progress: %I[
on_task_in_progress
],
experiment_status_done: %I[
on_all_tasks_done
]
},
projects: {
project_status_in_progress: %I[
on_experiment_in_progress
],
project_status_done: %I[
on_all_experiments_done
]
}
}
TEAM_AUTOMATIONS_OBSERVERS_CONFIG = {
'Experiment' => ['AutomationObservers::AllExperimentsDoneObserver', 'AutomationObservers::ExperimentStatusChangeObserver'],
'MyModule' => ['AutomationObservers::AllTasksDoneObserver', 'AutomationObservers::TaskStatusChangeObserver'],
'Protocol' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'Asset' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'Table' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'StepText' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'ChecklistItem' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'Checklist' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'FormFieldValue' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'StepOrderableElement' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'StepComment' => ['AutomationObservers::TaskProtocolContentChangeObserver'],
'Step' => ['AutomationObservers::AllStepsCompletionObserver', 'AutomationObservers::StepCompletionObserver', 'AutomationObservers::TaskProtocolContentChangeObserver'],
'Result' => ['AutomationObservers::ResultCreateObserver']
}
DEFAULT_TEAM_SETTINGS = {}
WHITELISTED_USER_SETTINGS = %w(
LabelTemplates_active_state
LabelTemplates_archived_state
@ -822,6 +874,8 @@ class Extends
StorageLocationsTable_active_state
StorageLocationsContainer_active_state
StorageLocationsContainerGrid_active_state
UserGroups_active_state
UserGroup_active_state
task_step_states
results_order
repository_export_file_type

View file

@ -428,6 +428,14 @@ en:
activities: "Activity"
archive: "Archived results"
smart_annotations:
private:
project: "#private project"
experiment: "#private experiment"
my_module: "#private task"
repository_row: "#private inventory item"
object: "#private object"
attachments:
menu:
office_file: "New Microsoft 365 file"
@ -3479,6 +3487,7 @@ en:
preferences: "My preferences"
addons: "Add-ons"
connected_accounts: "Connected accounts"
automations: "Automations"
account:
preferences:
head_title: "Settings | My preferences"
@ -4056,6 +4065,7 @@ en:
timestamp: "Created on %{date} by %{user}"
timestamp_iso_html: "Created on <span class='iso-formatted-date'>%{date}</span> by %{user}"
manage_links: "Manage links"
archived_result: "Archived"
link_results: "Link results to this step"
linked_results: "Linked results"
status:
@ -4317,6 +4327,30 @@ en:
time: "%H:%M"
short: "%H"
team_automations:
description: "Automate repetitive work to enhance efficiency. Enabled settings apply to all projects, experiments, and tasks in your current workspace. Automations start when enabled and don't affect past data. Manual changes remain available and will override automated updates."
groups:
tasks: 'Task automations'
experiments: 'Experiment automations'
projects: 'Project automations'
sub_groups:
task_status_in_progress: 'Automatically update task status to "In progress" when:'
task_status_completed: 'Automatically update task status to “Completed” when:'
task_status_done: 'Automatically update task status to "Done" when:'
experiment_status_in_progress: 'Automatically update experiment status to "In progress" when:'
experiment_status_done: 'Automatically update experiment status to "Done" when:'
project_status_in_progress: 'Automatically update project status to "In progress" when:'
project_status_done: 'Automatically update project status to "Done" when:'
sub_group_element:
on_protocol_content_change: 'Protocol content is added (including step comments)'
on_step_completion: 'At least one step is marked as completed'
on_added_result: 'Task result is added'
on_task_in_progress: 'At least one task moves from "Not started" to "In progress" or other status'
on_all_tasks_done: 'All tasks inside reach their final status.'
on_experiment_in_progress: 'At least one experiment moves to "In progress" or "Done"'
on_all_experiments_done: 'All experiments inside are marked as "Done"'
on_all_steps_completion: 'All steps are marked as done'
notifications:
title: "Notifications"
sub_title: "Select the updates you want to be notified about and where you receive them."
@ -4919,6 +4953,7 @@ en:
label_printer: "Label printer"
fluics_printer: "Fluics printer"
forms: "Forms"
settings: "Settings"
Add: "Add"
Asset: "File"

View file

@ -417,6 +417,14 @@ en:
repository_access_revoked_html: "%{user} removed %{user_target} with user role %{role} from inventory %{repository}."
repository_access_granted_all_team_members_html: "%{user} granted access to all workspace members of %{team} workspace with user role %{role} to inventory %{repository}."
repository_access_changed_all_team_members_html: "%{user} changed %{team}s role on inventory %{repository} to %{role}."
repository_access_revoked_all_team_members_html: "%{user} removed %{team} team members with user role %{role} from inventory %{repository}."
repository_access_granted_user_group_html: "%{user} granted access to %{user_group} with user role %{role} to inventory template %{repository}."
repository_access_changed_user_group_html: "%{user} changed %{user_group}'s role on inventory template %{repository} to %{role}."
repository_access_revoked_user_group_html: "%{user} removed group %{user_group} with user role %{role} from inventory template %{repository}."
automation_task_status_changed_html: "%{user} triggered automatic status change from <strong>%{my_module_status_old}</strong> to <strong>%{my_module_status_new}</strong> for task %{my_module}."
automation_experiment_status_changed_html: "%{user} triggered automatic status change from <strong>%{experiment_status_old}</strong> to <strong>%{experiment_status_new}</strong> for experiment %{experiment}."
automation_project_status_changed_html: "%{user} triggered automatic status change from <strong>%{project_status_old}</strong> to <strong>%{project_status_new}</strong> for project %{project}."
import_protocol_in_repository_from_protocols_io_html: "%{user} imported protocol %{protocol} to Protocol repository from protocols.io."
repository_access_revoked_all_team_members_html: "%{user} removed %{team} workspace members with user role %{role} from inventory %{repository}."
repository_access_granted_user_group_html: "%{user} granted access to %{user_group} with user role %{role} to inventory %{repository}."
repository_access_changed_user_group_html: "%{user} changed %{user_group}'s role on inventory %{repository} to %{role}."
@ -800,6 +808,10 @@ en:
repository_access_granted_user_group: "Grant access to group"
repository_access_changed_user_group: "Change role of group"
repository_access_revoked_user_group: "Remove access to group"
automation_task_status_changed: "Task status changed automatically"
automation_experiment_status_changed: "Experiment status changed automatically"
automation_project_status_changed: "Project status changed automatically"
import_protocol_in_repository_from_protocols_io: "Protocol imported from protocols.io"
experiment_access_changed_all_team_members: "Change role of all workspace members"
my_module_access_changed_all_team_members: "Change role of all workspace members"
activity_group:

View file

@ -275,6 +275,9 @@ Rails.application.routes.draw do
get 'atwho_experiments', to: 'at_who#experiments'
get 'atwho_my_modules', to: 'at_who#my_modules'
get 'atwho_menu_items', to: 'at_who#menu_items'
get :automations
get :settings
put :update_settings
end
# External protocols routes

View file

@ -76,7 +76,8 @@ const entryList = {
vue_favorites_widget: './app/javascript/packs/vue/favorites_widget.js',
vue_experiment_description_modal: './app/javascript/packs/vue/experiment_description_modal.js',
vue_user_groups_table: './app/javascript/packs/vue/user_groups_table.js',
vue_user_groups_show: './app/javascript/packs/vue/user_groups_show.js'
vue_user_groups_show: './app/javascript/packs/vue/user_groups_show.js',
vue_team_automations: './app/javascript/packs/vue/team_automations.js'
};
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddSettingTeamColumns < ActiveRecord::Migration[7.0]
def change
add_column :teams, :settings, :jsonb, default: {}, null: false
# rubocop:disable Rails/SkipsModelValidations
Team.find_each do |team|
team.update_columns(settings: Extends::DEFAULT_TEAM_SETTINGS)
end
# rubocop:enable Rails/SkipsModelValidations
end
end

View file

@ -1335,6 +1335,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_08_27_145326) do
t.string "description"
t.bigint "space_taken", default: 1048576, null: false
t.boolean "shareable_links_enabled", default: false, null: false
t.jsonb "settings", default: {}, null: false
t.index ["created_by_id"], name: "index_teams_on_created_by_id"
t.index ["last_modified_by_id"], name: "index_teams_on_last_modified_by_id"
t.index ["name"], name: "index_teams_on_name"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -26,8 +26,4 @@ describe StepComment, type: :model do
describe 'Relations' do
it { should belong_to :step }
end
describe 'Validations' do
it { should validate_presence_of :step }
end
end