From e0c9e2802c2771a2cd077352b86352c2ea59770f Mon Sep 17 00:00:00 2001 From: Varun Choudhary <68095256+Varun-Choudhary@users.noreply.github.com> Date: Thu, 1 Sep 2022 13:33:18 +0530 Subject: [PATCH] console: develop carded table for role based allow list permission PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5648 Co-authored-by: Daniele Cammareri <5709409+dancamma@users.noreply.github.com> GitOrigin-RevId: c1cdd6899e5c50032e097496696097874f5abfd5 --- .../AllowListPermissions.stories.tsx | 25 ++++ .../AllowListPermissions.tsx | 134 ++++++++++++++++++ .../useEnabledRolesFromAllowListState.ts | 24 ++++ .../useSetRoleToAllowListPermissions.tsx | 92 ++++++++++++ console/src/features/MetadataAPI/types.ts | 2 + 5 files changed, 277 insertions(+) create mode 100644 console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.stories.tsx create mode 100644 console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.tsx create mode 100644 console/src/features/AllowLists/hooks/AllowListPermissions/useEnabledRolesFromAllowListState.ts create mode 100644 console/src/features/AllowLists/hooks/AllowListPermissions/useSetRoleToAllowListPermissions.tsx diff --git a/console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.stories.tsx b/console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.stories.tsx new file mode 100644 index 00000000000..5e9b11c6e8b --- /dev/null +++ b/console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.stories.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Story, Meta } from '@storybook/react'; +import { ReactQueryDecorator } from '@/storybook/decorators/react-query'; +import { + AllowListPermissions, + AllowListPermissionsTabProps, +} from './AllowListPermissions'; +import { handlers } from '../../hooks/AllowListPermissions/mock/handlers.mocks'; + +export default { + title: 'Features/Allow List Permission/CardedTable', + component: AllowListPermissions, + decorators: [ReactQueryDecorator()], + parameters: { + msw: handlers(), + }, +} as Meta; + +export const Default: Story = args => { + return ; +}; + +Default.args = { + collectionName: 'allowed-queries', +}; diff --git a/console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.tsx b/console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.tsx new file mode 100644 index 00000000000..de7f62dce1e --- /dev/null +++ b/console/src/features/AllowLists/components/AllowListPermissions/AllowListPermissions.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { Switch } from '@/new-components/Switch'; +import { FaCheck } from 'react-icons/fa'; +import { useSetRoleToAllowListPermission } from '../../hooks/AllowListPermissions/useSetRoleToAllowListPermissions'; +import { useEnabledRolesFromAllowListState } from '../../hooks/AllowListPermissions/useEnabledRolesFromAllowListState'; + +export interface AllowListPermissionsTabProps { + collectionName: string; +} + +export const AllowListPermissions: React.FC = ({ + collectionName, +}) => { + const { + allAvailableRoles, + newRoles, + setNewRoles, + enabledRoles, + setEnabledRoles, + } = useEnabledRolesFromAllowListState(collectionName); + + const { setRoleToAllowListPermission } = + useSetRoleToAllowListPermission(collectionName); + + const handleToggle = (roleName: string, index: number) => { + let newEnabledRoles = []; + // add roleName to enabledRoles, remove duplicates + if (enabledRoles.includes(roleName)) { + newEnabledRoles = Array.from( + new Set(enabledRoles.filter(role => role !== roleName)) + ); + setEnabledRoles(newEnabledRoles); + } else { + newEnabledRoles = Array.from(new Set([...enabledRoles, roleName])); + // remove enabled role from newRoles + setNewRoles( + newRoles.length > 1 + ? newRoles.filter(role => role !== newRoles[index]) + : newRoles + ); + setEnabledRoles(newEnabledRoles); + } + + setRoleToAllowListPermission(newEnabledRoles); + }; + + const handleNewRole = (value: string, index: number) => { + const newAddedRoles = [...newRoles]; + newAddedRoles[index] = value; + + // if last item is not an empty string, add empty string as last index + if (newAddedRoles[newAddedRoles.length - 1] !== '') { + newAddedRoles.push(''); + } + // drop last one + if (newAddedRoles[newAddedRoles.length - 2] === '') { + newAddedRoles.pop(); + } + + setNewRoles(newAddedRoles); + }; + + return ( + <> +
+
+ + + + + + + + + + + + + {allAvailableRoles.map((roleName, index) => ( + + + + + ))} + {newRoles.map((newRole, index) => ( + + + + + ))} + +
+ Role + + Access +
+
+ +
+
+ +
+
+ +
+
+ handleToggle(roleName, index)} + /> +
+ handleNewRole(e.target.value, index)} + /> + + handleToggle(newRole, index) + : () => {} + } + /> +
+
+
+ + ); +}; diff --git a/console/src/features/AllowLists/hooks/AllowListPermissions/useEnabledRolesFromAllowListState.ts b/console/src/features/AllowLists/hooks/AllowListPermissions/useEnabledRolesFromAllowListState.ts new file mode 100644 index 00000000000..1a5ba3bd064 --- /dev/null +++ b/console/src/features/AllowLists/hooks/AllowListPermissions/useEnabledRolesFromAllowListState.ts @@ -0,0 +1,24 @@ +import React, { useEffect } from 'react'; +import { useRoles } from '@/features/MetadataAPI'; +import { useEnabledRolesFromAllowList } from './useEnabledRolesFromAllowList'; + +export const useEnabledRolesFromAllowListState = (collectionName: string) => { + const { data: allAvailableRoles } = useRoles(); + const { data: allowListRoles } = useEnabledRolesFromAllowList(collectionName); + const [enabledRoles, setEnabledRoles] = React.useState([]); + const [newRoles, setNewRoles] = React.useState(['']); + + useEffect(() => { + if (allowListRoles && allowListRoles !== enabledRoles) { + setEnabledRoles(allowListRoles); + } + }, [allowListRoles]); + + return { + allAvailableRoles, + newRoles, + setNewRoles, + enabledRoles, + setEnabledRoles, + }; +}; diff --git a/console/src/features/AllowLists/hooks/AllowListPermissions/useSetRoleToAllowListPermissions.tsx b/console/src/features/AllowLists/hooks/AllowListPermissions/useSetRoleToAllowListPermissions.tsx new file mode 100644 index 00000000000..2451e0a4965 --- /dev/null +++ b/console/src/features/AllowLists/hooks/AllowListPermissions/useSetRoleToAllowListPermissions.tsx @@ -0,0 +1,92 @@ +import { + REQUEST_SUCCESS, + updateSchemaInfo, +} from '@/components/Services/Data/DataActions'; +import { + allowedMetadataTypes, + useMetadata, + useMetadataMigration, +} from '@/features/MetadataAPI'; +import { useDispatch } from 'react-redux'; +import { AnyAction } from 'redux'; +import { ReduxState } from '@/types'; +import { ThunkDispatch } from 'redux-thunk'; +import { useFireNotification } from '@/new-components/Notifications'; + +export const useSetRoleToAllowListPermission = (collectionName: string) => { + const { fireNotification } = useFireNotification(); + const { data: metadata } = useMetadata(); + const dispatch: ThunkDispatch = useDispatch(); + + // to check if collection is present in allowList + const isAllowList = metadata?.metadata.allowlist?.find( + qs => qs.collection === collectionName + ); + + const mutation = useMetadataMigration({ + onSuccess: () => { + dispatch({ type: REQUEST_SUCCESS }); + dispatch(updateSchemaInfo()).then(() => { + fireNotification({ + title: 'Success!', + message: 'Permission added', + type: 'success', + }); + }); + }, + onError: (error: Error) => { + fireNotification({ + title: 'Error', + message: error?.message ?? 'Error while adding permission', + type: 'error', + }); + }, + }); + + const setRoleToAllowListPermission = (roles: string[]): void => { + // drop collection from allow list if we are disabling the last role + if (roles.length === 0) { + const type: allowedMetadataTypes = 'drop_collection_from_allowlist'; + mutation.mutate({ + query: { + type, + args: { + collection: collectionName, + }, + }, + }); + } else if (!isAllowList) { + // add collection to allow list and add roles if we are enabling the 1st role + const type: allowedMetadataTypes = 'add_collection_to_allowlist'; + mutation.mutate({ + query: { + type, + args: { + collection: collectionName, + scope: { + global: false, + roles, + }, + }, + }, + }); + } else { + const type: allowedMetadataTypes = + 'update_scope_of_collection_in_allowlist'; + mutation.mutate({ + query: { + type, + args: { + collection: collectionName, + scope: { + global: false, + roles, + }, + }, + }, + }); + } + }; + + return { setRoleToAllowListPermission }; +}; diff --git a/console/src/features/MetadataAPI/types.ts b/console/src/features/MetadataAPI/types.ts index 6568e94ef45..9c96576fc9d 100644 --- a/console/src/features/MetadataAPI/types.ts +++ b/console/src/features/MetadataAPI/types.ts @@ -40,6 +40,8 @@ export const allowedMetadataTypesArr = [ 'delete_remote_schema_remote_relationship', 'add_remote_schema', 'update_scope_of_collection_in_allowlist', + 'drop_collection_from_allowlist', + 'add_collection_from_allowlist', 'bulk', ] as const;