Resolution note source mobile app (#3174)
# What this PR does Fixes https://github.com/grafana/oncall/issues/2320 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required)
This commit is contained in:
parent
bf197b09c2
commit
2179e7a1c9
9 changed files with 130 additions and 10 deletions
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- Discard old pending network requests in the UI (Users/Schedules) [#3172](https://github.com/grafana/oncall/pull/3172)
|
||||
- Fix resolution note source for mobile app by @vadimkerr ([#3174](https://github.com/grafana/oncall/pull/3174))
|
||||
|
||||
## v1.3.45 (2023-10-19)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.20 on 2023-10-20 13:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('alerts', '0033_alertgrouplogrecord_action_source'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='resolutionnote',
|
||||
name='source',
|
||||
field=models.IntegerField(choices=[(0, 'Slack'), (1, 'Web'), (2, 'Mobile App')], default=None, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -120,8 +120,9 @@ class ResolutionNote(models.Model):
|
|||
objects_with_deleted = models.Manager()
|
||||
|
||||
class Source(models.IntegerChoices):
|
||||
SLACK = 0, "slack"
|
||||
WEB = 1, "web"
|
||||
SLACK = 0, "Slack"
|
||||
WEB = 1, "Web"
|
||||
MOBILE_APP = 2, "Mobile App"
|
||||
|
||||
public_primary_key = models.CharField(
|
||||
max_length=20,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from rest_framework import serializers
|
|||
|
||||
from apps.alerts.models import AlertGroup, ResolutionNote
|
||||
from apps.api.serializers.user import FastUserSerializer
|
||||
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
|
||||
from common.api_helpers.custom_fields import OrganizationFilteredPrimaryKeyRelatedField
|
||||
from common.api_helpers.exceptions import BadRequest
|
||||
from common.api_helpers.mixins import EagerLoadingMixin
|
||||
|
|
@ -36,7 +37,13 @@ class ResolutionNoteSerializer(EagerLoadingMixin, serializers.ModelSerializer):
|
|||
|
||||
def create(self, validated_data):
|
||||
validated_data["author"] = self.context["request"].user
|
||||
validated_data["source"] = ResolutionNote.Source.WEB
|
||||
|
||||
if isinstance(self.context["request"].successful_authenticator, MobileAppAuthTokenAuthentication):
|
||||
source = ResolutionNote.Source.MOBILE_APP
|
||||
else:
|
||||
source = ResolutionNote.Source.WEB
|
||||
validated_data["source"] = source
|
||||
|
||||
created_instance = super().create(validated_data)
|
||||
return created_instance
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.test import APIClient
|
||||
|
||||
from apps.alerts.constants import ActionSource
|
||||
from apps.alerts.models import AlertGroup, AlertGroupLogRecord
|
||||
from apps.alerts.models import AlertGroup, AlertGroupLogRecord, ResolutionNote
|
||||
from apps.alerts.tasks import wipe
|
||||
from apps.api.errors import AlertGroupAPIError
|
||||
from apps.api.permissions import LegacyAccessControlRole
|
||||
|
|
@ -1883,6 +1883,68 @@ def test_alert_group_resolve_resolution_note(
|
|||
assert mock_signal.called
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_alert_group_resolve_resolution_note_mobile_app(
|
||||
make_organization_and_user,
|
||||
make_mobile_app_auth_token_for_user,
|
||||
make_alert_receive_channel,
|
||||
make_channel_filter,
|
||||
make_alert_group,
|
||||
make_alert,
|
||||
make_user_auth_headers,
|
||||
):
|
||||
organization, user = make_organization_and_user()
|
||||
organization.is_resolution_note_required = True
|
||||
organization.save()
|
||||
_, token = make_mobile_app_auth_token_for_user(user, organization)
|
||||
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:alertgroup-resolve", kwargs={"pk": alert_group.public_primary_key})
|
||||
response = client.post(url, format="json", data={"resolution_note": "hi"}, HTTP_AUTHORIZATION=token)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert alert_group.resolution_notes.get().source == ResolutionNote.Source.MOBILE_APP
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source", ResolutionNote.Source)
|
||||
@pytest.mark.django_db
|
||||
def test_timeline_resolution_note_source(
|
||||
make_organization_and_user_with_plugin_token,
|
||||
make_alert_receive_channel,
|
||||
make_channel_filter,
|
||||
make_alert_group,
|
||||
make_alert,
|
||||
make_resolution_note_slack_message,
|
||||
make_resolution_note,
|
||||
make_user_auth_headers,
|
||||
source,
|
||||
):
|
||||
"""The 'type' field in timeline items should hold the source of the resolution note"""
|
||||
organization, user, token = make_organization_and_user_with_plugin_token()
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
channel_filter = make_channel_filter(alert_receive_channel, is_default=True)
|
||||
alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter)
|
||||
make_alert(alert_group=alert_group, raw_request_data=alert_raw_request_data)
|
||||
|
||||
# Create resolution note
|
||||
resolution_note_slack_message = make_resolution_note_slack_message(
|
||||
alert_group=alert_group, user=user, added_by_user=user, text="resolution note"
|
||||
)
|
||||
make_resolution_note(
|
||||
alert_group=alert_group, author=user, resolution_note_slack_message=resolution_note_slack_message, source=source
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:alertgroup-detail", kwargs={"pk": alert_group.public_primary_key})
|
||||
response = client.get(url, **make_user_auth_headers(user, token))
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["render_after_resolve_report_json"][0]["type"] == source.value
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_timeline_api_action(
|
||||
make_organization_and_user_with_plugin_token,
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ def test_create_resolution_note(
|
|||
"id": resolution_note.public_primary_key,
|
||||
"alert_group": alert_group.public_primary_key,
|
||||
"source": {
|
||||
"id": resolution_note.source,
|
||||
"display_name": resolution_note.get_source_display(),
|
||||
"id": ResolutionNote.Source.WEB.value,
|
||||
"display_name": ResolutionNote.Source.WEB.label,
|
||||
},
|
||||
"author": {
|
||||
"pk": user.public_primary_key,
|
||||
|
|
@ -50,6 +50,31 @@ def test_create_resolution_note(
|
|||
assert response.data == result
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_resolution_note_mobile_app(
|
||||
make_organization_and_user, make_mobile_app_auth_token_for_user, make_alert_receive_channel, make_alert_group
|
||||
):
|
||||
organization, user = make_organization_and_user()
|
||||
_, token = make_mobile_app_auth_token_for_user(user, organization)
|
||||
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
|
||||
client = APIClient()
|
||||
url = reverse("api-internal:resolution_note-list")
|
||||
data = {
|
||||
"alert_group": alert_group.public_primary_key,
|
||||
"text": "Test Message",
|
||||
}
|
||||
|
||||
response = client.post(url, data=data, format="json", HTTP_AUTHORIZATION=token)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert response.data["source"] == {
|
||||
"id": ResolutionNote.Source.MOBILE_APP.value,
|
||||
"display_name": ResolutionNote.Source.MOBILE_APP.label,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_resolution_note_invalid_text(
|
||||
make_organization_and_user_with_plugin_token,
|
||||
|
|
@ -516,7 +516,11 @@ class AlertGroupView(
|
|||
rn = ResolutionNote.objects.create(
|
||||
alert_group=alert_group,
|
||||
author=self.request.user,
|
||||
source=ResolutionNote.Source.WEB,
|
||||
source=(
|
||||
ResolutionNote.Source.MOBILE_APP
|
||||
if isinstance(self.request.successful_authenticator, MobileAppAuthTokenAuthentication)
|
||||
else ResolutionNote.Source.WEB
|
||||
),
|
||||
message_text=resolution_note_text[:3000], # trim text to fit in the db field
|
||||
)
|
||||
send_update_resolution_note_signal.apply_async(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type ResolutionNoteSourceTypesOptions = {
|
|||
[key: number]: string;
|
||||
};
|
||||
export const ResolutionNoteSourceTypesToDisplayName: ResolutionNoteSourceTypesOptions = {
|
||||
0: 'slack',
|
||||
1: 'web',
|
||||
0: 'Slack',
|
||||
1: 'Web',
|
||||
2: 'Mobile App',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -520,7 +520,8 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
|
|||
<VerticalGroup spacing="none">
|
||||
{item.realm === TimeLineRealm.ResolutionNote && (
|
||||
<Text type="secondary" size="small">
|
||||
{item.author && item.author.username} via {ResolutionNoteSourceTypesToDisplayName[item.type]}
|
||||
{item.author && item.author.username} via{' '}
|
||||
{ResolutionNoteSourceTypesToDisplayName[item.type] || 'Web'}
|
||||
</Text>
|
||||
)}
|
||||
<Text type="primary">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue