sapiopycommons 2024.10.25a345__py3-none-any.whl → 2024.10.30a348__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sapiopycommons might be problematic. Click here for more details.

@@ -57,9 +57,9 @@ class ExperimentHandler:
57
57
  # additional queries to obtain, but may also be repeatedly accessed. In such cases, cache the information after it
58
58
  # has been requested so that the user doesn't need to worry about caching it themselves.
59
59
  # CR-46341: Replace class variables with instance variables.
60
- __exp_record: DataRecord
60
+ __exp_record: DataRecord | None
61
61
  """The data record for this experiment. Only cached when first accessed."""
62
- __exp_template: ElnTemplate
62
+ __exp_template: ElnTemplate | None
63
63
  """The template for this experiment. Only cached when first accessed."""
64
64
  __exp_options: dict[str, str]
65
65
  """Experiment options for this experiment. Only cached when first accessed."""
@@ -277,6 +277,7 @@ class ExperimentHandler:
277
277
  """
278
278
  template_id: int | None = self.__eln_exp.template_id
279
279
  if template_id is None:
280
+ self.__exp_template = None
280
281
  if exception_on_none:
281
282
  raise SapioException(f"Experiment with ID {self.__exp_id} has no template ID.")
282
283
  return None
@@ -635,6 +636,9 @@ class ExperimentHandler:
635
636
  if not records:
636
637
  return
637
638
  dt: str = AliasUtil.to_singular_data_type_name(records)
639
+ if ElnBaseDataType.is_base_data_type(dt):
640
+ raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
641
+ f"Use add_eln_rows or add_sample_details instead.")
638
642
  if dt != step.get_data_type_names()[0]:
639
643
  raise SapioException(f"Cannot add {dt} records to entry {step.get_name()} of type "
640
644
  f"{step.get_data_type_names()[0]}.")
@@ -659,6 +663,9 @@ class ExperimentHandler:
659
663
  if not records:
660
664
  return
661
665
  dt: str = AliasUtil.to_singular_data_type_name(records)
666
+ if ElnBaseDataType.is_base_data_type(dt):
667
+ raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
668
+ f"Use remove_eln_rows or remove_sample_details instead.")
662
669
  if dt != step.get_data_type_names()[0]:
663
670
  raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
664
671
  f"{step.get_data_type_names()[0]}.")
@@ -687,6 +694,9 @@ class ExperimentHandler:
687
694
  step = self.__to_eln_step(step)
688
695
  if records:
689
696
  dt: str = AliasUtil.to_singular_data_type_name(records)
697
+ if ElnBaseDataType.is_base_data_type(dt):
698
+ raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
699
+ f"Use add_eln_rows or add_sample_details instead.")
690
700
  if dt != step.get_data_type_names()[0]:
691
701
  raise SapioException(f"Cannot set {dt} records for entry {step.get_name()} of type "
692
702
  f"{step.get_data_type_names()[0]}.")
@@ -779,7 +789,7 @@ class ExperimentHandler:
779
789
  raise SapioException("The provided step is not an ELN data type entry.")
780
790
  if not records:
781
791
  return
782
- record_dt: str = AliasUtil.to_singular_data_type_name(records)
792
+ record_dt: str = AliasUtil.to_singular_data_type_name(records, False)
783
793
  if record_dt != dt:
784
794
  raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
785
795
  f"{step.get_data_type_names()[0]}.")
@@ -1,7 +1,9 @@
1
1
  from sapiopylib.rest.ELNService import ElnManager
2
2
  from sapiopylib.rest.User import SapioUser
3
+ from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, AbstractReportTerm, RawReportTerm
3
4
  from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
4
5
  from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, ElnExperimentQueryCriteria
6
+ from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnExperimentStatus, ElnBaseDataType
5
7
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
6
8
 
7
9
  from sapiopycommons.customreport.custom_report_builder import CustomReportBuilder
@@ -11,9 +13,131 @@ from sapiopycommons.datatype.pseudo_data_types import EnbEntryOptionsPseudoDef,
11
13
  from sapiopycommons.general.aliases import SapioRecord, UserIdentifier, AliasUtil, FieldValue, \
12
14
  ExperimentEntryIdentifier, ExperimentIdentifier
13
15
  from sapiopycommons.general.custom_report_util import CustomReportUtil
16
+ from sapiopycommons.general.exceptions import SapioException
14
17
  from sapiopycommons.recordmodel.record_handler import RecordHandler
15
18
 
16
19
 
20
+ # FR-47234: Create an ExperimentReportCriteria class for finer experiment searching
21
+ # and create/update ExperimentReportUtil functions to use it.
22
+ class ExperimentReportCriteria:
23
+ """
24
+ Experiment report criteria is used to restrict the results of searches for experiments.
25
+ """
26
+ notebook_ids: list[int] | None
27
+ not_notebook_ids: list[int] | None
28
+ names: list[str] | None
29
+ not_names: list[str] | None
30
+ created_by: list[str] | None
31
+ not_created_by: list[str] | None
32
+ last_modified_by: list[str] | None
33
+ not_last_modified_by: list[str] | None
34
+ owners: list[str] | None
35
+ not_owners: list[str] | None
36
+ statuses: list[str] | None
37
+ not_statuses: list[str] | None
38
+ templates: str | None
39
+ not_templates: str | None
40
+ is_from_template: bool | None
41
+ is_from_protocol_template: bool | None
42
+ is_modifiable: bool | None
43
+ is_active: bool | None
44
+ created_before: int | None
45
+ created_after: int | None
46
+ due_before: int | None
47
+ due_after: int | None
48
+ last_modified_before: int | None
49
+ last_modified_after: int | None
50
+
51
+ def __init__(self, *,
52
+ notebook_ids: list[ExperimentIdentifier] | None = None,
53
+ not_notebook_ids: list[ExperimentIdentifier] | None = None,
54
+ names: list[str] | None = None,
55
+ not_names: list[str] | None = None,
56
+ created_by: list[str] | None = None,
57
+ not_created_by: list[str] | None = None,
58
+ last_modified_by: list[str] | None = None,
59
+ not_last_modified_by: list[str] | None = None,
60
+ owners: list[str] | None = None,
61
+ not_owners: list[str] | None = None,
62
+ statuses: list[ElnExperimentStatus | str] | None = None,
63
+ not_statuses: list[ElnExperimentStatus | str] | None = None,
64
+ templates: list[str] | None = None,
65
+ not_templates: list[str] | None = None,
66
+ is_from_template: bool | None = None,
67
+ is_from_protocol_template: bool | None = None,
68
+ is_modifiable: bool | None = None,
69
+ is_active: bool | None = None,
70
+ created_after: int | None = None,
71
+ created_before: int | None = None,
72
+ due_after: int | None = None,
73
+ due_before: int | None = None,
74
+ last_modified_after: int | None = None,
75
+ last_modified_before: int | None = None):
76
+ """
77
+ Restrict searches using the following criteria.
78
+
79
+ :param notebook_ids: The allowed notebook ID(s) of the experiment.
80
+ :param not_notebook_ids: The disallowed notebook ID(s) of the experiment.
81
+ :param names: The allowed name(s) of the experiment.
82
+ :param not_names: The disallowed name(s) of the experiment.
83
+ :param created_by: The allowed username(s) of the user who created the experiment.
84
+ :param not_created_by: The disallowed username(s) of the user who created the experiment.
85
+ :param last_modified_by: The allowed username(s) of the user who last modified the experiment.
86
+ :param not_last_modified_by: The disallowed username(s) of the user who last modified the experiment.
87
+ :param owners: The allowed username(s) of the user who owns the experiment.
88
+ :param not_owners: The disallowed username(s) of the user who owns the experiment.
89
+ :param statuses: The allowed status(es) of the experiment.
90
+ :param not_statuses: The disallowed status(es) of the experiment.
91
+ :param templates: The allowed template name(s) that the experiment was created from.
92
+ :param not_templates: The disallowed template name(s) that the experiment was created from.
93
+ :param is_from_template: Whether the experiment was created from a template.
94
+ :param is_from_protocol_template: Whether the experiment was created from a protocol template.
95
+ :param is_modifiable: Whether the experiment is modifiable.
96
+ :param is_active: Whether the experiment is from an active template.
97
+ :param created_after: A timestamp after which the experiment was created.
98
+ :param created_before: A timestamp before which the experiment was created.
99
+ :param due_after: A timestamp after which the experiment's approval is due.
100
+ :param due_before: A timestamp before which the experiment's approval is due.
101
+ :param last_modified_after: A timestamp after which the experiment last modified.
102
+ :param last_modified_before: A timestamp before which the experiment last modified.
103
+ """
104
+ self.notebook_ids = notebook_ids
105
+ self.not_notebook_ids = not_notebook_ids
106
+ self.names = names
107
+ self.not_names = not_names
108
+ self.created_by = created_by
109
+ self.not_created_by = not_created_by
110
+ self.last_modified_by = last_modified_by
111
+ self.not_last_modified_by = not_last_modified_by
112
+ self.owners = owners
113
+ self.not_owners = not_owners
114
+ self.statuses = statuses
115
+ self.not_statuses = not_statuses
116
+ self.templates = templates
117
+ self.not_templates = not_templates
118
+
119
+ self.is_from_template = is_from_template
120
+ self.is_from_protocol_template = is_from_protocol_template
121
+ self.is_modifiable = is_modifiable
122
+ self.is_active = is_active
123
+
124
+ self.created_before = created_before
125
+ self.created_after = created_after
126
+ self.due_before = due_before
127
+ self.due_after = due_after
128
+ self.last_modified_before = last_modified_before
129
+ self.last_modified_after = last_modified_after
130
+
131
+ if self.notebook_ids is not None:
132
+ self.notebook_ids = AliasUtil.to_notebook_ids(self.notebook_ids)
133
+ if self.not_notebook_ids is not None:
134
+ self.not_notebook_ids = AliasUtil.to_notebook_ids(self.not_notebook_ids)
135
+ if self.statuses is not None:
136
+ self.statuses = [x.description if isinstance(x, ElnExperimentStatus) else x for x in self.statuses]
137
+ if self.not_statuses is not None:
138
+ self.not_statuses = [x.description if isinstance(x, ElnExperimentStatus) else x for x in self.not_statuses]
139
+
140
+
17
141
  # FR-46908 - Provide a utility class that holds experiment related custom reports e.g. getting all the experiments
18
142
  # that given records were used in or getting all records of a datatype used in given experiments.
19
143
  class ExperimentReportUtil:
@@ -34,7 +158,7 @@ class ExperimentReportUtil:
34
158
  user: SapioUser = AliasUtil.to_sapio_user(context)
35
159
  data_type_name: str = AliasUtil.to_singular_data_type_name(records)
36
160
 
37
- record_ids = [record.record_id for record in records]
161
+ record_ids: list[int] = AliasUtil.to_record_ids(records)
38
162
  rows = ExperimentReportUtil.__get_record_experiment_relation_rows(user, data_type_name, record_ids=record_ids)
39
163
 
40
164
  id_to_record: dict[int, SapioRecord] = RecordHandler.map_by_id(records)
@@ -47,6 +171,45 @@ class ExperimentReportUtil:
47
171
 
48
172
  return {record: list(exps) for record, exps in record_to_exps.items()}
49
173
 
174
+ @staticmethod
175
+ def map_eln_records_to_experiment_ids(context: UserIdentifier, records: list[SapioRecord]) \
176
+ -> dict[SapioRecord, int]:
177
+ """
178
+ Return a dictionary mapping each ELN record to the ID of the experiments that each were used in.
179
+
180
+ :param context: The current webhook context or a user object to send requests from.
181
+ :param records: A list of ELN data type records. They are permitted to be of mixed data type names.
182
+ :return: A dictionary mapping each record to the ID of the experiment it was used in.
183
+ """
184
+ if not records:
185
+ return {}
186
+
187
+ data_types: set[str] = AliasUtil.to_data_type_names(records, True, False)
188
+ for data_type in data_types:
189
+ if not ElnBaseDataType.is_eln_type(data_type):
190
+ raise SapioException(f"Data type {data_type} is not an ELN data type.")
191
+
192
+ dt_to_record: dict[str, list[SapioRecord]] = {}
193
+ for record in records:
194
+ dt_to_record.setdefault(AliasUtil.to_data_type_name(record, False), []).append(record)
195
+
196
+ report_builder = CustomReportBuilder(EnbEntryPseudoDef.DATA_TYPE_NAME)
197
+ report_builder.add_column(EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME)
198
+ report_builder.add_column(EnbEntryPseudoDef.EXPERIMENT_ID__FIELD_NAME)
199
+ report_builder.set_root_term(TermBuilder.is_term(EnbEntryPseudoDef.DATA_TYPE_NAME,
200
+ EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME,
201
+ data_types))
202
+ criteria = report_builder.build_report_criteria()
203
+
204
+ ret_val: dict[SapioRecord, int] = {}
205
+ rows: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, criteria)
206
+ for row in rows:
207
+ dt: str = row[EnbEntryPseudoDef.DATA_TYPE_NAME__FIELD_NAME.field_name]
208
+ exp_id: int = row[EnbEntryPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
209
+ for record in dt_to_record[dt]:
210
+ ret_val[record] = exp_id
211
+ return ret_val
212
+
50
213
  @staticmethod
51
214
  def map_experiments_to_records_of_type(context: UserIdentifier, exp_ids: list[ExperimentIdentifier],
52
215
  wrapper_type: type[WrappedType]) -> dict[int, list[WrappedType]]:
@@ -148,52 +311,299 @@ class ExperimentReportUtil:
148
311
  return options
149
312
 
150
313
  @staticmethod
151
- def get_experiments_by_name(context: UserIdentifier, name: str) -> list[ElnExperiment]:
314
+ def get_template_names_for_experiments(context: UserIdentifier, experiments: list[ExperimentIdentifier]) \
315
+ -> dict[int, str]:
316
+ """
317
+ Run a custom report to retrieve the template names for all the provided experiments.
318
+
319
+ :param context: The current webhook context or a user object to send requests from.
320
+ :param experiments: The experiment identifiers to retrieve the template names for.
321
+ :return: A dictionary mapping the notebook experiment ID to the template name that the experiment was created from.
322
+ """
323
+ exp_ids: list[int] = AliasUtil.to_notebook_ids(experiments)
324
+
325
+ report_builder = CustomReportBuilder(NotebookExperimentPseudoDef.DATA_TYPE_NAME)
326
+ root = TermBuilder.is_term(NotebookExperimentPseudoDef.DATA_TYPE_NAME,
327
+ NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
328
+ exp_ids)
329
+ report_builder.add_join(TermBuilder.compare_is_term("ELNExperiment",
330
+ "RecordId",
331
+ NotebookExperimentPseudoDef.DATA_TYPE_NAME,
332
+ NotebookExperimentPseudoDef.EXPERIMENT_RECORD_ID__FIELD_NAME))
333
+ report_builder.set_root_term(root)
334
+ report_builder.add_column(NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME)
335
+ report_builder.add_column("TemplateExperimentName", FieldType.STRING, data_type="ELNExperiment")
336
+ report = report_builder.build_report_criteria()
337
+
338
+ ret_val: dict[int, str] = {}
339
+ results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(context, report)
340
+ for row in results:
341
+ exp_id: int = row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name]
342
+ name: str = row["TemplateExperimentName"]
343
+ ret_val[exp_id] = name
344
+ return ret_val
345
+
346
+ @staticmethod
347
+ def get_experiments_for_criteria(context: UserIdentifier, criteria: ExperimentReportCriteria) -> list[ElnExperiment]:
348
+ """
349
+ Run a custom report that retrieves every experiment in the system for the given search criteria.
350
+
351
+ :param context: The current webhook context or a user object to send requests from.
352
+ :param criteria: The search criteria to query for experiments with.
353
+ :return: A list of every experiment in the system that matches the search criteria.
354
+ """
355
+ report: CustomReportCriteria = ExperimentReportUtil.build_experiment_id_report(criteria)
356
+ return ExperimentReportUtil.get_experiments_for_report(context, report)
357
+
358
+ @staticmethod
359
+ def get_experiments_by_name(context: UserIdentifier, name: str,
360
+ criteria: ExperimentReportCriteria = ExperimentReportCriteria()) -> list[ElnExperiment]:
152
361
  """
153
362
  Run a custom report that retrieves every experiment in the system with a given name.
154
363
 
155
364
  :param context: The current webhook context or a user object to send requests from.
156
365
  :param name: The name of the experiment to query for.
157
- :return: A list of every experiment in the system with a name that matches the input.
366
+ :param criteria: Additional search criteria to filter the results.
367
+ :return: A list of every experiment in the system with a name that matches the input
368
+ that matches the given criteria.
158
369
  """
159
- return ExperimentReportUtil.get_experiments_by_names(context, [name])[name]
370
+ return ExperimentReportUtil.get_experiments_by_names(context, [name], criteria)[name]
160
371
 
161
372
  @staticmethod
162
- def get_experiments_by_names(context: UserIdentifier, names: list[str]) -> dict[str, list[ElnExperiment]]:
373
+ def get_experiments_by_names(context: UserIdentifier, names: list[str],
374
+ criteria: ExperimentReportCriteria = ExperimentReportCriteria()) -> dict[str, list[ElnExperiment]]:
163
375
  """
164
376
  Run a custom report that retrieves every experiment in the system with a name from a list of names.
165
377
 
166
378
  :param context: The current webhook context or a user object to send requests from.
167
379
  :param names: The names of the experiment to query for.
168
- :return: A dictionary mapping the experiment name to a list of every experiment in the system with that name.
380
+ :param criteria: Additional search criteria to filter the results.
381
+ :return: A dictionary mapping the experiment name to a list of every experiment in the system with that name
382
+ that matches the given criteria.
169
383
  """
170
- user = AliasUtil.to_sapio_user(context)
384
+ criteria.names = names
385
+ experiments: list[ElnExperiment] = ExperimentReportUtil.get_experiments_for_criteria(context, criteria)
171
386
 
172
- report_builder = CustomReportBuilder(NotebookExperimentPseudoDef.DATA_TYPE_NAME)
387
+ # Ensure that each name appears in the dictionary, even if it has no experiments.
388
+ ret_val: dict[str, list[ElnExperiment]] = {x: [] for x in names}
389
+ for experiment in experiments:
390
+ ret_val.get(experiment.notebook_experiment_name).append(experiment)
391
+ return ret_val
392
+
393
+ @staticmethod
394
+ def get_experiments_by_owner(context: UserIdentifier, owner: str,
395
+ criteria: ExperimentReportCriteria = ExperimentReportCriteria()) -> list[ElnExperiment]:
396
+ """
397
+ Run a custom report that retrieves every experiment in the system with a given owner.
398
+
399
+ :param context: The current webhook context or a user object to send requests from.
400
+ :param owner: The username of the owner of the experiments to query for.
401
+ :param criteria: Additional search criteria to filter the results.
402
+ :return: A list of every experiment in the system with the owner that matches the input
403
+ that matches the given criteria.
404
+ """
405
+ return ExperimentReportUtil.get_experiments_by_owners(context, [owner], criteria)[owner]
406
+
407
+ @staticmethod
408
+ def get_experiments_by_owners(context: UserIdentifier, owners: list[str],
409
+ criteria: ExperimentReportCriteria = ExperimentReportCriteria) \
410
+ -> dict[str, list[ElnExperiment]]:
411
+ """
412
+ Run a custom report that retrieves every experiment in the system with a given list of owners.
413
+
414
+ :param context: The current webhook context or a user object to send requests from.
415
+ :param owners: The usernames of the owner of the experiments to query for.
416
+ :param criteria: Additional search criteria to filter the results.
417
+ :return: A dictionary mapping the owner username to a list of every experiment in the system from that owner
418
+ that matches the given criteria.
419
+ """
420
+ criteria.owners = owners
421
+ experiments: list[ElnExperiment] = ExperimentReportUtil.get_experiments_for_criteria(context, criteria)
422
+
423
+ # Ensure that each name appears in the dictionary, even if it has no experiments.
424
+ ret_val: dict[str, list[ElnExperiment]] = {x: [] for x in owners}
425
+ for experiment in experiments:
426
+ ret_val.get(experiment.owner).append(experiment)
427
+ return ret_val
428
+
429
+ @staticmethod
430
+ def get_experiments_by_creator(context: UserIdentifier, created_by: str,
431
+ criteria: ExperimentReportCriteria = ExperimentReportCriteria()) \
432
+ -> list[ElnExperiment]:
433
+ """
434
+ Run a custom report that retrieves every experiment in the system with a given creator.
435
+
436
+ :param context: The current webhook context or a user object to send requests from.
437
+ :param created_by: The username of the creator of the experiments to query for.
438
+ :param criteria: Additional search criteria to filter the results.
439
+ :return: A list of every experiment in the system with the creator that matches the input
440
+ that matches the given criteria.
441
+ """
442
+ return ExperimentReportUtil.get_experiments_by_creators(context, [created_by], criteria=criteria)[created_by]
443
+
444
+ @staticmethod
445
+ def get_experiments_by_creators(context: UserIdentifier, created_by: list[str],
446
+ criteria: ExperimentReportCriteria = ExperimentReportCriteria()) \
447
+ -> dict[str, list[ElnExperiment]]:
448
+ """
449
+ Run a custom report that retrieves every experiment in the system with a given list of creators.
450
+
451
+ :param context: The current webhook context or a user object to send requests from.
452
+ :param created_by: The usernames of the creator of the experiments to query for.
453
+ :param criteria: Additional search criteria to filter the results.
454
+ :return: A dictionary mapping the owner username to a list of every experiment in the system from that creator
455
+ that matches the given criteria.
456
+ """
457
+ criteria.created_by = created_by
458
+ experiments: list[ElnExperiment] = ExperimentReportUtil.get_experiments_for_criteria(context, criteria)
459
+
460
+ # Ensure that each name appears in the dictionary, even if it has no experiments.
461
+ ret_val: dict[str, list[ElnExperiment]] = {x: [] for x in created_by}
462
+ for experiment in experiments:
463
+ ret_val.get(experiment.created_by).append(experiment)
464
+ return ret_val
465
+
466
+ @staticmethod
467
+ def build_experiment_id_report(criteria: ExperimentReportCriteria = ExperimentReportCriteria()) \
468
+ -> CustomReportCriteria:
469
+ """
470
+ Construct a custom report using the provided ExperimentReportCriteria.
471
+
472
+ :param criteria: The criteria to construct a custom report from.
473
+ :return: A custom report that can be used to search for experiment IDs that match the given criteria.
474
+ """
475
+ dt: str = NotebookExperimentPseudoDef.DATA_TYPE_NAME
476
+ report_builder = CustomReportBuilder(dt)
173
477
  report_builder.add_column(NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME)
174
- root = TermBuilder.is_term(NotebookExperimentPseudoDef.DATA_TYPE_NAME,
175
- NotebookExperimentPseudoDef.EXPERIMENT_NAME__FIELD_NAME,
176
- names)
478
+
479
+ root: AbstractReportTerm | None = None
480
+ if criteria.notebook_ids is not None:
481
+ root = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME, criteria.notebook_ids)
482
+ if criteria.not_notebook_ids is not None:
483
+ term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME, criteria.not_notebook_ids)
484
+ if root is not None:
485
+ root = TermBuilder.and_terms(root, term)
486
+ if root is None:
487
+ root = ExperimentReportUtil.all_notebook_experiments_term()
488
+
489
+ if criteria.names is not None:
490
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_NAME__FIELD_NAME, criteria.names)
491
+ root = TermBuilder.and_terms(root, term)
492
+ if criteria.not_names is not None:
493
+ term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_NAME__FIELD_NAME, criteria.not_names)
494
+ root = TermBuilder.and_terms(root, term)
495
+
496
+ if criteria.created_by is not None:
497
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.CREATED_BY__FIELD_NAME, criteria.created_by)
498
+ root = TermBuilder.and_terms(root, term)
499
+ if criteria.not_created_by is not None:
500
+ term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.CREATED_BY__FIELD_NAME, criteria.not_created_by)
501
+ root = TermBuilder.and_terms(root, term)
502
+
503
+ if criteria.last_modified_by is not None:
504
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_BY__FIELD_NAME, criteria.last_modified_by)
505
+ root = TermBuilder.and_terms(root, term)
506
+ if criteria.not_last_modified_by is not None:
507
+ term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_BY__FIELD_NAME, criteria.not_last_modified_by)
508
+ root = TermBuilder.and_terms(root, term)
509
+
510
+ if criteria.owners is not None:
511
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_OWNER__FIELD_NAME, criteria.owners)
512
+ root = TermBuilder.and_terms(root, term)
513
+ if criteria.not_owners is not None:
514
+ term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.EXPERIMENT_OWNER__FIELD_NAME, criteria.not_owners)
515
+ root = TermBuilder.and_terms(root, term)
516
+
517
+ if criteria.statuses is not None:
518
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.STATUS__FIELD_NAME, criteria.statuses)
519
+ root = TermBuilder.and_terms(root, term)
520
+ if criteria.not_statuses is not None:
521
+ term = TermBuilder.not_term(dt, NotebookExperimentPseudoDef.STATUS__FIELD_NAME, criteria.not_statuses)
522
+ root = TermBuilder.and_terms(root, term)
523
+
524
+ # For the template name term, we need to join on the experiment record.
525
+ if criteria.templates is not None or criteria.not_templates is not None:
526
+ join = TermBuilder.compare_is_term("ELNExperiment",
527
+ "RecordId",
528
+ NotebookExperimentPseudoDef.DATA_TYPE_NAME,
529
+ NotebookExperimentPseudoDef.EXPERIMENT_RECORD_ID__FIELD_NAME)
530
+ report_builder.add_join(join)
531
+ if criteria.templates is not None:
532
+ term = TermBuilder.is_term("ELNExperiment", "TemplateExperimentName", criteria.templates)
533
+ root = TermBuilder.and_terms(root, term)
534
+ if criteria.not_templates is not None:
535
+ term = TermBuilder.not_term("ELNExperiment", "TemplateExperimentName", criteria.not_templates)
536
+ root = TermBuilder.and_terms(root, term)
537
+
538
+ if criteria.is_from_template is not None:
539
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_TEMPLATE__FIELD_NAME, criteria.is_from_template)
540
+ root = TermBuilder.and_terms(root, term)
541
+ if criteria.is_from_protocol_template is not None:
542
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_PROTOCOL_TEMPLATE__FIELD_NAME, criteria.is_from_protocol_template)
543
+ root = TermBuilder.and_terms(root, term)
544
+ if criteria.is_modifiable is not None:
545
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_MODIFIABLE__FIELD_NAME, criteria.is_modifiable)
546
+ root = TermBuilder.and_terms(root, term)
547
+ if criteria.is_active is not None:
548
+ term = TermBuilder.is_term(dt, NotebookExperimentPseudoDef.IS_ACTIVE__FIELD_NAME, criteria.is_active)
549
+ root = TermBuilder.and_terms(root, term)
550
+
551
+ if criteria.created_after is not None:
552
+ term = TermBuilder.gte_term(dt, NotebookExperimentPseudoDef.DATE_CREATED__FIELD_NAME, criteria.created_after)
553
+ root = TermBuilder.and_terms(root, term)
554
+ if criteria.created_before is not None:
555
+ term = TermBuilder.lte_term(dt, NotebookExperimentPseudoDef.DATE_CREATED__FIELD_NAME, criteria.created_before)
556
+ root = TermBuilder.and_terms(root, term)
557
+
558
+ if criteria.last_modified_after is not None:
559
+ term = TermBuilder.gte_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_DATE__FIELD_NAME, criteria.last_modified_after)
560
+ root = TermBuilder.and_terms(root, term)
561
+ if criteria.last_modified_before is not None:
562
+ term = TermBuilder.lte_term(dt, NotebookExperimentPseudoDef.LAST_MODIFIED_DATE__FIELD_NAME, criteria.last_modified_before)
563
+ root = TermBuilder.and_terms(root, term)
564
+
565
+ if criteria.due_after is not None:
566
+ term = TermBuilder.gte_term(dt, NotebookExperimentPseudoDef.APPROVAL_DUE_DATE__FIELD_NAME, criteria.due_after)
567
+ root = TermBuilder.and_terms(root, term)
568
+ if criteria.due_before is not None:
569
+ term = TermBuilder.lte_term(dt, NotebookExperimentPseudoDef.APPROVAL_DUE_DATE__FIELD_NAME, criteria.due_before)
570
+ root = TermBuilder.and_terms(root, term)
571
+
177
572
  report_builder.set_root_term(root)
573
+ return report_builder.build_report_criteria()
178
574
 
179
- # Ensure that each entry appears in the dictionary, even if it has no experiments.
180
- ret_val: dict[str, list[ElnExperiment]] = {x: [] for x in names}
575
+ @staticmethod
576
+ def get_experiments_for_report(context: UserIdentifier, report: CustomReportCriteria) -> list[ElnExperiment]:
577
+ """
578
+ Retrieve the ELN experiment objects for experiments whose notebook IDs appear in a custom report.
181
579
 
580
+ :param context: The current webhook context or a user object to send requests from.
581
+ :param report: A custom report that searches for ELN experiments, containing an "Experiment ID" column to
582
+ query for the experiments of.
583
+ :return: The ELN experiments that match the provided report.
584
+ """
585
+ user = AliasUtil.to_sapio_user(context)
182
586
  exp_ids: list[int] = []
183
- for row in CustomReportUtil.run_custom_report(user, report_builder.build_report_criteria()):
587
+ for row in CustomReportUtil.run_custom_report(user, report):
184
588
  exp_ids.append(row[NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME.field_name])
185
589
  if not exp_ids:
186
- return ret_val
590
+ return []
187
591
 
188
592
  criteria = ElnExperimentQueryCriteria(notebook_experiment_id_white_list=exp_ids)
189
- experiments: list[ElnExperiment] = ElnManager(user).get_eln_experiment_by_criteria(criteria)
190
- for experiment in experiments:
191
- ret_val.get(experiment.notebook_experiment_name).append(experiment)
192
- return ret_val
593
+ return ElnManager(user).get_eln_experiment_by_criteria(criteria)
594
+
595
+ @staticmethod
596
+ def all_notebook_experiments_term() -> RawReportTerm:
597
+ """
598
+ :return: A report term searching for all notebook experiments.
599
+ """
600
+ return TermBuilder.gte_term(NotebookExperimentPseudoDef.DATA_TYPE_NAME,
601
+ NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
602
+ 0)
193
603
 
194
604
  @staticmethod
195
605
  def __get_record_experiment_relation_rows(user: SapioUser, data_type_name: str, record_ids: list[int] | None = None,
196
- exp_ids: list[int] | None = None) -> list[dict[str, int]]:
606
+ exp_ids: list[int] | None = None) -> list[dict[str, FieldValue]]:
197
607
  """
198
608
  Return a list of dicts mapping \"RECORDID\" to the record id and \"EXPERIMENTID\" to the experiment id.
199
609
  At least one of record_ids and exp_ids should be provided.
@@ -212,17 +622,15 @@ class ExperimentReportUtil:
212
622
  exp_ids)
213
623
  else:
214
624
  # Get all experiments
215
- exp_term = TermBuilder.gte_term(NotebookExperimentPseudoDef.DATA_TYPE_NAME,
216
- NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
217
- 0)
625
+ exp_term = ExperimentReportUtil.all_notebook_experiments_term()
218
626
 
219
627
  root_term = TermBuilder.and_terms(records_term, exp_term)
220
628
 
221
629
  # Join records on the experiment entry records that correspond to them.
222
- records_entry_join = TermBuilder.compare_is_term(ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME,
223
- ExperimentEntryRecordPseudoDef.RECORD_ID__FIELD_NAME,
224
- data_type_name,
225
- "RecordId")
630
+ records_entry_join = TermBuilder.compare_is_term(data_type_name,
631
+ "RecordId",
632
+ ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME,
633
+ ExperimentEntryRecordPseudoDef.RECORD_ID__FIELD_NAME)
226
634
  # Join entry records on the experiment entries they are in.
227
635
  experiment_entry_enb_entry_join = TermBuilder.compare_is_term(EnbEntryPseudoDef.DATA_TYPE_NAME,
228
636
  EnbEntryPseudoDef.ENTRY_ID__FIELD_NAME,
@@ -239,7 +647,7 @@ class ExperimentReportUtil:
239
647
  report_builder.add_column("RecordId", FieldType.LONG)
240
648
  report_builder.add_column(NotebookExperimentPseudoDef.EXPERIMENT_ID__FIELD_NAME,
241
649
  data_type=NotebookExperimentPseudoDef.DATA_TYPE_NAME)
242
- report_builder.add_join(records_entry_join)
243
- report_builder.add_join(experiment_entry_enb_entry_join)
244
- report_builder.add_join(enb_entry_experiment_join)
650
+ report_builder.add_join(records_entry_join, ExperimentEntryRecordPseudoDef.DATA_TYPE_NAME)
651
+ report_builder.add_join(experiment_entry_enb_entry_join, EnbEntryPseudoDef.DATA_TYPE_NAME)
652
+ report_builder.add_join(enb_entry_experiment_join, NotebookExperimentPseudoDef.DATA_TYPE_NAME)
245
653
  return CustomReportUtil.run_custom_report(user, report_builder.build_report_criteria())