scout-browser 4.97.0__py3-none-any.whl → 4.99.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 (56) hide show
  1. scout/adapter/mongo/institute.py +42 -55
  2. scout/adapter/mongo/variant.py +19 -15
  3. scout/adapter/mongo/variant_loader.py +11 -11
  4. scout/build/individual.py +2 -0
  5. scout/build/variant/variant.py +8 -0
  6. scout/commands/download/ensembl.py +18 -2
  7. scout/commands/update/individual.py +2 -0
  8. scout/commands/update/panelapp.py +15 -2
  9. scout/constants/__init__.py +6 -7
  10. scout/constants/clnsig.py +2 -0
  11. scout/constants/file_types.py +12 -0
  12. scout/constants/igv_tracks.py +8 -6
  13. scout/constants/panels.py +3 -0
  14. scout/constants/variant_tags.py +6 -6
  15. scout/demo/643594.config.yaml +1 -0
  16. scout/load/panelapp.py +11 -5
  17. scout/models/case/case_loading_models.py +4 -0
  18. scout/parse/variant/clnsig.py +38 -0
  19. scout/parse/variant/genotype.py +4 -10
  20. scout/parse/variant/models.py +5 -11
  21. scout/parse/variant/rank_score.py +5 -13
  22. scout/parse/variant/variant.py +90 -111
  23. scout/server/app.py +33 -22
  24. scout/server/blueprints/alignviewers/controllers.py +29 -10
  25. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +41 -11
  26. scout/server/blueprints/cases/templates/cases/case.html +1 -1
  27. scout/server/blueprints/cases/templates/cases/utils.html +6 -6
  28. scout/server/blueprints/clinvar/controllers.py +29 -14
  29. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +13 -4
  30. scout/server/blueprints/clinvar/views.py +14 -2
  31. scout/server/blueprints/institutes/controllers.py +10 -2
  32. scout/server/blueprints/institutes/templates/overview/filters.html +14 -1
  33. scout/server/blueprints/login/controllers.py +112 -12
  34. scout/server/blueprints/login/views.py +38 -60
  35. scout/server/blueprints/public/templates/public/index.html +5 -1
  36. scout/server/blueprints/variant/controllers.py +1 -1
  37. scout/server/blueprints/variant/templates/variant/acmg.html +6 -2
  38. scout/server/blueprints/variant/templates/variant/components.html +19 -0
  39. scout/server/blueprints/variant/templates/variant/utils.html +3 -3
  40. scout/server/blueprints/variants/controllers.py +10 -1
  41. scout/server/blueprints/variants/templates/variants/components.html +28 -0
  42. scout/server/blueprints/variants/templates/variants/mei-variants.html +8 -6
  43. scout/server/blueprints/variants/templates/variants/sv-variants.html +9 -7
  44. scout/server/blueprints/variants/templates/variants/utils.html +8 -12
  45. scout/server/blueprints/variants/templates/variants/variants.html +4 -25
  46. scout/server/config.py +8 -0
  47. scout/server/utils.py +22 -5
  48. scout/utils/acmg.py +25 -26
  49. scout/utils/ensembl_biomart_clients.py +1 -1
  50. scout/utils/ensembl_rest_clients.py +25 -32
  51. scout/utils/hgvs.py +1 -1
  52. {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/METADATA +10 -14
  53. {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/RECORD +56 -56
  54. {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/WHEEL +0 -0
  55. {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/entry_points.txt +0 -0
  56. {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/licenses/LICENSE +0 -0
@@ -325,21 +325,21 @@
325
325
 
326
326
  {% if variant.category in ("mei", "str", "snv", "cancer") %}
327
327
  {% if variant.category == "cancer" %}
328
- <a href="{{ url_for('variant.cancer_variant',
328
+ <a href='{{ url_for('variant.cancer_variant',
329
329
  institute_id=variant.institute,
330
330
  case_name=case.display_name,
331
- variant_id=variant._id) }}" target="_blank">
331
+ variant_id=variant._id) }}' target='_blank'>
332
332
  {% else %}
333
- <a href="{{ url_for('variant.variant',
333
+ <a href='{{ url_for('variant.variant',
334
334
  institute_id=variant.institute,
335
335
  case_name=case.display_name,
336
- variant_id=variant._id) }}" target="_blank">
336
+ variant_id=variant._id) }}' target='_blank'>
337
337
  {% endif %}
338
338
  {% else %} <!-- structural variants -->
339
- <a href="{{ url_for('variant.sv_variant',
339
+ <a href='{{ url_for('variant.sv_variant',
340
340
  institute_id=variant.institute,
341
341
  case_name=case.display_name,
342
- variant_id=variant._id) }}" target="_blank">
342
+ variant_id=variant._id) }}' target='_blank'>
343
343
  {% endif %}
344
344
  {{ pretty_variant(variant) }}
345
345
  </a>
@@ -28,30 +28,45 @@ LOG = logging.getLogger(__name__)
28
28
 
29
29
 
30
30
  def _get_var_tx_hgvs(case_obj: dict, variant_obj: dict) -> List[Tuple[str, str]]:
31
- """Retrieve all transcripts / hgvs for a given variant."""
31
+ """Retrieve all transcripts / HGVS for a given variant."""
32
32
 
33
33
  build = str(case_obj.get("genome_build", "37"))
34
34
  tx_hgvs_list = [("", "Do not specify")]
35
+ case_has_build_37 = "37" in case_obj.get("genome_build", "37")
36
+
35
37
  add_gene_info(store, variant_obj, genome_build=build)
38
+
36
39
  for gene in variant_obj.get("genes", []):
37
- for tx in gene.get("transcripts", []):
40
+ transcripts = gene.get("transcripts", [])
41
+
42
+ for tx in transcripts:
43
+
44
+ refseq_id = tx.get("refseq_id")
45
+ coding_seq_name = tx.get("coding_sequence_name")
46
+ if not (refseq_id and coding_seq_name):
47
+ continue # Skip transcripts missing required fields
48
+
38
49
  mane_select = tx.get("mane_select_transcript")
39
- if all([tx.get("refseq_id"), tx.get("coding_sequence_name")]):
40
- for refseq in tx.get("refseq_identifiers"):
41
- refseq_version = fetch_refseq_version(refseq) # adds version to a refseq ID
42
- hgvs_simple = ":".join([refseq_version, tx["coding_sequence_name"]])
50
+ mane_plus_clinical = tx.get("mane_plus_clinical_transcript")
51
+
52
+ for refseq in tx.get("refseq_identifiers", []):
53
+ refseq_version = fetch_refseq_version(refseq) # Adds version to a RefSeq ID
54
+ hgvs_simple = f"{refseq_version}:{coding_seq_name}"
55
+
56
+ refseq_is_mane_select = mane_select == refseq_version
57
+ refseq_is_mane_plus_clinical = mane_plus_clinical == refseq_version
43
58
 
44
- # Validate descriptor using VariantValidator
45
- validated = validate_hgvs(build, hgvs_simple)
59
+ # Transcript is validate only when conditions are met
60
+ validated = (
61
+ validate_hgvs(build, hgvs_simple)
62
+ if (case_has_build_37 or refseq_is_mane_select or refseq_is_mane_plus_clinical)
63
+ else ""
64
+ )
46
65
 
47
- label = hgvs_simple
48
- if validated:
49
- label += "_validated_"
66
+ label = f"{hgvs_simple}{'_validated_' if validated else ''}{'_mane-select_' if refseq_is_mane_select else ''}{'_mane-plus-clinical_' if refseq_is_mane_plus_clinical else ''}"
50
67
 
51
- if mane_select and mane_select == refseq_version:
52
- label += "_mane-select_"
68
+ tx_hgvs_list.append((hgvs_simple, label))
53
69
 
54
- tx_hgvs_list.append((hgvs_simple, label))
55
70
  return tx_hgvs_list
56
71
 
57
72
 
@@ -92,7 +92,7 @@
92
92
  {% if var_category == 'snv' %}
93
93
  <br><br>
94
94
  <!-- Transcripts & HGVS:(optional) -->
95
- {{variant_data.var_form.tx_hgvs.label(class="fw-bold, text-dark")}}
95
+ {{ variant_data.var_form.tx_hgvs.label(class="fw-bold, text-dark") }}
96
96
 
97
97
  <span class="text-danger" data-bs-toggle='tooltip' title="If you do not provide any HGVS expression, chromosome coordinates will be used to describe this variant instead (automatic). HGVS expressions were validated using VariantValidator"><strong>?</strong></span>
98
98
  <span class="badge bg-primary float-end"><a class="text-white" href="https://variantvalidator.org/service/validate/" target="_blank" rel="noopener">VariantValidator</a></span>
@@ -101,7 +101,7 @@
101
101
  {% with messages = get_flashed_messages() %}
102
102
  {% if messages %}
103
103
  {% for message in messages %}
104
- <span class="ml-3" style="color:red">{{ message }}</span><br>
104
+ <span class="ml-3">{{ message }}</span><br>
105
105
  {% endfor %}
106
106
  <br><br>
107
107
  {% endif %}
@@ -113,14 +113,17 @@
113
113
  {% for item_row in variant_data.var_form.tx_hgvs | batch(3) %}
114
114
  <tr>
115
115
  {% for item in item_row %}
116
- {% set ns = namespace(label='', validated=false, mane_select=false) %}
117
-
116
+ {% set ns = namespace(label='', validated=false, mane_select=false, mane_plus_clinical=false) %}
118
117
  {% if "_validated_" in item.label.text %}
119
118
  {% set ns.validated = true %}
120
119
  {% endif %}
121
120
  {% if "_mane-select_" in item.label.text %}
122
121
  {% set ns.mane_select = true %}
123
122
  {% endif %}
123
+ {% if "_mane-plus-clinical_" in item.label.text %}
124
+ {% set ns.mane_plus_clinical = true %}
125
+ {% endif %}
126
+
124
127
  {% set ns.label = item.label.text | replace("_validated_", "") | replace("_mane-select_", "") %}
125
128
 
126
129
  <td style="width: 3%; text-align: right; vertical-align: baseline;">
@@ -138,6 +141,9 @@
138
141
  {% if ns.mane_select %}
139
142
  <span class='badge bg-dark'>MANE SELECT</span>
140
143
  {% endif %}
144
+ {% if ns.mane_plus_clinical %}
145
+ <span class='badge bg-dark'>MANE PLUS CLINICAL</span>
146
+ {% endif %}
141
147
  {% if ns.validated %}
142
148
  <em class="fa fa-check text-success" aria-hidden="true" data-bs-toggle="tooltip" title="Verified by VariantValidator"></em>
143
149
  {% endif %}
@@ -147,6 +153,9 @@
147
153
  </tr>
148
154
  {% endfor %}
149
155
  </table>
156
+
157
+ <span class="text-danger">HGVS validation is performed only for variants in build GRCh37 or MANE Select and MANE Plus Clinical transcripts. For any other available HGVS present in this list, manual validation can be performed using the VariantValidator link above.</span>
158
+
150
159
  <br><br>
151
160
  <!-- dbSNP identifier -->
152
161
  {{variant_data.var_form.variations_ids.label(class="fw-bold, text-dark")}}
@@ -3,10 +3,22 @@ from json import dumps
3
3
  from tempfile import NamedTemporaryFile
4
4
  from typing import List, Tuple
5
5
 
6
- from flask import Blueprint, flash, redirect, render_template, request, send_file, url_for
6
+ from flask import (
7
+ Blueprint,
8
+ flash,
9
+ redirect,
10
+ render_template,
11
+ request,
12
+ send_file,
13
+ url_for,
14
+ )
7
15
  from flask_login import current_user
8
16
 
9
- from scout.constants.clinvar import CASEDATA_HEADER, CLINVAR_HEADER, GERMLINE_CLASSIF_TERMS
17
+ from scout.constants.clinvar import (
18
+ CASEDATA_HEADER,
19
+ CLINVAR_HEADER,
20
+ GERMLINE_CLASSIF_TERMS,
21
+ )
10
22
  from scout.server.extensions import clinvar_api, store
11
23
  from scout.server.utils import institute_and_case
12
24
 
@@ -391,8 +391,16 @@ def update_institute_settings(store: MongoAdapter, institute_obj: Dict, form: Mu
391
391
  updated_institute = store.update_institute(
392
392
  internal_id=institute_obj["_id"],
393
393
  sanger_recipients=get_sanger_recipients(form),
394
- coverage_cutoff=int(form.get("coverage_cutoff")),
395
- frequency_cutoff=float(form.get("frequency_cutoff")),
394
+ coverage_cutoff=(
395
+ int(form["coverage_cutoff"])
396
+ if form.get("coverage_cutoff")
397
+ else form.get("coverage_cutoff")
398
+ ),
399
+ frequency_cutoff=(
400
+ float(form["frequency_cutoff"])
401
+ if form.get("frequency_cutoff")
402
+ else form.get("frequency_cutoff")
403
+ ),
396
404
  show_all_cases_status=form.getlist("show_all_cases_status"),
397
405
  display_name=form.get("display_name"),
398
406
  phenotype_groups=phenotype_groups,
@@ -34,8 +34,21 @@
34
34
  <div class="row" id="body-row"> <!--sidebar and main container are on the same row-->
35
35
  {{ institute_actionbar(institute) }} <!-- This is the sidebar -->
36
36
  <div class="col">
37
+
37
38
  <div class="card">
38
- <div class="card-header"><b>Filters created for institute on variants pages</b></div>
39
+ <div class="card-header"><b>Soft filters created by an admin on the institute's settings page</b></div>
40
+ <div class="card-body">
41
+ {% for filter_tag in institute.soft_filters %}
42
+ <span class="badge bg-secondary">{{filter_tag}}</span>
43
+ {% else%}
44
+ No soft filters available
45
+ {% endfor %}
46
+
47
+ </div>
48
+ </div>
49
+
50
+ <div class="card mt-3">
51
+ <div class="card-header"><b>Custom filters created for institute on variants pages</b></div>
39
52
  <div class="card-body">
40
53
  <ul class="list-group">
41
54
  <li class="list-group-item list-group-item-heading">
@@ -1,22 +1,20 @@
1
1
  import logging
2
+ from datetime import datetime
3
+ from typing import Optional
2
4
 
3
- from flask import current_app, flash
5
+ import requests
6
+ from flask import Response, current_app, flash, redirect, session, url_for
7
+ from flask_login import login_user
4
8
 
5
- from scout.server.extensions import ldap_manager
6
-
7
- LOG = logging.getLogger(__name__)
9
+ from scout.server.extensions import ldap_manager, oauth_client, store
8
10
 
11
+ from .models import LoginUser
9
12
 
10
- def ldap_authorized(userid, password):
11
- """Log in a LDAP user
13
+ LOG = logging.getLogger(__name__)
12
14
 
13
- Args:
14
- userid(str): user id provided in ldap login form (usually and email)
15
- password(str): user passord provided in ldap loding form
16
15
 
17
- Returns:
18
- authorized(bool): True or False
19
- """
16
+ def ldap_authorized(userid: str, password: str) -> bool:
17
+ """Log in an LDAP user."""
20
18
  authorized = False
21
19
  try:
22
20
  authorized = ldap_manager.authenticate(
@@ -63,3 +61,105 @@ def users(store):
63
61
  return dict(
64
62
  users=sorted(user_objs, key=lambda user: -user["events"]),
65
63
  )
64
+
65
+
66
+ def user_has_consented(user_consent: Optional[str]) -> bool:
67
+ """Check if user has given consent for activity logging."""
68
+ if not current_app.config.get("USERS_ACTIVITY_LOG_PATH"):
69
+ return True
70
+
71
+ if user_consent is None and "consent_given" not in session:
72
+ flash(
73
+ "Logging user activity is a requirement for using this site and accessing your account. Without consent to activity logging, you will not be able to log in into Scout.",
74
+ "warning",
75
+ )
76
+ return False
77
+ return True
78
+
79
+
80
+ def ldap_login(ldap_user: Optional[str], ldap_password: Optional[str]) -> Optional[str]:
81
+ """Authenticate user via LDAP and return user ID if authorized."""
82
+
83
+ if not ldap_user or not ldap_password:
84
+ return None
85
+
86
+ authorized: bool = ldap_authorized(ldap_user, ldap_password)
87
+
88
+ if authorized:
89
+ return ldap_user
90
+
91
+ flash("User not authorized by LDAP server", "warning")
92
+
93
+
94
+ def google_login() -> Optional[Response]:
95
+ """Authenticate user via Google OIDC and redirect to the redirect URI. The name of this endpoint should be present on the Google login settings."""
96
+ if "email" in session:
97
+ return redirect(url_for("public.login")) # Redirect to the login route with session info
98
+
99
+ redirect_uri: str = url_for("login.authorized", _external=True)
100
+ try:
101
+ return oauth_client.google.authorize_redirect(redirect_uri)
102
+ except Exception:
103
+ flash("An error has occurred while logging in user using Google OAuth", "warning")
104
+ return None
105
+
106
+
107
+ def keycloak_login() -> Optional[Response]:
108
+ """Authenticate user via Keycloak OIDC and redirect to the redirect URI. The name of this endpoint should be present on the Keycloak login settings."""
109
+
110
+ redirect_uri: str = url_for("login.authorized", _external=True)
111
+ try:
112
+ return oauth_client.keycloak.authorize_redirect(redirect_uri)
113
+ except Exception:
114
+ flash("An error has occurred while logging in user using Keycloak", "warning")
115
+ return None
116
+
117
+
118
+ def database_login(user_mail: Optional[str]) -> Optional[str]:
119
+ """Authenticate user against the Scout database and return email if successful."""
120
+ return user_mail
121
+
122
+
123
+ def validate_and_login_user(user_mail: Optional[str], user_id: Optional[str]) -> Response:
124
+ """Validate user in Scout database and log them in."""
125
+ user_obj: Optional[dict] = store.user(email=user_mail, user_id=user_id)
126
+
127
+ if user_obj is None:
128
+ flash("User not found in Scout database", "warning")
129
+ session.pop("email", None)
130
+ return redirect(url_for("public.index"))
131
+
132
+ user_obj["accessed_at"] = datetime.now()
133
+
134
+ if session.get("name"): # Set name & locale if available (Google Auth)
135
+ user_obj["name"] = session.get("name")
136
+ user_obj["locale"] = session.get("locale")
137
+
138
+ store.update_user(user_obj)
139
+ return perform_flask_login(LoginUser(user_obj))
140
+
141
+
142
+ def perform_flask_login(user_dict: "LoginUser") -> Response:
143
+ """Login user using Flask-Login."""
144
+ if login_user(user_dict, remember=True):
145
+ flash(f"Welcome {user_dict.name}", "success")
146
+ return redirect(url_for("cases.index"))
147
+
148
+ flash("Sorry, you could not log in", "warning")
149
+ return redirect(url_for("public.index"))
150
+
151
+
152
+ def logout_oidc_user(session, provider: str):
153
+ """Log out a user from an OIDC login provider-"""
154
+ logout_url = current_app.config[provider].get("logout_url")
155
+ if not logout_url or not session.get("token_response"):
156
+ return
157
+ refresh_token = session["token_response"]["refresh_token"]
158
+ requests.post(
159
+ logout_url,
160
+ data={
161
+ "client_id": current_app.config[provider]["client_id"],
162
+ "client_secret": current_app.config[provider]["client_secret"],
163
+ "refresh_token": refresh_token,
164
+ },
165
+ )
@@ -1,9 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import logging
3
- from datetime import datetime
3
+ from typing import Optional
4
4
 
5
5
  from flask import (
6
6
  Blueprint,
7
+ Response,
7
8
  current_app,
8
9
  flash,
9
10
  redirect,
@@ -45,82 +46,67 @@ def load_user(user_id):
45
46
 
46
47
  @login_bp.route("/login", methods=["GET", "POST"])
47
48
  @public_endpoint
48
- def login():
49
+ def login() -> Response:
49
50
  """Login a user if they have access."""
50
51
 
51
- if current_app.config.get("USERS_ACTIVITY_LOG_PATH"):
52
- if request.form.get("consent_checkbox") is None and "consent_given" not in session:
53
- flash(
54
- "Logging user activity is a requirement for using this site and accessing your account. Without consent to activity logging, you will not be able to log in into Scout.",
55
- "warning",
56
- )
57
- return redirect(url_for("public.index"))
58
- session["consent_given"] = True
52
+ if controllers.user_has_consented(user_consent=request.form.get("consent_checkbox")) is False:
53
+ return redirect(url_for("public.index"))
59
54
 
60
- user_id = None
61
- user_mail = None
55
+ user_id: Optional[str] = None
56
+ user_mail: Optional[str] = None
62
57
 
63
58
  if current_app.config.get("LDAP_HOST", current_app.config.get("LDAP_SERVER")):
64
- ldap_authorized = controllers.ldap_authorized(
65
- request.form.get("ldap_user"), request.form.get("ldap_password")
59
+ user_id = controllers.ldap_login(
60
+ ldap_user=request.form.get("ldap_user"), ldap_password=request.form.get("ldap_password")
66
61
  )
67
- if ldap_authorized is True:
68
- user_id = request.form.get("ldap_user")
69
- else:
70
- flash("User not authorized by LDAP server", "warning")
62
+ if user_id is None:
71
63
  return redirect(url_for("public.index"))
72
64
 
73
65
  elif current_app.config.get("GOOGLE"):
74
66
  if session.get("email"):
75
67
  user_mail = session["email"]
76
- session.pop("email", None)
77
68
  else:
78
- redirect_uri = url_for(".authorized", _external=True)
79
- try:
80
- return oauth_client.google.authorize_redirect(redirect_uri)
81
- except Exception as ex:
82
- flash("An error has occurred while logging in user using Google OAuth")
83
-
84
- elif request.form.get("email"): # log in against Scout database
85
- user_mail = request.form["email"]
86
- LOG.info("Validating user %s email %s against Scout database", user_id, user_mail)
87
-
88
- user_obj = store.user(email=user_mail, user_id=user_id)
89
- if user_obj is None:
90
- flash("User not found in Scout database", "warning")
91
- return redirect(url_for("public.index"))
69
+ # Redirect to Google OAuth if not completed
70
+ return controllers.google_login()
71
+
72
+ elif current_app.config.get("KEYCLOAK"):
73
+ if session.get("email"):
74
+ user_mail = session["email"]
75
+ else:
76
+ return controllers.keycloak_login()
92
77
 
93
- user_obj["accessed_at"] = datetime.now()
94
- if session.get("name"): # These args come from google auth
95
- user_obj["name"] = session.get("name")
96
- user_obj["locale"] = session.get("locale")
97
- store.update_user(user_obj)
78
+ elif request.form.get("email"):
79
+ user_mail = controllers.database_login(user_mail=request.form.get("email"))
98
80
 
99
- user_dict = LoginUser(user_obj)
100
- return perform_login(user_dict)
81
+ return controllers.validate_and_login_user(user_mail=user_mail, user_id=user_id)
101
82
 
102
83
 
103
84
  @login_bp.route("/authorized")
104
85
  @public_endpoint
105
86
  def authorized():
106
- """Google auth callback function"""
107
-
108
- token = oauth_client.google.authorize_access_token()
109
- google_user = oauth_client.google.parse_id_token(token, None)
110
- session["email"] = google_user.get("email").lower()
111
- session["name"] = google_user.get("name")
112
- session["locale"] = google_user.get("locale")
87
+ """OIDC callback function."""
88
+ if current_app.config.get("GOOGLE"):
89
+ client = oauth_client.google
90
+ if current_app.config.get("KEYCLOAK"):
91
+ client = oauth_client.keycloak
92
+ token = client.authorize_access_token()
93
+ user = client.parse_id_token(token, None)
94
+
95
+ session["email"] = user.get("email").lower()
96
+ session["name"] = user.get("name")
97
+ session["locale"] = user.get("locale")
98
+ session["token_response"] = token
113
99
 
114
100
  return redirect(url_for(".login"))
115
101
 
116
102
 
117
103
  @login_bp.route("/logout")
118
104
  def logout():
119
- logout_user()
120
- session.pop("email", None)
121
- session.pop("name", None)
122
- session.pop("locale", None)
123
- session.pop("consent_given", None)
105
+ logout_user() # logs out user from scout
106
+ for provider in ["GOOGLE", "KEYCLOAK"]:
107
+ if current_app.config.get(provider):
108
+ controllers.logout_oidc_user(session, provider)
109
+ session.clear()
124
110
  flash("you logged out", "success")
125
111
  return redirect(url_for("public.index"))
126
112
 
@@ -130,11 +116,3 @@ def users():
130
116
  """Show all users in the system."""
131
117
  data = controllers.users(store)
132
118
  return render_template("login/users.html", **data)
133
-
134
-
135
- def perform_login(user_dict):
136
- if login_user(user_dict, remember=True):
137
- flash("you logged in as: {}".format(user_dict.name), "success")
138
- return redirect(url_for("cases.index"))
139
- flash("sorry, you could not log in", "warning")
140
- return redirect(url_for("public.index"))
@@ -21,7 +21,11 @@
21
21
  </p>
22
22
  {% else %}
23
23
  <form class="row" method="POST" action="{{ url_for('login.login') }}">
24
- {% if config.GOOGLE %}
24
+ {% if config.KEYCLOAK %}
25
+ <button name="googleLogin" class="btn btn-primary btn-lg text-white" href="{{ url_for('login.login') }}" type="submit">
26
+ Login with Keycloak
27
+ </button>
28
+ {% elif config.GOOGLE %}
25
29
  <button name="googleLogin" class="btn btn-primary btn-lg text-white" href="{{ url_for('login.login') }}" type="submit">
26
30
  Login with Google
27
31
  </button>
@@ -369,7 +369,7 @@ def variant(
369
369
 
370
370
  overlapping_vars = []
371
371
  if get_overlapping:
372
- for var in store.overlapping(variant_obj):
372
+ for var in store.hgnc_overlapping(variant_obj):
373
373
  var.update(predictions(var.get("genes", [])))
374
374
  overlapping_vars.append(var)
375
375
  variant_obj["end_chrom"] = variant_obj.get("end_chrom", variant_obj["chromosome"])
@@ -79,7 +79,11 @@
79
79
  <div class="row">
80
80
  <select {{ 'disabled' if evaluation and edit is false}} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
81
81
  <option value="" {% if not modifier %}selected{% endif %}>Strength modifier...</option>
82
- {% for level in "Strong", "Moderate", "Supporting" %}
82
+ {% set sa_level = "Stand-alone" %}
83
+ {% if category == "pathogenicity" %}
84
+ {% set sa_level = "Very Strong" %}
85
+ {% endif %}
86
+ {% for level in sa_level, "Strong", "Moderate", "Supporting" %}
83
87
  {% if(level != evidence) %}
84
88
  <option id="{{ criterion_code }}-{{ level }}" value="{{ level }}" {% if modifier == level %}selected{% endif %}>{{ level }}</option>
85
89
  {% endif %}
@@ -148,7 +152,7 @@
148
152
 
149
153
  function update_classification() {
150
154
  var criteria = $(':checked').map(function(idx, elem) {
151
- const modifiers = ["Strong", "Moderate", "Supporting"];
155
+ const modifiers = ["Stand-alone", "Very Strong", "Strong", "Moderate", "Supporting"];
152
156
  for (possible_modifier of modifiers) {
153
157
  var modifier_option;
154
158
  if(elem.value !== null && elem.value !== '') {
@@ -32,6 +32,25 @@
32
32
  </tbody>
33
33
  </table>
34
34
  {% endif %}
35
+
36
+ {% if variant.clnsig_onc %}
37
+ <table id='clinsig_table' class="table table-bordered table-sm">
38
+ <thead>
39
+ <th>Oncogenicity</th>
40
+ <th>Disease name</th>
41
+ <th>Revstat</th>
42
+ </thead>
43
+ <tbody>
44
+ {% for classif in variant.clnsig_onc %}
45
+ <tr>
46
+ <td>{{ classif.value|capitalize }}</td>
47
+ <td>{{ classif.dn|replace("_", " ")|replace(",", ", ") }}</td>
48
+ <td>{{ classif.revstat }}</td>
49
+ </tr>
50
+ {% endfor %}
51
+ </tbody>
52
+ </table>
53
+ {% endif %}
35
54
  {% endmacro %}
36
55
 
37
56
  {% macro acmg_classification_item(variant, data) %}
@@ -118,10 +118,10 @@
118
118
 
119
119
  {% macro overlapping_panel(variant, overlapping_vars, case, institute) %}
120
120
  <div class="card panel-default">
121
- {% if variant.category == 'sv' %}
122
- <div class="panel-heading">Overlapping SNVs</div>
121
+ {% if variant.category != "snv" %}
122
+ <div class="panel-heading">Gene overlapping variants</div>
123
123
  {% else %}
124
- <div class="panel-heading">Overlapping SVs</div>
124
+ <div class="panel-heading">Gene overlapping non-SNVs</div>
125
125
  {% endif %}
126
126
  <div class="card-body">
127
127
  <div class="table-responsive">
@@ -137,7 +137,7 @@ def variants(
137
137
 
138
138
  variants = []
139
139
  for variant_obj in variant_res:
140
- overlapping_svs = list(store.overlapping(variant_obj))
140
+ overlapping_svs = list(store.hgnc_overlapping(variant_obj))
141
141
  variant_obj["overlapping"] = overlapping_svs or None
142
142
 
143
143
  evaluations = []
@@ -233,6 +233,9 @@ def sv_variants(store, institute_obj, case_obj, variants_query, variant_count, p
233
233
  case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
234
234
 
235
235
  for variant_obj in variants_query.skip(skip_count).limit(per_page):
236
+ overlapping_svs = list(store.hgnc_overlapping(variant_obj))
237
+ variant_obj["overlapping"] = overlapping_svs or None
238
+
236
239
  # show previous classifications for research variants
237
240
  clinical_var_obj = variant_obj
238
241
  if variant_obj["variant_type"] == "research":
@@ -279,6 +282,9 @@ def mei_variants(
279
282
  case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
280
283
 
281
284
  for variant_obj in variants_query.skip(skip_count).limit(per_page):
285
+ overlapping = list(store.hgnc_overlapping(variant_obj))
286
+ variant_obj["overlapping"] = overlapping or None
287
+
282
288
  # show previous classifications for research variants
283
289
  clinical_var_obj = variant_obj
284
290
  if variant_obj["variant_type"] == "research":
@@ -1448,6 +1454,9 @@ def cancer_variants(store, institute_id, case_name, variants_query, variant_coun
1448
1454
  variant_obj["second_rep_gene"] = secondary_gene
1449
1455
  variant_obj["clinical_assessments"] = get_manual_assessments(variant_obj)
1450
1456
 
1457
+ overlapping = list(store.hgnc_overlapping(variant_obj))
1458
+ variant_obj["overlapping"] = overlapping or None
1459
+
1451
1460
  evaluations = []
1452
1461
  # Get previous ClinGen-CGC-VIGG evaluations of the variant from other cases
1453
1462
  for evaluation_obj in store.get_ccv_evaluations(variant_obj):