From 6401c24c9d1bb2c0ce818efff29a30ad39ad54ec Mon Sep 17 00:00:00 2001 From: Tiago Fonseca Date: Thu, 30 Jan 2025 10:50:27 -0300 Subject: [PATCH] gates feature health with flags --- frontend/common/services/useHealthProvider.ts | 25 ++++ frontend/common/types/requests.ts | 1 + frontend/common/types/responses.ts | 1 + .../web/components/EditHealthProvider.tsx | 137 ++++++++++-------- frontend/web/components/FeatureRow.js | 10 +- .../web/components/UnhealthyFlagWarning.tsx | 2 +- frontend/web/components/pages/HomeAside.tsx | 5 +- .../components/pages/ProjectSettingsPage.js | 28 ++-- frontend/web/components/tags/TagContent.tsx | 6 +- 9 files changed, 127 insertions(+), 88 deletions(-) diff --git a/frontend/common/services/useHealthProvider.ts b/frontend/common/services/useHealthProvider.ts index 75cb892f1b53..e0df2f34f240 100644 --- a/frontend/common/services/useHealthProvider.ts +++ b/frontend/common/services/useHealthProvider.ts @@ -17,6 +17,15 @@ export const healthProviderService = service 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'] @@ -56,10 +65,26 @@ export async function createHealthProvider( ), ) } + +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 diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index 33b88d7cbe61..6e9d7d85cfc2 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -117,6 +117,7 @@ export type Req = { 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 6e230aaaa13a..dfedc50e2a32 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -647,6 +647,7 @@ export type HealthEvent = { } export type HealthProvider = { + id: number created_by: string name: string project: number diff --git a/frontend/web/components/EditHealthProvider.tsx b/frontend/web/components/EditHealthProvider.tsx index bec731ea0e0b..ce0f2b32a13d 100644 --- a/frontend/web/components/EditHealthProvider.tsx +++ b/frontend/web/components/EditHealthProvider.tsx @@ -1,58 +1,35 @@ -import React, { FC } from 'react' -import { - HealthProvider, - Role, - User, - UserGroupSummary, - UserPermission, -} from 'common/types/responses' +import React, { FC, useEffect } from 'react' +import { HealthProvider } from 'common/types/responses' import PanelSearch from './PanelSearch' import Button from './base/forms/Button' -import { PermissionLevel, Req } from 'common/types/requests' -import { RouterChildContext } from 'react-router' - -import ConfigProvider from 'common/providers/ConfigProvider' import Icon from './Icon' import Utils from 'common/utils/utils' import { useCreateHealthProviderMutation, + useDeleteHealthProviderMutation, useGetHealthProvidersQuery, } from 'common/services/useHealthProvider' import { components } from 'react-select' -type EditPermissionModalType = { - group?: UserGroupSummary +type EditHealthProviderType = { projectId: number - className?: string - isGroup?: boolean - level: PermissionLevel - name: string - onSave?: () => void - envId?: number | string | undefined - parentId?: string - parentLevel?: string - parentSettingsLink?: string - roleTabTitle?: string - permissions?: UserPermission[] - push: (route: string) => void - user?: User - role?: Role - roles?: Role[] - permissionChanged?: () => void - isEditUserPermission?: boolean - isEditGroupPermission?: boolean + tabClassName?: string } -type EditHealthProviderType = Omit & { - router: RouterChildContext['router'] - 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, { isError, isLoading, isSuccess }] = + const [createProvider, { error, isError, isLoading, isSuccess }] = useCreateHealthProviderMutation() const providers = [{ name: 'Sample' }, { name: 'Grafana' }] @@ -62,6 +39,17 @@ const CreateHealthProviderForm = ({ projectId }: { projectId: number }) => { value: provider.name, })) + useEffect(() => { + if (isSuccess) { + setSelected(undefined) + } + }, [isSuccess]) + + useEffect(() => { + if (!isError || !error) return + handleError(error?.message, 'Failed to create provider') + }, [error, isError]) + return (
{ disabled={!providerOptions?.length} placeholder='Select a provider' data-test='add-health-provider-select' - components={{ - Option: (props: any) => { - return ( - - {props.children} - - ) - }, - }} + components={{ Option }} value={providerOptions.find((v) => v.value === selected)} onChange={(option: { value: string }) => { setSelected(option.value) @@ -111,23 +91,45 @@ const CreateHealthProviderForm = ({ projectId }: { projectId: number }) => { } const EditHealthProvider: FC = ({ - envId, - level, - permissions, projectId, - roleTabTitle, - roles, - router, tabClassName, }) => { - const { data: healthProviders, isLoading } = useGetHealthProvidersQuery({ + const { + data: healthProviders, + error: errorFetching, + isError: isErrorFetching, + isLoading, + } = useGetHealthProvidersQuery({ projectId, }) + // TODO: API Needs to expose provider id + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [ + deleteProvider, + { error: deleteError, isError: isDeleteError, isSuccess: isDeleteSuccess }, + ] = useDeleteHealthProviderMutation() + + useEffect(() => { + if (isDeleteSuccess) { + toast('Provider deleted successfully', 'success') + } + }, [isDeleteSuccess]) + + useEffect(() => { + if (isDeleteError) { + handleError(deleteError?.message, 'Failed to delete provider') + } + + if (isErrorFetching) { + handleError(errorFetching?.message, 'Failed to fetch providers') + } + }, [deleteError, isDeleteError, isErrorFetching, errorFetching]) + return (
-
Create Health Providers
+
Manage Health Providers

Flagsmith lets you connect health providers for tagging feature flags @@ -178,7 +180,7 @@ const EditHealthProvider: FC = ({ className={`list-item${ matchingPermissions?.admin ? '' : ' clickable' }`} - key={projectId} + key={provider.name} >

{name}
@@ -197,7 +199,7 @@ const EditHealthProvider: FC = ({
) @@ -231,7 +244,5 @@ const EditHealthProvider: FC = ({ ) } - -export default ConfigProvider(EditHealthProvider) as unknown as FC< - Omit -> +// export default ConfigProvider(EditHealthProvider) +export default EditHealthProvider diff --git a/frontend/web/components/FeatureRow.js b/frontend/web/components/FeatureRow.js index 2ba00f13f6f8..d221c15d464d 100644 --- a/frontend/web/components/FeatureRow.js +++ b/frontend/web/components/FeatureRow.js @@ -261,6 +261,10 @@ class TheComponent extends Component { ) } + + const isFeatureHealthEnabled = + Utils.getFlagsmithHasFeature('feature_health') + return ( {!!isCompact && } - {!!isCompact && ( + {isFeatureHealthEnabled && !!isCompact && ( )} {!isCompact && } - {!isCompact && } + {isFeatureHealthEnabled && !isCompact && ( + + )} {description && !isCompact && (
= ({ return (
- {/* Provider info and link to issue will be provided by reason */} + {/* TODO: Provider info and link to issue will be provided by reason via the API */} {latestHealthEvent.reason} {latestHealthEvent.reason && ( diff --git a/frontend/web/components/pages/HomeAside.tsx b/frontend/web/components/pages/HomeAside.tsx index 40858d334cd6..fb5c8b4755db 100644 --- a/frontend/web/components/pages/HomeAside.tsx +++ b/frontend/web/components/pages/HomeAside.tsx @@ -32,7 +32,7 @@ type HomeAsideType = { type OptionProps = ComponentProps type EnvSelectOptionProps = OptionProps & { - hasWarning?: string[] + hasWarning?: boolean } const EnvSelectOption = ({ hasWarning, ...rest }: EnvSelectOptionProps) => { @@ -41,7 +41,7 @@ const EnvSelectOption = ({ hasWarning, ...rest }: EnvSelectOptionProps) => {
{rest.children}
- {hasWarning && ( + {Utils.getFlagsmithHasFeature('feature_health') && hasWarning && ( @@ -167,7 +167,6 @@ const HomeAside: FC = ({ projectId={projectId} components={{ Menu: ({ ...props }: any) => { - console.log({ props }) return ( {props.children} diff --git a/frontend/web/components/pages/ProjectSettingsPage.js b/frontend/web/components/pages/ProjectSettingsPage.js index 2eb70f678324..6de0258eeef6 100644 --- a/frontend/web/components/pages/ProjectSettingsPage.js +++ b/frontend/web/components/pages/ProjectSettingsPage.js @@ -580,23 +580,17 @@ const ProjectSettingsPage = class extends Component { projectId={this.props.match.params.projectId} /> - - { - this.getPermissions() - }} - permissions={this.state.permissions} - tabClassName='flat-panel' - projectId={this.props.match.params.projectId} - level='project' - roleTabTitle='Project Permissions' - role - roles={this.state.roles} - /> - + {Utils.getFlagsmithHasFeature('feature_health') && ( + + + + )} { diff --git a/frontend/web/components/tags/TagContent.tsx b/frontend/web/components/tags/TagContent.tsx index 29c7c8a685d8..c8668e8e0c77 100644 --- a/frontend/web/components/tags/TagContent.tsx +++ b/frontend/web/components/tags/TagContent.tsx @@ -64,6 +64,7 @@ const getTooltip = (tag: TTag | undefined) => { const disabled = Utils.tagDisabled(tag) const truncated = Format.truncateText(tag.label, 12) const isTruncated = truncated !== tag.label ? tag.label : null + const isFeatureHealthEnabled = Utils.getFlagsmithHasFeature('feature_health') let tooltip = null switch (tag.type) { case 'STALE': { @@ -75,8 +76,9 @@ const getTooltip = (tag: TTag | undefined) => { break } case 'UNHEALTHY': { - tooltip = - 'This feature is tagged as unhealthy in one or more environments.' + tooltip = isFeatureHealthEnabled + ? 'This feature is tagged as unhealthy in one or more environments.' + : '' break } default: