From 6e0ee86f5ad0cc0f533c4001c45c79be95e7a03c Mon Sep 17 00:00:00 2001 From: Hana Snow Date: Fri, 28 Jun 2024 11:52:57 -0400 Subject: [PATCH 1/3] add vlm contact db field; --- CHANGELOG.md | 1 + .../0068_project_vlm_contact_email.py | 29 +++++++++++++++++++ seqr/models.py | 5 +++- settings.py | 2 ++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 seqr/migrations/0068_project_vlm_contact_email.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 984ec30bbb..cf141bec2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # _seqr_ Changes ## dev +* Add VLM contact for Projects (REQUIRES DB MIGRATION) ## 6/11/24 * Add "Partial Phenotype Contribution" functional tag (REQUIRES DB MIGRATION) diff --git a/seqr/migrations/0068_project_vlm_contact_email.py b/seqr/migrations/0068_project_vlm_contact_email.py new file mode 100644 index 0000000000..c158184936 --- /dev/null +++ b/seqr/migrations/0068_project_vlm_contact_email.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.23 on 2024-06-28 15:44 + +from django.db import migrations, models + + +def update_vlm_contact_email(apps, schema_editor): + Project = apps.get_model('seqr', 'Project') + db_alias = schema_editor.connection.alias + + projects = Project.objects.using(db_alias).all() + for project in projects: + project.vlm_contact_email = project.mme_contact_url.replace('mailto:', '').replace('matchmaker', 'vlm') + Project.objects.using(db_alias).bulk_update(projects, ['vlm_contact_email']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('seqr', '0067_alter_variantfunctionaldata_functional_data_tag'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='vlm_contact_email', + field=models.TextField(blank=True, default='vlm@broadinstitute.org', null=True), + ), + migrations.RunPython(update_vlm_contact_email, reverse_code=migrations.RunPython.noop), + ] diff --git a/seqr/models.py b/seqr/models.py index 1cd1f46b0e..770a37cce1 100644 --- a/seqr/models.py +++ b/seqr/models.py @@ -17,7 +17,8 @@ from seqr.utils.xpos_utils import get_chrom_pos from seqr.views.utils.terra_api_utils import anvil_enabled from reference_data.models import GENOME_VERSION_GRCh37, GENOME_VERSION_CHOICES -from settings import MME_DEFAULT_CONTACT_NAME, MME_DEFAULT_CONTACT_HREF, MME_DEFAULT_CONTACT_INSTITUTION +from settings import MME_DEFAULT_CONTACT_NAME, MME_DEFAULT_CONTACT_HREF, MME_DEFAULT_CONTACT_INSTITUTION, \ + VLM_DEFAULT_CONTACT_EMAIL logger = SeqrLogger(__name__) @@ -196,6 +197,8 @@ class Project(ModelWithGUID): mme_contact_url = models.TextField(null=True, blank=True, default=MME_DEFAULT_CONTACT_HREF) mme_contact_institution = models.TextField(null=True, blank=True, default=MME_DEFAULT_CONTACT_INSTITUTION) + vlm_contact_email = models.TextField(null=True, blank=True, default=VLM_DEFAULT_CONTACT_EMAIL) + has_case_review = models.BooleanField(default=False) enable_hgmd = models.BooleanField(default=False) all_user_demo = models.BooleanField(default=False) diff --git a/settings.py b/settings.py index b7fd626bfa..852d1ddac4 100644 --- a/settings.py +++ b/settings.py @@ -352,6 +352,8 @@ MME_DEFAULT_CONTACT_EMAIL = 'matchmaker@broadinstitute.org' MME_DEFAULT_CONTACT_HREF = 'mailto:{}'.format(MME_DEFAULT_CONTACT_EMAIL) +VLM_DEFAULT_CONTACT_EMAIL = 'vlm@broadinstitute.org' + MME_CONFIG_DIR = os.environ.get('MME_CONFIG_DIR', '') MME_NODES = {} if MME_CONFIG_DIR: From d1c6891ed4953c8bb0322f996a08a79d259f0950 Mon Sep 17 00:00:00 2001 From: Hana Snow Date: Fri, 28 Jun 2024 12:10:11 -0400 Subject: [PATCH 2/3] use vlm_contact_email in create and responses --- seqr/fixtures/1kg_project.json | 4 +++ seqr/models.py | 1 + seqr/views/apis/anvil_workspace_api.py | 1 + seqr/views/apis/anvil_workspace_api_tests.py | 29 ++++++++++++++------ seqr/views/apis/project_api_tests.py | 27 ++++++++++++++---- seqr/views/utils/test_utils.py | 2 +- 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/seqr/fixtures/1kg_project.json b/seqr/fixtures/1kg_project.json index bad151c995..6f83fda996 100644 --- a/seqr/fixtures/1kg_project.json +++ b/seqr/fixtures/1kg_project.json @@ -20,6 +20,7 @@ "has_case_review": true, "mme_primary_data_owner": "PI", "mme_contact_url": "mailto:test@broadinstitute.org,matchmaker@broadinstitute.org", + "vlm_contact_email": "test@broadinstitute.org,vlm@broadinstitute.org", "last_accessed_date": "2017-09-15T18:15:50.827Z" } }, @@ -41,6 +42,7 @@ "can_view_group": 3, "is_mme_enabled": false, "mme_primary_data_owner": "", + "vlm_contact_email": "vlm@broadinstitute.org", "last_accessed_date": "2017-09-15T18:15:50.827Z" } }, @@ -63,6 +65,7 @@ "is_demo": true, "mme_primary_data_owner": "", "mme_contact_url": "mailto:seqr-test@gmail.com,test@broadinstitute.org", + "vlm_contact_email": "seqr-test@gmail.com,test@broadinstitute.org", "last_accessed_date": "2017-09-15T18:15:50.827Z" } }, @@ -81,6 +84,7 @@ "last_accessed_date": "2017-09-15T18:15:50.827Z", "consent_code": "H", "genome_version": "38", + "vlm_contact_email": "vlm@broadinstitute.org", "workspace_name": "anvil-non-analyst-project 1000 Genomes Demo", "workspace_namespace": "ext-data" } diff --git a/seqr/models.py b/seqr/models.py index 770a37cce1..bda633b9b7 100644 --- a/seqr/models.py +++ b/seqr/models.py @@ -264,6 +264,7 @@ class Meta: 'name', 'description', 'created_date', 'last_modified_date', 'genome_version', 'mme_contact_institution', 'last_accessed_date', 'is_mme_enabled', 'mme_primary_data_owner', 'mme_contact_url', 'guid', 'consent_code', 'workspace_namespace', 'workspace_name', 'has_case_review', 'enable_hgmd', 'is_demo', 'all_user_demo', + 'vlm_contact_email', ] diff --git a/seqr/views/apis/anvil_workspace_api.py b/seqr/views/apis/anvil_workspace_api.py index 281be65beb..9a0f006c14 100644 --- a/seqr/views/apis/anvil_workspace_api.py +++ b/seqr/views/apis/anvil_workspace_api.py @@ -184,6 +184,7 @@ def create_project_from_workspace(request, namespace, name): 'workspace_name': name, 'mme_primary_data_owner': request.user.get_full_name(), 'mme_contact_url': 'mailto:{}'.format(request.user.email), + 'vlm_contact_email': request.user.email, } project = create_model_from_json(Project, project_args, user=request.user) diff --git a/seqr/views/apis/anvil_workspace_api_tests.py b/seqr/views/apis/anvil_workspace_api_tests.py index f24cc485d1..4f000b641b 100644 --- a/seqr/views/apis/anvil_workspace_api_tests.py +++ b/seqr/views/apis/anvil_workspace_api_tests.py @@ -559,14 +559,27 @@ def test_create_project_from_workspace(self): self.assertEqual(response.status_code, 200) project = Project.objects.get(workspace_namespace=TEST_WORKSPACE_NAMESPACE, workspace_name=TEST_NO_PROJECT_WORKSPACE_NAME) response_json = response.json() - self.assertEqual(project.guid, response_json['projectGuid']) - self.assertListEqual( - [project.genome_version, project.description, project.workspace_namespace, project.workspace_name], - ['38', 'A test project', TEST_WORKSPACE_NAMESPACE, TEST_NO_PROJECT_WORKSPACE_NAME]) - - self.assertListEqual( - [project.mme_contact_institution, project.mme_primary_data_owner, project.mme_contact_url], - ['Broad Center for Mendelian Genomics', 'Test Manager User', 'mailto:test_user_manager@test.com']) + self.assertDictEqual({k: getattr(project, k) for k in project._meta.json_fields}, { + 'guid': response_json['projectGuid'], + 'name': TEST_NO_PROJECT_WORKSPACE_NAME, + 'description': 'A test project', + 'workspace_namespace': TEST_WORKSPACE_NAMESPACE, + 'workspace_name': TEST_NO_PROJECT_WORKSPACE_NAME, + 'has_case_review': False, + 'enable_hgmd': False, + 'is_demo': False, + 'all_user_demo': False, + 'consent_code': None, + 'created_date': mock.ANY, + 'last_modified_date': mock.ANY, + 'last_accessed_date': mock.ANY, + 'genome_version': '38', + 'is_mme_enabled': True, + 'mme_contact_institution': 'Broad Center for Mendelian Genomics', + 'mme_primary_data_owner': 'Test Manager User', + 'mme_contact_url': 'mailto:test_user_manager@test.com', + 'vlm_contact_email': 'test_user_manager@test.com', + }) self._assert_valid_operation(project, test_add_data=False) diff --git a/seqr/views/apis/project_api_tests.py b/seqr/views/apis/project_api_tests.py index 3f07dd8526..7c5c821542 100644 --- a/seqr/views/apis/project_api_tests.py +++ b/seqr/views/apis/project_api_tests.py @@ -73,15 +73,30 @@ def test_create_and_delete_project(self, mock_airtable_logger): # check that project was created new_project = Project.objects.get(name='new_project') - self.assertEqual(new_project.description, 'new project description') - self.assertEqual(new_project.genome_version, '38') - self.assertEqual(new_project.consent_code, 'H') - self.assertTrue(new_project.is_demo) - self.assertFalse(new_project.is_mme_enabled) self.assertEqual(new_project.created_by, self.pm_user) self.assertEqual(new_project.projectcategory_set.count(), 0) expected_workspace_name = self.CREATE_PROJECT_JSON.get('workspaceName') - self.assertEqual(new_project.workspace_name, expected_workspace_name) + self.assertDictEqual({k: getattr(new_project, k) for k in new_project._meta.json_fields}, { + 'guid': mock.ANY, + 'name': 'new_project', + 'description': 'new project description', + 'workspace_namespace': self.CREATE_PROJECT_JSON.get('workspaceNamespace'), + 'workspace_name': expected_workspace_name, + 'has_case_review': False, + 'enable_hgmd': False, + 'is_demo': True, + 'all_user_demo': False, + 'consent_code': 'H', + 'created_date': mock.ANY, + 'last_modified_date': mock.ANY, + 'last_accessed_date': mock.ANY, + 'genome_version': '38', + 'is_mme_enabled': False, + 'mme_contact_institution': 'Broad Center for Mendelian Genomics', + 'mme_primary_data_owner': 'Samantha Baxter', + 'mme_contact_url': 'mailto:matchmaker@broadinstitute.org', + 'vlm_contact_email': 'vlm@broadinstitute.org', + }) self._check_created_project_groups(new_project) project_guid = new_project.guid diff --git a/seqr/views/utils/test_utils.py b/seqr/views/utils/test_utils.py index f0ed234d4f..7ff2f6e935 100644 --- a/seqr/views/utils/test_utils.py +++ b/seqr/views/utils/test_utils.py @@ -738,7 +738,7 @@ def _get_list_param(call, param): 'projectGuid', 'projectCategoryGuids', 'canEdit', 'name', 'description', 'createdDate', 'lastModifiedDate', 'lastAccessedDate', 'mmeContactUrl', 'genomeVersion', 'mmePrimaryDataOwner', 'mmeContactInstitution', 'isMmeEnabled', 'workspaceName', 'workspaceNamespace', 'hasCaseReview', 'enableHgmd', 'isDemo', 'allUserDemo', - 'userIsCreator', 'consentCode', 'isAnalystProject', + 'userIsCreator', 'consentCode', 'isAnalystProject', 'vlmContactEmail', } ANALYSIS_GROUP_FIELDS = {'analysisGroupGuid', 'description', 'name', 'projectGuid', 'familyGuids'} From 0c6a3d1b43791f058e90322dac5102a6211191c9 Mon Sep 17 00:00:00 2001 From: Hana Snow Date: Fri, 28 Jun 2024 13:20:14 -0400 Subject: [PATCH 3/3] edit vlm cotnact --- .../components/buttons/EditProjectButton.jsx | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/ui/shared/components/buttons/EditProjectButton.jsx b/ui/shared/components/buttons/EditProjectButton.jsx index f1ca2212b4..3c3bedee63 100644 --- a/ui/shared/components/buttons/EditProjectButton.jsx +++ b/ui/shared/components/buttons/EditProjectButton.jsx @@ -3,6 +3,8 @@ import { connect } from 'react-redux' import PropTypes from 'prop-types' import { updateProject } from 'redux/rootReducer' +import { BaseSemanticInput } from '../form/Inputs' +import { validators } from '../form/FormHelpers' import UpdateButton from './UpdateButton' import { EDITABLE_PROJECT_FIELDS, @@ -11,10 +13,49 @@ import { MATCHMAKER_CONTACT_URL_FIELD, } from '../../utils/constants' -const MATCHMAKER_PROJECT_FIELDS = [ +const setBoolVal = onChange => data => onChange(data.checked ? null : 'vlm@broadinstitute.org') + +const VlmContactInput = ({ value, onChange, ...props }) => ([ + , + , +]) + +VlmContactInput.propTypes = { + value: PropTypes.string, + onChange: PropTypes.func, +} + +const VLM_CONTACT_FIELD = { + name: 'vlmContactEmail', + parse: val => val || null, + format: val => val || '', + validate: value => (!value ? undefined : validators.requiredEmail(value)), + component: VlmContactInput, +} + +const MATCHMAKER_PROJECT_FIELDS = [VLM_CONTACT_FIELD, ...[ { ...MATCHMAKER_CONTACT_NAME_FIELD, name: 'mmePrimaryDataOwner' }, { ...MATCHMAKER_CONTACT_URL_FIELD, name: 'mmeContactUrl' }, -].map(({ label, ...field }) => ({ ...field, label: `Matchmaker ${label}` })) +].map(({ label, ...field }) => ({ ...field, label: `Matchmaker ${label}` }))] // Field mapping based on whether project has matchmaker and user is a PM. Usage: FIELD_LOOKUP[isMmeEnabled][isPm] const FIELD_LOOKUP = {