Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 128 additions & 35 deletions tests/unit/admin/views/test_organizations.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: Apache-2.0

import datetime

from datetime import date, timedelta

import pretend
Expand All @@ -14,10 +16,12 @@
OrganizationFactory,
OrganizationManualActivationFactory,
OrganizationOIDCIssuerFactory,
OrganizationProjectFactory,
OrganizationRoleFactory,
OrganizationStripeCustomerFactory,
OrganizationStripeSubscriptionFactory,
)
from tests.common.db.packaging import ProjectFactory, ReleaseFactory
from tests.common.db.subscriptions import (
StripeCustomerFactory,
StripeSubscriptionFactory,
Expand All @@ -33,6 +37,7 @@
OrganizationType,
)
from warehouse.subscriptions.interfaces import IBillingService
from warehouse.subscriptions.models import StripeSubscriptionStatus


class TestOrganizationForm:
Expand Down Expand Up @@ -177,42 +182,21 @@ def test_description_query(self, db_request):
assert result["query"] == f"description:'{organizations[0].description}'"
assert result["terms"] == [f"description:{organizations[0].description}"]

def test_is_active_query(self, db_request):
organizations = sorted(
OrganizationFactory.create_batch(5),
key=lambda o: o.normalized_name,
)
organizations[0].is_active = True
organizations[1].is_active = True
organizations[2].is_active = False
organizations[3].is_active = False
organizations[4].is_active = False
db_request.GET["q"] = "is:active"
result = views.organization_list(db_request)

assert result == {
"organizations": organizations[:2],
"query": "is:active",
"terms": ["is:active"],
}

def test_is_inactive_query(self, db_request):
def test_activity_inactive_query(self, db_request):
organizations = sorted(
OrganizationFactory.create_batch(5),
key=lambda o: o.normalized_name,
)
organizations[0].is_active = True
organizations[1].is_active = True
organizations[2].is_active = False
organizations[3].is_active = False
organizations[4].is_active = False
db_request.GET["q"] = "is:inactive"
db_request.GET["q"] = "activity:inactive"
result = views.organization_list(db_request)

assert result == {
"organizations": organizations[2:],
"query": "is:inactive",
"terms": ["is:inactive"],
"query": "activity:inactive",
"terms": ["activity:inactive"],
}

def test_type_query(self, db_request):
Expand Down Expand Up @@ -248,20 +232,129 @@ def test_invalid_type_query(self, db_request):
"terms": ["type:invalid"],
}

def test_is_invalid_query(self, db_request):
organizations = sorted(
OrganizationFactory.create_batch(5),
key=lambda o: o.normalized_name,
def test_activity_query(self, db_request):
# Active: owns a project released within the window.
active_org = OrganizationFactory.create()
active_project = ProjectFactory.create()
OrganizationProjectFactory.create(
organization=active_org, project=active_project
)
db_request.GET["q"] = "is:not-actually-a-valid-query"
result = views.organization_list(db_request)
ReleaseFactory.create(project=active_project)

assert result == {
"organizations": organizations[:25],
"query": "is:not-actually-a-valid-query",
"terms": ["is:not-actually-a-valid-query"],
# Dormant: owns a project, but only released long ago.
dormant_org = OrganizationFactory.create()
dormant_project = ProjectFactory.create()
OrganizationProjectFactory.create(
organization=dormant_org, project=dormant_project
)
ReleaseFactory.create(
project=dormant_project, created=datetime.datetime(2020, 1, 1)
)

# Unused: owns no projects.
unused_org = OrganizationFactory.create()

db_request.GET["q"] = "activity:active"
assert views.organization_list(db_request)["organizations"] == [active_org]

db_request.GET["q"] = "activity:dormant"
assert views.organization_list(db_request)["organizations"] == [dormant_org]

db_request.GET["q"] = "activity:unused"
assert views.organization_list(db_request)["organizations"] == [unused_org]

def test_has_subscription_query(self, db_request):
active_org = OrganizationFactory.create(orgtype=OrganizationType.Company)
OrganizationStripeSubscriptionFactory.create(
organization=active_org,
subscription=StripeSubscriptionFactory.create(
status=StripeSubscriptionStatus.Active
),
)
# A trialing subscription also counts.
trialing_org = OrganizationFactory.create(orgtype=OrganizationType.Company)
OrganizationStripeSubscriptionFactory.create(
organization=trialing_org,
subscription=StripeSubscriptionFactory.create(
status=StripeSubscriptionStatus.Trialing
),
)

# A canceled subscription does not count.
OrganizationStripeSubscriptionFactory.create(
organization=OrganizationFactory.create(orgtype=OrganizationType.Company),
subscription=StripeSubscriptionFactory.create(
status=StripeSubscriptionStatus.Canceled
),
)
# A Community org with a subscription does not count -- only Company
# organizations can subscribe.
OrganizationStripeSubscriptionFactory.create(
organization=OrganizationFactory.create(orgtype=OrganizationType.Community),
subscription=StripeSubscriptionFactory.create(
status=StripeSubscriptionStatus.Active
),
)
OrganizationFactory.create() # no subscription

db_request.GET["q"] = "has:subscription"
assert set(views.organization_list(db_request)["organizations"]) == {
active_org,
trialing_org,
}

def test_invalid_activity_and_has_queries(self, db_request):
organization = OrganizationFactory.create()
db_request.GET["q"] = "activity:nope has:nope"
result = views.organization_list(db_request)
assert result["organizations"] == [organization]

def test_annotates_status(self, db_request):
active = OrganizationFactory.create()
project = ProjectFactory.create()
OrganizationProjectFactory.create(organization=active, project=project)
ReleaseFactory.create(project=project)
unused = OrganizationFactory.create()
inactive = OrganizationFactory.create(is_active=False)

status = {
organization.id: organization.activity
for organization in views.organization_list(db_request)["organizations"]
}
assert status[active.id] == "Active"
assert status[unused.id] == "Unused"
assert status[inactive.id] == "Inactive"


class TestOrganizationActivity:
def test_unused(self, db_request):
organization = OrganizationFactory.create()
assert views._organization_activity(db_request, organization) == "Unused"

def test_dormant(self, db_request):
organization = OrganizationFactory.create()
project = ProjectFactory.create()
OrganizationProjectFactory.create(organization=organization, project=project)
ReleaseFactory.create(project=project, created=datetime.datetime(2020, 1, 1))
assert views._organization_activity(db_request, organization) == "Dormant"

def test_dormant_with_no_releases(self, db_request):
organization = OrganizationFactory.create()
project = ProjectFactory.create()
OrganizationProjectFactory.create(organization=organization, project=project)
assert views._organization_activity(db_request, organization) == "Dormant"

def test_active(self, db_request):
organization = OrganizationFactory.create()
project = ProjectFactory.create()
OrganizationProjectFactory.create(organization=organization, project=project)
ReleaseFactory.create(project=project)
assert views._organization_activity(db_request, organization) == "Active"

def test_inactive(self, db_request):
organization = OrganizationFactory.create(is_active=False)
assert views._organization_activity(db_request, organization) == "Inactive"


class TestOrganizationDetail:
def test_detail(self, db_request):
Expand Down
12 changes: 12 additions & 0 deletions warehouse/admin/templates/admin/organizations/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ <h3 class="widget-user-username text-center">{{ organization.display_name }}</h3
<p class="text-muted text-center">
Created on {{ organization.created|format_date() }}
</p>
<p class="text-center">
Activity:
{% if activity == "Active" %}
<span class="badge badge-success" title="Owns a project released in the last year">Active</span>
{% elif activity == "Dormant" %}
<span class="badge badge-warning" title="Owns projects, none released in the last year">Dormant</span>
{% elif activity == "Unused" %}
<span class="badge badge-secondary" title="Owns no projects">Unused</span>
{% else %}
<span class="badge badge-danger" title="Deactivated">Inactive</span>
{% endif %}
</p>
</div>
</div>

Expand Down
20 changes: 17 additions & 3 deletions warehouse/admin/templates/admin/organizations/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@
<br>
Filters:&nbsp;
<div class="btn-group btn-group-sm" role="group" data-input="q" data-state="status" aria-label="Status">
<button type="button" class="btn btn-secondary {{ 'active' if 'is:active' in terms else '' }}" data-status="is:active">Active</button>
<button type="button" class="btn btn-secondary {{ 'active' if 'is:inactive' in terms else '' }}" data-status="is:inactive">Inactive</button>
<button type="button" class="btn btn-secondary {{ 'active' if 'activity:active' in terms else '' }}" data-status="activity:active" title="Owns a project released in the last year">Active</button>
<button type="button" class="btn btn-secondary {{ 'active' if 'activity:dormant' in terms else '' }}" data-status="activity:dormant" title="Owns projects, none released in the last year">Dormant</button>
<button type="button" class="btn btn-secondary {{ 'active' if 'activity:unused' in terms else '' }}" data-status="activity:unused" title="Owns no projects">Unused</button>
<button type="button" class="btn btn-secondary {{ 'active' if 'activity:inactive' in terms else '' }}" data-status="activity:inactive" title="Deactivated">Inactive</button>
</div>&nbsp;
<div class="btn-group btn-group-sm" role="group" data-input="q" data-state="status" aria-label="Type">
<button type="button" class="btn btn-secondary {{ 'active' if 'type:company' in terms else '' }}" data-status="type:company">Company</button>
<button type="button" class="btn btn-secondary {{ 'active' if 'type:community' in terms else '' }}" data-status="type:community">Community</button>
</div>&nbsp;
<div class="btn-group btn-group-sm" role="group" data-input="q" data-state="status" aria-label="Subscription">
<button type="button" class="btn btn-secondary {{ 'active' if 'has:subscription' in terms else '' }}" data-status="has:subscription" title="Has an active, valid paying subscription">Has Subscription</button>
</div>
</div>
</form>
Expand Down Expand Up @@ -67,11 +72,20 @@
</td>
<td>{{ organization.description }}</td>
<td>{{ organization.orgtype.value }}</td>
{% if organization.is_active %}
{% if organization.activity == "Active" %}
<td><i class="fa fa-fw fa-check text-green"></i> Active</td>
{% elif organization.activity == "Dormant" %}
<td><i class="fa fa-fw fa-clock text-yellow"></i> Dormant</td>
{% elif organization.activity == "Unused" %}
<td><i class="fa fa-fw fa-minus text-muted"></i> Unused</td>
{% else %}
<td><i class="fa fa-fw fa-times text-red"></i> Inactive</td>
{% endif %}
{% if organization.active_subscription %}
<td><i class="fa fa-fw fa-check text-green"></i> Yes</td>
{% else %}
<td><i class="fa fa-fw fa-times text-red"></i> No</td>
{% endif %}
{% if organization.good_standing %}
<td><i class="fa fa-fw fa-check text-green"></i> Yes</td>
{% else %}
Expand Down
19 changes: 6 additions & 13 deletions warehouse/admin/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
OrganizationRole,
OrganizationType,
)
from warehouse.subscriptions.models import StripeSubscription, StripeSubscriptionStatus
from warehouse.subscriptions.models import (
ACTIVE_SUBSCRIPTION_STATUSES,
StripeSubscription,
)


@view_config(
Expand Down Expand Up @@ -61,12 +64,7 @@ def dashboard(request):
.filter(Organization.orgtype == OrganizationType.Company)
.filter(
Organization.subscriptions.any(
StripeSubscription.status.in_(
(
StripeSubscriptionStatus.Active.value,
StripeSubscriptionStatus.Trialing.value,
)
)
StripeSubscription.status.in_(ACTIVE_SUBSCRIPTION_STATUSES)
)
)
.scalar()
Expand All @@ -77,12 +75,7 @@ def dashboard(request):
.filter(Organization.orgtype == OrganizationType.Company)
.filter(
Organization.subscriptions.any(
StripeSubscription.status.in_(
(
StripeSubscriptionStatus.Active.value,
StripeSubscriptionStatus.Trialing.value,
)
)
StripeSubscription.status.in_(ACTIVE_SUBSCRIPTION_STATUSES)
)
)
.scalar()
Expand Down
Loading
Loading