Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation for managing organizations #11261

Merged
merged 25 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
641bda1
Added three get organizations variations
sterbo Apr 19, 2022
af9e24e
List organizations on /manage/organizations/ page
divbzero Apr 19, 2022
48bc586
Add SVG of `fa-users` from Font Awesome 5.13.0
divbzero Apr 22, 2022
7fd7eb4
Convert users.svg to light gray
divbzero Apr 22, 2022
c9b78a1
3 shades of gray for users.svg
divbzero Apr 22, 2022
084a380
Darker shades of gray for users.svg
divbzero Apr 22, 2022
44dfd93
Add Font Awesome 5.13.0 attribution to users.svg
divbzero Apr 22, 2022
15597d7
Resize users.svg to match size of white-cube.svg
divbzero Apr 25, 2022
d0a3d61
Convert {users.svg => users.png}
divbzero Apr 25, 2022
0f2da8e
NFC: Rename {_package-snippet => _snippet}.scss
divbzero Apr 25, 2022
5d40f2c
NFC: Rename .package-snippet__* classes
divbzero Apr 25, 2022
c5ff0f2
`snippet` mixin for organizations and packages
divbzero Apr 25, 2022
a27b0b3
Add "Your organizations" to "Your account" sidebar
divbzero Apr 26, 2022
50ed814
Display callout block if user has no organizations
divbzero Apr 26, 2022
81997b3
NFC: Use for..else Jinja2 syntax for projects.html
divbzero Apr 26, 2022
81eb8fc
"Manage" collaborators for each organization
divbzero Apr 26, 2022
3fdb89b
Add sidebar for managing "Your organizations"
divbzero Apr 26, 2022
5e2fbf5
Clean up SCSS imports to fix `bin/static_lint`
divbzero Apr 26, 2022
9e43639
Rename {Collaborators => People} for organizations
divbzero Apr 28, 2022
2ed0c34
Update "Your account" sidebar for mobile
divbzero Apr 28, 2022
78cb27b
Add status/role badges for /manage/organizations/
divbzero Apr 29, 2022
18d6d34
admin: Fix table spacing for cells with long text
divbzero May 1, 2022
43cd066
admin: Stabilize /admin/organizations/ tests more
divbzero May 4, 2022
d9f54b4
Merge branch 'main' into feature/manage-organizations-navigation
ewdurbin May 10, 2022
6532265
Apply suggestions from code review
ewdurbin May 10, 2022
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
1 change: 1 addition & 0 deletions Gulpfile.babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ gulp.task("dist:admin:css", () => {
"warehouse/admin/static/css/ionicons.min.css",
"warehouse/admin/static/css/AdminLTE.min.css",
"warehouse/admin/static/css/skins/skin-purple.min.css",
"warehouse/admin/static/css/admin.css",
];
return gulp.src(files)
.pipe(gulpConcat("all.css"))
Expand Down
6 changes: 2 additions & 4 deletions tests/common/db/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,13 @@ class Meta:
link_url = factory.Faker("uri")
description = factory.Faker("sentence")
is_active = True
is_approved = False
is_approved = None
created = factory.Faker(
"date_time_between_dates",
datetime_start=datetime.datetime(2020, 1, 1),
datetime_end=datetime.datetime(2022, 1, 1),
)
date_approved = factory.Faker(
"date_time_between_dates", datetime_start=datetime.datetime(2020, 1, 1)
)
date_approved = None


class OrganizationEventFactory(WarehouseFactory):
Expand Down
40 changes: 15 additions & 25 deletions tests/unit/admin/views/test_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,9 @@ def test_basic_query(self, enable_organizations, db_request):
db_request.GET["q"] = organizations[0].name
result = views.organization_list(db_request)

assert result == {
"organizations": [organizations[0]],
"query": organizations[0].name,
"terms": [organizations[0].name],
}
assert organizations[0] in result["organizations"]
assert result["query"] == organizations[0].name
assert result["terms"] == [organizations[0].name]

def test_name_query(self, enable_organizations, db_request):
organizations = sorted(
Expand All @@ -73,11 +71,9 @@ def test_name_query(self, enable_organizations, db_request):
db_request.GET["q"] = f"name:{organizations[0].name}"
result = views.organization_list(db_request)

assert result == {
"organizations": [organizations[0]],
"query": f"name:{organizations[0].name}",
"terms": [f"name:{organizations[0].name}"],
}
assert organizations[0] in result["organizations"]
assert result["query"] == f"name:{organizations[0].name}"
assert result["terms"] == [f"name:{organizations[0].name}"]

def test_organization_query(self, enable_organizations, db_request):
organizations = sorted(
Expand All @@ -87,11 +83,9 @@ def test_organization_query(self, enable_organizations, db_request):
db_request.GET["q"] = f"organization:{organizations[0].display_name}"
result = views.organization_list(db_request)

assert result == {
"organizations": [organizations[0]],
"query": f"organization:{organizations[0].display_name}",
"terms": [f"organization:{organizations[0].display_name}"],
}
assert organizations[0] in result["organizations"]
assert result["query"] == f"organization:{organizations[0].display_name}"
assert result["terms"] == [f"organization:{organizations[0].display_name}"]

def test_url_query(self, enable_organizations, db_request):
organizations = sorted(
Expand All @@ -101,11 +95,9 @@ def test_url_query(self, enable_organizations, db_request):
db_request.GET["q"] = f"url:{organizations[0].link_url}"
result = views.organization_list(db_request)

assert result == {
"organizations": [organizations[0]],
"query": f"url:{organizations[0].link_url}",
"terms": [f"url:{organizations[0].link_url}"],
}
assert organizations[0] in result["organizations"]
assert result["query"] == f"url:{organizations[0].link_url}"
assert result["terms"] == [f"url:{organizations[0].link_url}"]

def test_description_query(self, enable_organizations, db_request):
organizations = sorted(
Expand All @@ -115,11 +107,9 @@ def test_description_query(self, enable_organizations, db_request):
db_request.GET["q"] = f"description:'{organizations[0].description}'"
result = views.organization_list(db_request)

assert result == {
"organizations": [organizations[0]],
"query": f"description:'{organizations[0].description}'",
"terms": [f"description:{organizations[0].description}"],
}
assert organizations[0] in result["organizations"]
assert result["query"] == f"description:'{organizations[0].description}'"
assert result["terms"] == [f"description:{organizations[0].description}"]

def test_is_approved_query(self, enable_organizations, db_request):
organizations = sorted(
Expand Down
45 changes: 43 additions & 2 deletions tests/unit/manage/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from warehouse.utils.project import remove_documentation

from ...common.db.accounts import EmailFactory
from ...common.db.organizations import OrganizationFactory
from ...common.db.packaging import (
FileFactory,
JournalEntryFactory,
Expand Down Expand Up @@ -2313,18 +2314,37 @@ def test_default_response(self, monkeypatch):
)
monkeypatch.setattr(views, "CreateOrganizationForm", create_organization_cls)

organization = pretend.stub(name=pretend.stub())

user_organizations = pretend.call_recorder(
lambda *a, **kw: {
"organizations_managed": [],
"organizations_owned": [organization],
"organizations_billing": [],
}
)
monkeypatch.setattr(views, "user_organizations", user_organizations)

organization_service = pretend.stub(
get_organizations_by_user=lambda *a, **kw: [organization]
)
user_service = pretend.stub()
request = pretend.stub(
user=pretend.stub(id=pretend.stub(), username=pretend.stub()),
find_service=lambda interface, **kw: {
IOrganizationService: pretend.stub(),
IUserService: pretend.stub(),
IOrganizationService: organization_service,
IUserService: user_service,
}[interface],
)

view = views.ManageOrganizationsViews(request)

assert view.default_response == {
"create_organization_form": create_organization_obj,
"organizations": [organization],
"organizations_managed": [],
"organizations_owned": [organization.name],
"organizations_billing": [],
}

def test_manage_organizations(self, monkeypatch):
Expand Down Expand Up @@ -2593,6 +2613,27 @@ def test_create_organizations_disable_organizations(self, monkeypatch):
]


class TestManageOrganizationRoles:
def test_get_manage_organization_roles(self, db_request):
organization = OrganizationFactory.create(name="foobar")
request = pretend.stub(
flags=pretend.stub(enabled=pretend.call_recorder(lambda *a: False)),
)

result = views.manage_organization_roles(organization, request)

assert result == {"organization": organization}

def test_get_manage_organization_roles_disable_organizations(self, db_request):
organization = OrganizationFactory.create(name="foobar")
request = pretend.stub(
flags=pretend.stub(enabled=pretend.call_recorder(lambda *a: True)),
)

with pytest.raises(HTTPNotFound):
views.manage_organization_roles(organization, request)


class TestManageProjects:
def test_manage_projects(self, db_request):
older_release = ReleaseFactory(created=datetime.datetime(2015, 1, 1))
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/organizations/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

from warehouse.organizations.models import OrganizationFactory

from ...common.db.organizations import OrganizationFactory as DBOrganizationFactory


class TestOrganizationFactory:
@pytest.mark.parametrize(("name", "normalized"), [("foo", "foo"), ("Bar", "bar")])
def test_traversal_finds(self, db_request, name, normalized):
organization = DBOrganizationFactory.create(name=name)
root = OrganizationFactory(db_request)

assert root[normalized] == organization

def test_traversal_cant_find(self, db_request):
organization = DBOrganizationFactory.create()
root = OrganizationFactory(db_request)

with pytest.raises(KeyError):
root[organization.name + "invalid"]
48 changes: 48 additions & 0 deletions tests/unit/organizations/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,54 @@ def test_find_organizationid(self, organization_service):
def test_find_organizationid_nonexistent_org(self, organization_service):
assert organization_service.find_organizationid("a_spoon_in_the_matrix") is None

def test_get_organizations(self, organization_service):
organization = OrganizationFactory.create(name="org")
another_organization = OrganizationFactory.create(name="another_org")
orgs = organization_service.get_organizations()

assert organization in orgs
assert another_organization in orgs

def test_get_organizations_needing_approval(self, organization_service):
i_need_it = OrganizationFactory.create()
assert i_need_it.is_approved is None

i_has_it = OrganizationFactory.create()
organization_service.approve_organization(i_has_it.id)
assert i_has_it.is_approved is True

orgs_needing_approval = (
organization_service.get_organizations_needing_approval()
)

assert i_need_it in orgs_needing_approval
assert i_has_it not in orgs_needing_approval

def test_get_organizations_by_user(self, organization_service, user_service):
user_organization = OrganizationFactory.create()
user = UserFactory.create()
organization_service.add_organization_role(
OrganizationRoleType.Owner.value, user.id, user_organization.id
)

another_user_organization = OrganizationFactory.create()
another_user = UserFactory.create()
organization_service.add_organization_role(
OrganizationRoleType.Owner.value,
another_user.id,
another_user_organization.id,
)

user_orgs = organization_service.get_organizations_by_user(user.id)
another_user_orgs = organization_service.get_organizations_by_user(
another_user.id
)

assert user_organization in user_orgs
assert user_organization not in another_user_orgs
assert another_user_organization in another_user_orgs
assert another_user_organization not in user_orgs

def test_add_organization(self, organization_service):
organization = OrganizationFactory.create()
new_org = organization_service.add_organization(
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@ def add_policy(name, filename):
pretend.call(
"manage.organizations", "/manage/organizations/", domain=warehouse
),
pretend.call(
"manage.organization.roles",
"/manage/organization/{organization_name}/people/",
factory="warehouse.organizations.models:OrganizationFactory",
traverse="/{organization_name}",
domain=warehouse,
),
pretend.call("manage.projects", "/manage/projects/", domain=warehouse),
pretend.call(
"manage.project.settings",
Expand Down
22 changes: 22 additions & 0 deletions warehouse/admin/static/css/admin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*!
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@charset "utf-8";

/* Admin styles for the Warehouse project (PyPI) */

.table td,
.table th {
max-width: 30em;
}
Loading