From 965ea969f90cbc57c2fdd939fe07fec28d6e4f9b Mon Sep 17 00:00:00 2001 From: Tiago Paiva Date: Mon, 17 Feb 2025 12:11:54 -0300 Subject: [PATCH] feat: migrates webhooks to RTK (#5087) --- frontend/common/providers/withWebhooks.js | 70 ------------ frontend/common/services/useWebhooks.ts | 102 ++++++++++++++++++ frontend/common/types/requests.ts | 20 ++++ frontend/common/types/responses.ts | 11 ++ .../pages/EnvironmentSettingsPage.js | 68 ++++++++++-- 5 files changed, 193 insertions(+), 78 deletions(-) delete mode 100644 frontend/common/providers/withWebhooks.js create mode 100644 frontend/common/services/useWebhooks.ts diff --git a/frontend/common/providers/withWebhooks.js b/frontend/common/providers/withWebhooks.js deleted file mode 100644 index 7ec473328fab..000000000000 --- a/frontend/common/providers/withWebhooks.js +++ /dev/null @@ -1,70 +0,0 @@ -import data from 'common/data/base/_data' - -export default (WrappedComponent) => { - class HOC extends React.Component { - static displayName = 'withWebhooks' - - getWebhooks = () => - data - .get( - `${Project.api}environments/${this.props.match.params.environmentId}/webhooks/`, - ) - .then((webhooks) => { - this.setState({ - webhooks, - webhooksLoading: false, - }) - }) - - deleteWebhook = (webhook) => { - this.setState({ webhooksLoading: true }) - return data - .delete( - `${Project.api}environments/${this.props.match.params.environmentId}/webhooks/${webhook.id}/`, - ) - .then(() => { - this.getWebhooks() - }) - } - - saveWebhook = (webhook) => { - this.setState({ webhooksLoading: true }) - return data - .put( - `${Project.api}environments/${this.props.match.params.environmentId}/webhooks/${webhook.id}/`, - webhook, - ) - .then(() => { - this.getWebhooks() - }) - } - - createWebhook = (webhook) => { - this.setState({ webhooksLoading: true }) - return data - .post( - `${Project.api}environments/${this.props.match.params.environmentId}/webhooks/`, - webhook, - ) - .then(() => { - this.getWebhooks() - }) - } - - render() { - return ( - - ) - } - } - - return HOC -} diff --git a/frontend/common/services/useWebhooks.ts b/frontend/common/services/useWebhooks.ts new file mode 100644 index 000000000000..4ef5cd69572a --- /dev/null +++ b/frontend/common/services/useWebhooks.ts @@ -0,0 +1,102 @@ +import { service } from 'common/service' +import { Res } from 'common/types/responses' +import { Req } from 'common/types/requests' + +export const webhookService = service + .enhanceEndpoints({ addTagTypes: ['Webhooks'] }) + .injectEndpoints({ + endpoints: (builder) => ({ + createWebhook: builder.mutation({ + invalidatesTags: [{ id: 'LIST', type: 'Webhooks' }], + query: ({ environmentId, ...rest }) => ({ + body: { + ...rest, + }, + method: 'POST', + url: `environments/${environmentId}/webhooks/`, + }), + }), + + deleteWebhook: builder.mutation({ + invalidatesTags: [{ id: 'LIST', type: 'Webhooks' }], + query: (query) => ({ + method: 'DELETE', + url: `environments/${query.environmentId}/webhooks/${query.id}/`, + }), + }), + + getWebhooks: builder.query({ + providesTags: [{ id: 'LIST', type: 'Webhooks' }], + query: (query) => ({ + url: `environments/${query.environmentId}/webhooks/`, + }), + }), + + updateWebhook: builder.mutation({ + invalidatesTags: [{ id: 'LIST', type: 'Webhooks' }], + query: ({ environmentId, ...rest }) => ({ + body: { + ...rest, + }, + method: 'PUT', + url: `environments/${environmentId}/webhooks/${rest.id}/`, + }), + }), + }), + }) + +export async function createWebhook( + store: any, + data: Req['createWebhook'], + options?: Parameters< + typeof webhookService.endpoints.createWebhook.initiate + >[1], +) { + return store.dispatch( + webhookService.endpoints.createWebhook.initiate(data, options), + ) +} +export async function getWebhooks( + store: any, + data: Req['getWebhooks'], + options?: Parameters[1], +) { + return store.dispatch( + webhookService.endpoints.getWebhooks.initiate(data, options), + ) +} +export async function updateWebhook( + store: any, + data: Req['updateWebhook'], + options?: Parameters< + typeof webhookService.endpoints.updateWebhook.initiate + >[1], +) { + return store.dispatch( + webhookService.endpoints.deleteWebhook.initiate(data, options), + ) +} +export async function deleteWebhook( + store: any, + data: Req['deleteWebhook'], + options?: Parameters< + typeof webhookService.endpoints.deleteWebhook.initiate + >[1], +) { + return store.dispatch( + webhookService.endpoints.deleteWebhook.initiate(data, options), + ) +} + +export const { + useCreateWebhookMutation, + useDeleteWebhookMutation, + useGetWebhooksQuery, + useUpdateWebhookMutation, +} = webhookService + +/* Usage examples: +const { data, isLoading } = useGetWebhooksQuery({ environmentId: 1 }) //get hook +const [createWebhook, { isLoading, data, isSuccess }] = useCreateWebhookMutation() //create hook +webhookService.endpoints.getWebhooks.select({id: 2})(store.getState()) //access data from any function +*/ diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index ab22c7ac00ee..2207a01f3d2c 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -91,6 +91,26 @@ export type Req = { | 'previous_billing_period' | '90_day_period' } + getWebhooks: { + environmentId: string + } + createWebhook: { + environmentId: string + enabled: boolean + secret: string + url: string + } + updateWebhook: { + id: number + environmentId: string + enabled: boolean + secret: string + url: string + } + deleteWebhook: { + id: number + environmentId: string + } deleteIdentity: { id: string environmentId: string diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index ea8c3d03c246..a48f6a98ecb4 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -658,6 +658,15 @@ export type HealthProvider = { webhook_url: number } +export type Webhook = { + id: number + url: string + secret: string + enabled: boolean + created_at: string + updated_at: string +} + export type Res = { segments: PagedResponse segment: Segment @@ -667,6 +676,8 @@ export type Res = { projects: ProjectSummary[] project: Project environments: PagedResponse + webhook: Webhook + webhooks: Webhook[] organisationUsage: { totals: { flags: number diff --git a/frontend/web/components/pages/EnvironmentSettingsPage.js b/frontend/web/components/pages/EnvironmentSettingsPage.js index b7f2e2431c0c..d931a5c4f454 100644 --- a/frontend/web/components/pages/EnvironmentSettingsPage.js +++ b/frontend/web/components/pages/EnvironmentSettingsPage.js @@ -2,7 +2,6 @@ import React, { Component } from 'react' import ConfirmRemoveEnvironment from 'components/modals/ConfirmRemoveEnvironment' import ProjectStore from 'common/stores/project-store' import ConfigProvider from 'common/providers/ConfigProvider' -import withWebhooks from 'common/providers/withWebhooks' import CreateWebhookModal from 'components/modals/CreateWebhook' import ConfirmRemoveWebhook from 'components/modals/ConfirmRemoveWebhook' import ConfirmToggleEnvFeature from 'components/modals/ConfirmToggleEnvFeature' @@ -26,6 +25,12 @@ import { getSupportedContentType } from 'common/services/useSupportedContentType import EnvironmentVersioningListener from 'components/EnvironmentVersioningListener' import Format from 'common/utils/format' import Setting from 'components/Setting' +import { + createWebhook, + deleteWebhook, + getWebhooks, + updateWebhook, +} from 'common/services/useWebhooks' const showDisabledFlagOptions = [ { label: 'Inherit from Project', value: null }, @@ -47,6 +52,8 @@ const EnvironmentSettingsPage = class extends Component { environmentContentType: {}, roles: [], showMetadataList: false, + webhooks: [], + webhooksLoading: true, } AppActions.getProject(this.props.match.params.projectId) } @@ -54,7 +61,22 @@ const EnvironmentSettingsPage = class extends Component { componentDidMount = () => { API.trackPage(Constants.pages.ENVIRONMENT_SETTINGS) this.getEnvironment() - this.props.getWebhooks() + this.fetchWebhooks(this.props.match.params.environmentId) + } + + fetchWebhooks = (environmentId) => { + if (!environmentId) return + + this.setState({ webhooksLoading: true }) + getWebhooks(getStore(), { environmentId }) + .then((res) => { + this.setState({ + webhooks: res.data, + }) + }) + .finally(() => { + this.setState({ webhooksLoading: false }) + }) } getEnvironment = () => { @@ -97,7 +119,7 @@ const EnvironmentSettingsPage = class extends Component { this.setState({ environmentContentType: environmentContentType }) }) } - this.props.getWebhooks() + this.fetchWebhooks(this.props.match.params.environmentId) } onSave = () => { @@ -200,7 +222,16 @@ const EnvironmentSettingsPage = class extends Component { router={this.context.router} environmentId={this.props.match.params.environmentId} projectId={this.props.match.params.projectId} - save={this.props.createWebhook} + save={(webhook) => + createWebhook(getStore(), { + environmentId: this.props.match.params.environmentId, + ...webhook, + }).then((res) => { + this.setState({ + webhooks: this.state.webhooks.concat(res.data), + }) + }) + } />, 'side-modal', ) @@ -215,7 +246,18 @@ const EnvironmentSettingsPage = class extends Component { isEdit environmentId={this.props.match.params.environmentId} projectId={this.props.match.params.projectId} - save={this.props.saveWebhook} + save={(webhook) => + updateWebhook(getStore(), { + environmentId: this.props.match.params.environmentId, + ...webhook, + }).then(() => { + this.setState({ + webhooks: this.state.webhooks.map((w) => + w.id === webhook.id ? webhook : w, + ), + }) + }) + } />, 'side-modal', ) @@ -228,7 +270,16 @@ const EnvironmentSettingsPage = class extends Component { environmentId={this.props.match.params.environmentId} projectId={this.props.match.params.projectId} url={webhook.url} - cb={() => this.props.deleteWebhook(webhook)} + cb={() => + deleteWebhook(getStore(), { + environmentId: this.props.match.params.environmentId, + id: webhook.id, + }).then(() => { + this.setState({ + webhooks: this.state.webhooks.filter((w) => w.id !== webhook.id), + }) + }) + } />, 'p-0', ) @@ -255,7 +306,6 @@ const EnvironmentSettingsPage = class extends Component { render() { const { - props: { webhooks, webhooksLoading }, state: { allow_client_traits, hide_sensitive_data, @@ -263,6 +313,8 @@ const EnvironmentSettingsPage = class extends Component { use_identity_composite_key_for_hashing, use_identity_overrides_in_local_eval, use_v2_feature_versioning, + webhooks, + webhooksLoading, }, } = this const has4EyesPermission = Utils.getPlansPermission('4_EYES') @@ -917,4 +969,4 @@ const EnvironmentSettingsPage = class extends Component { EnvironmentSettingsPage.propTypes = {} -module.exports = ConfigProvider(withWebhooks(EnvironmentSettingsPage)) +module.exports = ConfigProvider(EnvironmentSettingsPage)