diff --git a/CHANGELOG.md b/CHANGELOG.md index 53d9bc95..f5602d1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add debounce on Select UI components to avoid making API search requests on each key-down event by @maskin25 ([#2466](https://github.com/grafana/oncall/pull/2466)) +- Make Direct paging integration configurable ([2483](https://github.com/grafana/oncall/pull/2483)) ## v1.3.8 (2023-07-11) diff --git a/engine/apps/alerts/paging.py b/engine/apps/alerts/paging.py index 53a92ba9..d17f6f03 100644 --- a/engine/apps/alerts/paging.py +++ b/engine/apps/alerts/paging.py @@ -42,7 +42,7 @@ def _trigger_alert( deleted_at=None, defaults={ "author": from_user, - "verbal_name": f"Direct paging ({team.name if team else 'General'} team)", + "verbal_name": "Direct paging", }, ) if alert_receive_channel.default_channel_filter is None: @@ -149,8 +149,6 @@ def direct_paging( Otherwise, create a new alert using given title and message. """ - if not users and not schedules and not escalation_chain: - return if users is None: users = [] diff --git a/engine/apps/alerts/tests/test_paging.py b/engine/apps/alerts/tests/test_paging.py index 5d3c79b0..daa73a6a 100644 --- a/engine/apps/alerts/tests/test_paging.py +++ b/engine/apps/alerts/tests/test_paging.py @@ -154,20 +154,6 @@ def test_check_user_availability_on_call( assert warnings == [] -@pytest.mark.django_db -def test_direct_paging_no_one(make_organization, make_user_for_organization): - organization = make_organization() - from_user = make_user_for_organization(organization) - - with patch("apps.alerts.paging.notify_user_task") as notify_task: - direct_paging(organization, None, from_user) - - # no alert group - assert AlertGroup.all_objects.count() == 0 - # no notifications - assert not notify_task.apply_async.called - - @pytest.mark.django_db def test_direct_paging_user(make_organization, make_user_for_organization): organization = make_organization() diff --git a/engine/apps/api/serializers/alert_receive_channel.py b/engine/apps/api/serializers/alert_receive_channel.py index 7e60e943..2cea5a96 100644 --- a/engine/apps/api/serializers/alert_receive_channel.py +++ b/engine/apps/api/serializers/alert_receive_channel.py @@ -117,8 +117,15 @@ class AlertReceiveChannelSerializer(EagerLoadingMixin, serializers.ModelSerializ connection_error = GrafanaAlertingSyncManager.check_for_connection_errors(organization) if connection_error: raise BadRequest(detail=connection_error) + for _integration in AlertReceiveChannel._config: + if _integration.slug == integration: + is_able_to_autoresolve = _integration.is_able_to_autoresolve + instance = AlertReceiveChannel.create( - **validated_data, organization=organization, author=self.context["request"].user + **validated_data, + organization=organization, + author=self.context["request"].user, + allow_source_based_resolving=is_able_to_autoresolve, ) return instance @@ -172,8 +179,11 @@ class AlertReceiveChannelSerializer(EagerLoadingMixin, serializers.ModelSerializ return obj.channel_filters.count() def get_connected_escalations_chains_count(self, obj) -> int: - return len( - set(ChannelFilter.objects.filter(alert_receive_channel=obj).values_list("escalation_chain", flat=True)) + return ( + ChannelFilter.objects.filter(alert_receive_channel=obj, escalation_chain__isnull=False) + .values("escalation_chain") + .distinct() + .count() ) @@ -192,8 +202,7 @@ class FastAlertReceiveChannelSerializer(serializers.ModelSerializer): fields = ["id", "integration", "verbal_name", "deleted"] def get_deleted(self, obj): - # Treat direct paging integrations as deleted, so integration settings are disabled on the frontend - return obj.deleted_at is not None or obj.integration == AlertReceiveChannel.INTEGRATION_DIRECT_PAGING + return obj.deleted_at is not None class FilterAlertReceiveChannelSerializer(serializers.ModelSerializer): diff --git a/engine/apps/api/serializers/paging.py b/engine/apps/api/serializers/paging.py index 7ebaf9a4..b764f7ef 100644 --- a/engine/apps/api/serializers/paging.py +++ b/engine/apps/api/serializers/paging.py @@ -56,17 +56,12 @@ class DirectPagingSerializer(serializers.Serializer): def validate(self, attrs): organization = self.context["organization"] - users = attrs["users"] - schedules = attrs["schedules"] escalation_chain_id = attrs["escalation_chain_id"] alert_group_id = attrs["alert_group_id"] title = attrs["title"] message = attrs["message"] - if len(users) == 0 and len(schedules) == 0 and not escalation_chain_id: - raise serializers.ValidationError("Provide users, schedules, or an escalation chain") - if alert_group_id and (title or message): raise serializers.ValidationError("alert_group_id and (title, message) are mutually exclusive") diff --git a/engine/apps/api/tests/test_alert_group.py b/engine/apps/api/tests/test_alert_group.py index 736ff69c..0bd09c83 100644 --- a/engine/apps/api/tests/test_alert_group.py +++ b/engine/apps/api/tests/test_alert_group.py @@ -8,7 +8,7 @@ from rest_framework import status from rest_framework.response import Response from rest_framework.test import APIClient -from apps.alerts.models import AlertGroup, AlertGroupLogRecord, AlertReceiveChannel +from apps.alerts.models import AlertGroup, AlertGroupLogRecord from apps.api.errors import AlertGroupAPIError from apps.api.permissions import LegacyAccessControlRole from apps.base.models import UserNotificationPolicyLogRecord @@ -1813,28 +1813,6 @@ def test_alert_group_paged_users( assert response.json()["paged_users"] == [user2.short()] -@pytest.mark.django_db -def test_direct_paging_integration_treated_as_deleted( - make_organization_and_user_with_plugin_token, - make_alert_receive_channel, - alert_group_internal_api_setup, - make_channel_filter, - make_alert_group, - make_user_auth_headers, -): - organization, user, token = make_organization_and_user_with_plugin_token() - alert_receive_channel = make_alert_receive_channel( - organization, integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING - ) - alert_group = make_alert_group(alert_receive_channel) - - client = APIClient() - url = reverse("api-internal:alertgroup-detail", kwargs={"pk": alert_group.public_primary_key}) - - response = client.get(url, format="json", **make_user_auth_headers(user, token)) - assert response.json()["alert_receive_channel"]["deleted"] is True - - @pytest.mark.django_db def test_alert_group_resolve_resolution_note( make_organization_and_user_with_plugin_token, diff --git a/engine/apps/api/tests/test_alert_receive_channel.py b/engine/apps/api/tests/test_alert_receive_channel.py index e2f42d39..277a8be7 100644 --- a/engine/apps/api/tests/test_alert_receive_channel.py +++ b/engine/apps/api/tests/test_alert_receive_channel.py @@ -675,23 +675,6 @@ def test_alert_receive_channel_counters_per_integration_permissions( assert response.status_code == expected_status -@pytest.mark.django_db -def test_get_alert_receive_channels_direct_paging_hidden_from_list( - make_organization_and_user_with_plugin_token, make_alert_receive_channel, make_user_auth_headers -): - organization, user, token = make_organization_and_user_with_plugin_token() - make_alert_receive_channel(user.organization, integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING) - - client = APIClient() - url = reverse("api-internal:alert_receive_channel-list") - response = client.get(url, format="json", **make_user_auth_headers(user, token)) - - # Check no direct paging integrations in the response - assert response.status_code == status.HTTP_200_OK - assert response.json()["count"] == 0 - assert len(response.json()["results"]) == 0 - - @pytest.mark.django_db def test_get_alert_receive_channels_direct_paging_present_for_filters( make_organization_and_user_with_plugin_token, make_alert_receive_channel, make_user_auth_headers diff --git a/engine/apps/api/views/alert_receive_channel.py b/engine/apps/api/views/alert_receive_channel.py index 9bcb3d8e..2e213ae7 100644 --- a/engine/apps/api/views/alert_receive_channel.py +++ b/engine/apps/api/views/alert_receive_channel.py @@ -18,6 +18,7 @@ from apps.api.serializers.alert_receive_channel import ( ) from apps.api.throttlers import DemoAlertThrottler from apps.auth_token.auth import PluginAuthentication +from apps.user_management.models.team import Team from common.api_helpers.exceptions import BadRequest from common.api_helpers.filters import ByTeamModelFieldFilterMixin, TeamModelMultipleChoiceFilter from common.api_helpers.mixins import ( @@ -104,10 +105,39 @@ class AlertReceiveChannelView( } def create(self, request, *args, **kwargs): - if request.data["integration"] is not None and ( - request.data["integration"] in AlertReceiveChannel.WEB_INTEGRATION_CHOICES - ): - return super().create(request, *args, **kwargs) + organization = request.auth.organization + user = request.user + team_lookup = {} + if "team" in request.data: + team_public_pk = request.data.get("team", None) + if team_public_pk is not None: + try: + team = user.available_teams.get(public_primary_key=team_public_pk) + team_lookup = {"team": team} + except Team.DoesNotExist: + return Response(data="invalid team", status=status.HTTP_400_BAD_REQUEST) + else: + team_lookup = {"team__isnull": True} + + if request.data["integration"] is not None: + if request.data["integration"] in AlertReceiveChannel.WEB_INTEGRATION_CHOICES: + # Don't allow multiple Direct Paging integrations + if request.data["integration"] == AlertReceiveChannel.INTEGRATION_DIRECT_PAGING: + try: + AlertReceiveChannel.objects.get( + organization=organization, + integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING, + deleted_at=None, + **team_lookup, + ) + return Response( + data="Direct paging integration already exists for this team", + status=status.HTTP_400_BAD_REQUEST, + ) + except AlertReceiveChannel.DoesNotExist: + pass + return super().create(request, *args, **kwargs) + return Response(data="invalid integration", status=status.HTTP_400_BAD_REQUEST) def perform_update(self, serializer): @@ -147,10 +177,6 @@ class AlertReceiveChannelView( if not ignore_filtering_by_available_teams: queryset = queryset.filter(*self.available_teams_lookup_args).distinct() - # Hide direct paging integrations from the list view, but not from the filters - if not is_filters_request: - queryset = queryset.exclude(integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING) - return queryset @action(detail=True, methods=["post"], throttle_classes=[DemoAlertThrottler]) diff --git a/engine/apps/public_api/tests/test_integrations.py b/engine/apps/public_api/tests/test_integrations.py index 3a4a2db3..6b75c897 100644 --- a/engine/apps/public_api/tests/test_integrations.py +++ b/engine/apps/public_api/tests/test_integrations.py @@ -726,25 +726,6 @@ def test_set_default_messaging_backend_template( assert response.data == expected_response -@pytest.mark.django_db -def test_get_list_integrations_direct_paging_hidden( - make_organization_and_user_with_token, - make_alert_receive_channel, - make_channel_filter, - make_integration_heartbeat, -): - organization, user, token = make_organization_and_user_with_token() - make_alert_receive_channel(organization, integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING) - - client = APIClient() - url = reverse("api-public:integrations-list") - response = client.get(url, format="json", HTTP_AUTHORIZATION=f"{token}") - - # Check no direct paging integrations in the response - assert response.status_code == status.HTTP_200_OK - assert response.json()["results"] == [] - - @pytest.mark.django_db def test_get_list_integrations_link_and_inbound_email( make_organization_and_user_with_token, diff --git a/engine/apps/public_api/views/integrations.py b/engine/apps/public_api/views/integrations.py index eac090eb..5bcb92fb 100644 --- a/engine/apps/public_api/views/integrations.py +++ b/engine/apps/public_api/views/integrations.py @@ -48,9 +48,6 @@ class IntegrationView( queryset = self.serializer_class.setup_eager_loading(queryset) queryset = queryset.annotate(alert_groups_count_annotated=Count("alert_groups", distinct=True)) - # Hide direct paging integrations - queryset = queryset.exclude(integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING) - return queryset def get_object(self): diff --git a/engine/config_integrations/direct_paging.py b/engine/config_integrations/direct_paging.py index 40d6e791..755c1cd7 100644 --- a/engine/config_integrations/direct_paging.py +++ b/engine/config_integrations/direct_paging.py @@ -4,7 +4,7 @@ title = "Direct paging" slug = "direct_paging" short_description = None description = None -is_displayed_on_web = False +is_displayed_on_web = True is_featured = False is_able_to_autoresolve = False is_demo_alert_enabled = False diff --git a/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.config.ts b/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.config.ts index 5ce80b2a..e4a5f0cd 100644 --- a/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.config.ts +++ b/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.config.ts @@ -15,19 +15,5 @@ export const manualAlertFormConfig: { name: string; fields: FormItem[] } = { label: 'Description', validation: { required: true }, }, - { - name: 'team', - label: 'Assign to team', - description: - 'Assigning to the teams allows you to filter Alert Groups and configure their visibility. Go to OnCall -> Settings -> Team and Access Settings for more details', - type: FormItemType.GSelect, - extra: { - modelName: 'grafanaTeamStore', - displayField: 'name', - valueField: 'id', - showSearch: true, - allowClear: true, - }, - }, ], }; diff --git a/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.module.css b/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.module.css index 2b167d9c..a421dabc 100644 --- a/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.module.css +++ b/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.module.css @@ -6,3 +6,23 @@ background: var(--secondary-background); width: 100%; } + +.responders-list { + list-style-type: none; + margin-bottom: 20px; + width: 100%; + background: var(--background-secondary); + + & > li .hover-button { + display: none; + } + + & > li:hover .hover-button { + display: inline-flex; + } + + & > li { + padding: 10px 12px; + width: 100%; + } +} diff --git a/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.tsx b/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.tsx index 274b84dd..255f3ea8 100644 --- a/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.tsx +++ b/grafana-plugin/src/components/ManualAlertGroup/ManualAlertGroup.tsx @@ -1,15 +1,35 @@ import React, { FC, useCallback, useState } from 'react'; -import { Button, Drawer, HorizontalGroup, Icon, VerticalGroup } from '@grafana/ui'; +import { + Alert, + Button, + Drawer, + Field, + HorizontalGroup, + Icon, + IconButton, + IconName, + Label, + LoadingPlaceholder, + Tooltip, + VerticalGroup, +} from '@grafana/ui'; import cn from 'classnames/bind'; -import Block from 'components/GBlock/Block'; import GForm from 'components/GForm/GForm'; +import PluginLink from 'components/PluginLink/PluginLink'; import Text from 'components/Text/Text'; import EscalationVariants from 'containers/EscalationVariants/EscalationVariants'; import { prepareForUpdate } from 'containers/EscalationVariants/EscalationVariants.helpers'; -import { Alert } from 'models/alertgroup/alertgroup.types'; +import GrafanaTeamSelect from 'containers/GrafanaTeamSelect/GrafanaTeamSelect'; +import TeamName from 'containers/TeamName/TeamName'; +import { AlertReceiveChannelStore } from 'models/alert_receive_channel/alert_receive_channel'; +import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types'; +import { Alert as AlertType } from 'models/alertgroup/alertgroup.types'; +import { GrafanaTeam } from 'models/grafana_team/grafana_team.types'; +import IntegrationHelper from 'pages/integration/Integration.helper'; import { useStore } from 'state/useStore'; +import { openWarningNotification } from 'utils'; import { manualAlertFormConfig } from './ManualAlertGroup.config'; @@ -17,7 +37,8 @@ import styles from './ManualAlertGroup.module.css'; interface ManualAlertGroupProps { onHide: () => void; - onCreate: (id: Alert['pk']) => void; + onCreate: (id: AlertType['pk']) => void; + alertReceiveChannelStore: AlertReceiveChannelStore; } const cx = cn.bind(styles); @@ -26,13 +47,24 @@ const ManualAlertGroup: FC = (props) => { const store = useStore(); const [userResponders, setUserResponders] = useState([]); const [scheduleResponders, setScheduleResponders] = useState([]); - const { onHide, onCreate } = props; - const data = { team: store.userStore.currentUser?.current_team }; + const { onHide, onCreate, alertReceiveChannelStore } = props; + + const [selectedTeamId, setSelectedTeam] = useState(); + const [selectedTeamDirectPaging, setSelectedTeamDirectPaging] = useState(); + const [directPagingLoading, setdirectPagingLoading] = useState(); + + const [chatOpsAvailableChannels, setChatopsAvailableChannels] = useState(); + + const data = {}; const handleFormSubmit = async (data) => { + if (selectedTeamId === undefined) { + openWarningNotification('Select team first'); + return; + } store.directPagingStore - .createManualAlertRule(prepareForUpdate(userResponders, scheduleResponders, data)) - .then(({ alert_group_id: id }: { alert_group_id: Alert['pk'] }) => { + .createManualAlertRule(prepareForUpdate(userResponders, scheduleResponders, { team: selectedTeamId, ...data })) + .then(({ alert_group_id: id }: { alert_group_id: AlertType['pk'] }) => { onCreate(id); }) .finally(() => { @@ -40,48 +72,174 @@ const ManualAlertGroup: FC = (props) => { }); }; + const onUpdateSelectedTeam = async (selectedTeamId: GrafanaTeam['id']) => { + setdirectPagingLoading(true); + setSelectedTeamDirectPaging(null); + setSelectedTeam(selectedTeamId); + await alertReceiveChannelStore.updateItems({ team: selectedTeamId, integration: 'direct_paging' }); + const directPagingAlertReceiveChannel = + alertReceiveChannelStore.getSearchResult() && alertReceiveChannelStore.getSearchResult()[0]; + if (directPagingAlertReceiveChannel) { + setSelectedTeamDirectPaging(directPagingAlertReceiveChannel); + await alertReceiveChannelStore.updateChannelFilters(directPagingAlertReceiveChannel.id); + await store.slackChannelStore.updateItems(); + + // The code below is used to get the unique available chotops channels for all routes in integraion + // This is the workaround for IntegrationHelper.getChatOpsChannels, it should be moved to the helper + const filterIds = alertReceiveChannelStore.channelFilterIds[directPagingAlertReceiveChannel.id]; + let availableChannels = []; + let channelKeys = new Set(); + filterIds.map((channelFilterId) => { + IntegrationHelper.getChatOpsChannels(alertReceiveChannelStore.channelFilters[channelFilterId], store) + .filter((channel) => channel) + .map((channel) => { + if (!channelKeys.has(channel.name + channel.icon)) { + availableChannels.push(channel); + channelKeys.add(channel.name + channel.icon); + } + }); + }); + setChatopsAvailableChannels(Array.from(availableChannels)); + } + setdirectPagingLoading(false); + }; + const onUpdateEscalationVariants = useCallback( (value) => { setUserResponders(value.userResponders); - setScheduleResponders(value.scheduleResponders); }, [userResponders, scheduleResponders] ); + const DirectPagingIntegrationVariants = ({ selectedTeamId, selectedTeamDirectPaging, chatOpsAvailableChannels }) => { + const escalationChainsExist = selectedTeamDirectPaging?.connected_escalations_chains_count === 0; + + return ( + + {selectedTeamId && + (directPagingLoading ? ( + + ) : selectedTeamDirectPaging ? ( + + +
    +
  • + + + {escalationChainsExist && ( + + + + )} + {selectedTeamDirectPaging.verbal_name} + + + Team: + + + + {chatOpsAvailableChannels && ( + <> + ChatOps:{' '} + {chatOpsAvailableChannels.map( + (chatOpsChannel: { name: string; icon: IconName }, chatOpsIndex) => ( +
    + {chatOpsChannel.icon && } + {chatOpsChannel.name || ''} +
    + ) + )} + + )} +
    + + + + + +
    +
  • +
+ + {(escalationChainsExist || !chatOpsAvailableChannels) && ( + + + {escalationChainsExist && ( + + Integration doesn't have connected escalation policies. Consider adding responders manually by + user or by email + + )} + {!chatOpsAvailableChannels && ( + Integration doesn't have connected ChatOps channels in messengers. + )} + + + )} +
+ ) : ( + + + + Empty integration for this team will be created automatically. Consider selecting responders by + schedule or user below + + + + ))} +
+ ); + }; + + const submitButtonDisabled = !( + selectedTeamId && + (selectedTeamDirectPaging || userResponders.length || scheduleResponders.length) + ); + return ( - <> - - - - - {store.teamStore.currentTeam.slack_team_identity && ( - - {' '} - - The alert group will also be posted to #{store.teamStore.currentTeam?.slack_channel?.display_name} Slack - channel. - - - )} - - - - - - - + + + + + + + + + + + + + + ); }; diff --git a/grafana-plugin/src/containers/CreateAlertReceiveChannelContainer/CreateAlertReceiveChannelContainer.tsx b/grafana-plugin/src/containers/CreateAlertReceiveChannelContainer/CreateAlertReceiveChannelContainer.tsx index c4177cad..98ee9d81 100644 --- a/grafana-plugin/src/containers/CreateAlertReceiveChannelContainer/CreateAlertReceiveChannelContainer.tsx +++ b/grafana-plugin/src/containers/CreateAlertReceiveChannelContainer/CreateAlertReceiveChannelContainer.tsx @@ -68,7 +68,7 @@ const CreateAlertReceiveChannelContainer = observer((props: CreateAlertReceiveCh label="Assign to team" description="OnCall teams allow you to organize integrations so you can filter and set up access. " > - +
diff --git a/grafana-plugin/src/containers/EscalationVariants/EscalationVariants.tsx b/grafana-plugin/src/containers/EscalationVariants/EscalationVariants.tsx index 07ca0b8e..c03b19b8 100644 --- a/grafana-plugin/src/containers/EscalationVariants/EscalationVariants.tsx +++ b/grafana-plugin/src/containers/EscalationVariants/EscalationVariants.tsx @@ -26,6 +26,7 @@ export interface EscalationVariantsProps { variant?: 'secondary' | 'primary'; hideSelected?: boolean; disabled?: boolean; + withLabels?: boolean; } const EscalationVariants = observer( @@ -35,6 +36,7 @@ const EscalationVariants = observer( variant = 'primary', hideSelected = false, disabled, + withLabels = false, }: EscalationVariantsProps) => { const [showEscalationVariants, setShowEscalationVariants] = useState(false); @@ -103,7 +105,7 @@ const EscalationVariants = observer(
{!hideSelected && Boolean(value.userResponders.length || value.scheduleResponders.length) && ( <> - +
    {value.userResponders.map((responder, index) => ( )}
    + {withLabels && }
    @@ -230,11 +233,11 @@ const UserResponder = ({ important, data, onImportantChange, handleDelete }) => }} onChange={onImportantChange} /> - notification chain + notification policies ) : ( - + diff --git a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx index fff5cc91..4f7bebe0 100644 --- a/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx +++ b/grafana-plugin/src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx @@ -18,15 +18,16 @@ interface GrafanaTeamSelectProps { onSelect: (id: GrafanaTeam['id']) => void; onHide?: () => void; withoutModal?: boolean; + defaultValue?: GrafanaTeam['id']; } -const GrafanaTeamSelect = observer(({ onSelect, onHide, withoutModal }: GrafanaTeamSelectProps) => { +const GrafanaTeamSelect = observer(({ onSelect, onHide, withoutModal, defaultValue }: GrafanaTeamSelectProps) => { const store = useStore(); const { userStore, grafanaTeamStore } = store; const user = userStore.currentUser; - const [selectedTeam, setSelectedTeam] = useState(user.current_team); + const [selectedTeam, setSelectedTeam] = useState(defaultValue); const grafanaTeams = grafanaTeamStore.getSearchResult(); diff --git a/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx b/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx index d550d6c9..543b4c79 100644 --- a/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx +++ b/grafana-plugin/src/containers/IntegrationForm/IntegrationForm.tsx @@ -62,8 +62,12 @@ const IntegrationForm = observer((props: IntegrationFormProps) => { .then((response) => { history.push(`${PLUGIN_ROOT}/integrations/${response.id}`); }) - .catch(() => { - openErrorNotification('Something went wrong, please try again later.'); + .catch((err) => { + if (err.response?.data?.length > 0) { + openErrorNotification(err.response.data); + } else { + openErrorNotification('Something went wrong, please try again later.'); + } }) : alertReceiveChannelStore.update(id, data) ).then(() => { diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index b4a91ed2..f26f8bb4 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -111,7 +111,7 @@ class Incidents extends React.Component const { showAddAlertGroupForm } = this.state; const { store, - store: { alertGroupStore }, + store: { alertGroupStore, alertReceiveChannelStore }, } = this.props; if (!alertGroupStore.irmPlan && !store.isOpenSource()) { @@ -126,7 +126,7 @@ class Incidents extends React.Component Alert Groups @@ -142,6 +142,7 @@ class Incidents extends React.Component onCreate={(id: Alert['pk']) => { history.push(`${PLUGIN_ROOT}/alert-groups/${id}`); }} + alertReceiveChannelStore={alertReceiveChannelStore} /> )} diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx index a8f178a6..550dce15 100644 --- a/grafana-plugin/src/pages/integration/Integration.tsx +++ b/grafana-plugin/src/pages/integration/Integration.tsx @@ -326,7 +326,7 @@ class Integration extends React.Component { Autoresolve: - {IntegrationHelper.truncateLine(templates['resolve_condition_template'] || '')} + {IntegrationHelper.truncateLine(templates['resolve_condition_template'] || 'disabled')}
@@ -964,6 +964,17 @@ const HowToConnectComponent: React.FC<{ id: AlertReceiveChannel['id'] }> = ({ id const item = alertReceiveChannelStore.items[id]; const url = item?.integration_url || item?.inbound_email; + const howToConnectTagName = (integration: string) => { + switch (integration) { + case 'direct_paging': + return 'Manual'; + case 'email': + return 'Inbound Email'; + default: + return 'HTTP Endpoint'; + } + }; + return ( = ({ id className={cx('how-to-connect__tag')} > - {item?.inbound_email ? 'Inbound Email' : 'HTTP Endpoint'} + {howToConnectTagName(item?.integration)} - {url && ( - + {item?.integration === 'direct_paging' ? ( + <> + Alert Groups raised manually via Web or ChatOps + + + + How it works + + + + + + ) : ( + <> + {url && ( + + )} + + + + How to connect + + + + + )} - - - - How to connect - - - - } content={hasAlerts ? null : renderContent()} @@ -1006,12 +1038,20 @@ const HowToConnectComponent: React.FC<{ id: AlertReceiveChannel['id'] }> = ({ id ); function renderContent() { + const callToAction = () => { + if (item?.integration === 'direct_paging') { + return try to raise a demo alert group via Web or Chatops; + } else { + return item.demo_alert_enabled && ; try to send a demo alert; + } + }; + return ( {!hasAlerts && ( - No alerts yet; try to send a demo alert + No alerts yet; {callToAction()} )}