Skip to content

Commit

Permalink
Merge pull request #681 from UW-GAC/deploy/stage
Browse files Browse the repository at this point in the history
Deploy ubuntu 22.04 upgrade to prod
  • Loading branch information
amstilp authored Aug 1, 2024
2 parents ad23d38 + 4cf9c6c commit 304e52d
Show file tree
Hide file tree
Showing 25 changed files with 329 additions and 128 deletions.
8 changes: 2 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,10 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
python-version: ["3.10"]
backend: ["sqlite", "mariadb"]
mariadb-version: ["10.4"]
mariadb-version: ["10.6"]
include:
- python-version: "3.10" # Future ubuntu 22.04 upgrade.
backend: "mariadb"
mariadb-version: "10.6"
pip-recompile: true
- python-version: 3.12 # Future ubuntu 24.04.01 upgrade.
backend: "mariadb"
mariadb-version: "10.11"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/combine-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
steps:
- name: combine-prs
id: combine-prs
uses: github/combine-prs@v5.0.0
uses: github/combine-prs@v5.1.0
with:
labels: combined-pr # Optional: add a label to the combined PR
ci_required: true # require all checks to pass before combining
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/pip-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ on:

jobs:
update-requirements-files:
permissions:
pull-requests: write
runs-on: ubuntu-latest

steps:
Expand All @@ -16,10 +14,10 @@ jobs:
with:
ref: ${{ github.head_ref }}

- name: Set up Python 3.8
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: "3.10"

- name: Update requirements files
uses: UW-GAC/pip-tools-actions/[email protected]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Temporary script to create some test data.
# Run with: python manage.py shell < add_phenotype_inventory_input_example_data.py
# Run with: python manage.py shell < add_inventory_input_example_data.py

from anvil_consortium_manager.tests.factories import (
ManagedGroupFactory,
Expand Down
1 change: 1 addition & 0 deletions primed-apps-activate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export DJANGO_SITE_DIR=/var/www/django/primed_apps/
export DJANGO_SITE_USER=primedweb
export DJANGO_SETTINGS_MODULE=config.settings.apps
export DJANGO_WSGI_FILE=config/primed_apps_wsgi.py
export DJANGO_CRONTAB=primed_apps.cron
export GAC_ENV=primed_apps
cd $DJANGO_SITE_DIR
. venv/bin/activate
1 change: 1 addition & 0 deletions primed-apps-dev-activate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export DJANGO_SITE_DIR=/var/www/django/primed_apps_dev/
export DJANGO_SITE_USER=primedweb
export DJANGO_SETTINGS_MODULE=config.settings.apps_dev
export DJANGO_WSGI_FILE=config/primed_apps_dev_wsgi.py
export DJANGO_CRONTAB=primed_apps_dev.cron
export GAC_ENV=primed_apps_dev
cd $DJANGO_SITE_DIR
. venv/bin/activate
4 changes: 3 additions & 1 deletion primed/dbgap/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,9 @@ def test_dbgap_dar_data_missing_DAC_abbrev(self):
def test_dbgap_project_id_does_not_match(self):
"""Form is not valid when the dbgap_project_id does not match."""
form_data = {
"dbgap_dar_data": json.dumps([factories.dbGaPJSONProjectFactory(Project_id=2)]),
"dbgap_dar_data": json.dumps(
[factories.dbGaPJSONProjectFactory(Project_id=self.dbgap_application.dbgap_project_id + 1)]
),
"dbgap_application": self.dbgap_application,
}
form = self.form_class(data=form_data)
Expand Down
7 changes: 4 additions & 3 deletions primed/primed_anvil/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ def get_summary_table_data():
return data


def get_workspaces_for_phenotype_inventory():
def get_workspaces_for_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
See dockstore link: https://dockstore.org/workflows/github.com/UW-GAC/primed-inventory-workflows/primed_inventory:main?tab=info
The "workspaces" field has the format:
{
Expand Down Expand Up @@ -212,7 +212,8 @@ def get_workspaces_for_phenotype_inventory():

# Combine all querysets and process into the expected output for the AnVIL workflow.
workspaces = dbgap_workspaces.union(cdsa_workspaces).union(open_access_workspaces)

# Sort by workspace_name so that itertools.groupby works as expected.
workspaces = sorted(workspaces, key=lambda x: x["workspace_name"])
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]
Expand Down
198 changes: 169 additions & 29 deletions primed/primed_anvil/tests/test_helpers.py

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions primed/primed_anvil/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,19 @@ def test_site_user_table_when_member_group_is_set(self):
self.assertIsInstance(table, tables.UserAccountSingleGroupMembershipTable)
self.assertEqual(table.managed_group, member_group)

def test_site_user_table_does_not_include_inactive_users(self):
"""Site user table does not include inactive users."""
obj = self.model_factory.create()
inactive_site_user = UserFactory.create()
inactive_site_user.study_sites.set([obj])
inactive_site_user.is_active = False
inactive_site_user.save()
self.client.force_login(self.user)
response = self.client.get(self.get_url(obj.pk))
table = response.context_data["tables"][0]
self.assertEqual(len(table.rows), 0)
self.assertNotIn(inactive_site_user, table.data)

def test_member_group_table(self):
member_group = ManagedGroupFactory.create()
obj = self.model_factory.create(member_group=member_group)
Expand Down Expand Up @@ -1234,8 +1247,8 @@ def test_includes_cdsa_workspaces(self):
self.assertEqual(len(response.context_data["summary_table"].rows), 1)


class PhenotypeInventoryInputsViewTest(TestCase):
"""Tests for the PhenotypeInventoryInputsView view."""
class InventoryInputsViewTest(TestCase):
"""Tests for the InventoryInputsView view."""

def setUp(self):
"""Set up test class."""
Expand All @@ -1249,11 +1262,11 @@ def setUp(self):

def get_url(self, *args):
"""Get the url for the view being tested."""
return reverse("primed_anvil:utilities:phenotype_inventory_inputs", args=args)
return reverse("primed_anvil:utilities:inventory_inputs", args=args)

def get_view(self):
"""Return the view being tested."""
return views.PhenotypeInventoryInputsView.as_view()
return views.InventoryInputsView.as_view()

def test_view_redirect_not_logged_in(self):
"View redirects to login view when user is not logged in."
Expand Down
6 changes: 3 additions & 3 deletions primed/primed_anvil/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
utilities_patterns = (
[
path(
"phenotype_inventory_inputs/",
views.PhenotypeInventoryInputsView.as_view(),
name="phenotype_inventory_inputs",
"inventory_inputs/",
views.InventoryInputsView.as_view(),
name="inventory_inputs",
),
],
"utilities",
Expand Down
8 changes: 4 additions & 4 deletions primed/primed_anvil/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class StudySiteDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin,
model = models.StudySite

def get_tables(self):
user_qs = User.objects.filter(study_sites=self.object)
user_qs = User.objects.filter(is_active=True, study_sites=self.object)
if self.object.member_group:
user_table = tables.UserAccountSingleGroupMembershipTable(user_qs, managed_group=self.object.member_group)
else:
Expand Down Expand Up @@ -180,10 +180,10 @@ def get_context_data(self, **kwargs):
return context


class PhenotypeInventoryInputsView(AnVILConsortiumManagerStaffViewRequired, TemplateView):
template_name = "primed_anvil/phenotype_inventory_inputs.html"
class InventoryInputsView(AnVILConsortiumManagerStaffViewRequired, TemplateView):
template_name = "primed_anvil/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)
context["workspaces_input"] = json.dumps(helpers.get_workspaces_for_inventory(), indent=2)
return context
2 changes: 1 addition & 1 deletion primed/templates/anvil_consortium_manager/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="{% url 'primed_anvil:utilities:phenotype_inventory_inputs' %}">Phenotype inventory</a>
<a class="dropdown-item" href="{% url 'primed_anvil:utilities:inventory_inputs' %}">Inventory inputs</a>
</li>

<li><hr class="dropdown-divider"></li>
Expand Down
5 changes: 3 additions & 2 deletions primed/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
{# Placed at the top of the document so pages load faster with defer #}
{% block javascript %}
<!-- Bootstrap JS and its dependencies-->
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://unpkg.com/[email protected]" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>

<!-- Your stuff: Third-party javascript libraries go here -->
Expand Down Expand Up @@ -119,9 +119,10 @@
{% endif %}

{% if request.user.is_authenticated and not request.user.account %}
<div class="alert alert-warning" role="alert">
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-1"></i>
You must <a href="{% url 'anvil_consortium_manager:accounts:link' %}">link your AnVIL account</a> before you can access any data on AnVIL.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h2>Phenotype inventory workflow</h2>

<p>
This page creates the input for the "workspaces" field in the
<a href="https://dockstore.org/workflows/github.com/UW-GAC/primed-inventory-workflows/primed_phenotype_inventory:main?tab=info">PRIMED phenotype inventory workflow</a>.
<a href="https://dockstore.org/workflows/github.com/UW-GAC/primed-inventory-workflows/primed_inventory:main?tab=info">PRIMED phenotype inventory workflow</a>.
Copy the text in the box below and paste it into the "workspaces" field when running the workflow on AnVIL.
</p>

Expand Down
2 changes: 1 addition & 1 deletion primed/templates/primed_anvil/studysite_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ <h2 class="accordion-header" id="headingMembersOne">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseMembersOne" aria-expanded="false" aria-controls="collapseMembersOne">
<span class="fa-solid fa-cloud-arrow-down mx-2"></span>
View consortium members with data access
<span class="badge mx-2 bg-secondary pill"> {{ tables.1.rows|length }}</span>
<span class="badge mx-2 bg-secondary pill"> {{ tables.3.rows|length }}</span>
</button>
</h2>
<div id="collapseMembersOne" class="accordion-collapse collapse" aria-labelledby="headingMembersOne" data-bs-parent="#accordionMembers">
Expand Down
17 changes: 17 additions & 0 deletions primed/templates/users/user_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
{% block content %}
<div class="container">

{% if not object.is_active %}

<p class='alert alert-danger mt-3'>
<i class="bi bi-exclamation-triangle-fill"></i>
This user is inactive.
</p>

{% endif %}

<div class="row row-cols-1 rows-cols-sm-2 row-cols-md-2 g-2 mt-3">
<div class="col">
<div class='card card-shadow-sm'>
Expand Down Expand Up @@ -57,6 +66,14 @@ <h3><i class="bi bi-link-45deg"></i> {% if object == request.user %}My{% else %}
<div class='card-body'>
{% if object.account %}
<p><i class="bi bi-check-circle-fill text-success"></i> Profile has a linked AnVIL account established</p>

{% if not object.account.status %}
<p class='alert alert-danger mt-3'>
<i class="bi bi-exclamation-triangle-fill"></i>
This account is inactive.
</p>
{% endif %}

<ul class='list-group'>
<li class='list-group-item'>
<h5>Account Email</h5>
Expand Down
2 changes: 1 addition & 1 deletion primed/users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class UserLookupForm(Bootstrap5MediaFormMixin, forms.Form):
"""Form for the user lookup"""

user = forms.ModelChoiceField(
queryset=User.objects.all(),
queryset=User.objects.filter(is_active=True),
widget=autocomplete.ModelSelect2(
url="users:autocomplete",
attrs={"data-theme": "bootstrap-5"},
Expand Down
13 changes: 13 additions & 0 deletions primed/users/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,16 @@ def test_invalid_missing_name(self):
self.assertIn("user", form.errors)
self.assertEqual(len(form.errors["user"]), 1)
self.assertIn("required", form.errors["user"][0])

def test_inactive_user(self):
"""Form is invalid when user is inactive."""
user_obj = UserFactory.create(is_active=False)
form_data = {
"user": user_obj,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
self.assertEqual(len(form.errors), 1)
self.assertIn("user", form.errors)
self.assertEqual(len(form.errors["user"]), 1)
self.assertIn("valid choice", form.errors["user"][0])
62 changes: 62 additions & 0 deletions primed/users/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,39 @@ def test_acm_staff_view(self):
self.assertIn(agreement_1.signed_agreement, response.context["signed_agreements"])
self.assertIn(agreement_2.signed_agreement, response.context["signed_agreements"])

def test_inactive_user_inactive_message(self):
"""Inactive user alert is shown for an inactive user."""
user = UserFactory.create(is_active=False)
self.client.force_login(self.user)
response = self.client.get(user.get_absolute_url())
self.assertEqual(response.status_code, 200)
self.assertContains(response, "This user is inactive.")

def test_active_user_no_inactive_message(self):
"""Inactive user alert is not shown for an active user."""
self.client.force_login(self.user)
response = self.client.get(self.user.get_absolute_url())
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, "This user is inactive.")

def test_inactive_anvil_account_alert_is_inactive(self):
"""Alert is shown when AnVIL account is inactive."""
account = AccountFactory.create(email="[email protected]", user=self.user, verified=True)
account.status = account.INACTIVE_STATUS
account.save()
self.client.force_login(self.user)
response = self.client.get(self.user.get_absolute_url())
self.assertEqual(response.status_code, 200)
self.assertContains(response, "This account is inactive.")

def test_inactive_anvil_account_alert_is_active(self):
"""Alert is not shown when AnVIL account is active."""
AccountFactory.create(email="[email protected]", user=self.user, verified=True)
self.client.force_login(self.user)
response = self.client.get(self.user.get_absolute_url())
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, "This account is inactive.")


class UserAutocompleteTest(TestCase):
def setUp(self):
Expand Down Expand Up @@ -723,6 +756,17 @@ def test_get_selected_result_label(self):
view.setup(request)
self.assertEqual(view.get_selected_result_label(instance), "First Last ([email protected])")

def test_excludes_inactive_users(self):
"""Queryset excludes excludes inactive users."""
UserFactory.create(is_active=False)
request = self.factory.get(self.get_url())
request.user = self.user
response = self.get_view()(request)
returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]]
# Only test user.
self.assertEqual(len(returned_ids), 1)
self.assertEqual(returned_ids, [self.user.pk])


class UserLookup(TestCase):
"""Test for UserLookup view"""
Expand Down Expand Up @@ -799,3 +843,21 @@ def test_blank_user(self):
self.assertIn("user", form.errors.keys())
self.assertEqual(len(form.errors["user"]), 1)
self.assertIn("required", form.errors["user"][0])

def test_invalid_inactive_user(self):
"""Form is invalid with an inactive user."""
object = UserFactory.create(
username="user1",
password="passwd",
email="[email protected]",
is_active=False,
)
self.client.force_login(self.user)
response = self.client.post(self.get_url(), {"user": object.pk})
self.assertEqual(response.status_code, 200)
form = response.context_data["form"]
self.assertFalse(form.is_valid())
self.assertEqual(len(form.errors), 1)
self.assertIn("user", form.errors)
self.assertEqual(len(form.errors["user"]), 1)
self.assertIn("valid choice", form.errors["user"][0])
4 changes: 3 additions & 1 deletion primed/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ def get_result_label(self, result):

def get_queryset(self):
"""Filter to users matching the query."""
qs = User.objects.all().order_by("username")
qs = User.objects.filter(
is_active=True,
).order_by("username")

if self.q:
# Filter to users whose name or email matches the query.
Expand Down
Loading

0 comments on commit 304e52d

Please sign in to comment.