-
Notifications
You must be signed in to change notification settings - Fork 501
refactor: modernise Server-side SDK Keys page #7003
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
Open
talissoncosta
wants to merge
8
commits into
main
Choose a base branch
from
refactor/server-side-sdk-keys-rtk-migration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
80d6652
refactor: add RTK Query version of ServerSideSDKKeys components
talissoncosta b3d5891
refactor: add sdk-keys feature page with barrel exports
talissoncosta 7cde34e
feat: gate new sdk-keys page behind rtk_server_side_sdk_keys flag
talissoncosta c4ade31
fix: correct data-test attribute from segments-page to sdk-keys-page
talissoncosta 7587694
refactor: use React 19 useActionState and clean up SDK keys components
talissoncosta 775d612
fix: cache invalidation, per-row delete loading, and icon colours
talissoncosta a677608
refactor(sdk-keys): lift environment query to page, pass name as prop
talissoncosta 9533d5e
refactor(sdk-keys): gate feature flag at component level, not page level
talissoncosta 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
There are no files selected for viewing
This file contains hidden or 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 hidden or 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
69 changes: 69 additions & 0 deletions
69
frontend/web/components/pages/sdk-keys/components/CreateServerSideKeyModal.tsx
This file contains hidden or 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,69 @@ | ||
| import React, { FC, useActionState, useEffect, useRef } from 'react' | ||
| import Button from 'components/base/forms/Button' | ||
| import ModalHR from 'components/modals/ModalHR' | ||
|
|
||
| type CreateServerSideKeyModalProps = { | ||
| environmentName: string | ||
| onSubmit: (name: string) => Promise<void> | ||
| } | ||
|
|
||
| const CreateServerSideKeyModal: FC<CreateServerSideKeyModalProps> = ({ | ||
| environmentName, | ||
| onSubmit, | ||
| }) => { | ||
| const inputRef = useRef<HTMLInputElement>(null) | ||
|
|
||
| useEffect(() => { | ||
| const timer = setTimeout(() => inputRef.current?.focus(), 500) | ||
| return () => clearTimeout(timer) | ||
| }, []) | ||
|
|
||
| const [error, submitAction, isPending] = useActionState( | ||
| async (_prev: string | null, formData: FormData) => { | ||
| const name = (formData.get('name') as string)?.trim() | ||
| if (!name) return 'Name is required' | ||
| try { | ||
| await onSubmit(name) | ||
| return null | ||
| } catch { | ||
| return 'Failed to create key. Please try again.' | ||
| } | ||
| }, | ||
| null, | ||
| ) | ||
|
|
||
| return ( | ||
| <div> | ||
| <form action={submitAction}> | ||
| <div className='modal-body'> | ||
| <div className='mb-2'> | ||
| This will create a Server-side Environment Key for the environment{' '} | ||
| <strong>{environmentName}</strong>. | ||
| </div> | ||
| <InputGroup | ||
| title='Key Name' | ||
| placeholder='New Key' | ||
| className='mb-2' | ||
| ref={inputRef} | ||
| inputProps={{ | ||
| className: 'full-width modal-input', | ||
| name: 'name', | ||
| }} | ||
| /> | ||
| {error && <div className='text-danger mt-2'>{error}</div>} | ||
| </div> | ||
| <ModalHR /> | ||
| <div className='modal-footer'> | ||
| <Button onClick={closeModal} theme='secondary' className='mr-2'> | ||
| Cancel | ||
| </Button> | ||
| <Button type='submit' disabled={isPending}> | ||
| {isPending ? 'Creating...' : 'Create'} | ||
| </Button> | ||
| </div> | ||
| </form> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export default CreateServerSideKeyModal | ||
54 changes: 54 additions & 0 deletions
54
frontend/web/components/pages/sdk-keys/components/ServerSideKeyRow.tsx
This file contains hidden or 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,54 @@ | ||
| import React, { FC } from 'react' | ||
| import cn from 'classnames' | ||
| import Button from 'components/base/forms/Button' | ||
| import Icon from 'components/Icon' | ||
| import Token from 'components/Token' | ||
| import Utils from 'common/utils/utils' | ||
|
|
||
| type ServerSideKeyRowProps = { | ||
| id: string | ||
| keyValue: string | ||
| name: string | ||
| isDeleting: boolean | ||
| onRemove: (id: string, name: string) => void | ||
| } | ||
|
|
||
| const ServerSideKeyRow: FC<ServerSideKeyRowProps> = ({ | ||
| id, | ||
| isDeleting, | ||
| keyValue, | ||
| name, | ||
| onRemove, | ||
| }) => { | ||
| return ( | ||
| <Row | ||
| className={cn('list-item', { | ||
| 'opacity-50 pointer-events-none': isDeleting, | ||
| })} | ||
| > | ||
| <Flex className='table-column px-3 font-weight-medium'>{name}</Flex> | ||
| <div className='table-column'> | ||
| <Token style={{ width: 280 }} token={keyValue} /> | ||
| </div> | ||
| <Button | ||
| onClick={() => { | ||
| Utils.copyToClipboard(keyValue) | ||
| }} | ||
| className='ml-2 btn-with-icon text-body' | ||
| > | ||
| <Icon name='copy' width={20} /> | ||
| </Button> | ||
| <div className='table-column'> | ||
| <Button | ||
| onClick={() => onRemove(id, name)} | ||
| id='remove-sdk-key' | ||
| className='btn btn-with-icon text-body' | ||
| > | ||
| <Icon name='trash-2' width={20} /> | ||
| </Button> | ||
| </div> | ||
| </Row> | ||
| ) | ||
| } | ||
|
|
||
| export default ServerSideKeyRow |
115 changes: 115 additions & 0 deletions
115
frontend/web/components/pages/sdk-keys/components/ServerSideSDKKeys.tsx
This file contains hidden or 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,115 @@ | ||
| import React, { FC } from 'react' | ||
| import Button from 'components/base/forms/Button' | ||
| import Tooltip from 'components/Tooltip' | ||
| import Constants from 'common/constants' | ||
| import { useHasPermission } from 'common/providers/Permission' | ||
| import { EnvironmentPermission } from 'common/types/permissions.types' | ||
| import CreateServerSideKeyModal from './CreateServerSideKeyModal' | ||
| import ServerSideKeyRow from './ServerSideKeyRow' | ||
| import { useServerSideKeys } from 'components/pages/sdk-keys/hooks/useServerSideKeys' | ||
|
|
||
| type ServerSideKey = { | ||
| id: string | ||
| key: string | ||
| name: string | ||
| } | ||
|
|
||
| type ServerSideSDKKeysProps = { | ||
| environmentId: string | ||
| environmentName: string | ||
| } | ||
|
|
||
| const ServerSideSDKKeys: FC<ServerSideSDKKeysProps> = ({ | ||
| environmentId, | ||
| environmentName, | ||
| }) => { | ||
| const { permission: isAdmin } = useHasPermission({ | ||
| id: environmentId, | ||
| level: 'environment', | ||
| permission: EnvironmentPermission.ADMIN, | ||
| }) | ||
|
|
||
| const { handleCreateKey, handleRemove, isDeletingKey, isLoading, keys } = | ||
| useServerSideKeys({ environmentId }) | ||
|
|
||
| const handleCreate = () => { | ||
| openModal( | ||
| 'Create Server-side Environment Keys', | ||
| <CreateServerSideKeyModal | ||
| environmentName={environmentName} | ||
| onSubmit={handleCreateKey} | ||
| />, | ||
| 'p-0', | ||
| ) | ||
| } | ||
|
|
||
| const filterByName = (item: ServerSideKey, search: string) => | ||
| item.name.toLowerCase().includes(search.toLowerCase()) | ||
|
|
||
| const renderKeyRow = ({ id, key, name }: ServerSideKey) => ( | ||
| <ServerSideKeyRow | ||
| id={id} | ||
| keyValue={key} | ||
| name={name} | ||
| isDeleting={isDeletingKey(id)} | ||
| onRemove={handleRemove} | ||
| /> | ||
| ) | ||
|
|
||
| return ( | ||
| <FormGroup className='my-4'> | ||
| <div className='col-md-6'> | ||
| <h5 className='mb-2'>Server-side Environment Keys</h5> | ||
| <p className='fs-small lh-sm mb-0'> | ||
| Flags can be evaluated locally within your own Server environments | ||
| using our{' '} | ||
| <Button | ||
| theme='text' | ||
| href='https://docs.flagsmith.com/clients/overview#server-side-sdks' | ||
| target='_blank' | ||
| > | ||
| Server-side Environment Keys | ||
| </Button> | ||
| . | ||
| </p> | ||
| <p className='fs-small lh-sm mb-0'> | ||
| Server-side SDKs should be initialised with a Server-side Environment | ||
| Key. | ||
| </p> | ||
| {isAdmin ? ( | ||
| <Button onClick={handleCreate} className='my-4'> | ||
| Create Server-side Environment Key | ||
| </Button> | ||
| ) : ( | ||
| <Tooltip | ||
| title={ | ||
| <Button className='my-4' disabled> | ||
| Create Server-side Environment Key | ||
| </Button> | ||
| } | ||
| place='right' | ||
| > | ||
| {Constants.environmentPermissions(EnvironmentPermission.ADMIN)} | ||
| </Tooltip> | ||
| )} | ||
| </div> | ||
| {isLoading && ( | ||
| <div className='text-center'> | ||
| <Loader /> | ||
| </div> | ||
| )} | ||
| {keys && !!keys.length && ( | ||
| <PanelSearch | ||
| id='server-side-keys-list' | ||
| title='Server-side Environment Keys' | ||
| className='no-pad' | ||
| items={keys} | ||
| filterRow={filterByName} | ||
| renderRow={renderKeyRow} | ||
| /> | ||
| )} | ||
| </FormGroup> | ||
| ) | ||
| } | ||
|
|
||
| export default ServerSideSDKKeys |
This file contains hidden or 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,3 @@ | ||
| export { default as CreateServerSideKeyModal } from './CreateServerSideKeyModal' | ||
| export { default as ServerSideKeyRow } from './ServerSideKeyRow' | ||
| export { default as ServerSideSDKKeys } from './ServerSideSDKKeys' |
55 changes: 55 additions & 0 deletions
55
frontend/web/components/pages/sdk-keys/hooks/useServerSideKeys.tsx
This file contains hidden or 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,55 @@ | ||
| import React from 'react' | ||
| import { | ||
| useCreateServersideEnvironmentKeysMutation, | ||
| useDeleteServersideEnvironmentKeysMutation, | ||
| useGetServersideEnvironmentKeysQuery, | ||
| } from 'common/services/useServersideEnvironmentKey' | ||
|
|
||
| type UseServerSideKeysParams = { | ||
| environmentId: string | ||
| } | ||
|
|
||
| export const useServerSideKeys = ({ | ||
| environmentId, | ||
| }: UseServerSideKeysParams) => { | ||
| const { data: keys, isLoading } = useGetServersideEnvironmentKeysQuery( | ||
| { environmentId }, | ||
| { skip: !environmentId }, | ||
| ) | ||
|
|
||
| const [createKey] = useCreateServersideEnvironmentKeysMutation() | ||
| const [deleteKey, { isLoading: isDeleting, originalArgs: deleteArgs }] = | ||
| useDeleteServersideEnvironmentKeysMutation() | ||
|
|
||
| const handleCreateKey = async (name: string) => { | ||
| await createKey({ data: { name }, environmentId }).unwrap() | ||
| closeModal() | ||
| } | ||
|
|
||
| const handleRemove = (id: string, name: string) => { | ||
| openConfirm({ | ||
| body: ( | ||
| <div> | ||
| Are you sure you want to remove the SDK key <strong>{name}</strong>? | ||
| This action cannot be undone. | ||
| </div> | ||
| ), | ||
| destructive: true, | ||
| onYes: () => { | ||
| deleteKey({ environmentId, id }) | ||
| }, | ||
| title: 'Delete Server-side Environment Keys', | ||
| yesText: 'Confirm', | ||
| }) | ||
| } | ||
|
|
||
| const isDeletingKey = (id: string) => isDeleting && deleteArgs?.id === id | ||
|
|
||
| return { | ||
| handleCreateKey, | ||
| handleRemove, | ||
| isDeletingKey, | ||
| isLoading, | ||
| keys, | ||
| } | ||
| } |
Oops, something went wrong.
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.
React 19's
useActionStatereplaces the manualuseState(false)forisSavingwe had before. It manages pending and error states automatically —isPendingresets on both success and failure, so the button can't get stuck disabled. The action receivesFormDatanatively from<form action={...}>, removing the need for controlled input state andUtils.preventDefault.