Skip to content

Commit

Permalink
gates feature health with flags
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagoapolo committed Jan 30, 2025
1 parent 93efa3b commit 6401c24
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 88 deletions.
25 changes: 25 additions & 0 deletions frontend/common/services/useHealthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ export const healthProviderService = service
url: `projects/${query.projectId}/feature-health/providers/`,
}),
}),
deleteHealthProvider: builder.mutation<void, Req['deleteHealthProvider']>(
{
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']
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ export type HealthEvent = {
}

export type HealthProvider = {
id: number
created_by: string
name: string
project: number
Expand Down
137 changes: 74 additions & 63 deletions frontend/web/components/EditHealthProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<EditPermissionModalType, 'onSave'> & {
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 <components.Option {...props}>{props.children}</components.Option>
}

const CreateHealthProviderForm = ({ projectId }: { projectId: number }) => {
const [selected, setSelected] = React.useState<string | undefined>()
const [createProvider, { isError, isLoading, isSuccess }] =
const [createProvider, { error, isError, isLoading, isSuccess }] =
useCreateHealthProviderMutation()

const providers = [{ name: 'Sample' }, { name: 'Grafana' }]
Expand All @@ -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 (
<form
className='col-md-8'
Expand All @@ -79,15 +67,7 @@ const CreateHealthProviderForm = ({ projectId }: { projectId: number }) => {
disabled={!providerOptions?.length}
placeholder='Select a provider'
data-test='add-health-provider-select'
components={{
Option: (props: any) => {
return (
<components.Option {...props}>
{props.children}
</components.Option>
)
},
}}
components={{ Option }}
value={providerOptions.find((v) => v.value === selected)}
onChange={(option: { value: string }) => {
setSelected(option.value)
Expand All @@ -111,23 +91,45 @@ const CreateHealthProviderForm = ({ projectId }: { projectId: number }) => {
}

const EditHealthProvider: FC<EditHealthProviderType> = ({
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 (
<div className='mt-4'>
<Row>
<h5>Create Health Providers</h5>
<h5>Manage Health Providers</h5>
</Row>
<p className='fs-small lh-sm col-md-8 mb-4'>
Flagsmith lets you connect health providers for tagging feature flags
Expand Down Expand Up @@ -178,7 +180,7 @@ const EditHealthProvider: FC<EditHealthProviderType> = ({
className={`list-item${
matchingPermissions?.admin ? '' : ' clickable'
}`}
key={projectId}
key={provider.name}
>
<Flex className='table-column px-3'>
<div className='mb-1 font-weight-medium'>{name}</div>
Expand All @@ -197,7 +199,7 @@ const EditHealthProvider: FC<EditHealthProviderType> = ({
</div>
<Button
onClick={() => {
Utils.copyFeatureName(webhook)
Utils.copyToClipboard(webhook)
}}
theme='icon'
className='ms-2'
Expand All @@ -207,10 +209,21 @@ const EditHealthProvider: FC<EditHealthProviderType> = ({
</div>
</Flex>
)}
<div style={{ width: '80px' }} className='text-center'>
{matchingPermissions?.admin && (
<Icon name='setting' width={20} fill='#656D7B' />
)}
<div className='table-column'>
<Button
id='delete-provider'
data-test='delete-provider'
type='button'
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
// TODO: API Needs to expose provider id
// deleteProvider({ projectId, providerId: provider.id })
}}
className='btn btn-with-icon'
>
<Icon name='trash-2' width={20} fill='#656D7B' />
</Button>
</div>
</Row>
)
Expand All @@ -231,7 +244,5 @@ const EditHealthProvider: FC<EditHealthProviderType> = ({
</div>
)
}

export default ConfigProvider(EditHealthProvider) as unknown as FC<
Omit<EditHealthProviderType, 'router'>
>
// export default ConfigProvider(EditHealthProvider)
export default EditHealthProvider
10 changes: 8 additions & 2 deletions frontend/web/components/FeatureRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ class TheComponent extends Component {
</Flex>
)
}

const isFeatureHealthEnabled =
Utils.getFlagsmithHasFeature('feature_health')

return (
<Row
className={classNames(
Expand Down Expand Up @@ -342,12 +346,14 @@ class TheComponent extends Component {
)}
</TagValues>
{!!isCompact && <StaleFlagWarning projectFlag={projectFlag} />}
{!!isCompact && (
{isFeatureHealthEnabled && !!isCompact && (
<UnhealthyFlagWarning projectFlag={projectFlag} />
)}
</Row>
{!isCompact && <StaleFlagWarning projectFlag={projectFlag} />}
{!isCompact && <UnhealthyFlagWarning projectFlag={projectFlag} />}
{isFeatureHealthEnabled && !isCompact && (
<UnhealthyFlagWarning projectFlag={projectFlag} />
)}
{description && !isCompact && (
<div
className='list-item-subtitle'
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/UnhealthyFlagWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const UnhealthyFlagWarning: FC<UnhealthyFlagWarningType> = ({

return (
<div className='fs-caption' style={{ color: Constants.tagColors[16] }}>
{/* 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 && (
<IonIcon style={{ marginBottom: -2 }} className='ms-1' icon={warning} />
Expand Down
5 changes: 2 additions & 3 deletions frontend/web/components/pages/HomeAside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type HomeAsideType = {

type OptionProps = ComponentProps<typeof components.Option>
type EnvSelectOptionProps = OptionProps & {
hasWarning?: string[]
hasWarning?: boolean
}

const EnvSelectOption = ({ hasWarning, ...rest }: EnvSelectOptionProps) => {
Expand All @@ -41,7 +41,7 @@ const EnvSelectOption = ({ hasWarning, ...rest }: EnvSelectOptionProps) => {
<div className='d-flex align-items-center'>
{rest.children}
<div className='d-flex flex-1 align-items-center justify-content-between '>
{hasWarning && (
{Utils.getFlagsmithHasFeature('feature_health') && hasWarning && (
<Tooltip
title={
<div className='flex ml-1'>
Expand Down Expand Up @@ -167,7 +167,6 @@ const HomeAside: FC<HomeAsideType> = ({
projectId={projectId}
components={{
Menu: ({ ...props }: any) => {
console.log({ props })
return (
<components.Menu {...props}>
{props.children}
Expand Down
28 changes: 11 additions & 17 deletions frontend/web/components/pages/ProjectSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,23 +580,17 @@ const ProjectSettingsPage = class extends Component {
projectId={this.props.match.params.projectId}
/>
</TabItem>
<TabItem
data-test='feature-health-settings'
tabLabel='Feature Health'
>
<EditHealthProvider
onSaveUser={() => {
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}
/>
</TabItem>
{Utils.getFlagsmithHasFeature('feature_health') && (
<TabItem
data-test='feature-health-settings'
tabLabel='Feature Health'
>
<EditHealthProvider
projectId={this.props.match.params.projectId}
tabClassName='flat-panel'
/>
</TabItem>
)}
<TabItem tabLabel='Permissions'>
<EditPermissions
onSaveUser={() => {
Expand Down
6 changes: 4 additions & 2 deletions frontend/web/components/tags/TagContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand All @@ -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:
Expand Down

0 comments on commit 6401c24

Please sign in to comment.