From e86d24b1fbebe25b80bb3e1f9db7018b70ff4b75 Mon Sep 17 00:00:00 2001 From: Erik Magnusson <32518962+ejkkan@users.noreply.github.com> Date: Mon, 23 Jan 2023 13:27:37 +0200 Subject: [PATCH] console: add root field permissions to GDC permissions tab PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7536 Co-authored-by: Matt Hardman <28978422+mattshardman@users.noreply.github.com> GitOrigin-RevId: eaa788e45e12900ac5237c4fb1c98d19f64778ed --- .../Services/Data/Schema/ManageDatabase.tsx | 4 +- .../Schema/components/GDCDatabaseListItem.tsx | 2 +- .../PermissionsConfirmationModal.stories.tsx | 2 +- .../ModifyTable/components/TableColumns.tsx | 5 +- .../features/Data/ModifyTable/hooks/index.ts | 1 - console/src/features/Data/hooks/index.ts | 1 + .../hooks/useListAllTableColumns.ts | 0 .../PermissionsForm.stories.tsx | 1 + .../PermissionsForm/PermissionsForm.tsx | 23 +- .../api/createSelectArgs.test.ts | 6 +- .../Permissions/PermissionsForm/api/utils.ts | 18 +- .../components/Aggregation.tsx | 120 +++++-- .../components/ClonePermissions.tsx | 4 +- .../components/ColumnPermissions.stories.tsx | 2 + .../components/ColumnPermissions.tsx | 219 +++++++++---- .../PermissionsConfirmationModal.stories.tsx | 24 ++ .../PermissionsConfirmationModal.tsx | 83 +++++ .../PermissionsConfirmationModal.utils.tsx | 44 +++ .../RootFieldPermissions.tsx | 201 ++++++++++++ .../SelectPermissionFields.tsx | 34 ++ .../SelectPermissionSectionHeader.tsx | 19 ++ .../SelectPermissionsRow.tsx | 75 +++++ .../hooks/useRootFieldPermissions.ts | 117 +++++++ .../components/RootFieldPermissions/types.ts | 34 ++ .../RootFieldPermissions/utils.spec.ts | 307 ++++++++++++++++++ .../components/RootFieldPermissions/utils.ts | 198 +++++++++++ .../useFormData/createDefaultValues/utils.ts | 6 + .../useFormData/mock/index.ts | 1 + .../useFormData/useFormData.test.ts | 3 + .../hooks/useSourceSupportStreaming.tsx | 12 + .../utils/getPermissionModalStatus.tsx | 4 + .../src/features/Permissions/schema/index.ts | 2 + .../permissions/permissions.ts | 4 +- .../PermissionsConfirmationModal.stories.tsx | 2 +- 34 files changed, 1461 insertions(+), 117 deletions(-) rename console/src/features/Data/{ModifyTable => }/hooks/useListAllTableColumns.ts (100%) create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.utils.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/RootFieldPermissions.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionFields.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionSectionHeader.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionsRow.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/hooks/useRootFieldPermissions.ts create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/types.ts create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.spec.ts create mode 100644 console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.ts create mode 100644 console/src/features/Permissions/PermissionsForm/hooks/useSourceSupportStreaming.tsx create mode 100644 console/src/features/Permissions/PermissionsForm/utils/getPermissionModalStatus.tsx diff --git a/console/src/components/Services/Data/Schema/ManageDatabase.tsx b/console/src/components/Services/Data/Schema/ManageDatabase.tsx index 8bbd0b9b983..2d1ce9770a5 100644 --- a/console/src/components/Services/Data/Schema/ManageDatabase.tsx +++ b/console/src/components/Services/Data/Schema/ManageDatabase.tsx @@ -183,7 +183,7 @@ const DatabaseListItem: React.FC = ({ )} - + = ({ )} - + {showUrl ? ( typeof dataSource.url === 'string' ? ( dataSource.url diff --git a/console/src/components/Services/Data/Schema/components/GDCDatabaseListItem.tsx b/console/src/components/Services/Data/Schema/components/GDCDatabaseListItem.tsx index f6d0c96bf94..74a2c79f599 100644 --- a/console/src/components/Services/Data/Schema/components/GDCDatabaseListItem.tsx +++ b/console/src/components/Services/Data/Schema/components/GDCDatabaseListItem.tsx @@ -85,7 +85,7 @@ export const GDCDatabaseListItem: React.FC = ({ Remove - +
{dataSource.name}{' '} ({dataSource.kind}) diff --git a/console/src/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx b/console/src/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx index c97ee9618ae..f7ce006a17f 100644 --- a/console/src/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx +++ b/console/src/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx @@ -6,7 +6,7 @@ import { } from './PermissionsConfirmationModal'; export default { - title: 'Features/Permissions Form/Permissions Confirmation Modal', + title: 'Features/Table Permissions/Permissions Confirmation Modal', component: PermissionsConfirmationModal, argTypes: { onSubmit: { action: true }, diff --git a/console/src/features/Data/ModifyTable/components/TableColumns.tsx b/console/src/features/Data/ModifyTable/components/TableColumns.tsx index 36b78e375fa..4aaaefb3c20 100644 --- a/console/src/features/Data/ModifyTable/components/TableColumns.tsx +++ b/console/src/features/Data/ModifyTable/components/TableColumns.tsx @@ -1,7 +1,8 @@ import { IndicatorCard } from '@/new-components/IndicatorCard'; import React, { useState } from 'react'; import Skeleton from 'react-loading-skeleton'; -import { useListAllTableColumns } from '../hooks'; +import { useListAllTableColumns } from '@/features/Data'; +import { TableColumn } from '@/features/DataSource'; import { ModifyTableColumn } from '../types'; import { EditTableColumnDialog } from './EditTableColumnDialog/EditTableColumnDialog'; import { TableColumnDescription } from './TableColumnDescription'; @@ -34,7 +35,7 @@ export const TableColumns: React.VFC = props => { return ( <> - {(columns ?? []).map(c => ( + {(columns ?? []).map((c: TableColumn) => ( = args => ( ); + GDCSelect.args = { dataSourceName: 'sqlite', queryType: 'select', diff --git a/console/src/features/Permissions/PermissionsForm/PermissionsForm.tsx b/console/src/features/Permissions/PermissionsForm/PermissionsForm.tsx index 107c0546839..165eb39599f 100644 --- a/console/src/features/Permissions/PermissionsForm/PermissionsForm.tsx +++ b/console/src/features/Permissions/PermissionsForm/PermissionsForm.tsx @@ -1,11 +1,8 @@ import React from 'react'; - import { useConsoleForm } from '@/new-components/Form'; import { Button } from '@/new-components/Button'; import { IndicatorCard } from '@/new-components/IndicatorCard'; - import { PermissionsSchema, schema } from './../schema'; - import { AccessType, QueryType } from '../types'; import { AggregationSection, @@ -17,6 +14,7 @@ import { } from './components'; import { useFormData, useUpdatePermissions } from './hooks'; +import ColumnRootFieldPermissions from './components/RootFieldPermissions/RootFieldPermissions'; export interface ComponentProps { dataSourceName: string; @@ -60,7 +58,7 @@ const Component = (props: ComponentProps) => { const isSubmittingError = updatePermissions.isError || deletePermissions.isError; - + // // for update it is possible to set pre update and post update row checks const rowPermissions = queryType === 'update' ? ['pre', 'post'] : [queryType]; @@ -101,7 +99,6 @@ const Component = (props: ComponentProps) => { {queryType}
- { ))} - {queryType !== 'delete' && ( )} - {['insert', 'update'].includes(queryType) && ( )} - {queryType === 'select' && ( )} - {['insert', 'update', 'delete'].includes(queryType) && ( )} -
+ {queryType === 'select' && ( + + )} +
{/* {!!tableNames?.length && ( { roles={allRoles} /> )} */} -
- -
- {/* {getExternalTablePermissionsMsg()} */} - - + const permissionsModalTitle = getPermissionsModalTitle({ + scenario: 'pks', + role: roleName, + primaryKeyColumns: tableColumns + ?.filter(column => column.isPrimaryKey) + ?.map(column => column.name) + ?.join(','), + }); + + const permissionsModalDescription = getPermissionsModalDescription('pks'); + + return ( + <> + + + +
+
+

+ Allow role {roleName}{' '} + {getAccessText(queryType)} +   + columns: +

+
+ +
+ {columns?.map(fieldName => ( + + ))} + +
+
+ {/* {getExternalTablePermissionsMsg()} */} +
+
+ {showConfirmation && ( + setShowConfirmationModal(null)} + onSubmit={() => { + handleUpdate(showConfirmation); + setShowConfirmationModal(null); + }} + /> + )} + ); }; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx new file mode 100644 index 00000000000..61e77edb9e2 --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { ComponentMeta, Story } from '@storybook/react'; +import { + PermissionsConfirmationModal, + Props, +} from './PermissionsConfirmationModal'; + +export default { + title: 'Features/Permissions/Confirmation Modal', + component: PermissionsConfirmationModal, + argTypes: { + onSubmit: { action: true }, + onClose: { action: true }, + }, +} as ComponentMeta; + +export const Base: Story = args => ( + +); + +Base.args = { + title: <>title, + description: <>description, +}; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.tsx b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.tsx new file mode 100644 index 00000000000..f733356b15e --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Analytics, REDACT_EVERYTHING } from '@/features/Analytics'; +import { FaExclamationTriangle } from 'react-icons/fa'; +import { Button } from '@/new-components/Button'; +import { Dialog } from '@/new-components/Dialog'; +import { LS_KEYS, setLSItem } from '@/utils/localStorage'; +import { Checkbox } from '@/new-components/Form'; + +type CustomDialogFooterProps = { + onSubmit: () => void; + onClose: () => void; +}; + +const CustomDialogFooter: React.FC = ({ + onClose, + onSubmit, +}) => { + const storeDoNotShowPermissionsDialogFlag = (enabled: string | boolean) => { + setLSItem( + LS_KEYS.permissionConfirmationModalStatus, + enabled ? 'disabled' : 'enabled' + ); + }; + + return ( +
+
+ { + storeDoNotShowPermissionsDialogFlag(enabled); + }} + > +
Don't ask me again
+
+
+
+ +
+ +
+
+
+ ); +}; + +export type Props = { + onSubmit: () => void; + onClose: () => void; + title: React.ReactElement; + description: React.ReactElement; +}; + +export const PermissionsConfirmationModal: React.FC = ({ + onSubmit, + onClose, + title, + description, +}) => { + return ( + } + > + +
+
+ +
+
+

{title}

+
+

{description}

+
+
+
+
+
+ ); +}; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.utils.tsx b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.utils.tsx new file mode 100644 index 00000000000..1971a1e93b7 --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/PermissionsConfirmationModal.utils.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { LS_KEYS, getLSItem } from '@/utils/localStorage'; + +type Scenario = 'aggregate' | 'pk' | 'pks'; + +type GetPermissionsModalTitleArgs = { + scenario: Scenario; + role: string; + primaryKeyColumns?: string; +}; + +export const getPermissionsModalTitle = ({ + scenario, + role, + primaryKeyColumns, +}: GetPermissionsModalTitleArgs) => + scenario === 'aggregate' ? ( + <>Are you sure you want to remove aggregation queries for role {role}? + ) : ( + <> + Are you sure you want to disable the access to column(s){' '} + {primaryKeyColumns} for role{' '} + {role}? + + ); + +export const getPermissionsModalDescription = (scenario: Scenario) => + scenario === 'aggregate' ? ( + <> + select_aggregate will be + disabled in GraphQL root field visibility since the permission is removed. + + ) : ( + <> + select_by_pk will be disabled in + GraphQL root field visibility since the primary key is disabled. + + ); + +export const getPermissionModalEnabled = () => { + const status = getLSItem(LS_KEYS.permissionConfirmationModalStatus); + const isEnabled = !status || status === 'enabled'; + return isEnabled; +}; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/RootFieldPermissions.tsx b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/RootFieldPermissions.tsx new file mode 100644 index 00000000000..451a17af793 --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/RootFieldPermissions.tsx @@ -0,0 +1,201 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; +import clsx from 'clsx'; +import { OverlayTrigger } from 'react-bootstrap'; +import { FaQuestionCircle } from 'react-icons/fa'; +import { Collapse } from '@/new-components/deprecated'; +import { Switch } from '@/new-components/Switch'; +import { Tooltip } from '@/new-components/Tooltip'; +import { Table } from '@/features/hasura-metadata-types'; +import { useListAllTableColumns } from '@/features/Data'; +import { + getSectionStatusLabel, + hasSelectedPrimaryKey as hasSelectedPrimaryKeyFinder, +} from './utils'; +import { SelectPermissionsRow } from './SelectPermissionsRow'; + +import { + QueryRootPermissionType, + SubscriptionRootPermissionType, + PermissionRootTypes, +} from './types'; +import { useRootFieldPermissions } from './hooks/useRootFieldPermissions'; +import { useSourceSupportStreaming } from '../../hooks/useSourceSupportStreaming'; + +export type RootKeyValues = 'query_root_fields' | 'subscription_root_fields'; + +export const QUERY_ROOT_VALUES = 'query_root_fields'; +export const SUBSCRIPTION_ROOT_VALUES = 'subscription_root_fields'; + +export const queryRootPermissionFields: QueryRootPermissionType[] = [ + 'select', + 'select_by_pk', + 'select_aggregate', +]; + +export const subscriptionRootPermissionFields: SubscriptionRootPermissionType[] = + ['select', 'select_by_pk', 'select_aggregate', 'select_stream']; + +const QueryRootFieldDescription = () => ( +
+ Allow the following root fields under the Query root field +
+); + +const SubscriptionRootFieldDescription = () => ( +
+ Allow the following root fields under the Subscription root field +
+); + +export interface ColumnPermissionsSectionProps { + columns?: string[]; + filterType: string; + table: Table; + dataSourceName: string; +} + +export const ColumnRootFieldPermissions: React.FC = + ({ dataSourceName, table, filterType }) => { + const { watch, setValue } = useFormContext(); + + const [ + hasEnabledAggregations, + selectedColumns, + queryRootFields, + subscriptionRootFields, + ] = watch([ + 'aggregationEnabled', + 'columns', + 'query_root_fields', + 'subscription_root_fields', + ]); + console.log('table', table); + const disabled = filterType === 'none'; + const { columns: tableColumns } = useListAllTableColumns( + dataSourceName, + table + ); + + const hasSelectedPrimaryKeys = hasSelectedPrimaryKeyFinder( + selectedColumns, + tableColumns + ); + + const updateFormValues = ( + key: RootKeyValues, + value: PermissionRootTypes + ) => { + setValue(key, value); + }; + + const rootFieldPermissions = useRootFieldPermissions({ + queryRootFields, + subscriptionRootFields, + hasEnabledAggregations, + hasSelectedPrimaryKeys, + updateFormValues, + }); + + const { + isSubscriptionStreamingEnabled, + onEnableSectionSwitchChange, + onToggleAll, + isRootPermissionsSwitchedOn, + onUpdatePermission, + } = rootFieldPermissions; + + const supportsStreaming = useSourceSupportStreaming(dataSourceName); + const getFilteredSubscriptionRootPermissionFields = ( + fields: SubscriptionRootPermissionType[] + ) => { + if (!supportsStreaming) + return fields.filter(field => field !== 'select_stream'); + return fields; + }; + + const bodyTitle = disabled ? 'Set row permissions first' : ''; + + return ( + + + +
+
+ +
+ Enable GraphQL root field visibility customization. +
+ + + By enabling this you can customize the root field + permissions. When this switch is turned off, all values are + enabled by default. + + } + > + +
+
+ } + hasEnabledAggregations={hasEnabledAggregations} + hasSelectedPrimaryKeys={hasSelectedPrimaryKeys} + isSubscriptionStreamingEnabled={isSubscriptionStreamingEnabled} + permissionFields={queryRootPermissionFields} + permissionType={QUERY_ROOT_VALUES} + onToggleAll={() => + onToggleAll(QUERY_ROOT_VALUES, queryRootFields) + } + onUpdate={onUpdatePermission} + /> + } + hasEnabledAggregations={hasEnabledAggregations} + hasSelectedPrimaryKeys={hasSelectedPrimaryKeys} + isSubscriptionStreamingEnabled={isSubscriptionStreamingEnabled} + permissionFields={getFilteredSubscriptionRootPermissionFields( + subscriptionRootPermissionFields + )} + permissionType={SUBSCRIPTION_ROOT_VALUES} + onToggleAll={() => + onToggleAll(SUBSCRIPTION_ROOT_VALUES, subscriptionRootFields) + } + onUpdate={onUpdatePermission} + /> +
+
+
+
+ ); + }; + +export default ColumnRootFieldPermissions; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionFields.tsx b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionFields.tsx new file mode 100644 index 00000000000..3ed42e3a155 --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionFields.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { PermissionRootType } from './types'; + +type Props = { + permission: PermissionRootType; + onPermissionChange: (value: PermissionRootType) => void; + disabled?: boolean; + checked: boolean; + title?: string; +}; + +export const SelectPermissionFields: React.FC = ({ + permission, + onPermissionChange, + disabled = false, + checked = true, + title, +}) => ( +
+
+ +
+
+); diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionSectionHeader.tsx b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionSectionHeader.tsx new file mode 100644 index 00000000000..95ae050a13f --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionSectionHeader.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Button } from '@/new-components/Button'; + +type Props = { + onToggle: () => void; + text: React.ReactElement; +}; + +export const SelectPermissionSectionHeader: React.FC = ({ + text, + onToggle, +}) => ( +
+ {text} + +
+); diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionsRow.tsx b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionsRow.tsx new file mode 100644 index 00000000000..0e5e38d166a --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/SelectPermissionsRow.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { SelectPermissionFields } from './SelectPermissionFields'; +import { SelectPermissionSectionHeader } from './SelectPermissionSectionHeader'; +import { getPermissionCheckboxState } from './utils'; +import { RootKeyValues } from './RootFieldPermissions'; +import { + PermissionRootType, + PermissionRootTypes, + CombinedPermissionRootTypes, +} from './types'; + +type Props = { + onToggleAll: () => void; + permissionFields: PermissionRootTypes; + currentPermissions: CombinedPermissionRootTypes; + onUpdate: ( + key: RootKeyValues, + value: PermissionRootType, + permissions: CombinedPermissionRootTypes + ) => void; + description: React.ReactElement; + hasEnabledAggregations: boolean; + hasSelectedPrimaryKeys: boolean; + permissionType: RootKeyValues; + isSubscriptionStreamingEnabled: boolean; +}; + +export const SelectPermissionsRow: React.FC = ({ + onToggleAll, + permissionFields, + currentPermissions, + onUpdate, + description, + hasEnabledAggregations, + hasSelectedPrimaryKeys, + permissionType, + isSubscriptionStreamingEnabled, +}) => { + return ( +
+ +
+ {permissionFields?.map(permission => { + const permissionCheckboxState = getPermissionCheckboxState({ + permission, + hasEnabledAggregations, + hasSelectedPrimaryKeys, + isSubscriptionStreamingEnabled, + rootPermissions: currentPermissions, + }); + + return ( + { + onUpdate(permissionType, value, currentPermissions); + }} + permission={permission} + /> + ); + })} +
+
+ ); +}; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/hooks/useRootFieldPermissions.ts b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/hooks/useRootFieldPermissions.ts new file mode 100644 index 00000000000..f4bb60d13b7 --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/hooks/useRootFieldPermissions.ts @@ -0,0 +1,117 @@ +import { useServerConfig } from '@/hooks'; +import { + RootKeyValues, + SUBSCRIPTION_ROOT_VALUES, + QUERY_ROOT_VALUES, + subscriptionRootPermissionFields, + queryRootPermissionFields, +} from '../RootFieldPermissions'; +import { + PermissionRootType, + RootFieldPermissionsType, + SubscriptionRootPermissionTypes, + PermissionRootTypes, + CombinedPermissionRootTypes, +} from '../types'; + +type Props = RootFieldPermissionsType; + +export const useRootFieldPermissions = ({ + queryRootFields, + subscriptionRootFields, + hasEnabledAggregations, + hasSelectedPrimaryKeys, + updateFormValues, +}: Props) => { + const { data: configData } = useServerConfig(); + const isSubscriptionStreamingEnabled = + !!configData?.experimental_features.includes('streaming_subscriptions'); + + const isRootPermissionsSwitchedOn = + queryRootFields !== null && subscriptionRootFields !== null; + + const onUpdatePermission = ( + key: RootKeyValues, + permission: PermissionRootType, + currentPermissionArray: CombinedPermissionRootTypes + ) => { + const containsString = currentPermissionArray?.includes(permission); + if (containsString || !currentPermissionArray) { + const newPermissionArray = currentPermissionArray?.filter( + (queryPermission: string) => queryPermission !== permission + ); + if (newPermissionArray) updateFormValues(key, newPermissionArray); + return; + } + + updateFormValues( + key, + [...currentPermissionArray, permission].filter(Boolean) + ); + }; + + const onEnableSection = ( + key: RootKeyValues, + permissionTypeFields: PermissionRootTypes + ) => { + if (permissionTypeFields === null) return; + + let newState = permissionTypeFields; + + if (!hasEnabledAggregations) { + newState = newState.filter( + (permission: string) => permission !== 'select_aggregate' + ); + } + + if (!hasSelectedPrimaryKeys) { + newState = newState.filter( + (permission: string) => permission !== 'select_by_pk' + ); + } + + if (key === SUBSCRIPTION_ROOT_VALUES && !isSubscriptionStreamingEnabled) { + newState = newState.filter( + (permission: string) => permission !== 'select_stream' + ); + } + + updateFormValues(key, newState); + }; + + const onToggleAll = ( + key: RootKeyValues, + currentPermissionArray: PermissionRootTypes + ) => { + if (currentPermissionArray && currentPermissionArray?.length > 0) { + return updateFormValues(key, []); + } + const toToggle: SubscriptionRootPermissionTypes = ['select']; + if (key === SUBSCRIPTION_ROOT_VALUES && isSubscriptionStreamingEnabled) { + toToggle.push('select_stream'); + } + if (hasEnabledAggregations) toToggle.push('select_aggregate'); + if (hasSelectedPrimaryKeys) toToggle.push('select_by_pk'); + + updateFormValues(key, toToggle); + }; + + const onEnableSectionSwitchChange = () => { + if (isRootPermissionsSwitchedOn) { + updateFormValues(SUBSCRIPTION_ROOT_VALUES, null); + updateFormValues(QUERY_ROOT_VALUES, null); + return; + } + + onEnableSection(SUBSCRIPTION_ROOT_VALUES, subscriptionRootPermissionFields); + onEnableSection(QUERY_ROOT_VALUES, queryRootPermissionFields); + }; + + return { + isSubscriptionStreamingEnabled, + onEnableSectionSwitchChange, + onToggleAll, + onUpdatePermission, + isRootPermissionsSwitchedOn, + }; +}; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/types.ts b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/types.ts new file mode 100644 index 00000000000..0b5231d642b --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/types.ts @@ -0,0 +1,34 @@ +import { RootKeyValues } from './RootFieldPermissions'; + +export type QueryRootPermissionType = + | 'select' + | 'select_by_pk' + | 'select_aggregate'; + +export type SubscriptionRootPermissionType = + | 'select' + | 'select_by_pk' + | 'select_aggregate' + | 'select_stream'; + +export type QueryRootPermissionTypes = QueryRootPermissionType[] | null; +export type SubscriptionRootPermissionTypes = + | SubscriptionRootPermissionType[] + | null; + +export type PermissionRootType = + | QueryRootPermissionType + | SubscriptionRootPermissionType; +export type PermissionRootTypes = + | QueryRootPermissionTypes + | SubscriptionRootPermissionTypes; +export type CombinedPermissionRootTypes = QueryRootPermissionTypes & + SubscriptionRootPermissionTypes; + +export type RootFieldPermissionsType = { + hasEnabledAggregations: boolean; + hasSelectedPrimaryKeys: boolean; + queryRootFields: QueryRootPermissionTypes; + subscriptionRootFields: SubscriptionRootPermissionTypes; + updateFormValues: (key: RootKeyValues, value: PermissionRootTypes) => void; +}; diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.spec.ts b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.spec.ts new file mode 100644 index 00000000000..b4404bbf854 --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.spec.ts @@ -0,0 +1,307 @@ +import { TableColumn } from '@/features/DataSource'; +import { + getSectionStatusLabel, + SectionLabelProps, + getPermissionCheckboxState, + PermissionCheckboxStateArg, + getSelectByPkCheckboxState, + getSelectStreamCheckboxState, + getSelectAggregateCheckboxState, + hasSelectedPrimaryKey, +} from './utils'; + +describe('hasSelectedPrimaryKey', () => { + describe('when pk is not selected', () => { + it('it returns false', () => { + const tableColumns = [ + { name: 'AlbumId', isPrimaryKey: true }, + ] as TableColumn[]; + expect(hasSelectedPrimaryKey({ AlbumId: false }, tableColumns)).toEqual( + false + ); + }); + }); + describe('when pk is selected', () => { + it('it returns true', () => { + const tableColumns = [ + { name: 'AlbumId', isPrimaryKey: true }, + ] as TableColumn[]; + expect(hasSelectedPrimaryKey({ AlbumId: true }, tableColumns)).toEqual( + true + ); + }); + }); +}); + +describe('getSectionStatusLabel', () => { + describe('when permissions are null', () => { + it('returns "all enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: null, + queryRootPermissions: null, + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: false, + }; + expect(getSectionStatusLabel(args)).toEqual(' - all enabled'); + }); + }); + + describe('when permissions are empty', () => { + it('returns "all disabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: [], + queryRootPermissions: [], + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: false, + }; + expect(getSectionStatusLabel(args)).toEqual(' - all disabled'); + }); + }); + + describe('when permissions are non empty', () => { + it('returns "partially disabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: [ + 'select', + 'select_by_pk', + 'select_aggregate', + 'select_stream', + ], + queryRootPermissions: [], + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: false, + }; + expect(getSectionStatusLabel(args)).toEqual(' - partially enabled'); + }); + }); + + describe('when permissions are all selected', () => { + it('returns "all enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: [ + 'select', + 'select_by_pk', + 'select_aggregate', + 'select_stream', + ], + queryRootPermissions: ['select', 'select_by_pk', 'select_aggregate'], + hasEnabledAggregations: true, + hasSelectedPrimaryKeys: true, + isSubscriptionStreamingEnabled: true, + }; + expect(getSectionStatusLabel(args)).toEqual(' - all enabled'); + }); + }); + + describe('when aggregations are not selected', () => { + describe('when permissions are all selected', () => { + it('returns "all enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: [ + 'select', + 'select_by_pk', + 'select_stream', + ], + queryRootPermissions: ['select', 'select_by_pk'], + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: true, + isSubscriptionStreamingEnabled: true, + }; + expect(getSectionStatusLabel(args)).toEqual(' - all enabled'); + }); + }); + describe('when some permissions are selected', () => { + it('returns "partially enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: ['select'], + queryRootPermissions: ['select'], + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: true, + isSubscriptionStreamingEnabled: true, + }; + expect(getSectionStatusLabel(args)).toEqual(' - partially enabled'); + }); + }); + }); + + describe('when primary keys are not selected', () => { + describe('when permissions are all selected', () => { + it('returns "all enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: [ + 'select', + 'select_aggregate', + 'select_stream', + ], + queryRootPermissions: ['select', 'select_aggregate'], + hasEnabledAggregations: true, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: true, + }; + expect(getSectionStatusLabel(args)).toEqual(' - all enabled'); + }); + }); + describe('when some permissions are selected', () => { + it('returns "partially enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: ['select'], + queryRootPermissions: ['select'], + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: true, + }; + expect(getSectionStatusLabel(args)).toEqual(' - partially enabled'); + }); + }); + }); + + describe('when subscription streaming is not selected', () => { + describe('when permissions are all selected', () => { + it('returns "all enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: [ + 'select', + 'select_by_pk', + 'select_aggregate', + ], + queryRootPermissions: ['select', 'select_by_pk', 'select_aggregate'], + hasEnabledAggregations: true, + hasSelectedPrimaryKeys: true, + isSubscriptionStreamingEnabled: false, + }; + expect(getSectionStatusLabel(args)).toEqual(' - all enabled'); + }); + }); + describe('when some permissions are selected', () => { + it('returns "partially enabled"', () => { + const args: SectionLabelProps = { + subscriptionRootPermissions: ['select', 'select_by_pk'], + queryRootPermissions: ['select', 'select_by_pk'], + hasEnabledAggregations: true, + hasSelectedPrimaryKeys: true, + isSubscriptionStreamingEnabled: false, + }; + expect(getSectionStatusLabel(args)).toEqual(' - partially enabled'); + }); + }); + }); +}); + +describe('getPermissionCheckboxState', () => { + describe('root permissions is null', () => { + it('returns disabled and checked', () => { + const args: PermissionCheckboxStateArg = { + permission: '', + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: false, + rootPermissions: null, + }; + expect(getPermissionCheckboxState(args)).toEqual({ + disabled: true, + checked: true, + }); + }); + }); + + describe('when permission is select', () => { + describe('when permission is in root permissions', () => { + it('returns default state', () => { + const args: PermissionCheckboxStateArg = { + rootPermissions: ['select'], + permission: 'select', + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: false, + }; + expect(getPermissionCheckboxState(args)).toEqual({ + disabled: false, + checked: true, + }); + }); + }); + }); + describe('when permission is NOT in root permissions', () => { + it('returns default state', () => { + const args: PermissionCheckboxStateArg = { + rootPermissions: ['select_by_pk'], + permission: 'select', + hasEnabledAggregations: false, + hasSelectedPrimaryKeys: false, + isSubscriptionStreamingEnabled: false, + }; + expect(getPermissionCheckboxState(args)).toEqual({ + disabled: false, + checked: false, + }); + }); + }); +}); + +describe('getSelectByPkCheckboxState', () => { + it.each` + rootPermissions | permission | hasSelectedPrimaryKeys | expected + ${['select_by_pk']} | ${'select_by_pk'} | ${false} | ${{ checked: false, disabled: true, title: 'Allow access to the table primary key column(s) first' }} + ${['select_by_pk']} | ${'select_by_pk'} | ${true} | ${{ checked: true, disabled: false, title: '' }} + ${['select']} | ${'select_by_pk'} | ${true} | ${{ checked: false, disabled: false, title: '' }} + `( + 'returns the select_by_pk checkbox state for rootPermissions $rootPermissions, permission $permission, hasSelectedPrimaryKeys $hasSelectedPrimaryKeys', + ({ rootPermissions, permission, hasSelectedPrimaryKeys, expected }) => { + expect( + getSelectByPkCheckboxState({ + hasSelectedPrimaryKeys, + rootPermissions, + permission, + }) + ).toEqual(expected); + } + ); +}); + +describe('getSelectStreamCheckboxState', () => { + it.each` + rootPermissions | permission | isSubscriptionStreamingEnabled | expected + ${['select_stream']} | ${'select_stream'} | ${false} | ${{ checked: true, disabled: true, title: 'Enable the streaming subscriptions experimental feature first' }} + ${['select_stream']} | ${'select_by_pk'} | ${false} | ${{ checked: false, disabled: true, title: 'Enable the streaming subscriptions experimental feature first' }} + ${['select_stream']} | ${'select_by_pk'} | ${true} | ${{ checked: false, disabled: false, title: '' }} + `( + 'returns the select_stream checkbox state for rootPermissions $rootPermissions, permission $permission, isSubscriptionStreamingEnabled $isSubscriptionStreamingEnabled', + ({ + rootPermissions, + permission, + isSubscriptionStreamingEnabled, + expected, + }) => { + expect( + getSelectStreamCheckboxState({ + isSubscriptionStreamingEnabled, + rootPermissions, + permission, + }) + ).toEqual(expected); + } + ); +}); + +describe('getSelectAggregateCheckboxState', () => { + it.each` + rootPermissions | permission | hasEnabledAggregations | expected + ${['select_stream']} | ${'select_stream'} | ${false} | ${{ checked: false, disabled: true, title: 'Enable aggregation queries permissions first' }} + ${['select_stream']} | ${'select_stream'} | ${true} | ${{ checked: true, disabled: false, title: '' }} + ${['select_stream']} | ${'select'} | ${true} | ${{ checked: false, disabled: false, title: '' }} + `( + 'returns the select_stream checkbox state for rootPermissions $rootPermissions, permission $permission, hasEnabledAggregations $hasEnabledAggregations', + ({ rootPermissions, permission, hasEnabledAggregations, expected }) => { + expect( + getSelectAggregateCheckboxState({ + hasEnabledAggregations, + rootPermissions, + permission, + }) + ).toEqual(expected); + } + ); +}); diff --git a/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.ts b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.ts new file mode 100644 index 00000000000..10c66626148 --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/components/RootFieldPermissions/utils.ts @@ -0,0 +1,198 @@ +import { TableColumn } from '@/features/DataSource'; +import { + queryRootPermissionFields, + subscriptionRootPermissionFields, +} from './RootFieldPermissions'; +import { + QueryRootPermissionTypes, + SubscriptionRootPermissionTypes, +} from './types'; + +export type SectionLabelProps = { + subscriptionRootPermissions: SubscriptionRootPermissionTypes; + queryRootPermissions: QueryRootPermissionTypes; + hasEnabledAggregations: boolean; + hasSelectedPrimaryKeys: boolean; + isSubscriptionStreamingEnabled: boolean | undefined; +}; + +export const getSectionStatusLabel = ({ + subscriptionRootPermissions, + queryRootPermissions, + hasEnabledAggregations, + hasSelectedPrimaryKeys, + isSubscriptionStreamingEnabled, +}: SectionLabelProps) => { + if (subscriptionRootPermissions === null && queryRootPermissions === null) + return ' - all enabled'; + + if ( + subscriptionRootPermissions?.length === 0 && + queryRootPermissions?.length === 0 + ) + return ' - all disabled'; + + let currentAmountOfAvailablePermission = + queryRootPermissionFields.length + subscriptionRootPermissionFields.length; + + if (!hasEnabledAggregations) { + // exists on both query and subscription + currentAmountOfAvailablePermission -= 2; + } + + if (!hasSelectedPrimaryKeys) { + // exists on both query and subscription + currentAmountOfAvailablePermission -= 2; + } + + if (!isSubscriptionStreamingEnabled) { + // exists only on subscription + currentAmountOfAvailablePermission -= 1; + } + + const amountOfSelectedPermissions = + (queryRootPermissions?.length || 0) + + (subscriptionRootPermissions?.length || 0); + + if (currentAmountOfAvailablePermission === amountOfSelectedPermissions) { + return ' - all enabled'; + } + + return ' - partially enabled'; +}; + +type CheckboxPermissionStateProps = { + checked: boolean; + disabled: boolean; + title?: string; +}; + +export type PermissionCheckboxStateArg = { + permission: string; + hasEnabledAggregations: boolean; + hasSelectedPrimaryKeys: boolean; + isSubscriptionStreamingEnabled: boolean | undefined; + rootPermissions: string[] | null; +}; + +type SelectByPkCheckboxStateArgs = { + hasSelectedPrimaryKeys: boolean; + rootPermissions: string[]; + permission: string; +}; + +export const getSelectByPkCheckboxState = ({ + hasSelectedPrimaryKeys, + rootPermissions, + permission, +}: SelectByPkCheckboxStateArgs): CheckboxPermissionStateProps => { + const getPkCheckedState = () => { + if (!hasSelectedPrimaryKeys) return false; + if (rootPermissions?.includes(permission)) return true; + return false; + }; + + return { + checked: getPkCheckedState(), + disabled: !hasSelectedPrimaryKeys, + title: !hasSelectedPrimaryKeys + ? 'Allow access to the table primary key column(s) first' + : '', + }; +}; + +type SelectStreamCheckboxStateArg = { + rootPermissions: string[]; + permission: string; + isSubscriptionStreamingEnabled: boolean | undefined; +}; + +export const getSelectStreamCheckboxState = ({ + rootPermissions, + permission, + isSubscriptionStreamingEnabled, +}: SelectStreamCheckboxStateArg): CheckboxPermissionStateProps => ({ + checked: rootPermissions?.includes(permission), + disabled: !isSubscriptionStreamingEnabled, + title: !isSubscriptionStreamingEnabled + ? 'Enable the streaming subscriptions experimental feature first' + : '', +}); + +type SelectAggregateCheckboxStateArg = { + hasEnabledAggregations: boolean; + rootPermissions: string[]; + permission: string; +}; +export const getSelectAggregateCheckboxState = ({ + hasEnabledAggregations, + rootPermissions, + permission, +}: SelectAggregateCheckboxStateArg) => { + const getAggregationCheckedState = () => { + if (!hasEnabledAggregations) return false; + if (rootPermissions?.includes(permission)) return true; + return false; + }; + return { + checked: getAggregationCheckedState(), + disabled: !hasEnabledAggregations, + title: !hasEnabledAggregations + ? 'Enable aggregation queries permissions first' + : '', + }; +}; + +export const getPermissionCheckboxState = ({ + permission, + hasEnabledAggregations, + hasSelectedPrimaryKeys, + isSubscriptionStreamingEnabled, + rootPermissions, +}: PermissionCheckboxStateArg): CheckboxPermissionStateProps => { + if (rootPermissions === null) + return { + disabled: true, + checked: true, + }; + + switch (permission) { + case 'select_by_pk': + return getSelectByPkCheckboxState({ + hasSelectedPrimaryKeys, + rootPermissions, + permission, + }); + + case 'select_stream': + return getSelectStreamCheckboxState({ + rootPermissions, + permission, + isSubscriptionStreamingEnabled, + }); + + case 'select_aggregate': + return getSelectAggregateCheckboxState({ + hasEnabledAggregations, + rootPermissions, + permission, + }); + default: + return { + disabled: false, + checked: rootPermissions?.includes(permission), + }; + } +}; + +export const hasSelectedPrimaryKey = ( + selectedColumns: Record, + columns: TableColumn[] +) => { + return !!columns.find(column => { + const isPrimaryKey = column.isPrimaryKey; + const colName = column.name; + const hasPickedColumn = selectedColumns[colName]; + return hasPickedColumn && isPrimaryKey; + }); +}; diff --git a/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/createDefaultValues/utils.ts b/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/createDefaultValues/utils.ts index e4eaeed728c..4bed2f575c4 100644 --- a/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/createDefaultValues/utils.ts +++ b/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/createDefaultValues/utils.ts @@ -151,6 +151,7 @@ export const createPermission = { const backendOnly: boolean = permission?.backend_only || false; return { + // Needs to be cast to const for the zod schema to accept it as a literal for the discriminated union queryType: 'insert' as const, check, checkType, @@ -182,6 +183,7 @@ export const createPermission = { const aggregationEnabled: boolean = permission?.allow_aggregations || false; const selectPermissions = { + // Needs to be cast to const for the zod schema to accept it as a literal for the discriminated union queryType: 'select' as const, filter, filterType, @@ -189,6 +191,8 @@ export const createPermission = { rowCount, aggregationEnabled, operators, + query_root_fields: permission.query_root_fields || null, + subscription_root_fields: permission.subscription_root_fields || null, }; if (rowCount) { @@ -214,6 +218,7 @@ export const createPermission = { }); return { + // Needs to be cast to const for the zod schema to accept it as a literal for the discriminated union queryType: 'update' as const, check, checkType, @@ -236,6 +241,7 @@ export const createPermission = { }); return { + // Needs to be cast to const for the zod schema to accept it as a literal for the discriminated union queryType: 'delete' as const, filter, filterType, diff --git a/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/mock/index.ts b/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/mock/index.ts index 4b84edc0c62..be49245e873 100644 --- a/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/mock/index.ts +++ b/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/mock/index.ts @@ -36,6 +36,7 @@ const metadata: Metadata = { }, allow_aggregations: true, limit: 3, + subscription_root_fields: ['select', 'select_by_pk'], }, }, ], diff --git a/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/useFormData.test.ts b/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/useFormData.test.ts index d8420110e8e..fb5ae4be49d 100644 --- a/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/useFormData.test.ts +++ b/console/src/features/Permissions/PermissionsForm/hooks/dataFetchingHooks/useFormData/useFormData.test.ts @@ -35,11 +35,14 @@ const defaultValuesMockResult: ReturnType = { typeName: 'ArtistId', }, }, + query_root_fields: null, + subscription_root_fields: ['select', 'select_by_pk'], queryType: 'select', rowCount: '3', }; test('use default values returns values correctly', () => { const result = createDefaultValues(defaultValuesInput); + expect(result).toEqual(defaultValuesMockResult); }); diff --git a/console/src/features/Permissions/PermissionsForm/hooks/useSourceSupportStreaming.tsx b/console/src/features/Permissions/PermissionsForm/hooks/useSourceSupportStreaming.tsx new file mode 100644 index 00000000000..eca0fe3efef --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/hooks/useSourceSupportStreaming.tsx @@ -0,0 +1,12 @@ +import { useMetadataSource } from '@/features/MetadataAPI'; + +export const sourcesSupportingStreaming = ['postgres', 'mssql']; + +export const useSourceSupportStreaming = (databaseName: string) => { + const { data: sourceMetadata } = useMetadataSource(databaseName); + const kind = sourceMetadata?.kind; + + if (!kind) return false; + + return sourcesSupportingStreaming.includes(kind); +}; diff --git a/console/src/features/Permissions/PermissionsForm/utils/getPermissionModalStatus.tsx b/console/src/features/Permissions/PermissionsForm/utils/getPermissionModalStatus.tsx new file mode 100644 index 00000000000..353c4648bcb --- /dev/null +++ b/console/src/features/Permissions/PermissionsForm/utils/getPermissionModalStatus.tsx @@ -0,0 +1,4 @@ +import { LS_KEYS, getLSItem } from '@/utils/localStorage'; + +export const isPermissionModalDisabled = () => + getLSItem(LS_KEYS.permissionConfirmationModalStatus) === 'disabled'; diff --git a/console/src/features/Permissions/schema/index.ts b/console/src/features/Permissions/schema/index.ts index f56294ea2b6..c584f6e8a40 100644 --- a/console/src/features/Permissions/schema/index.ts +++ b/console/src/features/Permissions/schema/index.ts @@ -31,6 +31,8 @@ export const schema = z.discriminatedUnion('queryType', [ rowCount: z.string().optional(), aggregationEnabled: z.boolean().optional(), clonePermissions: z.array(z.any()).optional(), + query_root_fields: z.array(z.string()).nullable().optional(), + subscription_root_fields: z.array(z.string()).nullable().optional(), }), z.object({ queryType: z.literal('update'), diff --git a/console/src/features/hasura-metadata-types/permissions/permissions.ts b/console/src/features/hasura-metadata-types/permissions/permissions.ts index b74b567c3a5..f3a9f41b69e 100644 --- a/console/src/features/hasura-metadata-types/permissions/permissions.ts +++ b/console/src/features/hasura-metadata-types/permissions/permissions.ts @@ -25,8 +25,8 @@ export interface SelectPermissionDefinition { columns?: string[]; filter?: Record; allow_aggregations?: boolean; - query_root_fields?: string[]; - subscription_root_fields?: string[]; + query_root_fields?: string[] | null; + subscription_root_fields?: string[] | null; limit?: number; } diff --git a/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx b/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx index c97ee9618ae..f7ce006a17f 100644 --- a/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/TablePermissions/RootFieldPermissions/PermissionsConfirmationModal.stories.tsx @@ -6,7 +6,7 @@ import { } from './PermissionsConfirmationModal'; export default { - title: 'Features/Permissions Form/Permissions Confirmation Modal', + title: 'Features/Table Permissions/Permissions Confirmation Modal', component: PermissionsConfirmationModal, argTypes: { onSubmit: { action: true },