scout-browser 4.101.0__py3-none-any.whl → 4.102.0__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.
Files changed (45) hide show
  1. scout/adapter/mongo/clinvar.py +7 -7
  2. scout/adapter/mongo/hgnc.py +7 -2
  3. scout/adapter/mongo/omics_variant.py +8 -0
  4. scout/adapter/mongo/variant_loader.py +6 -2
  5. scout/constants/__init__.py +1 -0
  6. scout/constants/igv_tracks.py +6 -2
  7. scout/constants/phenotype.py +1 -0
  8. scout/load/hpo.py +8 -2
  9. scout/server/blueprints/alignviewers/controllers.py +8 -6
  10. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +2 -0
  11. scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
  12. scout/server/blueprints/cases/controllers.py +56 -28
  13. scout/server/blueprints/cases/templates/cases/case_report.html +9 -88
  14. scout/server/blueprints/cases/templates/cases/matchmaker.html +1 -1
  15. scout/server/blueprints/cases/templates/cases/phenotype.html +1 -1
  16. scout/server/blueprints/cases/templates/cases/utils.html +26 -24
  17. scout/server/blueprints/cases/views.py +32 -33
  18. scout/server/blueprints/clinvar/controllers.py +3 -2
  19. scout/server/blueprints/diagnoses/controllers.py +4 -8
  20. scout/server/blueprints/diagnoses/templates/diagnoses/diagnoses.html +1 -1
  21. scout/server/blueprints/diagnoses/templates/diagnoses/disease_term.html +1 -1
  22. scout/server/blueprints/diagnoses/views.py +2 -2
  23. scout/server/blueprints/institutes/controllers.py +107 -73
  24. scout/server/blueprints/institutes/templates/overview/cases.html +1 -1
  25. scout/server/blueprints/login/controllers.py +2 -1
  26. scout/server/blueprints/login/views.py +5 -2
  27. scout/server/blueprints/omics_variants/views.py +2 -2
  28. scout/server/blueprints/phenotypes/controllers.py +15 -2
  29. scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +1 -1
  30. scout/server/blueprints/variant/controllers.py +10 -11
  31. scout/server/blueprints/variant/templates/variant/utils.html +1 -1
  32. scout/server/blueprints/variant/templates/variant/variant_details.html +68 -60
  33. scout/server/blueprints/variant/utils.py +25 -0
  34. scout/server/blueprints/variants/controllers.py +11 -42
  35. scout/server/blueprints/variants/views.py +9 -8
  36. scout/server/config.py +3 -0
  37. scout/server/extensions/beacon_extension.py +7 -2
  38. scout/server/templates/bootstrap_global.html +11 -1
  39. scout/server/templates/layout.html +6 -1
  40. scout/server/utils.py +24 -3
  41. {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/METADATA +1 -1
  42. {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/RECORD +45 -45
  43. {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/WHEEL +0 -0
  44. {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/entry_points.txt +0 -0
  45. {scout_browser-4.101.0.dist-info → scout_browser-4.102.0.dist-info}/licenses/LICENSE +0 -0
@@ -36,6 +36,7 @@ from scout.server.utils import (
36
36
  html_to_pdf_file,
37
37
  institute_and_case,
38
38
  jsonconverter,
39
+ safe_redirect_back,
39
40
  templated,
40
41
  user_cases,
41
42
  user_institutes,
@@ -148,7 +149,7 @@ def beacon_add_variants(institute_id, case_name):
148
149
  store, institute_id, case_name
149
150
  ) # This function checks if user has permissions to access the case
150
151
  beacon.add_variants(store, case_obj, request.form)
151
- return redirect(request.referrer)
152
+ return safe_redirect_back(request)
152
153
 
153
154
 
154
155
  @cases_bp.route("/beacon_remove_variants/<institute_id>/<case_name>", methods=["GET"])
@@ -158,7 +159,7 @@ def beacon_remove_variants(institute_id, case_name):
158
159
  store, institute_id, case_name
159
160
  ) # This function checks if user has permissions to access the case
160
161
  beacon.remove_variants(store, institute_id, case_obj)
161
- return redirect(request.referrer)
162
+ return safe_redirect_back(request)
162
163
 
163
164
 
164
165
  @cases_bp.route("/<institute_id>/<case_name>/mme_matches", methods=["GET", "POST"])
@@ -175,7 +176,7 @@ def matchmaker_match(institute_id, case_name, target):
175
176
  """Starts an internal match or a match against one or all MME external nodes"""
176
177
  institute_obj, case_obj = institute_and_case(store, institute_id, case_name)
177
178
  match_results = controllers.matchmaker_match(request, target, institute_id, case_name)
178
- return redirect(request.referrer)
179
+ return safe_redirect_back(request)
179
180
 
180
181
 
181
182
  @cases_bp.route("/<institute_id>/<case_name>/mme_add", methods=["POST"])
@@ -183,7 +184,7 @@ def matchmaker_add(institute_id, case_name):
183
184
  """Add or update a case in MatchMaker"""
184
185
  # Call matchmaker_delete controller to add a patient to MatchMaker
185
186
  controllers.matchmaker_add(request, institute_id, case_name)
186
- return redirect(request.referrer)
187
+ return safe_redirect_back(request)
187
188
 
188
189
 
189
190
  @cases_bp.route("/<institute_id>/<case_name>/mme_delete", methods=["POST"])
@@ -191,7 +192,7 @@ def matchmaker_delete(institute_id, case_name):
191
192
  """Remove a case from MatchMaker"""
192
193
  # Call matchmaker_delete controller to delete a patient from MatchMaker
193
194
  controllers.matchmaker_delete(request, institute_id, case_name)
194
- return redirect(request.referrer)
195
+ return safe_redirect_back(request)
195
196
 
196
197
 
197
198
  @cases_bp.route("/<institute_id>/<case_name>/individuals", methods=["POST"])
@@ -212,7 +213,7 @@ def update_individual(institute_id, case_name):
212
213
  age=age,
213
214
  tissue=tissue,
214
215
  )
215
- return redirect(request.referrer)
216
+ return safe_redirect_back(request)
216
217
 
217
218
 
218
219
  @cases_bp.route("/<institute_id>/<case_name>/samples", methods=["POST"])
@@ -237,7 +238,7 @@ def update_cancer_sample(institute_id, case_name):
237
238
  tumor_type=tumor_type,
238
239
  tumor_purity=tumor_purity,
239
240
  )
240
- return redirect(request.referrer)
241
+ return safe_redirect_back(request)
241
242
 
242
243
 
243
244
  @cases_bp.route("/<institute_id>/<case_name>/synopsis", methods=["POST"])
@@ -247,7 +248,7 @@ def case_synopsis(institute_id, case_name):
247
248
  user_obj = store.user(current_user.email)
248
249
  new_synopsis = request.form.get("synopsis")
249
250
  controllers.update_synopsis(store, institute_obj, case_obj, user_obj, new_synopsis)
250
- return redirect(request.referrer)
251
+ return safe_redirect_back(request)
251
252
 
252
253
 
253
254
  @cases_bp.route("/api/v1/<institute_id>/<case_name>/case_report", methods=["GET"])
@@ -344,7 +345,7 @@ def mt_report(institute_id, case_name):
344
345
  shutil.rmtree(temp_excel_dir)
345
346
 
346
347
  flash("No MT report excel file could be exported for this sample", "warning")
347
- return redirect(request.referrer)
348
+ return safe_redirect_back(request)
348
349
 
349
350
 
350
351
  @cases_bp.route("/<institute_id>/<case_name>/diagnose", methods=["POST"])
@@ -366,7 +367,7 @@ def case_diagnosis(institute_id, case_name):
366
367
  affected_inds=affected_inds,
367
368
  remove=True if request.args.get("remove") == "yes" else False,
368
369
  )
369
- return redirect("#".join([link, "disease_assign"]))
370
+ return safe_redirect_back(request, "#".join([link, "disease_assign"]))
370
371
 
371
372
 
372
373
  @cases_bp.route("/<institute_id>/<case_name>/phenotypes", methods=["POST"])
@@ -414,7 +415,7 @@ def phenotypes(institute_id, case_name, phenotype_id=None):
414
415
  )
415
416
  return redirect(case_url)
416
417
 
417
- return redirect("#".join([case_url, "phenotypes_panel"]))
418
+ return safe_redirect_back(request, "#".join([case_url, "phenotypes_panel"]))
418
419
 
419
420
 
420
421
  @cases_bp.route("/<institute_id>/<case_name>/phenotype_export", methods=["POST"])
@@ -526,7 +527,7 @@ def phenotypes_actions(institute_id, case_name):
526
527
  hgnc_ids = [result[0] for result in results if result[1] >= hpo_count]
527
528
  store.update_dynamic_gene_list(case_obj, hgnc_ids=hgnc_ids, phenotype_ids=hpo_ids)
528
529
 
529
- return redirect("#".join([case_url, "phenotypes_panel"]))
530
+ return safe_redirect_back(request, "#".join([case_url, "phenotypes_panel"]))
530
531
 
531
532
 
532
533
  @cases_bp.route("/<institute_id>/<case_name>/events", methods=["POST"])
@@ -567,7 +568,7 @@ def events(institute_id, case_name, event_id=None):
567
568
  # create a case comment
568
569
  store.comment(institute_obj, case_obj, user_obj, link, content=content)
569
570
 
570
- return redirect(request.referrer)
571
+ return safe_redirect_back(request)
571
572
 
572
573
 
573
574
  @cases_bp.route("/<institute_id>/<case_name>/status", methods=["POST"])
@@ -588,7 +589,7 @@ def status(institute_id, case_name):
588
589
  if tags or case_obj.get("tags") and tags != case_obj.get("tags"):
589
590
  store.tag_case(institute_obj, case_obj, user_obj, tags, link)
590
591
 
591
- return redirect(request.referrer)
592
+ return safe_redirect_back(request)
592
593
 
593
594
 
594
595
  @cases_bp.route("/<institute_id>/<case_name>/assign", methods=["POST"])
@@ -605,7 +606,7 @@ def assign(institute_id, case_name, user_id=None, inactivate=False):
605
606
  store.unassign(institute_obj, case_obj, user_obj, link, inactivate)
606
607
  else:
607
608
  store.assign(institute_obj, case_obj, user_obj, link)
608
- return redirect(request.referrer)
609
+ return safe_redirect_back(request)
609
610
 
610
611
 
611
612
  @cases_bp.route("/api/v1/cases", defaults={"institute_id": None})
@@ -688,7 +689,7 @@ def pin_variant(institute_id, case_name, variant_id):
688
689
  store.pin_variant(institute_obj, case_obj, user_obj, link, variant_obj)
689
690
  elif request.form["action"] == "DELETE":
690
691
  store.unpin_variant(institute_obj, case_obj, user_obj, link, variant_obj)
691
- return redirect(request.referrer or link)
692
+ return safe_redirect_back(request, request.referrer or link)
692
693
 
693
694
 
694
695
  @cases_bp.route("/<institute_id>/<case_name>/<variant_id>/validate", methods=["POST"])
@@ -705,7 +706,7 @@ def mark_validation(institute_id, case_name, variant_id):
705
706
  variant_id=variant_id,
706
707
  )
707
708
  store.validate(institute_obj, case_obj, user_obj, link, variant_obj, validate_type)
708
- return redirect(request.referrer or link)
709
+ return safe_redirect_back(request, request.referrer or link)
709
710
 
710
711
 
711
712
  @cases_bp.route(
@@ -748,8 +749,7 @@ def mark_causative(institute_id, case_name, variant_id, partial_causative=False)
748
749
  store.unmark_causative(institute_obj, case_obj, user_obj, link, variant_obj)
749
750
 
750
751
  # send the user back to the case that was marked as solved
751
- case_url = url_for(".case", institute_id=institute_id, case_name=case_name)
752
- return redirect(request.referrer)
752
+ return safe_redirect_back(request)
753
753
 
754
754
 
755
755
  @cases_bp.route("/<institute_id>/<case_name>/check-case", methods=["POST"])
@@ -761,7 +761,7 @@ def check_case(institute_id, case_name):
761
761
  store.case_collection.find_one_and_update(
762
762
  {"_id": case_obj["_id"]}, {"$set": {"needs_check": False}}
763
763
  )
764
- return redirect(request.referrer)
764
+ return safe_redirect_back(request)
765
765
 
766
766
 
767
767
  @cases_bp.route("/<institute_id>/<case_name>/report/<report_type>")
@@ -835,7 +835,7 @@ def share(institute_id, case_name):
835
835
  except ValueError as ex:
836
836
  flash(str(ex), "warning")
837
837
 
838
- return redirect(request.referrer)
838
+ return safe_redirect_back(request)
839
839
 
840
840
 
841
841
  @cases_bp.route("/<institute_id>/<case_name>/update_rerun_status")
@@ -846,7 +846,7 @@ def update_rerun_status(institute_id, case_name):
846
846
  link = url_for("cases.case", institute_id=institute_id, case_name=case_name)
847
847
 
848
848
  store.update_rerun_status(institute_obj, case_obj, user_obj, link)
849
- return redirect(link)
849
+ return safe_redirect_back(request, link)
850
850
 
851
851
 
852
852
  @cases_bp.route("/<institute_id>/<case_name>/monitor", methods=["POST"])
@@ -860,7 +860,7 @@ def rerun_monitor(institute_id, case_name):
860
860
  else:
861
861
  store.unmonitor(institute_obj, case_obj, user_obj, link)
862
862
 
863
- return redirect(request.referrer)
863
+ return safe_redirect_back(request)
864
864
 
865
865
 
866
866
  @cases_bp.route("/<institute_id>/<case_name>/reanalysis", methods=["POST"])
@@ -876,7 +876,7 @@ def reanalysis(institute_id, case_name):
876
876
  LOG.error(msg)
877
877
  flash(msg, "danger")
878
878
 
879
- return redirect(request.referrer)
879
+ return safe_redirect_back(request)
880
880
 
881
881
 
882
882
  @cases_bp.route("/<institute_id>/<case_name>/research", methods=["POST"])
@@ -886,7 +886,7 @@ def research(institute_id, case_name):
886
886
  user_obj = store.user(current_user.email)
887
887
  link = url_for(".case", institute_id=institute_id, case_name=case_name)
888
888
  store.open_research(institute_obj, case_obj, user_obj, link)
889
- return redirect(request.referrer)
889
+ return safe_redirect_back(request)
890
890
 
891
891
 
892
892
  @cases_bp.route("/<institute_id>/<case_name>/reset_research", methods=["GET"])
@@ -895,7 +895,7 @@ def reset_research(institute_id, case_name):
895
895
  user_obj = store.user(current_user.email)
896
896
  link = url_for(".case", institute_id=institute_id, case_name=case_name)
897
897
  store.reset_research(institute_obj, case_obj, user_obj, link)
898
- return redirect(request.referrer)
898
+ return safe_redirect_back(request)
899
899
 
900
900
 
901
901
  @cases_bp.route("/<institute_id>/<case_name>/cohorts", methods=["POST"])
@@ -909,7 +909,7 @@ def cohorts(institute_id, case_name):
909
909
  store.remove_cohort(institute_obj, case_obj, user_obj, link, cohort_tag)
910
910
  else:
911
911
  store.add_cohort(institute_obj, case_obj, user_obj, link, cohort_tag)
912
- return redirect("#".join([request.referrer, "cohorts"]))
912
+ return safe_redirect_back(request, "#".join([request.referrer, "cohorts"]))
913
913
 
914
914
 
915
915
  @cases_bp.route("/<institute_id>/<case_name>/default-panels", methods=["POST"])
@@ -917,7 +917,7 @@ def default_panels(institute_id, case_name):
917
917
  """Update default panels for a case."""
918
918
  panel_ids = request.form.getlist("panel_ids")
919
919
  controllers.update_default_panels(store, current_user, institute_id, case_name, panel_ids)
920
- return redirect(request.referrer)
920
+ return safe_redirect_back(request)
921
921
 
922
922
 
923
923
  @cases_bp.route("/<institute_id>/<case_name>/update-clinical-filter-hpo", methods=["POST"])
@@ -928,7 +928,7 @@ def update_clinical_filter_hpo(institute_id, case_name):
928
928
  controllers.update_clinical_filter_hpo(
929
929
  store, current_user, institute_obj, case_obj, hpo_clinical_filter
930
930
  )
931
- return redirect(request.referrer)
931
+ return safe_redirect_back(request)
932
932
 
933
933
 
934
934
  @cases_bp.route("/<institute_id>/<case_name>/add_case_group", methods=["GET", "POST"])
@@ -943,8 +943,7 @@ def add_case_group(institute_id, case_name):
943
943
  case_name = request.form.get("other_case_name")
944
944
 
945
945
  controllers.add_case_group(store, current_user, institute_id, case_name, group_id)
946
-
947
- return redirect(request.referrer + "#case_groups")
946
+ return safe_redirect_back(request, request.referrer + "#case_groups")
948
947
 
949
948
 
950
949
  @cases_bp.route("/<institute_id>/<case_name>/<case_group>/remove_case_group", methods=["GET"])
@@ -952,7 +951,7 @@ def remove_case_group(institute_id, case_name, case_group):
952
951
  """Unbind a case group from a case. Remove the group if it is no longer in use."""
953
952
  controllers.remove_case_group(store, current_user, institute_id, case_name, case_group)
954
953
 
955
- return redirect(request.referrer + "#case_groups")
954
+ return safe_redirect_back(request, request.referrer + "#case_groups")
956
955
 
957
956
 
958
957
  @cases_bp.route("/<case_group>/case_group_update_label", methods=["POST"])
@@ -962,7 +961,7 @@ def case_group_update_label(case_group):
962
961
 
963
962
  controllers.case_group_update_label(store, case_group, label)
964
963
 
965
- return redirect(request.referrer + "#case_groups")
964
+ return safe_redirect_back(request, request.referrer + "#case_groups")
966
965
 
967
966
 
968
967
  @cases_bp.route("/<institute_id>/<case_name>/download-hpo-genes/<category>", methods=["GET"])
@@ -19,6 +19,7 @@ from scout.constants.variant_tags import MANUAL_RANK_OPTIONS
19
19
  from scout.models.clinvar import clinvar_variant
20
20
  from scout.server.blueprints.variant.utils import add_gene_info
21
21
  from scout.server.extensions import clinvar_api, store
22
+ from scout.server.utils import get_case_genome_build
22
23
  from scout.utils.hgvs import validate_hgvs
23
24
  from scout.utils.scout_requests import fetch_refseq_version
24
25
 
@@ -30,7 +31,7 @@ LOG = logging.getLogger(__name__)
30
31
  def _get_var_tx_hgvs(case_obj: dict, variant_obj: dict) -> List[Tuple[str, str]]:
31
32
  """Retrieve all transcripts / HGVS for a given variant."""
32
33
 
33
- build = str(case_obj.get("genome_build", "37"))
34
+ build = get_case_genome_build(case_obj)
34
35
  tx_hgvs_list = [("", "Do not specify")]
35
36
  case_has_build_37 = "37" in case_obj.get("genome_build", "37")
36
37
 
@@ -399,7 +400,7 @@ def json_api_submission(submission_id):
399
400
 
400
401
  # Retrieve genome build for the case submitted
401
402
  case_obj = store.case(case_id=variant_data[0].get("case_id")) or {"genome_build": 37}
402
- extra_params["assembly"] = "GRCh37" if "37" in str(case_obj.get("genome_build")) else "GRCh38"
403
+ extra_params["assembly"] = "GRCh37" if "37" in get_case_genome_build(case_obj) else "GRCh38"
403
404
 
404
405
  def _write_file(afile, header, lines): # Write temp CSV file
405
406
  writes = csv.writer(afile, delimiter=",", quoting=csv.QUOTE_ALL)
@@ -1,16 +1,13 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  from scout.adapter import MongoAdapter
4
+ from scout.constants import HPO_LINK_URL
4
5
  from scout.server.links import disease_link
5
6
 
6
7
 
7
- def disease_entry(store, disease_id) -> dict:
8
+ def disease_entry(store: MongoAdapter, disease_id: str) -> dict:
8
9
  """Retrieve specific info for a disease term
9
10
 
10
- Args:
11
- store(obj): an adapter to the scout database
12
- disease_id(str): a disease_id
13
-
14
11
  Returns:
15
12
  disease_obj(obj): a disease term containing description and genes
16
13
  """
@@ -21,6 +18,7 @@ def disease_entry(store, disease_id) -> dict:
21
18
  store.hpo_term(hpo_id) for hpo_id in disease_obj.get("hpo_terms", [])
22
19
  ]
23
20
  disease_obj["disease_link"] = disease_link(disease_id=disease_obj["disease_id"])
21
+ disease_obj["hpo_link_url"] = HPO_LINK_URL
24
22
  return disease_obj
25
23
 
26
24
 
@@ -43,6 +41,4 @@ def disease_terms(store: MongoAdapter, query: str, source: str) -> dict:
43
41
  )
44
42
  disease.update({"genes": gene_ids_symbols})
45
43
 
46
- data = {"terms": list(disease_data)}
47
-
48
- return data
44
+ return {"terms": list(disease_data)}
@@ -95,7 +95,7 @@
95
95
  </td>
96
96
  <td id="hpo-container">
97
97
  <span class="text-body">
98
- <a id="hpo-link" class="badge bg-secondary text-white m-1" href="https://hpo.jax.org/app/browse/term/{{term}}" target="_blank" rel="noopener" ></a>
98
+ <a id="hpo-link" class="badge bg-secondary text-white m-1" href="{{hpo_link_url}}{{term}}" target="_blank" rel="noopener" ></a>
99
99
  </span>
100
100
  </td>
101
101
  </tr>
@@ -47,7 +47,7 @@
47
47
  <ul class="list-group list-group-flush">
48
48
  {% for pheno in hpo_complete %}
49
49
  <li class="list-group-item">
50
- <a href="http://hpo.jax.org/app/browse/term/{{pheno.hpo_id}}" target="_blank">{{ pheno.hpo_id }}</a>
50
+ <a href="{{hpo_link_url}}{{pheno.hpo_id}}" target="_blank">{{ pheno.hpo_id }}</a>
51
51
  {{pheno.description}}
52
52
  </li>
53
53
  {% else %}
@@ -2,6 +2,7 @@ from typing import Union
2
2
 
3
3
  from flask import Blueprint, jsonify, request
4
4
 
5
+ from scout.constants import HPO_LINK_URL
5
6
  from scout.server.extensions import store
6
7
  from scout.server.utils import public_endpoint, templated
7
8
 
@@ -30,8 +31,7 @@ def diagnosis(disease_id):
30
31
  def count_diagnoses():
31
32
  """Display the diagnosis counts for each coding system available in database"""
32
33
 
33
- data = {"counts": store.disease_terminology_count()}
34
- return data
34
+ return {"counts": store.disease_terminology_count(), "hpo_link_url": HPO_LINK_URL}
35
35
 
36
36
 
37
37
  @omim_bp.route("/api/v1/diagnoses")
@@ -25,7 +25,11 @@ from scout.server.blueprints.variant.utils import (
25
25
  update_representative_gene,
26
26
  )
27
27
  from scout.server.extensions import beacon, store
28
- from scout.server.utils import institute_and_case, user_institutes
28
+ from scout.server.utils import (
29
+ get_case_genome_build,
30
+ institute_and_case,
31
+ user_institutes,
32
+ )
29
33
 
30
34
  from .forms import BeaconDatasetForm, CaseFilterForm
31
35
 
@@ -47,6 +51,28 @@ VAR_SPECIFIC_EVENTS = [
47
51
  "cancel_sanger",
48
52
  ]
49
53
 
54
+ # Projection for fetching cases
55
+ ALL_CASES_PROJECTION = {
56
+ "analysis_date": 1,
57
+ "assignees": 1,
58
+ "beacon": 1,
59
+ "case_id": 1,
60
+ "display_name": 1,
61
+ "genome_build": 1,
62
+ "individuals": 1,
63
+ "is_rerun": 1,
64
+ "is_research": 1,
65
+ "mme_submission": 1,
66
+ "owner": 1,
67
+ "panels": 1,
68
+ "phenotype_terms": 1,
69
+ "rank_model_version": 1,
70
+ "status": 1,
71
+ "sv_rank_model_version": 1,
72
+ "track": 1,
73
+ "vcf_files": 1,
74
+ }
75
+
50
76
 
51
77
  def get_timeline_data(limit):
52
78
  """Retrieve chronologially ordered events from the database to display them in the timeline page
@@ -519,92 +545,106 @@ def export_case_samples(institute_id, filtered_cases) -> Response:
519
545
  )
520
546
 
521
547
 
522
- def cases(store: MongoAdapter, request: request, institute_id: str) -> dict:
523
- """Preprocess case objects for the 'cases' view."""
524
- data = {}
548
+ def get_cases_by_query(
549
+ store: MongoAdapter,
550
+ request: request,
551
+ institute_id: str,
552
+ ) -> list:
553
+ """Fetch additional cases based on filters given in request query form.
525
554
 
526
- # Initialize data (institute info, filters, and case counts)
527
- institute_obj = institute_and_case(store, institute_id)
528
- data["institute"] = institute_obj
529
- data["form"] = CaseFilterForm(request.form)
530
- data["status_ncases"] = store.nr_cases_by_status(institute_id=institute_id)
531
- data["nr_cases"] = sum(data["status_ncases"].values())
555
+ Sets metadata in data, e.g. sort order.
556
+ """
532
557
 
533
- # Fetch Sanger unevaluated and validated cases
534
- sanger_ordered_not_validated = get_sanger_unevaluated(store, institute_id, current_user.email)
535
- data["sanger_unevaluated"], data["sanger_validated_by_others"] = sanger_ordered_not_validated
558
+ name_query = request.form
559
+ all_cases = store.cases(
560
+ collaborator=institute_id,
561
+ name_query=name_query,
562
+ skip_assigned=request.form.get("skip_assigned"),
563
+ is_research=request.form.get("is_research"),
564
+ has_rna_data=request.form.get("has_rna"),
565
+ verification_pending=request.form.get("validation_ordered"),
566
+ has_clinvar_submission=request.form.get("clinvar_submitted"),
567
+ projection=ALL_CASES_PROJECTION,
568
+ )
569
+ return all_cases
536
570
 
537
- # Projection for fetching cases
538
- ALL_CASES_PROJECTION = {
539
- "analysis_date": 1,
540
- "assignees": 1,
541
- "beacon": 1,
542
- "case_id": 1,
543
- "display_name": 1,
544
- "genome_build": 1,
545
- "individuals": 1,
546
- "is_rerun": 1,
547
- "is_research": 1,
548
- "mme_submission": 1,
549
- "owner": 1,
550
- "panels": 1,
551
- "phenotype_terms": 1,
552
- "rank_model_version": 1,
553
- "status": 1,
554
- "sv_rank_model_version": 1,
555
- "track": 1,
556
- "vcf_files": 1,
557
- }
571
+
572
+ def get_and_set_cases_by_status(
573
+ store: MongoAdapter,
574
+ request: request,
575
+ institute_obj: dict,
576
+ previous_query_result_cases: list,
577
+ data: dict,
578
+ ) -> dict:
579
+ """Process cases for statuses that require all cases to be shown.
580
+ Group cases by status, process additional cases for the remaining statuses
581
+ and ensure that we don't dim cases that already appeared in query search results.
582
+ """
583
+
584
+ status_show_all_cases = institute_obj.get("show_all_cases_status", ["prioritized"])
585
+ nr_cases_showall_statuses = 0
558
586
 
559
587
  # Group cases by status
560
588
  case_groups = {status: [] for status in CASE_STATUSES}
561
- nr_cases_showall_statuses = 0
562
- status_show_all_cases = institute_obj.get("show_all_cases_status") or ["prioritized"]
563
589
 
564
- # Process cases for statuses that require all cases to be shown
565
590
  for status in status_show_all_cases:
566
591
  cases_in_status = store.cases_by_status(
567
- institute_id=institute_id, status=status, projection=ALL_CASES_PROJECTION
592
+ institute_id=institute_obj["_id"], status=status, projection=ALL_CASES_PROJECTION
568
593
  )
569
594
  cases_in_status = _sort_cases(data, request, cases_in_status)
570
595
  for case_obj in cases_in_status:
571
596
  populate_case_obj(case_obj, store)
597
+ case_obj["dimmed_in_search"] = True
572
598
  case_groups[status].append(case_obj)
573
599
  nr_cases_showall_statuses += 1
574
600
 
575
- # Fetch additional cases based on filters
576
- name_query = request.form
577
- limit = int(request.form.get("search_limit")) if request.form.get("search_limit") else 100
578
- all_cases = store.cases(
579
- collaborator=institute_id,
580
- name_query=name_query,
581
- skip_assigned=request.form.get("skip_assigned"),
582
- is_research=request.form.get("is_research"),
583
- has_rna_data=request.form.get("has_rna"),
584
- verification_pending=request.form.get("validation_ordered"),
585
- has_clinvar_submission=request.form.get("clinvar_submitted"),
586
- projection=ALL_CASES_PROJECTION,
587
- )
601
+ nr_name_query_matching_displayed_cases = 0
602
+ limit = int(request.form.get("search_limit", 100))
603
+ for case_obj in previous_query_result_cases:
604
+ if case_obj["status"] in status_show_all_cases:
605
+ for group_case in case_groups[status]:
606
+ if group_case["_id"] == case_obj["_id"]:
607
+ group_case["dimmed_in_search"] = False
608
+ elif nr_name_query_matching_displayed_cases == limit:
609
+ break
610
+ else:
611
+ populate_case_obj(case_obj, store)
612
+ case_groups[case_obj["status"]].append(case_obj)
613
+ nr_name_query_matching_displayed_cases += 1
614
+
615
+ data["found_cases"] = nr_name_query_matching_displayed_cases + nr_cases_showall_statuses
616
+ data["limit"] = limit
617
+ return case_groups
618
+
619
+
620
+ def cases(store: MongoAdapter, request: request, institute_id: str):
621
+ """Preprocess case objects for the 'cases' view.
622
+
623
+ Returns data dict for view display, or response in case of file export.
624
+ """
625
+ data = {}
626
+
627
+ # Initialize data (institute info, filters, and case counts)
628
+ institute_obj = institute_and_case(store, institute_id)
629
+ data["institute"] = institute_obj
630
+ data["form"] = CaseFilterForm(request.form)
631
+ data["status_ncases"] = store.nr_cases_by_status(institute_id=institute_id)
632
+ data["nr_cases"] = sum(data["status_ncases"].values())
633
+
634
+ # Fetch Sanger unevaluated and validated cases
635
+ sanger_ordered_not_validated = get_sanger_unevaluated(store, institute_id, current_user.email)
636
+ data["sanger_unevaluated"], data["sanger_validated_by_others"] = sanger_ordered_not_validated
637
+
638
+ all_cases = get_cases_by_query(store, request, institute_id)
588
639
  all_cases = _sort_cases(data, request, all_cases)
589
640
 
590
641
  if request.form.get("export"):
591
642
  return export_case_samples(institute_id, all_cases)
592
643
 
593
- # Process additional cases for the remaining statuses
594
- nr_cases = 0
595
- for case_obj in all_cases:
596
- if case_obj["status"] not in status_show_all_cases:
597
- if nr_cases == limit:
598
- break
599
- populate_case_obj(case_obj, store)
600
- case_groups[case_obj["status"]].append(case_obj)
601
- nr_cases += 1
644
+ case_groups = get_and_set_cases_by_status(store, request, institute_obj, all_cases, data)
602
645
 
603
646
  # Compile the final data
604
647
  data["cases"] = [(status, case_groups[status]) for status in CASE_STATUSES]
605
- data["found_cases"] = nr_cases + nr_cases_showall_statuses
606
- data["limit"] = limit
607
-
608
648
  return data
609
649
 
610
650
 
@@ -615,7 +655,9 @@ def populate_case_obj(case_obj: dict, store: MongoAdapter):
615
655
  analysis_types = set(["mixed"])
616
656
  case_obj["analysis_types"] = list(analysis_types)
617
657
 
618
- case_obj["assignees"] = [store.user(user_email) for user_email in case_obj.get("assignees", [])]
658
+ case_obj["assignees"] = [
659
+ store.user(user_id=user_id) for user_id in case_obj.get("assignees", [])
660
+ ]
619
661
 
620
662
  last_analysis_date = case_obj.get("analysis_date", datetime.datetime.now())
621
663
  all_analyses_dates = {
@@ -821,7 +863,7 @@ def gene_variants(store, pymongo_cursor, variant_count, page=1, per_page=50):
821
863
  case_display_name = variant_case_obj.get("display_name")
822
864
  variant_obj["case_display_name"] = case_display_name
823
865
 
824
- genome_build = get_genome_build(variant_case_obj)
866
+ genome_build = get_case_genome_build(variant_case_obj)
825
867
  update_variant_genes(store, variant_obj, genome_build)
826
868
  variants.append(variant_obj)
827
869
 
@@ -885,14 +927,6 @@ def update_variant_genes(store, variant_obj, genome_build):
885
927
  variant_obj["functional_annotations"] = get_annotations(gene_symbols, functional_annotations)
886
928
 
887
929
 
888
- def get_genome_build(variant_case_obj):
889
- """Find genom build in `variant_case_obj`. If not found use build #37"""
890
- build = str(variant_case_obj.get("genome_build"))
891
- if build in ["37", "38"]:
892
- return build
893
- return "37"
894
-
895
-
896
930
  def get_hgvs(gene_obj: Dict) -> Tuple[str, str, str]:
897
931
  """Analyse gene object for hgvs info
898
932
  Return:
@@ -63,7 +63,7 @@
63
63
 
64
64
 
65
65
  {% macro case_row(case) %}
66
- <tr class="{% if case.status == 'solved' %}causative{% endif %}">
66
+ <tr class="{% if case.status == 'solved' %}causative{% elif case.dimmed_in_search %}dismiss{% endif %}">
67
67
  <td>
68
68
  <a class="me-2"
69
69
  {% if case.individuals|length == 1 %} data-bs-toggle="tooltip" title="{{case.individuals[0].display_name}}" {% endif %}
@@ -98,7 +98,7 @@ def google_login() -> Optional[Response]:
98
98
 
99
99
  redirect_uri: str = url_for("login.authorized", _external=True)
100
100
  try:
101
- return oauth_client.google.authorize_redirect(redirect_uri)
101
+ return oauth_client.google.authorize_redirect(redirect_uri, prompt="select_account")
102
102
  except Exception:
103
103
  flash("An error has occurred while logging in user using Google OAuth", "warning")
104
104
  return None
@@ -151,6 +151,7 @@ def perform_flask_login(user_dict: "LoginUser") -> Response:
151
151
 
152
152
  def logout_oidc_user(session, provider: str):
153
153
  """Log out a user from an OIDC login provider-"""
154
+
154
155
  logout_url = current_app.config[provider].get("logout_url")
155
156
  if not logout_url or not session.get("token_response"):
156
157
  return