-
Notifications
You must be signed in to change notification settings - Fork 425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Implements feature healthcare UI #5043
Merged
+587
−17
Merged
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
f9f29d3
feat: Adds health events to the frontend
tiagoapolo 1091c4a
wip
tiagoapolo fd56137
adds warning to feature modal
tiagoapolo c5f6be5
changes copy text
tiagoapolo 14c577a
address changes and creates provider
tiagoapolo 0c664f3
gates feature health with flags
tiagoapolo 76945b9
adds todo
tiagoapolo 189db71
remove comment
tiagoapolo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
address changes and creates provider
commit 14c577ad0ddc6bcb5d259bdcbd57ade1c8a4818a
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
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/`, | ||
}), | ||
}), | ||
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, | ||
), | ||
) | ||
} | ||
// END OF FUNCTION_EXPORTS | ||
|
||
export const { | ||
useCreateHealthProviderMutation, | ||
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 | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
import React, { FC } from 'react' | ||
import { | ||
HealthProvider, | ||
Role, | ||
User, | ||
UserGroupSummary, | ||
UserPermission, | ||
} 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, | ||
useGetHealthProvidersQuery, | ||
} from 'common/services/useHealthProvider' | ||
import { components } from 'react-select' | ||
|
||
type EditPermissionModalType = { | ||
group?: UserGroupSummary | ||
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 | ||
} | ||
|
||
type EditHealthProviderType = Omit<EditPermissionModalType, 'onSave'> & { | ||
router: RouterChildContext['router'] | ||
tabClassName?: string | ||
} | ||
|
||
const CreateHealthProviderForm = ({ projectId }: { projectId: number }) => { | ||
const [selected, setSelected] = React.useState<string | undefined>() | ||
const [createProvider, { isError, isLoading, isSuccess }] = | ||
useCreateHealthProviderMutation() | ||
|
||
const providers = [{ name: 'Sample' }, { name: 'Grafana' }] | ||
|
||
const providerOptions = providers.map((provider) => ({ | ||
label: provider.name, | ||
value: provider.name, | ||
})) | ||
|
||
return ( | ||
<form | ||
className='col-md-8' | ||
onSubmit={(e) => { | ||
e.preventDefault() | ||
if (!selected) { | ||
return | ||
} | ||
createProvider({ name: selected, projectId }) | ||
}} | ||
> | ||
<Row className='align-items-start'> | ||
<Flex className='ml-0'> | ||
<Select | ||
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> | ||
) | ||
}, | ||
}} | ||
value={providerOptions.find((v) => v.value === selected)} | ||
onChange={(option: { value: string }) => { | ||
setSelected(option.value) | ||
}} | ||
options={providerOptions} | ||
/> | ||
</Flex> | ||
</Row> | ||
<div className='text-right mt-4'> | ||
<Button | ||
type='submit' | ||
id='save-proj-btn' | ||
disabled={isLoading || !selected} | ||
className='ml-3' | ||
> | ||
{isLoading ? 'Creating' : 'Create'} | ||
</Button> | ||
</div> | ||
</form> | ||
) | ||
} | ||
|
||
const EditHealthProvider: FC<EditHealthProviderType> = ({ | ||
envId, | ||
level, | ||
permissions, | ||
projectId, | ||
roleTabTitle, | ||
roles, | ||
router, | ||
tabClassName, | ||
}) => { | ||
const { data: healthProviders, isLoading } = useGetHealthProvidersQuery({ | ||
projectId, | ||
}) | ||
|
||
return ( | ||
<div className='mt-4'> | ||
<Row> | ||
<h5>Create 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 | ||
unhealthy state in different environments.{' '} | ||
<Button | ||
theme='text' | ||
href='' // TODO: Add docs | ||
target='_blank' | ||
className='fw-normal' | ||
> | ||
Learn about Feature Health. | ||
</Button> | ||
</p> | ||
|
||
<label>Provider Name</label> | ||
<CreateHealthProviderForm projectId={projectId} /> | ||
<hr className='py-0 my-4' /> | ||
|
||
<div className='mt-4'> | ||
{isLoading && ( | ||
<div className='centered-container'> | ||
<Loader /> | ||
</div> | ||
)} | ||
{!isLoading && !!healthProviders?.length && ( | ||
<div className={tabClassName}> | ||
<PanelSearch | ||
id='project-health-providers-list' | ||
title='Health Providers' | ||
className='panel--transparent' | ||
items={healthProviders} | ||
itemHeight={64} | ||
header={ | ||
<Row className='table-header'> | ||
<Flex className='table-column px-3'>Provider</Flex> | ||
<Flex className='table-column'>Webhook URL</Flex> | ||
</Row> | ||
} | ||
renderRow={(provider: HealthProvider) => { | ||
const { name, webhook_url: webhook } = provider | ||
const matchingPermissions = { | ||
admin: true, | ||
} | ||
|
||
return ( | ||
<Row | ||
space | ||
className={`list-item${ | ||
matchingPermissions?.admin ? '' : ' clickable' | ||
}`} | ||
key={projectId} | ||
> | ||
<Flex className='table-column px-3'> | ||
<div className='mb-1 font-weight-medium'>{name}</div> | ||
</Flex> | ||
{matchingPermissions?.admin && ( | ||
<Flex className='table-column fs-small lh-sm'> | ||
<div className='d-flex align-items-center'> | ||
<div | ||
style={{ | ||
overflow: 'hidden', | ||
textOverflow: 'ellipsis', | ||
width: '280px', | ||
}} | ||
> | ||
{webhook} | ||
</div> | ||
<Button | ||
onClick={() => { | ||
Utils.copyFeatureName(webhook) | ||
}} | ||
theme='icon' | ||
className='ms-2' | ||
> | ||
<Icon name='copy' /> | ||
</Button> | ||
</div> | ||
</Flex> | ||
)} | ||
<div style={{ width: '80px' }} className='text-center'> | ||
{matchingPermissions?.admin && ( | ||
<Icon name='setting' width={20} fill='#656D7B' /> | ||
)} | ||
</div> | ||
</Row> | ||
) | ||
}} | ||
renderNoResults={ | ||
<div>You have no health provider in this project.</div> | ||
} | ||
filterRow={(item: HealthProvider, search: string) => { | ||
const strToSearch = `${item.name} ${item.webhook_url}` | ||
return ( | ||
strToSearch.toLowerCase().indexOf(search.toLowerCase()) !== -1 | ||
) | ||
}} | ||
/> | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default ConfigProvider(EditHealthProvider) as unknown as FC< | ||
Omit<EditHealthProviderType, 'router'> | ||
> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Add documentation for feature health usage