From fe4550c464b3994d6c0f7c7ae495544863edbc4e Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 14:00:34 -0700 Subject: [PATCH 01/11] Add a helper function to generate pheno inventory json input The primed-phenotype-inventory workflow takes a "map" input, which essentially a json key-value pair. Add a helper function generate this input using data in the app. --- primed/primed_anvil/helpers.py | 100 +++++++- primed/primed_anvil/tests/test_helpers.py | 297 +++++++++++++++++++++- 2 files changed, 393 insertions(+), 4 deletions(-) diff --git a/primed/primed_anvil/helpers.py b/primed/primed_anvil/helpers.py index 39193ade..2544810c 100644 --- a/primed/primed_anvil/helpers.py +++ b/primed/primed_anvil/helpers.py @@ -1,6 +1,9 @@ +from itertools import groupby + import pandas as pd -from anvil_consortium_manager.models import WorkspaceGroupSharing -from django.db.models import Exists, F, OuterRef, Value +from anvil_consortium_manager.models import ManagedGroup, WorkspaceGroupSharing +from django.db.models import CharField, Exists, F, OuterRef, Value +from django.db.models.functions import Concat from primed.cdsa.models import CDSAWorkspace from primed.dbgap.models import dbGaPWorkspace @@ -127,3 +130,96 @@ def get_summary_table_data(): # Convert to a list of dictionaries for passing to the django-tables2 table. data = data.to_dict(orient="records") return data + + +def get_workspaces_for_phenotype_inventory(): + """Get input to the primed-phenotype-inventory workflow. + + This function generates the input for the "workspaces" field of the primed-phenotype-inventory workflow. Only + workspaces that have been shared with the consortium are included. + See dockstore link: https://dockstore.org/workflows/github.com/UW-GAC/primed-inventory-workflows/primed_phenotype_inventory:main?tab=info + + The "workspaces" field has the format: + { + "billing-project-1/workspace-1": "study1, study2", + "billing-project-2/workspace-2": "study3", + ... + } + """ # noqa: E501 + + # primed-all group. We will need this to determine if the workspace is shared with PRIMED_ALL. + primed_all = ManagedGroup.objects.get(name="PRIMED_ALL") + + dbgap_workspaces = ( + dbGaPWorkspace.objects.filter( + # Just those that are shared with PRIMED_ALL. + workspace__workspacegroupsharing__group=primed_all, + ) + .annotate( + workspace_name=Concat( + F("workspace__billing_project__name"), + Value("/"), + F("workspace__name"), + output_field=CharField(), + ), + study_names=F("dbgap_study_accession__studies__short_name"), + ) + .values( + # "workspace", + # "workspace_billing_project", + "workspace_name", + "study_names", + ) + ) + + cdsa_workspaces = ( + CDSAWorkspace.objects.filter( + # Just those that are shared with PRIMED_ALL. + workspace__workspacegroupsharing__group=primed_all, + ) + .annotate( + workspace_name=Concat( + F("workspace__billing_project__name"), + Value("/"), + F("workspace__name"), + output_field=CharField(), + ), + study_names=F("study__short_name"), + ) + .values( + "workspace_name", + "study_names", + ) + ) + + open_access_workspaces = ( + OpenAccessWorkspace.objects.filter( + # Just those that are shared with PRIMED_ALL. + workspace__workspacegroupsharing__group=primed_all, + ) + .annotate( + workspace_name=Concat( + F("workspace__billing_project__name"), + Value("/"), + F("workspace__name"), + output_field=CharField(), + ), + study_names=F("studies__short_name"), + ) + .values( + "workspace_name", + "study_names", + ) + ) + + # Combine all querysets and process into the expected output for the AnVIL workflow. + workspaces = dbgap_workspaces.union(cdsa_workspaces).union(open_access_workspaces) + + json = {} + for key, group in groupby(workspaces, lambda x: x["workspace_name"]): + study_names = [x["study_names"] if x["study_names"] else "" for x in group] + if not study_names: + study_names = "" + json[key] = ", ".join(study_names) + + return json diff --git a/primed/primed_anvil/tests/test_helpers.py b/primed/primed_anvil/tests/test_helpers.py index 78a20b57..a5bf54f1 100644 --- a/primed/primed_anvil/tests/test_helpers.py +++ b/primed/primed_anvil/tests/test_helpers.py @@ -1,10 +1,16 @@ """Tests of helper functions in the `primed_anvil` app.""" -from anvil_consortium_manager.tests.factories import WorkspaceGroupSharingFactory +from anvil_consortium_manager.tests.factories import ( + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) from django.test import TestCase from primed.cdsa.tests.factories import CDSAWorkspaceFactory -from primed.dbgap.tests.factories import dbGaPWorkspaceFactory +from primed.dbgap.tests.factories import ( + dbGaPStudyAccessionFactory, + dbGaPWorkspaceFactory, +) from primed.miscellaneous_workspaces.tests.factories import OpenAccessWorkspaceFactory from primed.primed_anvil.tests.factories import AvailableDataFactory, StudyFactory @@ -544,3 +550,290 @@ def test_one_cdsa_workspace_one_open_access_workspace_different_available_data( }, res, ) + + +class GetWorkspacesForPhenotypeInventoryTest(TestCase): + """Tests for the helpers.get_workspaces_for_phenotype_inventory method.""" + + def setUp(self): + """Set up the test case.""" + super().setUp() + # Create the PRIMED_ALL group. + self.primed_all_group = ManagedGroupFactory.create(name="PRIMED_ALL") + + def test_no_workspaces(self): + """get_workspaces_for_phenotype_inventory with no workspaces.""" + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_dbgap_workspace_not_shared(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + dbGaPWorkspaceFactory.create() + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_dbgap_workspace_shared_one_study(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + study = StudyFactory.create(short_name="TEST") + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + dbgap_study_accession__studies=[study], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST") + + def test_one_dbgap_workspace_shared_two_studies(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + study_1 = StudyFactory.create(short_name="TEST_2") + study_2 = StudyFactory.create(short_name="TEST_1") + study_accession = dbGaPStudyAccessionFactory.create(studies=[study_1, study_2]) + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + dbgap_study_accession=study_accession, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST_1, TEST_2") + + def test_two_dbgap_workspaces(self): + """get_workspaces_for_phenotype_inventory with two dbGaP workspaces.""" + study_1 = StudyFactory.create(short_name="TEST 1") + workspace_1 = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-1", + workspace__name="test-ws-1", + dbgap_study_accession__studies=[study_1], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_1.workspace, group=self.primed_all_group + ) + study_2 = StudyFactory.create(short_name="TEST 2") + workspace_2 = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-2", + workspace__name="test-ws-2", + dbgap_study_accession__studies=[study_2], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_2.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 2) + self.assertIn("test-bp-1/test-ws-1", res) + self.assertEqual(res["test-bp-1/test-ws-1"], "TEST 1") + self.assertIn("test-bp-2/test-ws-2", res) + self.assertEqual(res["test-bp-2/test-ws-2"], "TEST 2") + + def test_one_cdsa_workspace_not_shared(self): + """get_workspaces_for_phenotype_inventory with one CDSA workspace.""" + CDSAWorkspaceFactory.create() + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_cdsa_workspace_shared_one_study(self): + """get_workspaces_for_phenotype_inventory with one CDSA workspace.""" + study = StudyFactory.create(short_name="TEST") + workspace = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + study=study, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST") + + def test_two_cdsa_workspaces(self): + """get_workspaces_for_phenotype_inventory with two CDSA workspaces.""" + study_1 = StudyFactory.create(short_name="TEST 1") + workspace_1 = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-1", + workspace__name="test-ws-1", + study=study_1, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_1.workspace, group=self.primed_all_group + ) + study_2 = StudyFactory.create(short_name="TEST 2") + workspace_2 = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-2", + workspace__name="test-ws-2", + study=study_2, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_2.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 2) + self.assertIn("test-bp-1/test-ws-1", res) + self.assertEqual(res["test-bp-1/test-ws-1"], "TEST 1") + self.assertIn("test-bp-2/test-ws-2", res) + self.assertEqual(res["test-bp-2/test-ws-2"], "TEST 2") + + def test_one_open_access_workspace_not_shared(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + OpenAccessWorkspaceFactory.create() + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_open_access_workspace_shared_no_study(self): + """get_workspaces_for_phenotype_inventory with one Open access workspace.""" + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "") + + def test_one_open_access_workspace_shared_one_study(self): + """get_workspaces_for_phenotype_inventory with one Open access workspace.""" + study = StudyFactory.create(short_name="TEST") + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + ) + workspace.studies.add(study) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST") + + def test_one_open_access_workspace_shared_two_studies(self): + """get_workspaces_for_phenotype_inventory with one Open access workspace.""" + study_1 = StudyFactory.create(short_name="TEST_2") + study_2 = StudyFactory.create(short_name="TEST_1") + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + ) + workspace.studies.add(study_1, study_2) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST_1, TEST_2") + + def test_two_open_access_workspaces(self): + """get_workspaces_for_phenotype_inventory with two Open access workspace.""" + workspace_1 = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-1", + workspace__name="test-ws-1", + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_1.workspace, group=self.primed_all_group + ) + study_2 = StudyFactory.create(short_name="TEST 2") + workspace_2 = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-2", + workspace__name="test-ws-2", + ) + workspace_2.studies.add(study_2) + WorkspaceGroupSharingFactory.create( + workspace=workspace_2.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 2) + self.assertIn("test-bp-1/test-ws-1", res) + self.assertEqual(res["test-bp-1/test-ws-1"], "") + self.assertIn("test-bp-2/test-ws-2", res) + self.assertEqual(res["test-bp-2/test-ws-2"], "TEST 2") + + def test_multiple_workspace_types_same_study(self): + study = StudyFactory.create(short_name="TEST") + # dbgap + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-dbgap", + workspace__name="test-ws-dbgap", + dbgap_study_accession__studies=[study], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # CDSA + workspace = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-cdsa", + workspace__name="test-ws-cdsa", + study=study, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # Open access + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-open", + workspace__name="test-ws-open", + ) + workspace.studies.add(study) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 3) + self.assertIn("test-bp-dbgap/test-ws-dbgap", res) + self.assertEqual(res["test-bp-dbgap/test-ws-dbgap"], "TEST") + self.assertIn("test-bp-cdsa/test-ws-cdsa", res) + self.assertEqual(res["test-bp-cdsa/test-ws-cdsa"], "TEST") + self.assertIn("test-bp-open/test-ws-open", res) + self.assertEqual(res["test-bp-open/test-ws-open"], "TEST") + + def test_multiple_workspace_types_separate_studies(self): + study_1 = StudyFactory.create(short_name="TEST 1") + # dbgap + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-dbgap", + workspace__name="test-ws-dbgap", + dbgap_study_accession__studies=[study_1], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # CDSA + study_2 = StudyFactory.create(short_name="TEST 2") + workspace = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-cdsa", + workspace__name="test-ws-cdsa", + study=study_2, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # Open access + study_3 = StudyFactory.create(short_name="TEST 3") + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-open", + workspace__name="test-ws-open", + ) + workspace.studies.add(study_3) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 3) + self.assertIn("test-bp-dbgap/test-ws-dbgap", res) + self.assertEqual(res["test-bp-dbgap/test-ws-dbgap"], "TEST 1") + self.assertIn("test-bp-cdsa/test-ws-cdsa", res) + self.assertEqual(res["test-bp-cdsa/test-ws-cdsa"], "TEST 2") + self.assertIn("test-bp-open/test-ws-open", res) + self.assertEqual(res["test-bp-open/test-ws-open"], "TEST 3") From d68aad087dbff8ca2c903a2ade5127ef8dd45782 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 14:57:56 -0700 Subject: [PATCH 02/11] Add a view to show the generated workspaces input This view shows the workspaces input for the primed_phenotype_inventory workflow from helpers.get_workspaces_for_phenotype_inventory(). Add the url path, a view, a template, and tests for the view. --- primed/primed_anvil/tests/test_views.py | 84 ++++++++++++++++++- primed/primed_anvil/urls.py | 12 +++ primed/primed_anvil/views.py | 16 ++++ .../phenotype_inventory_inputs.html | 32 +++++++ 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 primed/templates/primed_anvil/phenotype_inventory_inputs.html diff --git a/primed/primed_anvil/tests/test_views.py b/primed/primed_anvil/tests/test_views.py index 9a95ea11..b800b6df 100644 --- a/primed/primed_anvil/tests/test_views.py +++ b/primed/primed_anvil/tests/test_views.py @@ -1,7 +1,11 @@ import json from anvil_consortium_manager import models as acm_models -from anvil_consortium_manager.tests.factories import AccountFactory +from anvil_consortium_manager.tests.factories import ( + AccountFactory, + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) from anvil_consortium_manager.views import AccountList from constance.test import override_config from django.conf import settings @@ -1267,3 +1271,81 @@ def test_includes_cdsa_workspaces(self): response = self.client.get(self.get_url()) self.assertIn("summary_table", response.context_data) self.assertEqual(len(response.context_data["summary_table"].rows), 1) + + +class PhenotypeInventoryInputsViewTest(TestCase): + """Tests for the PhenotypeInventoryInputsView view.""" + + def setUp(self): + """Set up test class.""" + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get( + codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME + ) + ) + self.primed_all = ManagedGroupFactory.create(name="PRIMED_ALL") + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("primed_anvil:utilities:phenotype_inventory_inputs", args=args) + + def get_view(self): + """Return the view being tested.""" + return views.PhenotypeInventoryInputsView.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url()) + self.assertRedirects( + response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() + ) + + def test_status_code_with_authenticated_user(self): + """Redirects to login view when user has no perms.""" + user_no_perms = User.objects.create_user( + username="test-none", password="test-none" + ) + request = self.factory.get(self.get_url()) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_status_code_with_view_user(self): + """Redirects to login view when user has view perm.""" + user = User.objects.create_user(username="test-none", password="test-none") + user.user_permissions.add( + Permission.objects.get( + codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME + ) + ) + request = self.factory.get(self.get_url()) + request.user = user + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_context_workspaces_input_no_workspaces(self): + """workspaces_input exists in the context and is correct with no workspaces.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("workspaces_input", response.context_data) + self.assertEqual(response.context_data["workspaces_input"], "{}") + + def test_context_workspaces_input_one_workspace(self): + """workspaces_input exists in the context and is correct with one shared workspace.""" + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all + ) + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("workspaces_input", response.context_data) + self.assertEqual( + response.context_data["workspaces_input"], + """{\n "test-bp/test-ws": ""\n}""", + ) diff --git a/primed/primed_anvil/urls.py b/primed/primed_anvil/urls.py index 909ad7fb..81557dcf 100644 --- a/primed/primed_anvil/urls.py +++ b/primed/primed_anvil/urls.py @@ -37,9 +37,21 @@ "summaries", ) +utilities_patterns = ( + [ + path( + "phenotype_inventory_inputs/", + views.PhenotypeInventoryInputsView.as_view(), + name="phenotype_inventory_inputs", + ), + ], + "utilities", +) + urlpatterns = [ path("studies/", include(study_patterns)), path("study_sites/", include(study_site_patterns)), path("available_data/", include(available_data_patterns)), path("summaries/", include(summary_patterns)), + path("utilities/", include(utilities_patterns)), ] diff --git a/primed/primed_anvil/views.py b/primed/primed_anvil/views.py index c89ab3cb..f85e787b 100644 --- a/primed/primed_anvil/views.py +++ b/primed/primed_anvil/views.py @@ -1,3 +1,5 @@ +import json + from anvil_consortium_manager.auth import ( AnVILConsortiumManagerStaffEditRequired, AnVILConsortiumManagerStaffViewRequired, @@ -183,3 +185,17 @@ def get_context_data(self, **kwargs): table_data = helpers.get_summary_table_data() context["summary_table"] = tables.DataSummaryTable(table_data) return context + + +class PhenotypeInventoryInputsView( + AnVILConsortiumManagerStaffViewRequired, TemplateView +): + + template_name = "primed_anvil/phenotype_inventory_inputs.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["workspaces_input"] = json.dumps( + helpers.get_workspaces_for_phenotype_inventory(), indent=2 + ) + return context diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html new file mode 100644 index 00000000..157882cf --- /dev/null +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load render_table from django_tables2 %} + +{% block extra_navbar %} + {% include 'anvil_consortium_manager/navbar.html' %} +{% endblock %} + +{% block content %} + +

Phenotype inventory workflow

+ +

+ This page creates the input for the "workspaces" field in the + PRIMED phenotype inventory workflow. + Copy the text in the box below and paste it into the "workspaces" when running the workflow. +

+ + +
+
+
Input for the workspaces field
+

+ +

{{ workspaces_input }}
+ + +

+
+
+ + +{% endblock content %} From 1d485ca144afdddab18f4f4eead907be90c65f23 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 15:05:15 -0700 Subject: [PATCH 03/11] Add some javascript to copy from a pre tag to the clipboard This isn't ideal but I want the record of it. Taken from this page: https://www.roboleary.net/2022/01/13/copy-code-to-clipboard-blog.html --- primed/static/js/project.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/primed/static/js/project.js b/primed/static/js/project.js index 9c90629e..75662f99 100644 --- a/primed/static/js/project.js +++ b/primed/static/js/project.js @@ -17,3 +17,31 @@ for(var i = 0; i < textInputs.length; i++){ // Console: print the clicked

element textInputs[i].addEventListener("paste", checkPasteLength); } + + +// Button to copy to the clipboard. +const copyButtonLabel = "Copy Code"; + +// use a class selector if available +let blocks = document.querySelectorAll("pre"); + +blocks.forEach((block) => { + // only add button if browser supports Clipboard API + if (navigator.clipboard) { + let button = document.createElement("button"); + + button.innerText = copyButtonLabel; + block.appendChild(button); + + button.addEventListener("click", async () => { + await copyCode(block); + }); + } +}); + +async function copyCode(block) { + let code = block.querySelector("code"); + let text = code.innerText; + + await navigator.clipboard.writeText(text); +} From cc726f920e239fce01650fa67d1e098656d2c92a Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 15:48:11 -0700 Subject: [PATCH 04/11] Change javascript to copy workspaces input on click Move the button to the card subtitle instead of dynamically adding it via the javascript (as the blog example did). Move the javascript into the template inline_javascript block, since this code is only relevant for this template at the moment - there's no need to put it in the project-wide javascript. To make the javascript work, there must be: - an html element with the class "copy-element". This must have an attribute "copy-target" that specifies the id of the element whose text should be copied. - an element with the id specified in the "copy-target" attribute --- primed/static/js/project.js | 28 ------------------- .../phenotype_inventory_inputs.html | 26 +++++++++++++---- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/primed/static/js/project.js b/primed/static/js/project.js index 75662f99..9c90629e 100644 --- a/primed/static/js/project.js +++ b/primed/static/js/project.js @@ -17,31 +17,3 @@ for(var i = 0; i < textInputs.length; i++){ // Console: print the clicked

element textInputs[i].addEventListener("paste", checkPasteLength); } - - -// Button to copy to the clipboard. -const copyButtonLabel = "Copy Code"; - -// use a class selector if available -let blocks = document.querySelectorAll("pre"); - -blocks.forEach((block) => { - // only add button if browser supports Clipboard API - if (navigator.clipboard) { - let button = document.createElement("button"); - - button.innerText = copyButtonLabel; - block.appendChild(button); - - button.addEventListener("click", async () => { - await copyCode(block); - }); - } -}); - -async function copyCode(block) { - let code = block.querySelector("code"); - let text = code.innerText; - - await navigator.clipboard.writeText(text); -} diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index 157882cf..9cbe01b7 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -18,15 +18,31 @@

Phenotype inventory workflow

-
Input for the workspaces field
+
Input for the workspaces field
+

- -

{{ workspaces_input }}
- - +
{{ workspaces_input }}

{% endblock content %} + + +{% block inline_javascript %} + +{% endblock %} From 54a3e27a399859b12e8ec7391830a7536d6b4a0f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 15:55:08 -0700 Subject: [PATCH 05/11] Typo fixes --- primed/templates/primed_anvil/phenotype_inventory_inputs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index 9cbe01b7..7a479f54 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -12,7 +12,7 @@

Phenotype inventory workflow

This page creates the input for the "workspaces" field in the PRIMED phenotype inventory workflow. - Copy the text in the box below and paste it into the "workspaces" when running the workflow. + Copy the text in the box below and paste it into the "workspaces" field when running the workflow on AnVIL.

From a403383bf9de41b6d3e8140c4dd8da7cdd778190 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 22 Apr 2024 10:25:11 -0700 Subject: [PATCH 06/11] Add script to create test data for the phenotype inventoryw orkflow --- add_phenotype_inventory_input_example_data.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 add_phenotype_inventory_input_example_data.py diff --git a/add_phenotype_inventory_input_example_data.py b/add_phenotype_inventory_input_example_data.py new file mode 100644 index 00000000..427c6477 --- /dev/null +++ b/add_phenotype_inventory_input_example_data.py @@ -0,0 +1,50 @@ +# Temporary script to create some test data. +# Run with: python manage.py shell < add_phenotype_inventory_input_example_data.py + +from anvil_consortium_manager.tests.factories import ( + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) + +from primed.cdsa.tests.factories import CDSAWorkspaceFactory +from primed.dbgap.tests.factories import dbGaPWorkspaceFactory +from primed.miscellaneous_workspaces.tests.factories import OpenAccessWorkspaceFactory +from primed.primed_anvil.tests.factories import StudyFactory + +# Create a dbGaP workspace. +fhs = StudyFactory.create(short_name="FHS", full_name="Framingham Heart Study") +workspace_dbgap = dbGaPWorkspaceFactory.create( + dbgap_study_accession__dbgap_phs=7, + dbgap_study_accession__studies=[fhs], + dbgap_version=33, + dbgap_participant_set=12, + dbgap_consent_code=1, + dbgap_consent_abbreviation="HMB", + workspace__name="DBGAP_FHS_v33_p12_HMB", +) + + +# Create a CDSA workspace. +workspace_cdsa = CDSAWorkspaceFactory.create( + study__short_name="MESA", + workspace__name="CDSA_MESA_HMB", +) + +# Create an open access workspace +workspace_open_access = OpenAccessWorkspaceFactory.create( + workspace__name="OPEN_ACCESS_FHS", +) +workspace_open_access.studies.add(fhs) + + +# Share workspaces with PRIMED_ALL +primed_all = ManagedGroupFactory.create(name="PRIMED_ALL") +WorkspaceGroupSharingFactory.create( + workspace=workspace_dbgap.workspace, group=primed_all +) +WorkspaceGroupSharingFactory.create( + workspace=workspace_cdsa.workspace, group=primed_all +) +WorkspaceGroupSharingFactory.create( + workspace=workspace_open_access.workspace, group=primed_all +) From e39fd01b7cbe36bc13077f4b736e402474f0d1dd Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 26 Apr 2024 16:07:56 -0700 Subject: [PATCH 07/11] Disable button and change text upon copy --- .../primed_anvil/phenotype_inventory_inputs.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index 7a479f54..c043c36a 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -36,9 +36,16 @@
+
+ Input for the workspaces field + + + +

{{ workspaces_input }}

From 6feec36fd3b2f4838e2509416e454d3a9f180563 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 26 Apr 2024 16:11:51 -0700 Subject: [PATCH 09/11] Remove right-alignment, which doesn't work well with a wide page --- primed/templates/primed_anvil/phenotype_inventory_inputs.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index f529ac42..7dc372ce 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -20,9 +20,7 @@

Phenotype inventory workflow

Input for the workspaces field - - - +

{{ workspaces_input }}
From 895880301646173a652e1a9b61df2f09b681535f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 14:29:14 -0700 Subject: [PATCH 10/11] Sort names of studies in phenotype inventory workflow helper Mostly for MySQL. --- primed/primed_anvil/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primed/primed_anvil/helpers.py b/primed/primed_anvil/helpers.py index 2544810c..e0f6cacf 100644 --- a/primed/primed_anvil/helpers.py +++ b/primed/primed_anvil/helpers.py @@ -220,6 +220,6 @@ def get_workspaces_for_phenotype_inventory(): study_names = [x["study_names"] if x["study_names"] else "" for x in group] if not study_names: study_names = "" - json[key] = ", ".join(study_names) + json[key] = ", ".join(sorted(study_names)) return json From f0c23278cb04324992aa8dc8116ade858dc5eb03 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 14:31:58 -0700 Subject: [PATCH 11/11] Try upgrading flake8 in pre-commit file --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 842469d1..3b9bd274 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 3.9.2 + rev: 7.0.0 hooks: - id: flake8 args: ['--config=setup.cfg']