Skip to content

Manage organization "Teams" #11665

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

Merged
merged 56 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
512d0ea
Create initial models for Teams
sterbo Jun 13, 2022
3276bf8
Add services for Teams to do all the things
sterbo Jun 13, 2022
d06236a
Add tests for the Teams services
sterbo Jun 13, 2022
6a5175a
Add "Teams" to manage organization menu
divbzero May 11, 2022
e9f5839
Template for manage organization "Teams"
divbzero Jun 14, 2022
8988cbd
Grant "manage:team" permissions to org Owner
divbzero Jun 14, 2022
c997d82
Relax Team name constraint and add normalized_name
divbzero Jun 14, 2022
5177cf7
`find_teamid` by organization ID and team name
divbzero Jun 14, 2022
25d8797
View and form for manage organization "Teams"
divbzero Jun 14, 2022
714cff3
.team-snippet style
divbzero Jun 15, 2022
e1ec25d
sitemap.{png,svg}
divbzero Jun 15, 2022
ef665ae
Tests for manage organization "Teams"
divbzero Jun 16, 2022
2225805
Emails for manage organization "Teams"
divbzero Jun 16, 2022
1826188
Base template for manage team pages
divbzero Jun 18, 2022
444246e
Switch icons for organizations and teams
divbzero Jun 18, 2022
514768e
Template for manage team "Projects"
divbzero Jun 18, 2022
dbf2959
View for manage team "Projects"
divbzero Jun 18, 2022
7110d08
Tests for manage team "Projects"
divbzero Jun 19, 2022
119e760
`TeamRoleType` and `TeamProjectRoleType` enums
divbzero Jun 19, 2022
98717e4
Template for manage team "Members"
divbzero Jun 19, 2022
e2219d0
View and form for manage team "Members"
divbzero Jun 21, 2022
d6e8174
Tests for manage team "Members"
divbzero Jun 21, 2022
b9a01f8
Template for manage team "Settings"
divbzero Jun 22, 2022
786b2ac
View and form for manage team "Settings"
divbzero Jun 22, 2022
62f8994
Tests for manage team "Settings"
divbzero Jun 22, 2022
a268c2a
Fix typo in "added-as-organization-member" email
divbzero Jun 23, 2022
8e89ce6
Fix typo in "organization-member-removed" email
divbzero Jun 23, 2022
ae51292
Emails for manage team "Members"
divbzero Jun 23, 2022
d078210
Emails for manage team "Settings"
divbzero Jun 23, 2022
acc6d31
Update Alembic revisions after merge branch 'main'
divbzero Jun 28, 2022
6c8e86d
Update template for internal project collaborators
divbzero Jun 23, 2022
b2d2331
Views and forms for internal project collaborators
divbzero Jun 23, 2022
2b435f8
Update project role journal entries and events
divbzero Jun 23, 2022
90fce92
Tests for internal project collaborators
divbzero Jun 24, 2022
0bc6fe6
Emails for internal project collaborators
divbzero Jun 27, 2022
b035924
List org owners as internal project collaborators
divbzero Jun 28, 2022
b4cafac
Restore 2FA column for project collaborators
divbzero Jun 28, 2022
caba60b
Fix redirect after removing team member
divbzero Jun 28, 2022
2a42e66
Fix redirect after renaming team
divbzero Jun 28, 2022
f39057d
Enable events for `Team`
divbzero Jul 7, 2022
c52dc88
Record events for `Team`
divbzero Jul 7, 2022
b43a5ba
Rename {Team.users => Team.members}
divbzero Jul 7, 2022
f8bd8c1
Fix tests after merge branch 'main'
divbzero Jul 7, 2022
9fb9595
Fix SQLAlchemy warnings for `Team`
divbzero Jul 7, 2022
b88657c
Revert "Update project role journal entries and events"
divbzero Jul 8, 2022
ef5cda6
Update Alembic revisions after rebase 'main'
divbzero Jul 20, 2022
26bbc2e
Revert renaming of roles for individual users
divbzero Jul 20, 2022
189b4b5
Rename permissions for teams {Admin => Administer}
divbzero Jul 20, 2022
90f1d9a
Clarify in emails that teams have "permissions"
divbzero Jul 20, 2022
ca4807c
Clarify in templates that teams have "permissions"
divbzero Jul 20, 2022
1c00802
Include team projects in user's list of projects
divbzero Jul 20, 2022
98a6853
Fix left align in "People" and "Members" tables
divbzero Jul 20, 2022
61e41b8
Tweak navigation for organizations and teams
divbzero Jul 21, 2022
3bce85c
Grant project permissions to team members
divbzero Jul 21, 2022
d605b2f
Merge branch 'main' into feature/manage-organization-teams
ewdurbin Jul 26, 2022
6fdfa29
re-order migrations
ewdurbin Jul 26, 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
37 changes: 37 additions & 0 deletions tests/common/db/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
OrganizationProject,
OrganizationRole,
OrganizationRoleType,
Team,
TeamProjectRole,
TeamProjectRoleType,
TeamRole,
TeamRoleType,
)

from .accounts import UserFactory
Expand Down Expand Up @@ -101,3 +106,35 @@ class Meta:
id = factory.Faker("uuid4", cast_to=None)
organization = factory.SubFactory(OrganizationFactory)
project = factory.SubFactory(ProjectFactory)


class TeamFactory(WarehouseFactory):
class Meta:
model = Team

id = factory.Faker("uuid4", cast_to=None)
name = factory.Faker("pystr", max_chars=12)
created = factory.Faker(
"date_time_between_dates",
datetime_start=datetime.datetime(2020, 1, 1),
datetime_end=datetime.datetime(2022, 1, 1),
)
organization = factory.SubFactory(OrganizationFactory)


class TeamRoleFactory(WarehouseFactory):
class Meta:
model = TeamRole

role_name = TeamRoleType.Member
user = factory.SubFactory(UserFactory)
team = factory.SubFactory(TeamFactory)


class TeamProjectRoleFactory(WarehouseFactory):
class Meta:
model = TeamProjectRole

role_name = TeamProjectRoleType.Administer
project = factory.SubFactory(ProjectFactory)
team = factory.SubFactory(TeamFactory)
263 changes: 263 additions & 0 deletions tests/unit/email/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from warehouse.email.services import EmailMessage

from ...common.db.accounts import EmailFactory, UserFactory
from ...common.db.organizations import TeamFactory


@pytest.mark.parametrize(
Expand Down Expand Up @@ -2794,6 +2795,163 @@ def test_send_organization_deleted_email(
]


class TestTeamMemberEmails:
@pytest.fixture
def team(self, pyramid_user):
self.user = UserFactory.create()
EmailFactory.create(user=self.user, verified=True)
self.submitter = pyramid_user
self.organization_name = "exampleorganization"
self.team_name = "Example Team"

@pytest.mark.parametrize(
("email_template_name", "send_team_member_email"),
[
("added-as-team-member", email.send_added_as_team_member_email),
("removed-as-team-member", email.send_removed_as_team_member_email),
("team-member-added", email.send_team_member_added_email),
("team-member-removed", email.send_team_member_removed_email),
],
)
def test_send_team_member_email(
self,
db_request,
team,
make_email_renderers,
send_email,
email_template_name,
send_team_member_email,
):
subject_renderer, body_renderer, html_renderer = make_email_renderers(
email_template_name
)

if email_template_name.endswith("-as-team-member"):
recipient = self.user
result = send_team_member_email(
db_request,
self.user,
submitter=self.submitter,
organization_name=self.organization_name,
team_name=self.team_name,
)
else:
recipient = self.submitter
result = send_team_member_email(
db_request,
self.submitter,
user=self.user,
submitter=self.submitter,
organization_name=self.organization_name,
team_name=self.team_name,
)

assert result == {
"username": self.user.username,
"submitter": self.submitter.username,
"organization_name": self.organization_name,
"team_name": self.team_name,
}
subject_renderer.assert_(**result)
body_renderer.assert_(**result)
html_renderer.assert_(**result)
assert db_request.task.calls == [pretend.call(send_email)]
assert send_email.delay.calls == [
pretend.call(
f"{recipient.name} <{recipient.email}>",
{
"subject": subject_renderer.string_response,
"body_text": body_renderer.string_response,
"body_html": (
f"<html>\n"
f"<head></head>\n"
f"<body><p>{html_renderer.string_response}</p></body>\n"
f"</html>\n"
),
},
{
"tag": "account:email:sent",
"user_id": recipient.id,
"additional": {
"from_": db_request.registry.settings["mail.sender"],
"to": recipient.email,
"subject": subject_renderer.string_response,
"redact_ip": recipient != self.submitter,
},
},
)
]


class TestTeamEmails:
@pytest.fixture
def team(self, pyramid_user):
self.user = pyramid_user
self.organization_name = "exampleorganization"
self.team_name = "Example Team"

@pytest.mark.parametrize(
("email_template_name", "send_team_email"),
[
("team-created", email.send_team_created_email),
("team-deleted", email.send_team_deleted_email),
],
)
def test_send_team_email(
self,
db_request,
team,
make_email_renderers,
send_email,
email_template_name,
send_team_email,
):
subject_renderer, body_renderer, html_renderer = make_email_renderers(
email_template_name
)

result = send_team_email(
db_request,
self.user,
organization_name=self.organization_name,
team_name=self.team_name,
)

assert result == {
"organization_name": self.organization_name,
"team_name": self.team_name,
}
subject_renderer.assert_(**result)
body_renderer.assert_(**result)
html_renderer.assert_(**result)
assert db_request.task.calls == [pretend.call(send_email)]
assert send_email.delay.calls == [
pretend.call(
f"{self.user.name} <{self.user.email}>",
{
"subject": subject_renderer.string_response,
"body_text": body_renderer.string_response,
"body_html": (
f"<html>\n"
f"<head></head>\n"
f"<body><p>{html_renderer.string_response}</p></body>\n"
f"</html>\n"
),
},
{
"tag": "account:email:sent",
"user_id": self.user.id,
"additional": {
"from_": db_request.registry.settings["mail.sender"],
"to": self.user.email,
"subject": subject_renderer.string_response,
"redact_ip": False,
},
},
)
]


class TestCollaboratorAddedEmail:
def test_collaborator_added_email(
self, pyramid_request, pyramid_config, monkeypatch
Expand Down Expand Up @@ -3615,6 +3773,111 @@ def test_role_changed_as_collaborator_email(
]


class TestTeamCollaboratorEmails:
@pytest.fixture
def team(self, pyramid_user):
self.user = UserFactory.create()
EmailFactory.create(user=self.user, verified=True)
self.submitter = pyramid_user
self.team = TeamFactory.create(name="Example Team")
self.project_name = "exampleproject"
self.role = "Admin"

@pytest.mark.parametrize(
("email_template_name", "send_team_collaborator_email"),
[
("added-as-team-collaborator", email.send_added_as_team_collaborator_email),
(
"removed-as-team-collaborator",
email.send_removed_as_team_collaborator_email,
),
(
"role-changed-as-team-collaborator",
email.send_role_changed_as_team_collaborator_email,
),
("team-collaborator-added", email.send_team_collaborator_added_email),
("team-collaborator-removed", email.send_team_collaborator_removed_email),
(
"team-collaborator-role-changed",
email.send_team_collaborator_role_changed_email,
),
],
)
def test_send_team_collaborator_email(
self,
db_request,
team,
make_email_renderers,
send_email,
email_template_name,
send_team_collaborator_email,
):
subject_renderer, body_renderer, html_renderer = make_email_renderers(
email_template_name
)

if "removed" in email_template_name:
result = send_team_collaborator_email(
db_request,
self.user,
team=self.team,
submitter=self.submitter,
project_name=self.project_name,
)
else:
result = send_team_collaborator_email(
db_request,
self.user,
team=self.team,
submitter=self.submitter,
project_name=self.project_name,
role=self.role,
)

if "removed" in email_template_name:
assert result == {
"team_name": self.team.name,
"project": self.project_name,
"submitter": self.submitter.username,
}
else:
assert result == {
"team_name": self.team.name,
"project": self.project_name,
"submitter": self.submitter.username,
"role": self.role,
}
subject_renderer.assert_(**result)
body_renderer.assert_(**result)
html_renderer.assert_(**result)
assert db_request.task.calls == [pretend.call(send_email)]
assert send_email.delay.calls == [
pretend.call(
f"{self.user.name} <{self.user.email}>",
{
"subject": subject_renderer.string_response,
"body_text": body_renderer.string_response,
"body_html": (
f"<html>\n"
f"<head></head>\n"
f"<body><p>{html_renderer.string_response}</p></body>\n"
f"</html>\n"
),
},
{
"tag": "account:email:sent",
"user_id": self.user.id,
"additional": {
"from_": db_request.registry.settings["mail.sender"],
"to": self.user.email,
"subject": subject_renderer.string_response,
"redact_ip": True,
},
},
)
]


class TestRemovedProjectEmail:
def test_removed_project_email_to_maintainer(
self, pyramid_request, pyramid_config, monkeypatch
Expand Down
Loading