diff --git a/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataRouter.js b/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataRouter.js index 6f100c54a7b..69763632baa 100644 --- a/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataRouter.js +++ b/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataRouter.js @@ -1,4 +1,4 @@ -import { IndexRedirect, IndexRoute, Route } from 'react-router'; +import { IndexRedirect, IndexRoute, Redirect, Route } from 'react-router'; import { SERVER_CONSOLE_MODE } from '../../../constants'; import globals from '../../../Globals'; @@ -39,6 +39,7 @@ import { ModifyTableContainer } from './TableModify/ModifyTableContainer'; import { LandingPageRoute as NativeQueries } from '../../../features/Data/LogicalModels/LandingPage/LandingPage'; import { TrackStoredProcedureRoute } from '../../../features/Data/LogicalModels/StoredProcedures/StoredProcedureWidget.route'; +import { LogicalModelPermissionsRoute } from '../../../features/Data/LogicalModels/LogicalModelPermissions/LogicalModelPermissionsPage'; import { ManageFunction } from '../../../features/Data/ManageFunction/ManageFunction'; import { UpdateNativeQueryRoute, @@ -90,7 +91,16 @@ const makeDataRouter = ( path="native-query/:source/:name" component={UpdateNativeQueryRoute} /> - + + + + + + + { const push = usePushRoute(); @@ -149,8 +149,10 @@ export const LandingPage = ({ pathname }: { pathname: string }) => { { - push?.('/data/native-queries/logical-models/permissions'); + onEditClick={model => { + push?.( + `/data/native-queries/logical-models/${model.source.name}/${model.name}/permissions` + ); }} onRemoveClick={handleRemoveLogicalModel} /> diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.stories.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.stories.tsx index ec74f10ba05..fcbd680f0a6 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.stories.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.stories.tsx @@ -1,7 +1,7 @@ import { StoryObj, Meta } from '@storybook/react'; import { buildMetadata } from '../../mocks/metadata'; -import { extractModelsAndQueriesFromMetadata } from '../../utils'; import { ListLogicalModels } from './ListLogicalModels'; +import { extractModelsAndQueriesFromMetadata } from '../../../../hasura-metadata-api/selectors'; export default { component: ListLogicalModels, diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.tsx index a0c29434cfc..994efa63764 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LandingPage/components/ListLogicalModels.tsx @@ -39,8 +39,9 @@ export const ListLogicalModels = ({ header: 'Actions', cell: ({ cell, row }) => (
- {/* Re add once we implement Edit functionality */} - {/* */} +
+ ), + }, + ]} + /> + ); +}; + +export const LogicalModelPermissionsRoute = withRouter<{ + location: Location; + router: InjectedRouter; + params: { + source: string; + name: string; + }; +}>(({ params }) => { + return ( + + + + ); +}); diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/LogicalModelPermissionsFormProvider.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/LogicalModelPermissionsFormProvider.tsx new file mode 100644 index 00000000000..07530007665 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/LogicalModelPermissionsFormProvider.tsx @@ -0,0 +1,15 @@ +import { ReactNode } from 'react'; +import { LogicalModelWithPermissions } from './types'; +import { useLogicalModelPermissionsForm } from '../hooks/usePermissionForm'; +import { FormProvider } from 'react-hook-form'; + +export function LogicalModelPermissionsFormProvider({ + children, + logicalModel, +}: { + children: ReactNode; + logicalModel: LogicalModelWithPermissions | undefined; +}) { + const methods = useLogicalModelPermissionsForm(logicalModel); + return {children}; +} diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionAccessCell.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionAccessCell.tsx new file mode 100644 index 00000000000..2abd7c2d0ee --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionAccessCell.tsx @@ -0,0 +1,36 @@ +import { PermissionsIcon } from '../../../../Permissions/PermissionsTable/components/PermissionsIcons'; + +interface PermissionAccessCellProps extends React.ComponentProps<'button'> { + access: 'fullAccess' | 'partialAccess' | 'noAccess'; + isEditable: boolean; + isCurrentEdit: boolean; +} + +export const PermissionAccessCell: React.FC = ({ + access, + isEditable, + isCurrentEdit, + ...rest +}) => { + if (!isEditable) { + return ( + + + + ); + } + + return ( + + + + ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsForm.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsForm.tsx new file mode 100644 index 00000000000..c8e3490fb80 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsForm.tsx @@ -0,0 +1,191 @@ +import { ReactNode } from 'react'; +import { Button } from '../../../../../new-components/Button'; +import { IconTooltip } from '../../../../../new-components/Tooltip'; +import { usePermissionsFormContext } from '../hooks/usePermissionForm'; +import { Permission } from './types'; +import { Collapse } from '../../../../../new-components/deprecated'; +import { getEdForm } from '../../../../../components/Services/Data/utils'; +import { Badge } from '../../../../../new-components/Badge'; + +type PermissionsFormProps = { + permission: Permission; + onSave: () => void; + onDelete: () => void; + PermissionsInput: ReactNode; + isCreating?: boolean; + isRemoving?: boolean; +}; + +export const PermissionsForm = ({ + permission, + onDelete, + onSave, + PermissionsInput, + isCreating, + isRemoving, +}: PermissionsFormProps) => { + const { + unsetActivePermission, + rowSelectPermissions, + setRowSelectPermissions, + columns, + toggleColumn, + toggleAllColumns, + columnPermissionsStatus, + setPermission, + } = usePermissionsFormContext(); + return ( +
{ + e.preventDefault(); + onSave(); + }} + > +
+
+ +

+ Role: + + {permission.roleName} + + Action: + + {permission.action} + +

+
+
+
+ +
+ +
+ + + {rowSelectPermissions === 'with_custom_filter' && ( +
+
{PermissionsInput}
+
+ )} +
+
+ + + + +
+
+

+ Allow role {permission.roleName} to access{' '} + columns: +

+
+
+ {columns?.map(column => ( + + ))} + +
+
+
+
+ +
+ + + +
+
+
+ ); +}; + +const NoChecksLabel = () => ( + Without any checks  +); + +const CustomLabel = () => ( + + With custom check: + + +); diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsRow.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsRow.tsx new file mode 100644 index 00000000000..14eee43b6a7 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsRow.tsx @@ -0,0 +1,66 @@ +import { forwardRef } from 'react'; +import { Action, Permission } from './types'; +import { PermissionsRowName } from './PermissionsRowName'; +import { PermissionAccessCell } from './PermissionAccessCell'; +import { usePermissionsFormContext } from '../hooks/usePermissionForm'; + +type PermissionsRowProps = { + permission: Permission; + index: number; + actions: string[]; + allowedActions: Action[]; +}; + +export const PermissionsRow = forwardRef( + ({ permission, actions, allowedActions, index }, ref) => { + const { + setActivePermission, + activePermission, + unsetActivePermission, + permissionAccess, + } = usePermissionsFormContext(); + return ( + + + {actions.map(actionName => { + const action = allowedActions.find( + allowedAction => allowedAction === actionName + ); + return ( + { + // Close form if user clicks cell for empty permission + if ( + activePermission !== index && + permission.isNew && + permission.roleName === '' + ) { + unsetActivePermission(); + } + // Focus on input so that user can enter new role name + if (permission.isNew && permission.roleName === '') { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ref.current?.focus(); + } else { + setActivePermission(index); + } + }} + /> + ); + })} + + ); + } +); diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsRowName.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsRowName.tsx new file mode 100644 index 00000000000..45c3f1c8981 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsRowName.tsx @@ -0,0 +1,40 @@ +import { forwardRef } from 'react'; +import { Role } from './types'; +import { usePermissionsFormContext } from '../hooks/usePermissionForm'; + +interface PermissionsRowNameProps { + roleName: Role['name']; + isNew: Role['isNew']; +} + +export const PermissionsRowName = forwardRef< + HTMLInputElement, + PermissionsRowNameProps +>(({ roleName, isNew }, inputRef) => { + const { setNewRoleName } = usePermissionsFormContext(); + if (isNew) { + return ( + + { + setNewRoleName(e.target.value); + }} + /> + + ); + } + + return ( + +
+ +
+ + ); +}); diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsTable.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsTable.tsx new file mode 100644 index 00000000000..030589ef731 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/PermissionsTable.tsx @@ -0,0 +1,68 @@ +import { createRef } from 'react'; +import { PermissionsIcon } from '../../../../Permissions/PermissionsTable/components/PermissionsIcons'; +import { Action, Permission } from './types'; +import { PermissionsRow } from './PermissionsRow'; + +export type PermissionsTableProps = { + allowedActions: Action[]; + permissions: Permission[]; +}; + +export const PermissionsTable = ({ + allowedActions, + permissions, +}: PermissionsTableProps) => { + const actions = ['insert', 'select', 'update', 'delete']; + const inputRef = createRef(); + return ( +
+
+ + +  - full access + + + +  - no access + + + +  - partial access + +
+
+ + + + + {actions.map(action => ( + + ))} + + + + + {permissions.map((permission, index) => ( + // Using index as key instead of role.name because role.name is editable + + ))} + +
+ ROLE + + {action.toUpperCase()} +
+
+
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/types.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/types.ts new file mode 100644 index 00000000000..f28580ae981 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/components/types.ts @@ -0,0 +1,44 @@ +import { LogicalModel, Source } from '../../../../hasura-metadata-types'; + +export type LogicalModelWithSourceName = LogicalModel & { + source: Source; +}; + +export type Permission = { + roleName: string; + source: string; + action: Action; + filter: Record; + columns: string[]; + isNew: boolean; +}; + +export type PermissionId = { + source: Permission['source']; + name: Permission['roleName']; +}; + +export type Action = 'select' | 'insert' | 'update' | 'delete'; + +export type LogicalModelWithPermissions = LogicalModelWithSourceName & { + select_permissions?: { + role: string; + permission: { + columns: string[]; + filter: Record; + }; + }[]; +}; + +export type Role = { + name: string; + isNew?: boolean; +}; + +export type AccessType = 'fullAccess' | 'noAccess' | 'partialAccess'; + +export type OnSave = (permission: Permission) => Promise; + +export type OnDelete = (permission: Permission) => Promise; + +export type RowSelectPermissionsType = 'with_custom_filter' | 'without_filter'; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/useCreateLogicalModelsPermissions.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/useCreateLogicalModelsPermissions.ts new file mode 100644 index 00000000000..33412e84326 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/useCreateLogicalModelsPermissions.ts @@ -0,0 +1,86 @@ +import { useCallback } from 'react'; +import { useMetadataMigration } from '../../../../MetadataAPI'; +import { exportMetadata } from '../../../../DataSource'; +import { useHttpClient } from '../../../../Network'; +import { getCreateLogicalModelBody } from './utils/getCreateLogicalModelBody'; +import { LogicalModel, Source } from '../../../../hasura-metadata-types'; +import { useQueryClient } from 'react-query'; +import { useFireNotification } from '../../../../../new-components/Notifications/index'; +import { METADATA_QUERY_KEY } from '../../../../hasura-metadata-api/useMetadata'; +import { errorTransform } from './utils/errorTransform'; + +const useCreateLogicalModelsPermissions = ({ + logicalModels, + source, +}: { + logicalModels: LogicalModel[]; + source: Source | undefined; +}) => { + const mutate = useMetadataMigration({ + errorTransform, + }); + const { fireNotification } = useFireNotification(); + const httpClient = useHttpClient(); + const queryClient = useQueryClient(); + + const create = useCallback( + async ({ permission, logicalModelName, onSuccess }) => { + const { resource_version } = await exportMetadata({ + httpClient, + }); + if (!source) return; + + const body = getCreateLogicalModelBody({ + permission, + logicalModelName, + logicalModels, + source, + }); + + try { + await mutate.mutateAsync( + { + query: { type: 'bulk', args: body, resource_version }, + }, + { + onSuccess: async () => { + fireNotification({ + type: 'success', + title: 'Success!', + message: 'Permissions saved successfully!', + }); + }, + onError: err => { + fireNotification({ + type: 'error', + title: 'Error!', + message: + err?.message ?? + 'Something went wrong while saving permissions', + }); + }, + onSettled: async () => { + await queryClient.invalidateQueries([METADATA_QUERY_KEY]); + onSuccess?.(); + }, + } + ); + } catch (error: any) { + fireNotification({ + type: 'error', + title: 'Error!', + message: + error?.message ?? 'Something went wrong while saving permissions', + }); + } + }, + [logicalModels, source] + ); + + return { + create, + ...mutate, + }; +}; + +export { useCreateLogicalModelsPermissions }; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/usePermissionForm.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/usePermissionForm.ts new file mode 100644 index 00000000000..b6d434c2341 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/usePermissionForm.ts @@ -0,0 +1,173 @@ +import { useForm, useFormContext } from 'react-hook-form'; +import { LogicalModelPermissionsState } from '../LogicalModelPermissions'; +import { + AccessType, + Action, + LogicalModelWithPermissions, + Permission, + RowSelectPermissionsType, +} from '../components/types'; +import isEmpty from 'lodash/isEmpty'; +import { permissionColumnAccess, permissionRowAccess } from '../utils'; + +export function useLogicalModelPermissionsForm( + logicalModel: LogicalModelWithPermissions | undefined +) { + const defaultPermissions: Permission[] = [ + ...(logicalModel?.select_permissions?.map(permission => ({ + roleName: permission.role, + filter: permission.permission.filter, + columns: permission.permission.columns, + action: 'select' as const, + isNew: false, + source: logicalModel?.source.name, + })) ?? []), + { + roleName: '', + filter: {}, + columns: [], + action: 'select' as const, + isNew: true, + source: logicalModel?.source.name || '', + }, + ]; + const methods = useForm({ + defaultValues: { + activePermission: null, + rowSelectPermissions: 'without_filter', + permissions: defaultPermissions, + columns: logicalModel?.fields?.map(field => field.name) ?? [], + }, + }); + return methods; +} + +export function usePermissionsFormContext() { + const { setValue, watch } = useFormContext(); + return { + permissions: watch('permissions'), + setPermission: (roleName: string, filter: Record) => { + const permissions = watch('permissions'); + setValue( + 'permissions', + permissions.map(permission => { + if (permission.roleName === roleName) { + return { + ...permission, + filter, + }; + } + return permission; + }) + ); + }, + rowSelectPermissions: watch('rowSelectPermissions'), + setRowSelectPermissions: (rowSelectPermissions: RowSelectPermissionsType) => + setValue('rowSelectPermissions', rowSelectPermissions), + columns: watch('columns'), + toggleColumn: (permission: Permission, column: string) => { + const permissions = watch('permissions'); + setValue( + 'permissions', + permissions.map(p => { + if ( + p.roleName === permission.roleName && + p.source === permission.source + ) { + const columns = p.columns.includes(column) + ? p.columns.filter(c => c !== column) + : [...p.columns, column]; + return { + ...p, + columns, + }; + } + return p; + }) + ); + }, + toggleAllColumns: (permission: Permission) => { + const permissions = watch('permissions'); + setValue( + 'permissions', + permissions.map(p => { + if ( + p.roleName === permission.roleName && + p.source === permission.source + ) { + const columns = + p.columns.length === watch('columns').length + ? [] + : watch('columns'); + return { + ...p, + columns, + }; + } + return p; + }) + ); + }, + columnPermissionsStatus: ( + permission: Permission + ): '' | 'No columns' | 'All columns' | 'Partial columns' => { + if (!permission) { + return ''; + } + if (permission.columns.length === 0) { + return 'No columns'; + } + if (permission.columns.length === watch('columns').length) { + return 'All columns'; + } + return 'Partial columns'; + }, + setActivePermission: (index: number) => { + const permission = watch('permissions')[index]; + const rowSelectPermissions: RowSelectPermissionsType = + permission.isNew || isEmpty(permission.filter) + ? 'without_filter' + : 'with_custom_filter'; + + setValue('activePermission', index); + setValue('rowSelectPermissions', rowSelectPermissions); + }, + unsetActivePermission: () => setValue('activePermission', null), + activePermission: watch('activePermission'), + setNewRoleName: (roleName: string) => { + const permissions = watch('permissions'); + setValue( + 'permissions', + permissions.map(permission => { + if (permission.isNew) { + return { + ...permission, + roleName, + }; + } + return permission; + }) + ); + }, + /** + * Returns the access type of the permission for the given action + */ + permissionAccess: ( + action: Action | undefined, + permission: Permission + ): AccessType => { + if (action !== 'select') { + return 'noAccess'; + } + const rowAccess = permissionRowAccess(permission); + const columnAccess = permissionColumnAccess(permission, watch('columns')); + if (rowAccess === 'noAccess' && columnAccess === 'noAccess') { + return 'noAccess'; + } + if (rowAccess === 'fullAccess' && columnAccess === 'fullAccess') { + return 'fullAccess'; + } + return 'partialAccess'; + }, + }; +} diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/useRemoveLogicalModelsPermissions.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/useRemoveLogicalModelsPermissions.ts new file mode 100644 index 00000000000..2e292a69100 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/useRemoveLogicalModelsPermissions.ts @@ -0,0 +1,94 @@ +import { useCallback } from 'react'; +import { useMetadataMigration } from '../../../../MetadataAPI'; +import { exportMetadata } from '../../../../DataSource'; +import { useHttpClient } from '../../../../Network'; +import { LogicalModel, Source } from '../../../../hasura-metadata-types'; +import { useQueryClient } from 'react-query'; +import { useFireNotification } from '../../../../../new-components/Notifications/index'; +import { METADATA_QUERY_KEY } from '../../../../hasura-metadata-api/useMetadata'; +import { errorTransform } from './utils/errorTransform'; +import { Permission } from '../components/types'; +import { getDeleteLogicalModelBody } from './utils/getDeleteLogicalModelBody'; + +const useRemoveLogicalModelsPermissions = ({ + logicalModels, + source, +}: { + logicalModels: LogicalModel[]; + source: Source | undefined; +}) => { + const mutate = useMetadataMigration({ + errorTransform, + }); + const { fireNotification } = useFireNotification(); + const httpClient = useHttpClient(); + const queryClient = useQueryClient(); + + const remove = useCallback( + async ({ + permission, + logicalModelName, + onSuccess, + }: { + permission: Permission; + logicalModelName: string; + onSuccess?: () => void; + }) => { + const { resource_version } = await exportMetadata({ + httpClient, + }); + if (!source) return; + + const body = getDeleteLogicalModelBody({ + permission, + logicalModelName, + source, + }); + + try { + await mutate.mutateAsync( + { + query: { type: 'bulk', args: body, resource_version }, + }, + { + onSuccess: async () => { + fireNotification({ + type: 'success', + title: 'Success!', + message: 'Permissions successfully deleted!', + }); + }, + onError: err => { + fireNotification({ + type: 'error', + title: 'Error!', + message: + err?.message ?? + 'Something went wrong while deleting permissions', + }); + }, + onSettled: async () => { + await queryClient.invalidateQueries([METADATA_QUERY_KEY]); + onSuccess?.(); + }, + } + ); + } catch (error: any) { + fireNotification({ + type: 'error', + title: 'Error!', + message: + error?.message ?? 'Something went wrong while saving permissions', + }); + } + }, + [source] + ); + + return { + remove, + ...mutate, + }; +}; + +export { useRemoveLogicalModelsPermissions }; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/errorTransform.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/errorTransform.ts new file mode 100644 index 00000000000..a2485118520 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/errorTransform.ts @@ -0,0 +1,16 @@ +export function errorTransform(error: any) { + const err = error as Record; + + let message = ''; + let name = `Error code: ${err.code}`; + + if ('internal' in err) { + name = err?.internal?.[0]?.name ?? name; + message = err?.internal?.[0]?.reason ?? 'Internal error'; + } else message = err.error; + + return { + name, + message, + }; +} diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getCreateLogicalModelBody.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getCreateLogicalModelBody.ts new file mode 100644 index 00000000000..07fd295c78e --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getCreateLogicalModelBody.ts @@ -0,0 +1,73 @@ +import { getPermissionValues } from './getPermissionValues'; +import { LogicalModel, Source } from '../../../../../hasura-metadata-types'; +import { Permission } from '../../components/types'; +import { mapPostgresToPg } from '.'; + +export interface CreateLogicalModalBodyArgs { + logicalModels: LogicalModel[]; + logicalModelName: string; + permission: Permission; + source: Source; +} + +type PermissionArgsType = { + name: string; + role: string; + permission?: Record; + source: string; +}; +type PermissionBodyType = { + type: string; + args: PermissionArgsType; +}; + +const doesRoleExist = (logicalModel: LogicalModel, roleName: string) => { + const permissionKeys = ['select_permissions'] as ['select_permissions']; + return permissionKeys.some( + key => + Array.isArray(logicalModel[key]) && + logicalModel[key]?.some(permission => permission.role === roleName) + ); +}; + +export const getCreateLogicalModelBody = ({ + logicalModelName, + permission, + logicalModels, + source, +}: CreateLogicalModalBodyArgs): PermissionBodyType[] => { + const permissionValues = getPermissionValues(permission); + const args = [ + { + type: `${mapPostgresToPg(source.kind)}_create_logical_model_${ + permission.action + }_permission`, + args: { + name: logicalModelName, + role: permission.roleName, + permission: permissionValues, + source: source.name, + }, + }, + ]; + + const permissionAlreadyExists = logicalModels.find((model: LogicalModel) => + doesRoleExist(model, permission.roleName) + ); + + if (permissionAlreadyExists) { + args.unshift({ + type: `${mapPostgresToPg(source.kind)}_drop_logical_model_${ + permission.action + }_permission`, + args: { + permission: {}, + name: logicalModelName, + role: permission.roleName, + source: source.name, + }, + }); + } + + return args; +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getDeleteLogicalModelBody.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getDeleteLogicalModelBody.ts new file mode 100644 index 00000000000..80a0c36aaab --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getDeleteLogicalModelBody.ts @@ -0,0 +1,40 @@ +import { mapPostgresToPg } from '.'; +import { Source } from '../../../../../hasura-metadata-types'; +import { Permission } from '../../components/types'; +export interface DeleteLogicalModalBodyArgs { + logicalModelName: string; + permission: Permission; + source: Source; +} + +type PermissionArgsType = { + name: string; + role: string; + source: string; +}; + +type PermissionBodyType = { + type: string; + args: PermissionArgsType; +}; + +export const getDeleteLogicalModelBody = ({ + logicalModelName, + permission, + source, +}: DeleteLogicalModalBodyArgs): PermissionBodyType[] => { + const args = [ + { + type: `${mapPostgresToPg(source.kind)}_drop_logical_model_${ + permission.action + }_permission`, + args: { + name: logicalModelName, + role: permission.roleName, + source: source.name, + }, + }, + ]; + + return args; +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getPermissionValues.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getPermissionValues.ts new file mode 100644 index 00000000000..35173d4254f --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/getPermissionValues.ts @@ -0,0 +1,36 @@ +type GetPermissionValuesInputType = { + roleName: string; + filter?: Record; + check?: Record; + columns?: string[]; + action: string; + isNew: boolean; + source: string; +}; + +type GetPermissionValuesOutputType = { + filter?: Record; + check?: Record; + columns?: string[]; +}; + +export const getPermissionValues = ( + obj: GetPermissionValuesInputType +): GetPermissionValuesOutputType => { + const { filter, check, columns } = obj; + const result: GetPermissionValuesOutputType = {}; + + if (filter !== undefined) { + result.filter = filter; + } + + if (check !== undefined) { + result.check = check; + } + + if (columns !== undefined) { + result.columns = columns; + } + + return result; +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/index.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/index.ts new file mode 100644 index 00000000000..47352fbc16c --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/hooks/utils/index.ts @@ -0,0 +1,3 @@ +export function mapPostgresToPg(kind: string) { + return kind === 'postgres' ? 'pg' : kind; +} diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/config.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/config.ts new file mode 100644 index 00000000000..dea92f8e176 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/config.ts @@ -0,0 +1,34 @@ +export default { + version: '12345', + is_function_permissions_inferred: true, + is_remote_schema_permissions_enabled: false, + is_admin_secret_set: true, + is_auth_hook_set: false, + is_jwt_set: false, + jwt: [], + is_allow_list_enabled: false, + live_queries: { batch_size: 100, refetch_delay: 1 }, + streaming_queries: { batch_size: 100, refetch_delay: 1 }, + console_assets_dir: null, + experimental_features: ['naming_convention'], + is_prometheus_metrics_enabled: false, + default_naming_convention: 'hasura-default', + feature_flags: [ + { + name: 'stored-procedures', + description: 'Expose stored procedures support', + enabled: false, + }, + { + name: 'native-query-interface', + description: + 'Expose custom views, permissions and advanced SQL functionality via custom queries', + enabled: true, + }, + { + name: 'test-flag', + description: 'Testing feature flag integration', + enabled: false, + }, + ], +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/delete.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/delete.ts new file mode 100644 index 00000000000..c24948c80f4 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/delete.ts @@ -0,0 +1,23 @@ +const payload = { + type: 'bulk', + args: [ + { + type: 'pg_drop_logical_model_select_permission', + args: { + name: 'LogicalModel', + role: 'editor', + source: 'Postgres', + }, + }, + ], + resource_version: 222, +}; + +const response = { + message: 'success', +}; + +export default { + payload, + response, +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/index.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/index.ts new file mode 100644 index 00000000000..ca1b6f7a150 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/index.ts @@ -0,0 +1,125 @@ +import { rest } from 'msw'; +import config from './config'; +import metadata from './metadata'; +import save from './save'; +import deleteMocks from './delete'; + +export const handlers = () => [ + rest.get('http://localhost:8080/v1alpha1/config', async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(config)); + }), + rest.post('http://localhost:8080/v1/metadata', async (req, res, ctx) => { + const reqBody = await req.json<{ + type: string; + args: any; + }>(); + if (reqBody.type === 'export_metadata') { + return res(ctx.status(200), ctx.json({ metadata })); + } + if ( + reqBody.type === 'bulk' && + reqBody.args.length === 2 && + reqBody.args[0].type === 'pg_drop_logical_model_select_permission' && + reqBody.args[1].type === 'pg_create_logical_model_select_permission' + ) { + return res(ctx.status(200), ctx.json(save.response)); + } + if ( + reqBody.type === 'bulk' && + reqBody.args.length === 1 && + reqBody.args[0].type === 'pg_drop_logical_model_select_permission' + ) { + return res(ctx.status(200), ctx.json(deleteMocks.response)); + } + }), + rest.get('http://localhost:8080/v1/entitlement', async (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + metadata_db_id: '58a9e616-5fe9-4277-95fa-e27f9d45e177', + status: 'none', + }) + ); + }), +]; + +export const deleteHandlers = () => { + let hasDeleted = false; + return [ + rest.get('http://localhost:8080/v1alpha1/config', async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(config)); + }), + rest.post('http://localhost:8080/v1/metadata', async (req, res, ctx) => { + const reqBody = await req.json<{ + type: string; + args: any; + }>(); + if (reqBody.type === 'export_metadata' && hasDeleted) { + return res( + ctx.status(200), + ctx.json({ + metadata: { + ...metadata, + sources: [ + ...metadata.sources.map(source => { + if (source.name !== 'Postgres') { + return source; + } + return { + ...source, + logical_models: source.logical_models.map(logical_model => { + if (logical_model.name !== 'LogicalModel') { + return logical_model; + } + return { + fields: logical_model.fields, + name: logical_model.name, + // Omit select_permissions to simulate deletion + }; + }), + }; + }), + ], + }, + }) + ); + } + if (reqBody.type === 'export_metadata') { + return res( + ctx.status(200), + ctx.json({ + metadata, + }) + ); + } + if (reqBody.type === 'export_metadata') { + return res(ctx.status(200), ctx.json({ metadata })); + } + if ( + reqBody.type === 'bulk' && + reqBody.args.length === 2 && + reqBody.args[0].type === 'pg_drop_logical_model_select_permission' && + reqBody.args[1].type === 'pg_create_logical_model_select_permission' + ) { + return res(ctx.status(200), ctx.json(save.response)); + } + if ( + reqBody.type === 'bulk' && + reqBody.args.length === 1 && + reqBody.args[0].type === 'pg_drop_logical_model_select_permission' + ) { + hasDeleted = true; + return res(ctx.status(200), ctx.json(deleteMocks.response)); + } + }), + rest.get('http://localhost:8080/v1/entitlement', async (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + metadata_db_id: '58a9e616-5fe9-4277-95fa-e27f9d45e177', + status: 'none', + }) + ); + }), + ]; +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/metadata.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/metadata.ts new file mode 100644 index 00000000000..9d6ee01a973 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/metadata.ts @@ -0,0 +1,175 @@ +export default { + version: 3, + sources: [ + { + name: 'MS', + kind: 'mssql', + tables: [], + logical_models: [ + { + fields: [], + name: 'Model', + }, + ], + configuration: { + connection_info: { + connection_string: + 'Driver={ODBC Driver 18 for SQL Server};Server=tcp:host.docker.internal,1433;Database=tempdb;Uid=sa;Pwd=Password!;Encrypt=optional', + pool_settings: { + idle_timeout: 5, + max_connections: null, + total_max_connections: null, + }, + }, + }, + }, + { + name: 'Postgres', + kind: 'postgres', + tables: [ + { + table: { + name: 'Album', + schema: 'public', + }, + object_relationships: [ + { + name: 'Album_Artist', + using: { + foreign_key_constraint_on: 'ArtistId', + }, + }, + { + name: 'D', + using: { + manual_configuration: { + column_mapping: { + ArtistId: 'ArtistId', + }, + insertion_order: null, + remote_table: { + name: 'Artist', + schema: 'public', + }, + }, + }, + }, + ], + remote_relationships: [ + { + definition: { + to_source: { + field_mapping: { + ArtistId: 'ArtistId', + }, + relationship_type: 'object', + source: 'Postgres', + table: { + name: 'Artist', + schema: 'public', + }, + }, + }, + name: 'A', + }, + { + definition: { + to_source: { + field_mapping: { + ArtistId: 'ArtistId', + }, + relationship_type: 'object', + source: 'Postgres', + table: { + name: 'Artist', + schema: 'public', + }, + }, + }, + name: 'B', + }, + { + definition: { + to_source: { + field_mapping: { + ArtistId: 'ArtistId', + }, + relationship_type: 'object', + source: 'Postgres', + table: { + name: 'Artist', + schema: 'public', + }, + }, + }, + name: 'C', + }, + ], + }, + { + table: { + name: 'Artist', + schema: 'public', + }, + }, + ], + logical_models: [ + { + fields: [ + { + name: 'id', + nullable: true, + type: 'integer', + }, + { + name: 'name', + nullable: true, + type: 'text', + }, + ], + name: 'LogicalModel', + select_permissions: [ + { + permission: { + columns: ['id'], + filter: {}, + }, + role: 'editor', + }, + ], + }, + { + fields: [], + name: 'M', + select_permissions: [ + { + permission: { + columns: [], + filter: { + _or: [{}], + }, + }, + role: 'M', + }, + { + permission: { + columns: [], + filter: { + _and: [{}], + }, + }, + role: 'reader', + }, + ], + }, + ], + configuration: { + connection_info: { + database_url: 'postgres://postgres:pass@postgres:5432/chinook', + isolation_level: 'read-committed', + use_prepared_statements: false, + }, + }, + }, + ], +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/save.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/save.ts new file mode 100644 index 00000000000..ff4aa7da885 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/mocks/save.ts @@ -0,0 +1,38 @@ +const payload = { + type: 'bulk', + args: [ + { + type: 'pg_drop_logical_model_select_permission', + args: { + permission: {}, + name: 'LogicalModel', + role: 'editor', + source: 'Postgres', + }, + }, + { + type: 'pg_create_logical_model_select_permission', + args: { + name: 'LogicalModel', + role: 'editor', + permission: { + filter: {}, + columns: [], + }, + source: 'Postgres', + }, + }, + ], + resource_version: 221, +}; + +const response = [ + { + message: 'success', + }, + { + message: 'success', + }, +]; + +export default { payload, response }; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/utils.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/utils.ts new file mode 100644 index 00000000000..42ab26272ba --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/LogicalModelPermissions/utils.ts @@ -0,0 +1,23 @@ +import isEmpty from 'lodash/isEmpty'; +import { AccessType, Permission } from './components/types'; + +export function permissionRowAccess(permission: Permission): AccessType { + if (!permission.filter || isEmpty(permission.filter)) { + return 'noAccess'; + } else { + return 'fullAccess'; + } +} + +export function permissionColumnAccess( + permission: Permission, + selectedColumns: string[] +): AccessType { + if (permission.columns.length === 0) { + return 'noAccess'; + } else if (permission.columns.length > selectedColumns.length) { + return 'partialAccess'; + } else { + return 'fullAccess'; + } +} diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/components/RouteWrapper.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/components/RouteWrapper.tsx index d4cc51e4bbf..6e30eedaa50 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/components/RouteWrapper.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/components/RouteWrapper.tsx @@ -1,5 +1,5 @@ import startCase from 'lodash/startCase'; -import { ReactNode } from 'react'; +import React, { ReactNode } from 'react'; import Skeleton from 'react-loading-skeleton'; import { useServerConfig } from '../../../../hooks'; import { Breadcrumbs } from '../../../../new-components/Breadcrumbs'; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/constants.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/constants.ts index 193945f7e1b..08de5846309 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/constants.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/constants.ts @@ -49,4 +49,9 @@ export const NATIVE_QUERY_ROUTES = { title: 'Track Stored Procedure', subtitle: 'Expose your stored SQL procedures via the GraphQL API', }, + '/data/native-queries/logical-models/{{source}}/{{name}}/permissions': { + title: 'Logical Models Permissions', + subtitle: + 'Add permissions to your Logical Models to control access to your data', + }, }; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/utils.ts b/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/utils.ts deleted file mode 100644 index 88e0565ebf9..00000000000 --- a/frontend/libs/console/legacy-ce/src/lib/features/Data/LogicalModels/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Metadata } from '../../hasura-metadata-types'; -import { LogicalModelWithSource, NativeQueryWithSource } from './types'; - -export const extractModelsAndQueriesFromMetadata = ( - m: Metadata -): { queries: NativeQueryWithSource[]; models: LogicalModelWithSource[] } => { - const sources = m.metadata.sources; - let models: LogicalModelWithSource[] = []; - let queries: NativeQueryWithSource[] = []; - - sources.forEach(s => { - if (s.logical_models && s.logical_models.length > 0) { - models = [...models, ...s.logical_models.map(m => ({ ...m, source: s }))]; - } - - if (s.native_queries && s.native_queries.length > 0) { - queries = [ - ...queries, - ...s.native_queries.map(q => ({ ...q, source: s })), - ]; - } - }); - - return { - models, - queries, - }; -}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx index f5b2fa97d8e..be8e3a5163f 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx @@ -41,6 +41,8 @@ export const RowPermissionBuilder = ({ }} table={table} tables={tables} + logicalModel={undefined} + logicalModels={[]} permissions={value} comparators={comparators} /> diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Comparator.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Comparator.tsx index 516ba88af0f..44091f6f4f5 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Comparator.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Comparator.tsx @@ -1,7 +1,9 @@ import { useContext } from 'react'; import { rowPermissionsContext } from './RowPermissionsProvider'; import { useOperators } from './utils/comparatorsFromSchema'; -import Select from 'react-select'; +import Select, { components } from 'react-select'; +import { FiChevronDown } from 'react-icons/fi'; +import clsx from 'clsx'; export const Comparator = ({ comparator, @@ -21,7 +23,20 @@ export const Comparator = ({ inputId={`${comparatorLevelId}-select-value`} isSearchable aria-label={comparatorLevelId} - components={{ DropdownIndicator: null }} + components={{ + DropdownIndicator: props => { + const { className } = props; + return ( + + + + ); + }, + IndicatorSeparator: () => null, + }} options={operators.map(o => ({ value: o.name, label: o.name, diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryType.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryType.tsx index 451bb80ba14..42fd25397ab 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryType.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryType.tsx @@ -5,6 +5,7 @@ import { isColumnComparator } from './utils'; import { ColumnComparatorEntry } from './EntryTypes/ColumnComparatorEntry'; import { useOperators } from './utils/comparatorsFromSchema'; import { ValueInput } from './ValueInput'; +import { useForbiddenFeatures } from './ForbiddenFeaturesProvider'; export const EntryType = ({ k, @@ -17,6 +18,7 @@ export const EntryType = ({ }) => { const operators = useOperators({ path }); const operator = operators.find(o => o.name === k); + const { hasFeature } = useForbiddenFeatures(); if (isColumnComparator(k)) { return ; } @@ -27,6 +29,9 @@ export const EntryType = ({ return ; } if (k === '_exists') { + if (!hasFeature('exists')) { + return null; + } return ; } if ( diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryTypes/ColumnComparatorEntry.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryTypes/ColumnComparatorEntry.tsx index 66b5faca5e2..70b524d899d 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryTypes/ColumnComparatorEntry.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EntryTypes/ColumnComparatorEntry.tsx @@ -3,8 +3,8 @@ import { isComparator } from '../utils/helpers'; import { tableContext } from '../TableProvider'; import { typesContext } from '../TypesProvider'; import { rowPermissionsContext } from '../RowPermissionsProvider'; -import { areTablesEqual } from '../../../../../../hasura-metadata-api'; import { createWrapper } from './utils'; +import { rootTableContext } from '../RootTableProvider'; export function ColumnComparatorEntry({ k, @@ -107,9 +107,9 @@ function RootColumnsSelect({ v: any; path: string[]; }) { - const { table, tables, setValue } = useContext(rowPermissionsContext); + const { setValue } = useContext(rowPermissionsContext); const value = v.find((v: any) => v !== '$'); - const rootTable = tables.find(t => areTablesEqual(t.table, table)); + const { rootTable } = useContext(rootTableContext); const testId = `${path.join('.')}-root-column-comparator-entry`; return ( { @@ -55,7 +59,20 @@ export const Operator = ({ ))} ) : null} - {operators.exist?.items.length ? ( + {rootLogicalModel?.fields.length ? ( + + {rootLogicalModel?.fields.map((field, index) => ( + + ))} + + ) : null} + {hasFeature('exists') && operators.exist?.items.length ? ( {operators.exist.items.map((item, index) => (