From cfcfa0336ee70e71eb494296a7bf21a9e34bcbc2 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Thu, 23 Mar 2023 09:52:59 +0800 Subject: [PATCH] Add filters to outgoing webhooks 2 (#1598) # What this PR does ## Which issue(s) this PR fixes ## Checklist - [ ] Tests updated - [ ] Documentation added - [ ] `CHANGELOG.md` updated --- engine/apps/api/views/webhooks.py | 36 ++++++++++++++++- .../OutgoingWebhook2Form.config.ts | 14 +++++++ .../outgoing_webhook_2/outgoing_webhook_2.ts | 11 +++-- .../outgoing_webhooks_2/OutgoingWebhooks2.tsx | 40 +++++++++++++++++++ 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/engine/apps/api/views/webhooks.py b/engine/apps/api/views/webhooks.py index 1a013926..de492a3d 100644 --- a/engine/apps/api/views/webhooks.py +++ b/engine/apps/api/views/webhooks.py @@ -1,22 +1,32 @@ from django.apps import apps from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from django_filters import rest_framework as filters +from rest_framework.decorators import action from rest_framework.exceptions import NotFound +from rest_framework.filters import SearchFilter from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from apps.api.permissions import RBACPermission from apps.api.serializers.webhook import WebhookSerializer from apps.auth_token.auth import PluginAuthentication from apps.webhooks.models import Webhook +from common.api_helpers.filters import ByTeamModelFieldFilterMixin, ModelFieldFilterMixin, TeamModelMultipleChoiceFilter from common.api_helpers.mixins import PublicPrimaryKeyMixin, TeamFilteringMixin +class WebhooksFilter(ByTeamModelFieldFilterMixin, ModelFieldFilterMixin, filters.FilterSet): + team = TeamModelMultipleChoiceFilter() + + class WebhooksView(TeamFilteringMixin, PublicPrimaryKeyMixin, ModelViewSet): authentication_classes = (PluginAuthentication,) permission_classes = (IsAuthenticated, RBACPermission) rbac_permissions = { "metadata": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ], + "filters": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ], "list": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ], "retrieve": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ], "create": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE], @@ -28,6 +38,10 @@ class WebhooksView(TeamFilteringMixin, PublicPrimaryKeyMixin, ModelViewSet): model = Webhook serializer_class = WebhookSerializer + filter_backends = [SearchFilter, filters.DjangoFilterBackend] + search_fields = ["public_primary_key", "name"] + filterset_class = WebhooksFilter + def get_queryset(self, ignore_filtering_by_available_teams=False): queryset = Webhook.objects.filter( organization=self.request.auth.organization, @@ -48,9 +62,8 @@ class WebhooksView(TeamFilteringMixin, PublicPrimaryKeyMixin, ModelViewSet): # use this method to get the object from the whole organization instead of the current team pk = self.kwargs["pk"] organization = self.request.auth.organization - try: - obj = organization.webhooks.get(public_primary_key=pk) + obj = organization.webhooks.filter(*self.available_teams_lookup_args).get(public_primary_key=pk) except ObjectDoesNotExist: raise NotFound @@ -83,3 +96,22 @@ class WebhooksView(TeamFilteringMixin, PublicPrimaryKeyMixin, ModelViewSet): )[0] if self.request.auth.organization.pk not in enabled_webhooks_2_orgs.json_value["org_ids"]: raise PermissionDenied("Webhooks 2 not enabled for organization. Permission denied.") + + @action(methods=["get"], detail=False) + def filters(self, request): + filter_name = request.query_params.get("search", None) + api_root = "/api/internal/v1/" + + filter_options = [ + { + "name": "team", + "type": "team_select", + "href": api_root + "teams/", + "global": True, + }, + ] + + if filter_name is not None: + filter_options = list(filter(lambda f: filter_name in f["name"], filter_options)) + + return Response(filter_options) diff --git a/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.ts b/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.ts index e071333e..f40881a7 100644 --- a/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.ts +++ b/grafana-plugin/src/containers/OutgoingWebhook2Form/OutgoingWebhook2Form.config.ts @@ -8,6 +8,20 @@ export const form: { name: string; fields: FormItem[] } = { type: FormItemType.Input, validation: { required: true }, }, + { + name: 'team', + label: 'Assign to team', + description: + 'Assigning to the teams allows you to filter Outgoing Webhooks 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, + }, + }, { name: 'trigger_type', label: 'Trigger type', diff --git a/grafana-plugin/src/models/outgoing_webhook_2/outgoing_webhook_2.ts b/grafana-plugin/src/models/outgoing_webhook_2/outgoing_webhook_2.ts index e1538963..893f5f84 100644 --- a/grafana-plugin/src/models/outgoing_webhook_2/outgoing_webhook_2.ts +++ b/grafana-plugin/src/models/outgoing_webhook_2/outgoing_webhook_2.ts @@ -1,6 +1,7 @@ import { action, observable } from 'mobx'; import BaseStore from 'models/base_store'; +import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types'; import { makeRequest } from 'network'; import { RootStore } from 'state'; @@ -52,9 +53,11 @@ export class OutgoingWebhook2Store extends BaseStore { } @action - async updateItems(query = '') { + async updateItems(query: any = '') { + const params = typeof query === 'string' ? { search: query } : query; + const results = await makeRequest(`${this.path}`, { - params: { search: query }, + params, }); this.items = { @@ -68,9 +71,11 @@ export class OutgoingWebhook2Store extends BaseStore { ), }; + const key = typeof query === 'string' ? query : ''; + this.searchResult = { ...this.searchResult, - [query]: results.map((item: OutgoingWebhook2) => item.id), + [key]: results.map((item: OutgoingWebhook) => item.id), }; } diff --git a/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx b/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx index 9adbb3ac..59f656e3 100644 --- a/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx +++ b/grafana-plugin/src/pages/outgoing_webhooks_2/OutgoingWebhooks2.tsx @@ -17,8 +17,12 @@ import Text from 'components/Text/Text'; import WithConfirm from 'components/WithConfirm/WithConfirm'; import OutgoingWebhook2Form from 'containers/OutgoingWebhook2Form/OutgoingWebhook2Form'; import OutgoingWebhook2Status from 'containers/OutgoingWebhook2Status/OutgoingWebhook2Status'; +import RemoteFilters from 'containers/RemoteFilters/RemoteFilters'; +import TeamName from 'containers/TeamName/TeamName'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { ActionDTO } from 'models/action'; +import { FiltersValues } from 'models/filters/filters.types'; +import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types'; import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types'; import { makeRequest } from 'network'; import { PageProps, WithStoreProps } from 'state/types'; @@ -122,6 +126,11 @@ class OutgoingWebhooks2 extends React.Component this.renderTeam(item, store.grafanaTeamStore.items), + }, { width: '20%', key: 'action', @@ -139,6 +148,7 @@ class OutgoingWebhooks2 extends React.Component ( <>
+ {this.renderOutgoingWebhooksFilters()} ( @@ -191,6 +201,36 @@ class OutgoingWebhooks2 extends React.Component + +
+ ); + } + + handleFiltersChange = (filters: FiltersValues, isOnMount) => { + const { store } = this.props; + + const { outgoingWebhook2Store } = store; + + outgoingWebhook2Store.updateItems(filters).then(() => { + if (isOnMount) { + this.parseQueryParams(); + } + }); + }; + + renderTeam(record: OutgoingWebhook, teams: any) { + return ; + } + renderActionButtons = (record: ActionDTO) => { return (