mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-10-05 06:18:04 +03:00
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
This commit is contained in:
parent
a6f81a7208
commit
e86d24b1fb
@ -183,7 +183,7 @@ const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-sm py-xs max-w-xs align-top">
|
||||
<td className="px-sm py-xs max-w-xs align-center">
|
||||
<CollapsibleToggle
|
||||
dataSource={dataSource}
|
||||
dbVersion={dbVersion}
|
||||
@ -202,7 +202,7 @@ const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
</ToolTip>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-sm py-xs max-w-xs align-top break-all">
|
||||
<td className="px-sm py-xs max-w-xs align-center break-all">
|
||||
{showUrl ? (
|
||||
typeof dataSource.url === 'string' ? (
|
||||
dataSource.url
|
||||
|
@ -85,7 +85,7 @@ export const GDCDatabaseListItem: React.FC<GDCDatabaseListItemItemProps> = ({
|
||||
Remove
|
||||
</Button>
|
||||
</td>
|
||||
<td className="px-sm py-xs max-w-xs align-top break-all">
|
||||
<td className="px-sm py-xs max-w-xs align-center break-all">
|
||||
<div className="font-bold">
|
||||
{dataSource.name}{' '}
|
||||
<span className="font-normal">({dataSource.kind})</span>
|
||||
|
@ -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 },
|
||||
|
@ -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<TableColumnProps> = props => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{(columns ?? []).map(c => (
|
||||
{(columns ?? []).map((c: TableColumn) => (
|
||||
<TableColumnDescription
|
||||
column={c}
|
||||
key={c.name}
|
||||
|
@ -1,2 +1 @@
|
||||
export { useListAllTableColumns } from './useListAllTableColumns';
|
||||
export { useUpdateTableConfiguration } from './useUpdateTableConfiguration';
|
||||
|
@ -1,2 +1,3 @@
|
||||
export { useTableDefinition } from './useTableDefinition';
|
||||
export { useDatabaseHierarchy } from './useDatabaseHierarchy';
|
||||
export { useListAllTableColumns } from './useListAllTableColumns';
|
||||
|
@ -19,6 +19,7 @@ const roleName = 'user';
|
||||
export const GDCSelect: Story<PermissionsFormProps> = args => (
|
||||
<PermissionsForm {...args} />
|
||||
);
|
||||
|
||||
GDCSelect.args = {
|
||||
dataSourceName: 'sqlite',
|
||||
queryType: 'select',
|
||||
|
@ -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}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<RowPermissionsSectionWrapper
|
||||
roleName={roleName}
|
||||
queryType={queryType}
|
||||
@ -131,32 +128,37 @@ const Component = (props: ComponentProps) => {
|
||||
</React.Fragment>
|
||||
))}
|
||||
</RowPermissionsSectionWrapper>
|
||||
|
||||
{queryType !== 'delete' && (
|
||||
<ColumnPermissionsSection
|
||||
roleName={roleName}
|
||||
queryType={queryType}
|
||||
columns={formData?.columns}
|
||||
table={table}
|
||||
dataSourceName={dataSourceName}
|
||||
/>
|
||||
)}
|
||||
|
||||
{['insert', 'update'].includes(queryType) && (
|
||||
<ColumnPresetsSection
|
||||
queryType={queryType}
|
||||
columns={formData?.columns}
|
||||
/>
|
||||
)}
|
||||
|
||||
{queryType === 'select' && (
|
||||
<AggregationSection queryType={queryType} roleName={roleName} />
|
||||
)}
|
||||
|
||||
{['insert', 'update', 'delete'].includes(queryType) && (
|
||||
<BackendOnlySection queryType={queryType} />
|
||||
)}
|
||||
|
||||
<hr className="my-4" />
|
||||
{queryType === 'select' && (
|
||||
<ColumnRootFieldPermissions
|
||||
filterType={filterType}
|
||||
dataSourceName={dataSourceName}
|
||||
table={table}
|
||||
/>
|
||||
)}
|
||||
|
||||
<hr className="my-4" />
|
||||
{/* {!!tableNames?.length && (
|
||||
<ClonePermissionsSection
|
||||
queryType={queryType}
|
||||
@ -165,7 +167,6 @@ const Component = (props: ComponentProps) => {
|
||||
roles={allRoles}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
<div className="pt-2 flex gap-2">
|
||||
<Button
|
||||
type="submit"
|
||||
|
@ -9,9 +9,9 @@ const selectArgs: CreateInsertArgs = {
|
||||
role: 'user',
|
||||
formData: {
|
||||
queryType: 'select',
|
||||
|
||||
filterType: 'none',
|
||||
|
||||
query_root_fields: null,
|
||||
subscription_root_fields: null,
|
||||
filter: {},
|
||||
rowCount: '0',
|
||||
columns: {
|
||||
@ -59,7 +59,7 @@ test('create select args object from form data', () => {
|
||||
{
|
||||
args: {
|
||||
permission: {
|
||||
aggregation_enabled: false,
|
||||
allow_aggregations: false,
|
||||
columns: ['email', 'type'],
|
||||
filter: {},
|
||||
},
|
||||
|
@ -8,8 +8,10 @@ import { PermissionsSchema } from '../../schema';
|
||||
type SelectPermissionMetadata = {
|
||||
columns: string[];
|
||||
filter: Record<string, any>;
|
||||
aggregation_enabled?: boolean;
|
||||
allow_aggregations?: boolean;
|
||||
limit?: number;
|
||||
query_root_fields?: any[];
|
||||
subscription_root_fields?: any[];
|
||||
};
|
||||
|
||||
const createSelectObject = (input: PermissionsSchema) => {
|
||||
@ -37,9 +39,17 @@ const createSelectObject = (input: PermissionsSchema) => {
|
||||
const permissionObject: SelectPermissionMetadata = {
|
||||
columns,
|
||||
filter,
|
||||
aggregation_enabled: input.aggregationEnabled,
|
||||
allow_aggregations: input.aggregationEnabled,
|
||||
};
|
||||
|
||||
if (input.query_root_fields) {
|
||||
permissionObject.query_root_fields = input.query_root_fields;
|
||||
}
|
||||
if (input.subscription_root_fields) {
|
||||
permissionObject.subscription_root_fields =
|
||||
input.subscription_root_fields;
|
||||
}
|
||||
|
||||
if (input.rowCount && input.rowCount !== '0') {
|
||||
permissionObject.limit = parseInt(input.rowCount, 10);
|
||||
}
|
||||
@ -71,7 +81,7 @@ const createPermission = (formData: PermissionsSchema) => {
|
||||
export interface CreateInsertArgs {
|
||||
dataSourceName: string;
|
||||
table: unknown;
|
||||
queryType: string;
|
||||
queryType: any;
|
||||
role: string;
|
||||
accessType: AccessType;
|
||||
formData: PermissionsSchema;
|
||||
@ -82,7 +92,7 @@ export interface CreateInsertArgs {
|
||||
interface ExistingPermission {
|
||||
table: unknown;
|
||||
role: string;
|
||||
queryType: string;
|
||||
queryType: any;
|
||||
}
|
||||
/**
|
||||
* creates the insert arguments to update permissions
|
||||
|
@ -1,11 +1,16 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { Collapse } from '@/new-components/deprecated';
|
||||
|
||||
import { isFeatureSupported } from '@/dataSources';
|
||||
import { useIsDisabled } from '../hooks/useIsDisabled';
|
||||
import { QueryType } from '../../types';
|
||||
import { PermissionsConfirmationModal } from './RootFieldPermissions/PermissionsConfirmationModal';
|
||||
import {
|
||||
getPermissionsModalTitle,
|
||||
getPermissionsModalDescription,
|
||||
} from './RootFieldPermissions/PermissionsConfirmationModal.utils';
|
||||
import { isPermissionModalDisabled } from '../utils/getPermissionModalStatus';
|
||||
|
||||
export interface AggregationProps {
|
||||
queryType: QueryType;
|
||||
@ -18,44 +23,99 @@ export const AggregationSection: React.FC<AggregationProps> = ({
|
||||
roleName,
|
||||
defaultOpen,
|
||||
}) => {
|
||||
const { register, watch } = useFormContext();
|
||||
|
||||
const { watch, setValue } = useFormContext();
|
||||
const [showConfirmationModal, setShowConfirmationModal] = useState(false);
|
||||
// if no row permissions are selected selection should be disabled
|
||||
const disabled = useIsDisabled(queryType);
|
||||
|
||||
const enabled = watch('aggregationEnabled');
|
||||
const [enabled, queryRootFields, subscriptionRootFields] = watch([
|
||||
'aggregationEnabled',
|
||||
'query_root_fields',
|
||||
'subscription_root_fields',
|
||||
]);
|
||||
|
||||
if (!isFeatureSupported('tables.permissions.aggregation')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleUpdate = () => {
|
||||
setValue('aggregationEnabled', !enabled);
|
||||
setValue(
|
||||
'query_root_fields',
|
||||
queryRootFields.filter((field: string) => field !== 'select_aggregate')
|
||||
);
|
||||
setValue(
|
||||
'subscription_root_fields',
|
||||
subscriptionRootFields.filter(
|
||||
(field: string) => field !== 'select_aggregate'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const permissionsModalTitle = getPermissionsModalTitle({
|
||||
scenario: 'aggregate',
|
||||
role: roleName,
|
||||
});
|
||||
|
||||
const permissionsModalDescription =
|
||||
getPermissionsModalDescription('aggregate');
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
title="Aggregation queries permissions"
|
||||
tooltip="Allow queries with aggregate functions like sum, count, avg,
|
||||
<>
|
||||
<Collapse
|
||||
title="Aggregation queries permissions"
|
||||
tooltip="Allow queries with aggregate functions like sum, count, avg,
|
||||
max, min, etc"
|
||||
status={enabled ? 'Enabled' : 'Disabled'}
|
||||
data-test="toggle-agg-permission"
|
||||
disabled={disabled}
|
||||
defaultOpen={defaultOpen || enabled}
|
||||
>
|
||||
<Collapse.Content>
|
||||
<div title={disabled ? 'Set row permissions first' : ''}>
|
||||
<label className="flex items-center gap-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
title={disabled ? 'Set row permissions first' : ''}
|
||||
disabled={disabled}
|
||||
className="m-0 mt-0 rounded shadow-sm border border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
|
||||
{...register('aggregationEnabled')}
|
||||
/>
|
||||
<span>
|
||||
Allow role <strong>{roleName}</strong> to make aggregation queries
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</Collapse.Content>
|
||||
</Collapse>
|
||||
status={enabled ? 'Enabled' : 'Disabled'}
|
||||
data-test="toggle-agg-permission"
|
||||
disabled={disabled}
|
||||
defaultOpen={defaultOpen || enabled}
|
||||
>
|
||||
<Collapse.Content>
|
||||
<div title={disabled ? 'Set row permissions first' : ''}>
|
||||
<label className="flex items-center gap-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
title={disabled ? 'Set row permissions first' : ''}
|
||||
disabled={disabled}
|
||||
className="m-0 mt-0 rounded shadow-sm border border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
const pkRootFieldsAreSelected =
|
||||
queryRootFields?.includes('select_aggregate') ||
|
||||
subscriptionRootFields?.includes('select_aggregate');
|
||||
const hideModal = isPermissionModalDisabled();
|
||||
if (
|
||||
!showConfirmationModal &&
|
||||
pkRootFieldsAreSelected &&
|
||||
!hideModal
|
||||
) {
|
||||
setShowConfirmationModal(true);
|
||||
return;
|
||||
}
|
||||
handleUpdate();
|
||||
}}
|
||||
/>
|
||||
<span>
|
||||
Allow role <strong>{roleName}</strong> to make aggregation
|
||||
queries
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</Collapse.Content>
|
||||
</Collapse>
|
||||
{showConfirmationModal && (
|
||||
<PermissionsConfirmationModal
|
||||
title={permissionsModalTitle}
|
||||
description={permissionsModalDescription}
|
||||
onClose={() => setShowConfirmationModal(false)}
|
||||
onSubmit={() => {
|
||||
handleUpdate();
|
||||
setShowConfirmationModal(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -105,7 +105,7 @@ export const ClonePermissionsRow: React.FC<ClonePermissionsRowProps> = ({
|
||||
};
|
||||
|
||||
export interface ClonePermissionsSectionProps {
|
||||
queryType: string;
|
||||
queryType: any;
|
||||
tables: string[];
|
||||
supportedQueryTypes: string[];
|
||||
roles: string[];
|
||||
@ -115,7 +115,7 @@ export interface ClonePermissionsSectionProps {
|
||||
export interface ClonePermission {
|
||||
id: number;
|
||||
tableName: string;
|
||||
queryType: string;
|
||||
queryType: any;
|
||||
roleName: string;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import { z } from 'zod';
|
||||
import { SimpleForm } from '@/new-components/Form';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
|
||||
import {
|
||||
ColumnPermissionsSection,
|
||||
@ -14,6 +15,7 @@ export default {
|
||||
title: 'Features/Permissions/Form/Column Section',
|
||||
component: ColumnPermissionsSection,
|
||||
decorators: [
|
||||
ReactQueryDecorator(),
|
||||
(StoryComponent: React.FC) => (
|
||||
<SimpleForm
|
||||
schema={schema}
|
||||
|
@ -1,14 +1,25 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { Button } from '@/new-components/Button';
|
||||
|
||||
import { Collapse } from '@/new-components/deprecated';
|
||||
|
||||
import { TableColumn } from '@/features/DataSource';
|
||||
import { useListAllTableColumns } from '@/features/Data';
|
||||
import { PermissionsConfirmationModal } from './RootFieldPermissions/PermissionsConfirmationModal';
|
||||
import { getEdForm } from '../../../../components/Services/Data/utils';
|
||||
import { useIsDisabled } from '../hooks/useIsDisabled';
|
||||
import { QueryType } from '../../types';
|
||||
import { isPermissionModalDisabled } from '../utils/getPermissionModalStatus';
|
||||
|
||||
const getAccessText = (queryType: string) => {
|
||||
import {
|
||||
getPermissionsModalTitle,
|
||||
getPermissionsModalDescription,
|
||||
} from './RootFieldPermissions/PermissionsConfirmationModal.utils';
|
||||
import {
|
||||
SubscriptionRootPermissionType,
|
||||
QueryRootPermissionType,
|
||||
} from './RootFieldPermissions/types';
|
||||
|
||||
const getAccessText = (queryType: any) => {
|
||||
if (queryType === 'insert') {
|
||||
return 'to set input for';
|
||||
}
|
||||
@ -24,6 +35,8 @@ export interface ColumnPermissionsSectionProps {
|
||||
queryType: QueryType;
|
||||
roleName: string;
|
||||
columns?: string[];
|
||||
table: unknown;
|
||||
dataSourceName: string;
|
||||
}
|
||||
|
||||
const useStatus = (disabled: boolean) => {
|
||||
@ -52,17 +65,50 @@ const useStatus = (disabled: boolean) => {
|
||||
return { data: 'Partial columns', isError: false };
|
||||
};
|
||||
|
||||
const checkIfConfirmationIsNeeded = (
|
||||
fieldName: string,
|
||||
tableColumns: TableColumn[],
|
||||
selectedColumns: Record<string, boolean>,
|
||||
queryRootFields: QueryRootPermissionType,
|
||||
subscriptionRootFields: SubscriptionRootPermissionType
|
||||
) => {
|
||||
const primaryKeys = tableColumns
|
||||
?.filter(column => column.isPrimaryKey)
|
||||
?.map(column => column.name);
|
||||
const pkRootFieldsAreSelected =
|
||||
queryRootFields?.includes('select_by_pk') ||
|
||||
subscriptionRootFields?.includes('select_by_pk');
|
||||
return (
|
||||
selectedColumns[fieldName] &&
|
||||
pkRootFieldsAreSelected &&
|
||||
primaryKeys.includes(fieldName)
|
||||
);
|
||||
};
|
||||
|
||||
// @todo
|
||||
// this hasn't been fully implemented, it still needs computed columns adding
|
||||
export const ColumnPermissionsSection: React.FC<ColumnPermissionsSectionProps> =
|
||||
({ roleName, queryType, columns }) => {
|
||||
const { register, setValue } = useFormContext();
|
||||
({ roleName, queryType, columns, table, dataSourceName }) => {
|
||||
const { setValue, watch } = useFormContext();
|
||||
const [showConfirmation, setShowConfirmationModal] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [selectedColumns, queryRootFields, subscriptionRootFields] = watch([
|
||||
'columns',
|
||||
'query_root_fields',
|
||||
'subscription_root_fields',
|
||||
]);
|
||||
|
||||
// if no row permissions are selected selection should be disabled
|
||||
const disabled = useIsDisabled(queryType);
|
||||
|
||||
const { data: status, isError } = useStatus(disabled);
|
||||
|
||||
const { columns: tableColumns } = useListAllTableColumns(
|
||||
dataSourceName,
|
||||
table
|
||||
);
|
||||
|
||||
const onClick = () => {
|
||||
columns?.forEach(column => {
|
||||
const toggleAllOn = status !== 'All columns';
|
||||
@ -76,58 +122,117 @@ export const ColumnPermissionsSection: React.FC<ColumnPermissionsSectionProps> =
|
||||
return <div>Error loading column permission data</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse defaultOpen={!disabled}>
|
||||
<Collapse.Header
|
||||
title={`Column ${queryType} permissions`}
|
||||
tooltip={`Choose columns allowed to be ${getEdForm(queryType)}`}
|
||||
status={status}
|
||||
disabled={disabled}
|
||||
disabledMessage="Set row permissions first"
|
||||
/>
|
||||
<Collapse.Content>
|
||||
<div
|
||||
title={disabled ? 'Set row permissions first' : ''}
|
||||
className="grid gap-2"
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<p>
|
||||
Allow role <strong>{roleName}</strong>{' '}
|
||||
{getAccessText(queryType)}
|
||||
|
||||
<strong>columns</strong>:
|
||||
</p>
|
||||
</div>
|
||||
const handleUpdate = (fieldName: string) => {
|
||||
setValue(
|
||||
'query_root_fields',
|
||||
queryRootFields.filter((field: string) => field !== 'select_by_pk')
|
||||
);
|
||||
setValue(
|
||||
'subscription_root_fields',
|
||||
subscriptionRootFields.filter(
|
||||
(field: string) => field !== 'select_by_pk'
|
||||
)
|
||||
);
|
||||
setValue(`columns.${fieldName}`, !selectedColumns[fieldName]);
|
||||
};
|
||||
|
||||
<fieldset className="flex gap-4 flex-wrap">
|
||||
{columns?.map(fieldName => (
|
||||
<label key={fieldName} className="flex gap-2 items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
title={disabled ? 'Set a row permission first' : ''}
|
||||
disabled={disabled}
|
||||
style={{ marginTop: '0px !important' }}
|
||||
className="rounded shadow-sm border border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
|
||||
{...register(`columns.${fieldName}`)}
|
||||
/>
|
||||
<i>{fieldName}</i>
|
||||
</label>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
title={disabled ? 'Set a row permission first' : ''}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
data-test="toggle-all-col-btn"
|
||||
>
|
||||
Toggle All
|
||||
</Button>
|
||||
</fieldset>
|
||||
</div>
|
||||
{/* {getExternalTablePermissionsMsg()} */}
|
||||
</Collapse.Content>
|
||||
</Collapse>
|
||||
const permissionsModalTitle = getPermissionsModalTitle({
|
||||
scenario: 'pks',
|
||||
role: roleName,
|
||||
primaryKeyColumns: tableColumns
|
||||
?.filter(column => column.isPrimaryKey)
|
||||
?.map(column => column.name)
|
||||
?.join(','),
|
||||
});
|
||||
|
||||
const permissionsModalDescription = getPermissionsModalDescription('pks');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Collapse defaultOpen={!disabled}>
|
||||
<Collapse.Header
|
||||
title={`Column ${queryType} permissions`}
|
||||
tooltip={`Choose columns allowed to be ${getEdForm(queryType)}`}
|
||||
status={status}
|
||||
disabled={disabled}
|
||||
disabledMessage="Set row permissions first"
|
||||
/>
|
||||
<Collapse.Content>
|
||||
<div
|
||||
title={disabled ? 'Set row permissions first' : ''}
|
||||
className="grid gap-2"
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<p>
|
||||
Allow role <strong>{roleName}</strong>{' '}
|
||||
{getAccessText(queryType)}
|
||||
|
||||
<strong>columns</strong>:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<fieldset className="flex gap-4 flex-wrap">
|
||||
{columns?.map(fieldName => (
|
||||
<label key={fieldName} className="flex gap-2 items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
title={disabled ? 'Set a row permission first' : ''}
|
||||
disabled={disabled}
|
||||
style={{ marginTop: '0px !important' }}
|
||||
className="rounded shadow-sm border border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
|
||||
checked={selectedColumns[fieldName]}
|
||||
onChange={() => {
|
||||
const hideModal = isPermissionModalDisabled();
|
||||
if (
|
||||
!hideModal &&
|
||||
!showConfirmation &&
|
||||
checkIfConfirmationIsNeeded(
|
||||
fieldName,
|
||||
tableColumns,
|
||||
selectedColumns,
|
||||
queryRootFields,
|
||||
subscriptionRootFields
|
||||
)
|
||||
) {
|
||||
setShowConfirmationModal(fieldName);
|
||||
return;
|
||||
}
|
||||
setValue(
|
||||
`columns.${fieldName}`,
|
||||
!selectedColumns[fieldName]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<i>{fieldName}</i>
|
||||
</label>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
title={disabled ? 'Set a row permission first' : ''}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
data-test="toggle-all-col-btn"
|
||||
>
|
||||
Toggle All
|
||||
</Button>
|
||||
</fieldset>
|
||||
</div>
|
||||
{/* {getExternalTablePermissionsMsg()} */}
|
||||
</Collapse.Content>
|
||||
</Collapse>
|
||||
{showConfirmation && (
|
||||
<PermissionsConfirmationModal
|
||||
title={permissionsModalTitle}
|
||||
description={permissionsModalDescription}
|
||||
onClose={() => setShowConfirmationModal(null)}
|
||||
onSubmit={() => {
|
||||
handleUpdate(showConfirmation);
|
||||
setShowConfirmationModal(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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<typeof PermissionsConfirmationModal>;
|
||||
|
||||
export const Base: Story<Props> = args => (
|
||||
<PermissionsConfirmationModal {...args} />
|
||||
);
|
||||
|
||||
Base.args = {
|
||||
title: <>title</>,
|
||||
description: <>description</>,
|
||||
};
|
@ -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<CustomDialogFooterProps> = ({
|
||||
onClose,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const storeDoNotShowPermissionsDialogFlag = (enabled: string | boolean) => {
|
||||
setLSItem(
|
||||
LS_KEYS.permissionConfirmationModalStatus,
|
||||
enabled ? 'disabled' : 'enabled'
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center border-t border-gray-300 bg-white p-sm">
|
||||
<div className="flex-grow">
|
||||
<Checkbox
|
||||
name="noPermissionsConfirmationDialog"
|
||||
onCheckedChange={enabled => {
|
||||
storeDoNotShowPermissionsDialogFlag(enabled);
|
||||
}}
|
||||
>
|
||||
<div>Don't ask me again</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<div className="ml-2">
|
||||
<Button mode="primary" onClick={onSubmit}>
|
||||
Disable
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
onSubmit: () => void;
|
||||
onClose: () => void;
|
||||
title: React.ReactElement;
|
||||
description: React.ReactElement;
|
||||
};
|
||||
|
||||
export const PermissionsConfirmationModal: React.FC<Props> = ({
|
||||
onSubmit,
|
||||
onClose,
|
||||
title,
|
||||
description,
|
||||
}) => {
|
||||
return (
|
||||
<Dialog
|
||||
title=""
|
||||
hasBackdrop
|
||||
footer={<CustomDialogFooter onSubmit={onSubmit} onClose={onClose} />}
|
||||
>
|
||||
<Analytics name="PermissionsConfirmationModal" {...REDACT_EVERYTHING}>
|
||||
<div className="flex items-top p-md">
|
||||
<div className="text-yellow-500">
|
||||
<FaExclamationTriangle className="w-9 h-9 mr-md fill-current" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold">{title}</p>
|
||||
<div className="overflow-y-auto max-h-[calc(100vh-14rem)]">
|
||||
<p className="m-0">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Analytics>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@ -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){' '}
|
||||
<span className="font-mono italic">{primaryKeyColumns}</span> for role{' '}
|
||||
{role}?
|
||||
</>
|
||||
);
|
||||
|
||||
export const getPermissionsModalDescription = (scenario: Scenario) =>
|
||||
scenario === 'aggregate' ? (
|
||||
<>
|
||||
<span className="font-mono italic">select_aggregate</span> will be
|
||||
disabled in GraphQL root field visibility since the permission is removed.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="font-mono italic">select_by_pk</span> 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;
|
||||
};
|
@ -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 = () => (
|
||||
<div>
|
||||
Allow the following root fields under the <b>Query root field</b>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SubscriptionRootFieldDescription = () => (
|
||||
<div>
|
||||
Allow the following root fields under the <b>Subscription root field</b>
|
||||
</div>
|
||||
);
|
||||
|
||||
export interface ColumnPermissionsSectionProps {
|
||||
columns?: string[];
|
||||
filterType: string;
|
||||
table: Table;
|
||||
dataSourceName: string;
|
||||
}
|
||||
|
||||
export const ColumnRootFieldPermissions: React.FC<ColumnPermissionsSectionProps> =
|
||||
({ 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 (
|
||||
<Collapse defaultOpen={!disabled}>
|
||||
<Collapse.Header
|
||||
title="Root field permissions"
|
||||
tooltip="Choose root fields to be added under the query and subscription root fields."
|
||||
status={getSectionStatusLabel({
|
||||
queryRootPermissions: queryRootFields,
|
||||
subscriptionRootPermissions: subscriptionRootFields,
|
||||
hasEnabledAggregations,
|
||||
hasSelectedPrimaryKeys,
|
||||
isSubscriptionStreamingEnabled,
|
||||
})}
|
||||
disabledMessage="Set row permissions first"
|
||||
/>
|
||||
<Collapse.Content>
|
||||
<div title={bodyTitle}>
|
||||
<div
|
||||
className={`px-md mb-xs flex items-center ${clsx(
|
||||
disabled && `opacity-70 pointer-events-none`
|
||||
)}`}
|
||||
>
|
||||
<Switch
|
||||
checked={isRootPermissionsSwitchedOn}
|
||||
onCheckedChange={onEnableSectionSwitchChange}
|
||||
/>
|
||||
<div className="mx-xs">
|
||||
Enable GraphQL root field visibility customization.
|
||||
</div>
|
||||
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={
|
||||
<Tooltip tooltipContentChildren>
|
||||
By enabling this you can customize the root field
|
||||
permissions. When this switch is turned off, all values are
|
||||
enabled by default.
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FaQuestionCircle aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
<div
|
||||
className={`px-md ${clsx(
|
||||
!isRootPermissionsSwitchedOn && 'hidden'
|
||||
)}`}
|
||||
>
|
||||
<SelectPermissionsRow
|
||||
currentPermissions={queryRootFields}
|
||||
description={<QueryRootFieldDescription />}
|
||||
hasEnabledAggregations={hasEnabledAggregations}
|
||||
hasSelectedPrimaryKeys={hasSelectedPrimaryKeys}
|
||||
isSubscriptionStreamingEnabled={isSubscriptionStreamingEnabled}
|
||||
permissionFields={queryRootPermissionFields}
|
||||
permissionType={QUERY_ROOT_VALUES}
|
||||
onToggleAll={() =>
|
||||
onToggleAll(QUERY_ROOT_VALUES, queryRootFields)
|
||||
}
|
||||
onUpdate={onUpdatePermission}
|
||||
/>
|
||||
<SelectPermissionsRow
|
||||
currentPermissions={subscriptionRootFields}
|
||||
description={<SubscriptionRootFieldDescription />}
|
||||
hasEnabledAggregations={hasEnabledAggregations}
|
||||
hasSelectedPrimaryKeys={hasSelectedPrimaryKeys}
|
||||
isSubscriptionStreamingEnabled={isSubscriptionStreamingEnabled}
|
||||
permissionFields={getFilteredSubscriptionRootPermissionFields(
|
||||
subscriptionRootPermissionFields
|
||||
)}
|
||||
permissionType={SUBSCRIPTION_ROOT_VALUES}
|
||||
onToggleAll={() =>
|
||||
onToggleAll(SUBSCRIPTION_ROOT_VALUES, subscriptionRootFields)
|
||||
}
|
||||
onUpdate={onUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Collapse.Content>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnRootFieldPermissions;
|
@ -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<Props> = ({
|
||||
permission,
|
||||
onPermissionChange,
|
||||
disabled = false,
|
||||
checked = true,
|
||||
title,
|
||||
}) => (
|
||||
<div className="mr-sm">
|
||||
<div className="checkbox">
|
||||
<label title={title}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="legacy-input-fix disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||||
checked={checked}
|
||||
value={permission}
|
||||
onChange={() => onPermissionChange(permission)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<i>{permission}</i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
@ -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<Props> = ({
|
||||
text,
|
||||
onToggle,
|
||||
}) => (
|
||||
<div className="flex items-center">
|
||||
<span className="mr-sm ">{text}</span>
|
||||
<Button size="sm" onClick={onToggle} data-test="toggle-all-col-btn">
|
||||
<div className="font-semibold">Toggle All</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
@ -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<Props> = ({
|
||||
onToggleAll,
|
||||
permissionFields,
|
||||
currentPermissions,
|
||||
onUpdate,
|
||||
description,
|
||||
hasEnabledAggregations,
|
||||
hasSelectedPrimaryKeys,
|
||||
permissionType,
|
||||
isSubscriptionStreamingEnabled,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<SelectPermissionSectionHeader
|
||||
onToggle={onToggleAll}
|
||||
text={description}
|
||||
/>
|
||||
<div className="px-md flex">
|
||||
{permissionFields?.map(permission => {
|
||||
const permissionCheckboxState = getPermissionCheckboxState({
|
||||
permission,
|
||||
hasEnabledAggregations,
|
||||
hasSelectedPrimaryKeys,
|
||||
isSubscriptionStreamingEnabled,
|
||||
rootPermissions: currentPermissions,
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectPermissionFields
|
||||
key={permission}
|
||||
title={
|
||||
currentPermissions === null
|
||||
? 'Set row permissions first'
|
||||
: permissionCheckboxState.title
|
||||
}
|
||||
disabled={permissionCheckboxState.disabled}
|
||||
checked={permissionCheckboxState.checked}
|
||||
onPermissionChange={(value: PermissionRootType) => {
|
||||
onUpdate(permissionType, value, currentPermissions);
|
||||
}}
|
||||
permission={permission}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -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,
|
||||
};
|
||||
};
|
@ -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;
|
||||
};
|
@ -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);
|
||||
}
|
||||
);
|
||||
});
|
@ -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<string, boolean | undefined>,
|
||||
columns: TableColumn[]
|
||||
) => {
|
||||
return !!columns.find(column => {
|
||||
const isPrimaryKey = column.isPrimaryKey;
|
||||
const colName = column.name;
|
||||
const hasPickedColumn = selectedColumns[colName];
|
||||
return hasPickedColumn && isPrimaryKey;
|
||||
});
|
||||
};
|
@ -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,
|
||||
|
@ -36,6 +36,7 @@ const metadata: Metadata = {
|
||||
},
|
||||
allow_aggregations: true,
|
||||
limit: 3,
|
||||
subscription_root_fields: ['select', 'select_by_pk'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -35,11 +35,14 @@ const defaultValuesMockResult: ReturnType<typeof createDefaultValues> = {
|
||||
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);
|
||||
});
|
||||
|
@ -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);
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
import { LS_KEYS, getLSItem } from '@/utils/localStorage';
|
||||
|
||||
export const isPermissionModalDisabled = () =>
|
||||
getLSItem(LS_KEYS.permissionConfirmationModalStatus) === 'disabled';
|
@ -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'),
|
||||
|
@ -25,8 +25,8 @@ export interface SelectPermissionDefinition {
|
||||
columns?: string[];
|
||||
filter?: Record<string, unknown>;
|
||||
allow_aggregations?: boolean;
|
||||
query_root_fields?: string[];
|
||||
subscription_root_fields?: string[];
|
||||
query_root_fields?: string[] | null;
|
||||
subscription_root_fields?: string[] | null;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
|
@ -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 },
|
||||
|
Loading…
Reference in New Issue
Block a user