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

Teams redesign #1528

Merged
merged 60 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
4f2a59b
Add alert group filtering based on owning team
iskhakov Mar 11, 2023
5aea1ee
part 2
iskhakov Mar 13, 2023
6f6548b
part 3
iskhakov Mar 13, 2023
b0201af
Rename owning team filter to team
iskhakov Mar 13, 2023
0230f0d
Add filters for schedule and escalation chains
iskhakov Mar 13, 2023
d98d675
Add integrations filter and maintenance filters endpoint
iskhakov Mar 13, 2023
9b3784e
Show team name next to the resource
iskhakov Mar 13, 2023
3535638
add RemoteFilters
Mar 14, 2023
8728e7d
move cards out of incidents filters
Mar 14, 2023
bd3defb
Refactor team filter
iskhakov Mar 15, 2023
6b40193
add RemoteFilters to all pages
Mar 15, 2023
4f1ce1d
Merge branch 'iskhakov/teams-refactoring' of github.com:grafana/oncal…
Mar 15, 2023
4a1fe3e
Display team
iskhakov Mar 15, 2023
78d4c60
add team selector to forms
Mar 15, 2023
a0f10eb
Merge branch 'iskhakov/teams-refactoring' of github.com:grafana/oncal…
Mar 15, 2023
4c3a600
Add teams settings page
iskhakov Mar 15, 2023
ef83c15
Fix bug
iskhakov Mar 15, 2023
3c6963a
Add schedule filters
iskhakov Mar 15, 2023
05f4fd4
Add schedule filters
iskhakov Mar 15, 2023
d7e4252
Add schedule filters
iskhakov Mar 15, 2023
b58a2cb
Add modal window for setting
iskhakov Mar 15, 2023
2df5c11
add TeamSettings modal, add Private esc chain render
Mar 15, 2023
7a9c39d
Clean up and add style name
iskhakov Mar 16, 2023
762279b
add private schedule, webhook display
Mar 16, 2023
6450113
Merge branch 'iskhakov/teams-refactoring' of github.com:grafana/oncal…
Mar 16, 2023
862bc71
Fix bug when unable to set default team
iskhakov Mar 16, 2023
78a544b
show team settings in main settings for old nav only
Mar 16, 2023
5d87a0b
Merge branch 'iskhakov/teams-refactoring' of github.com:grafana/oncal…
Mar 16, 2023
a0c7a27
Add shared teams support, fix team change for integration
iskhakov Mar 16, 2023
bf8b1c1
fix ts errors
Mar 16, 2023
c06fdc2
Merge branch 'iskhakov/teams-refactoring' of github.com:grafana/oncal…
Mar 16, 2023
3c9b32c
Clean up
iskhakov Mar 16, 2023
23e28f0
Add global flag to teams filter
iskhakov Mar 16, 2023
294b226
add support for global filters
Mar 16, 2023
7f8601b
fix integration team preselected, fix global filters
Mar 16, 2023
ae47988
Add team acess control to rest of the resources
iskhakov Mar 16, 2023
3d322dc
Cleanup
iskhakov Mar 16, 2023
1e424b1
Update grafana-plugin/src/components/PageErrorHandlingWrapper/PageErr…
iskhakov Mar 20, 2023
3d5aa3b
Apply suggestions from code review
iskhakov Mar 20, 2023
8da4b52
Fixes for code review
iskhakov Mar 20, 2023
dd364e5
Fix typos
iskhakov Mar 20, 2023
8108c10
Code optimisations after reviews
iskhakov Mar 20, 2023
c09edf1
Add tooltips and descriptions
iskhakov Mar 20, 2023
c12962a
Merge branch 'dev' into iskhakov/teams-refactoring
Mar 20, 2023
faaffcc
Deduplicate team ModelMultipleChoiceFilter
iskhakov Mar 20, 2023
68871ae
Add team field to direct paging
iskhakov Mar 20, 2023
d3dbabd
fix ts
Mar 20, 2023
e8438d9
fix escalation chains filtering
Mar 20, 2023
e365ed7
disable outgoing webhooks page test
Mar 20, 2023
6c8000b
fix remote filters parsers
Mar 20, 2023
9270721
Merge branch 'dev' into iskhakov/teams-refactoring
iskhakov Mar 21, 2023
cd54dfb
Change how team is displayed
iskhakov Mar 21, 2023
85d4468
Fix tests and minor bugs
iskhakov Mar 21, 2023
d7b849c
Merge branch 'dev' into iskhakov/teams-refactoring
iskhakov Mar 21, 2023
061899e
fix integrations and esc chains page autoselect
Mar 21, 2023
a245912
Add team selector to alert groups and add more descriptions
iskhakov Mar 21, 2023
b702d8c
Merge branch 'iskhakov/teams-refactoring' of github.com:grafana/oncal…
Mar 21, 2023
7665001
Fix integration tests and comment out integrations search test
iskhakov Mar 21, 2023
6d9217c
Merge branch 'dev' into iskhakov/teams-refactoring
iskhakov Mar 21, 2023
7733ab6
Add entry to changelog
iskhakov Mar 21, 2023
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v1.2.0 (2023-03-21)

### Changed

- Add team-based filtering for resources, so that users can see multiple resources at once and link them together ([1528](https://github.com/grafana/oncall/pull/1528))

## v1.1.41 (2023-03-21)

### Added
Expand Down
43 changes: 2 additions & 41 deletions engine/apps/alerts/models/alert_receive_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.conf import settings
from django.core.validators import MinLengthValidator
from django.db import models, transaction
from django.db.models import Count, Q
from django.db.models import Q
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
Expand Down Expand Up @@ -236,55 +236,16 @@ def hard_delete(self):
super(AlertReceiveChannel, self).delete()

def change_team(self, team_id, user):
EscalationPolicy = apps.get_model("alerts", "EscalationPolicy")

if team_id == self.team_id:
raise TeamCanNotBeChangedError("Integration is already in this team")

if team_id is not None:
new_team = user.teams.filter(public_primary_key=team_id).first()
new_team = user.available_teams.filter(public_primary_key=team_id).first()
if not new_team:
raise TeamCanNotBeChangedError("User is not a member of the selected team")
else:
new_team = None # means General team

escalation_chains_pks = self.channel_filters.all().values_list("escalation_chain", flat=True)
escalation_chains = self.organization.escalation_chains.filter(pk__in=escalation_chains_pks).annotate(
num_integrations=Count(
"channel_filters__alert_receive_channel",
distinct=True,
filter=Q(channel_filters__alert_receive_channel__deleted_at__isnull=True),
),
)
if escalation_chains:
# check if escalation chains are connected to routes of other integrations
shared_escalation_chains = []
for escalation_chain in escalation_chains:
if escalation_chain.num_integrations > 1:
shared_escalation_chains.append(escalation_chain)
if shared_escalation_chains:
shared_escalation_chains_verbal = ", ".join([ec.name for ec in shared_escalation_chains])
raise TeamCanNotBeChangedError(
f"Team cannot be changed because one or more escalation chain of integration routes "
f"is connected to other integration: {shared_escalation_chains_verbal}"
)

escalation_policies = EscalationPolicy.objects.filter(escalation_chain__in=escalation_chains)

users_in_escalation = self.organization.users.filter(escalationpolicy__in=escalation_policies)
if new_team:
team_members = new_team.users.filter(pk__in=[user.pk for user in users_in_escalation])
else:
team_members = self.organization.users.filter(pk__in=[user.pk for user in users_in_escalation])
not_team_members = set(users_in_escalation) - set(team_members)
if not_team_members:
not_team_members_verbal = ", ".join([user.username for user in not_team_members])
raise TeamCanNotBeChangedError(
f"Team cannot be changed because one or more user from escalation chain(s) is not a member "
f"of the selected team: {not_team_members_verbal}"
)

escalation_chains.update(team=new_team)
self.team = new_team
self.save(update_fields=["team"])

Expand Down
3 changes: 3 additions & 0 deletions engine/apps/api/serializers/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from apps.alerts.incident_appearance.renderers.web_renderer import AlertGroupWebRenderer
from apps.alerts.models import AlertGroup, AlertGroupLogRecord
from apps.user_management.models import User
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
from common.api_helpers.mixins import EagerLoadingMixin

from .alert import AlertSerializer
Expand Down Expand Up @@ -82,6 +83,7 @@ class AlertGroupListSerializer(EagerLoadingMixin, AlertGroupFieldsCacheSerialize
related_users = serializers.SerializerMethodField()
dependent_alert_groups = ShortAlertGroupSerializer(many=True)
root_alert_group = ShortAlertGroupSerializer()
team = TeamPrimaryKeyRelatedField(source="channel.team", allow_null=True)

alerts_count = serializers.IntegerField(read_only=True)
render_for_web = serializers.SerializerMethodField()
Expand Down Expand Up @@ -129,6 +131,7 @@ class Meta:
"root_alert_group",
"status",
"declare_incident_link",
"team",
]

def get_render_for_web(self, obj):
Expand Down
4 changes: 4 additions & 0 deletions engine/apps/api/serializers/paging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from rest_framework import serializers

from apps.alerts.models import AlertGroup
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
from common.api_helpers.utils import CurrentTeamDefault


class UserReferenceSerializer(serializers.Serializer):
Expand Down Expand Up @@ -49,6 +51,8 @@ class DirectPagingSerializer(serializers.Serializer):
title = serializers.CharField(required=False, default=None)
message = serializers.CharField(required=False, default=None)

team = TeamPrimaryKeyRelatedField(allow_null=True, default=CurrentTeamDefault())

def validate(self, attrs):
organization = self.context["organization"]

Expand Down
8 changes: 8 additions & 0 deletions engine/apps/api/serializers/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@ class Meta:
"name",
"email",
"avatar_url",
"is_sharing_resources_to_all",
)

read_only_fields = (
"id",
"name",
"email",
"avatar_url",
)
35 changes: 1 addition & 34 deletions engine/apps/api/tests/test_alert_receive_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,40 +559,7 @@ def test_alert_receive_channel_change_team(

assert integration.team != team

# return 400 on change team for integration if user is not a member of chosen team
response = client.put(
f"{url}?team_id={team.public_primary_key}", format="json", **make_user_auth_headers(user, token)
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
integration.refresh_from_db()
assert integration.team != team

team.users.add(user)
# return 400 on change team for integration if escalation_chain is connected to another integration
another_integration = make_alert_receive_channel(organization)
another_channel_filter = make_channel_filter(another_integration, escalation_chain=escalation_chain)
response = client.put(
f"{url}?team_id={team.public_primary_key}", format="json", **make_user_auth_headers(user, token)
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
integration.refresh_from_db()
assert integration.team != team

another_channel_filter.escalation_chain = None
another_channel_filter.save()

# return 400 on change team for integration if user from escalation policy is not a member of team
another_user = make_user_for_organization(organization)
escalation_policy.notify_to_users_queue.add(another_user)
response = client.put(
f"{url}?team_id={team.public_primary_key}", format="json", **make_user_auth_headers(user, token)
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
integration.refresh_from_db()
assert integration.team != team

team.users.add(another_user)
# otherwise change team
# return 200 on change team for integration as user is Admin
response = client.put(
f"{url}?team_id={team.public_primary_key}", format="json", **make_user_auth_headers(user, token)
)
Expand Down
20 changes: 0 additions & 20 deletions engine/apps/api/tests/test_custom_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,23 +457,3 @@ def test_get_custom_button_from_other_team_with_flag(

response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK


@pytest.mark.django_db
def test_custom_button_from_other_team_without_flag(
make_organization_and_user_with_plugin_token,
make_team,
make_user_auth_headers,
make_custom_action,
):
organization, user, token = make_organization_and_user_with_plugin_token()

team = make_team(organization)

custom_button = make_custom_action(organization=organization, team=team)
client = APIClient()

url = reverse("api-internal:custom_button-detail", kwargs={"pk": custom_button.public_primary_key})

response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_403_FORBIDDEN
25 changes: 0 additions & 25 deletions engine/apps/api/tests/test_schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -1734,28 +1734,3 @@ def test_get_schedule_from_other_team_with_flag(

response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK


@pytest.mark.django_db
def test_get_schedule_from_other_team_without_flag(
make_organization_and_user_with_plugin_token,
make_team,
make_user_auth_headers,
make_schedule,
):
organization, user, token = make_organization_and_user_with_plugin_token()

team = make_team(organization)

calendar_schedule = make_schedule(
organization,
schedule_class=OnCallScheduleCalendar,
name="test_calendar_schedule",
team=team,
)

client = APIClient()
url = reverse("api-internal:schedule-detail", kwargs={"pk": calendar_schedule.public_primary_key})

response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_403_FORBIDDEN
Loading