diff --git a/frontend/common/services/useHealthEvents.ts b/frontend/common/services/useHealthEvents.ts new file mode 100644 index 000000000000..6cf198a75388 --- /dev/null +++ b/frontend/common/services/useHealthEvents.ts @@ -0,0 +1,44 @@ +import { Res } from 'common/types/responses' +import { Req } from 'common/types/requests' +import { service } from 'common/service' + +export const healthService = service + .enhanceEndpoints({ addTagTypes: ['HealthEvents'] }) + .injectEndpoints({ + endpoints: (builder) => ({ + getHealthEvents: builder.query< + Res['healthEvents'], + Req['getHealthEvents'] + >({ + providesTags: [{ id: 'LIST', type: 'HealthEvents' }], + query: (query: Req['getHealthEvents']) => ({ + url: `projects/${query.projectId}/feature-health/events/`, + }), + }), + // END OF ENDPOINTS + }), + }) + +export async function getHealthEvents( + store: any, + data: Req['getHealthEvents'], + options?: Parameters< + typeof healthService.endpoints.getHealthEvents.initiate + >[1], +) { + return store.dispatch( + healthService.endpoints.getHealthEvents.initiate(data, options), + ) +} + +// END OF FUNCTION_EXPORTS + +export const { + useGetHealthEventsQuery, + // END OF EXPORTS +} = healthService + +/* Usage examples: +const { data, isLoading } = useGetHealthEventsQuery({ id: 2 }, {}) //get hook +healthService.endpoints.getHealthEvents.select({id: 2})(store.getState()) //access data from any function +*/ diff --git a/frontend/common/services/useHealthProvider.ts b/frontend/common/services/useHealthProvider.ts new file mode 100644 index 000000000000..e0df2f34f240 --- /dev/null +++ b/frontend/common/services/useHealthProvider.ts @@ -0,0 +1,95 @@ +import { Res } from 'common/types/responses' +import { Req } from 'common/types/requests' +import { service } from 'common/service' + +export const healthProviderService = service + .enhanceEndpoints({ addTagTypes: ['HealthProviders'] }) + .injectEndpoints({ + endpoints: (builder) => ({ + createHealthProvider: builder.mutation< + Res['healthProvider'], + Req['createHealthProvider'] + >({ + invalidatesTags: [{ id: 'LIST', type: 'HealthProviders' }], + query: (query: Req['createHealthProvider']) => ({ + body: { name: query.name }, + method: 'POST', + url: `projects/${query.projectId}/feature-health/providers/`, + }), + }), + deleteHealthProvider: builder.mutation( + { + invalidatesTags: [{ id: 'LIST', type: 'HealthProviders' }], + query: (query: Req['deleteHealthProvider']) => ({ + method: 'DELETE', + url: `projects/${query.projectId}/feature-health/providers/${query.providerId}/`, + }), + }, + ), + getHealthProviders: builder.query< + Res['healthProviders'], + Req['getHealthProviders'] + >({ + providesTags: [{ id: 'LIST', type: 'HealthProviders' }], + query: (query: Req['getHealthProviders']) => ({ + url: `projects/${query.projectId}/feature-health/providers/`, + }), + }), + // END OF ENDPOINTS + }), + }) + +export async function getHealthProviders( + store: any, + data: Req['getHealthProviders'], + options?: Parameters< + typeof healthProviderService.endpoints.getHealthProviders.initiate + >[1], +) { + return store.dispatch( + healthProviderService.endpoints.getHealthProviders.initiate(data, options), + ) +} + +export async function createHealthProvider( + store: any, + data: Req['createHealthProvider'], + options?: Parameters< + typeof healthProviderService.endpoints.createHealthProvider.initiate + >[1], +) { + return store.dispatch( + healthProviderService.endpoints.createHealthProvider.initiate( + data, + options, + ), + ) +} + +export async function deleteHealthProvider( + store: any, + data: Req['deleteHealthProvider'], + options?: Parameters< + typeof healthProviderService.endpoints.deleteHealthProvider.initiate + >[1], +) { + return store.dispatch( + healthProviderService.endpoints.deleteHealthProvider.initiate( + data, + options, + ), + ) +} +// END OF FUNCTION_EXPORTS + +export const { + useCreateHealthProviderMutation, + useDeleteHealthProviderMutation, + useGetHealthProvidersQuery, + // END OF EXPORTS +} = healthProviderService + +/* Usage examples: +const { data, isLoading } = useGetHealthProvidersQuery({ id: 2 }, {}) //get hook +healthProviderService.endpoints.getHealthProviders.select({id: 2})(store.getState()) //access data from any function +*/ diff --git a/frontend/common/services/useTag.ts b/frontend/common/services/useTag.ts index c31504295294..c62d11b13869 100644 --- a/frontend/common/services/useTag.ts +++ b/frontend/common/services/useTag.ts @@ -68,8 +68,7 @@ export async function getTags( data: Req['getTags'], options?: Parameters[1], ) { - store.dispatch(tagService.endpoints.getTags.initiate(data, options)) - return Promise.all(store.dispatch(tagService.util.getRunningQueriesThunk())) + return store.dispatch(tagService.endpoints.getTags.initiate(data, options)) } export async function createTag( store: any, diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index bad69b7d2a2a..6e9d7d85cfc2 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -114,6 +114,10 @@ export type Req = { getPermission: { id: string; level: PermissionLevel } getAvailablePermissions: { level: PermissionLevel } getTag: { id: string } + getHealthEvents: { projectId: number | string } + getHealthProviders: { projectId: number } + createHealthProvider: { projectId: number; name: string } + deleteHealthProvider: { projectId: number; providerId: number } updateTag: { projectId: string; tag: Tag } deleteTag: { id: number diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index ba774902fcc1..dfedc50e2a32 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -352,7 +352,7 @@ export type Tag = { label: string is_system_tag: boolean is_permanent: boolean - type: 'STALE' | 'NONE' + type: 'STALE' | 'UNHEALTHY' | 'NONE' } export type MultivariateFeatureStateValue = { @@ -637,6 +637,23 @@ export type SAMLAttributeMapping = { idp_attribute_name: string } +export type HealthEvent = { + created_at: string + environment: number + feature: number + provider_name: string + reason: string + type: 'HEALTHY' | 'UNHEALTHY' +} + +export type HealthProvider = { + id: number + created_by: string + name: string + project: number + webhook_url: number +} + export type Res = { segments: PagedResponse segment: Segment @@ -671,6 +688,9 @@ export type Res = { availablePermissions: AvailablePermission[] tag: Tag tags: Tag[] + healthEvents: HealthEvent[] + healthProvider: HealthProvider + healthProviders: HealthProvider[] account: Account userEmail: {} groupAdmin: { id: string } diff --git a/frontend/web/components/EditHealthProvider.tsx b/frontend/web/components/EditHealthProvider.tsx new file mode 100644 index 000000000000..4cd3d3a21a76 --- /dev/null +++ b/frontend/web/components/EditHealthProvider.tsx @@ -0,0 +1,249 @@ +import React, { FC, useEffect } from 'react' +import { HealthProvider } from 'common/types/responses' +import PanelSearch from './PanelSearch' +import Button from './base/forms/Button' + +import Icon from './Icon' + +import Utils from 'common/utils/utils' +import { + useCreateHealthProviderMutation, + useDeleteHealthProviderMutation, + useGetHealthProvidersQuery, +} from 'common/services/useHealthProvider' +import { components } from 'react-select' + +type EditHealthProviderType = { + projectId: number + tabClassName?: string +} + +const handleError = (error: Error, fallbackMessage?: string) => { + console.error(error) + toast(error?.message ?? fallbackMessage ?? 'Something went wrong!', 'danger') +} + +const Option = (props: any) => { + return {props.children} +} + +const CreateHealthProviderForm = ({ projectId }: { projectId: number }) => { + const [selected, setSelected] = React.useState() + const [createProvider, { error, isError, isLoading, isSuccess }] = + useCreateHealthProviderMutation() + + // TODO: Replace from list of provider options from API + const providers = [{ name: 'Sample' }, { name: 'Grafana' }] + + const providerOptions = providers.map((provider) => ({ + label: provider.name, + value: provider.name, + })) + + useEffect(() => { + if (isSuccess) { + setSelected(undefined) + } + }, [isSuccess]) + + useEffect(() => { + if (!isError || !error) return + handleError(error?.message, 'Failed to create provider') + }, [error, isError]) + + return ( +
{ + e.preventDefault() + if (!selected) { + return + } + createProvider({ name: selected, projectId }) + }} + > + + +