console: add clone permissions to GDC permissions tab

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7620
Co-authored-by: Julian <843342+okjulian@users.noreply.github.com>
GitOrigin-RevId: 0e94fe8f6dc4233233a3c038c0d48a3f6c4dab50
This commit is contained in:
Julian@Hasura 2023-02-07 02:16:38 -03:00 committed by hasura-bot
parent a5578cb4bd
commit 4062d5f515
23 changed files with 281 additions and 148 deletions

View File

@ -39,5 +39,8 @@ export const alloy: Database = {
const { name, schema } = table as AlloyDbTable;
return schema === 'public' ? name : `${schema}_${name}`;
},
getSupportedQueryTypes: async () => {
return ['select', 'insert', 'update', 'delete'];
},
},
};

View File

@ -39,5 +39,8 @@ export const bigquery: Database = {
const { name, dataset } = table as BigQueryTable;
return `${dataset}_${name}`;
},
getSupportedQueryTypes: async () => {
return ['select'];
},
},
};

View File

@ -74,5 +74,8 @@ export const citus: Database = {
const { name, schema } = table as CitusTable;
return schema === 'public' ? name : `${schema}_${name}`;
},
getSupportedQueryTypes: async () => {
return ['select', 'insert', 'update', 'delete'];
},
},
};

View File

@ -72,5 +72,8 @@ export const cockroach: Database = {
const { name, schema } = table as CockroachDBTable;
return schema === 'public' ? name : `${schema}_${name}`;
},
getSupportedQueryTypes: async () => {
return ['select', 'insert', 'update', 'delete'];
},
},
};

View File

@ -3,6 +3,7 @@ import { Database, Feature } from '..';
export const defaultDatabaseProps: Database = {
config: {
getDefaultQueryRoot: async () => Feature.NotImplemented,
getSupportedQueryTypes: async () => Feature.NotImplemented,
},
introspection: {
getDriverInfo: async () => Feature.NotImplemented,

View File

@ -42,5 +42,8 @@ export const gdc: Database = {
getDefaultQueryRoot: async (table: Table) => {
return (table as GDCTable).join('_');
},
getSupportedQueryTypes: async () => {
return ['select'];
},
},
};

View File

@ -47,6 +47,7 @@ import {
} from './api';
import { getAllSourceKinds } from './common/getAllSourceKinds';
import { getTableName } from './common/getTableName';
import { QueryType } from '../Permissions/types';
export enum Feature {
NotImplemented = 'Not Implemented',
@ -110,6 +111,9 @@ export type Database = {
getDefaultQueryRoot: (
table: Table
) => Promise<string | Feature.NotImplemented>;
getSupportedQueryTypes: (
table: Table
) => Promise<QueryType[] | Feature.NotImplemented>;
};
};
@ -431,11 +435,18 @@ export const DataSource = (httpClient: AxiosInstance) => ({
}) => {
const database = await getDatabaseMethods({ dataSourceName, httpClient });
const result = await database.config.getDefaultQueryRoot(table);
return database.config.getDefaultQueryRoot(table);
},
getSupportedQueryTypes: async ({
dataSourceName,
table,
}: {
dataSourceName: string;
table: Table;
}) => {
const database = await getDatabaseMethods({ dataSourceName, httpClient });
if (result === Feature.NotImplemented) return Feature.NotImplemented;
return result;
return database.config.getSupportedQueryTypes(table);
},
});

View File

@ -63,5 +63,8 @@ export const mssql: Database = {
const { name, schema } = table as MssqlTable;
return schema === 'dbo' ? name : `${schema}_${name}`;
},
getSupportedQueryTypes: async () => {
return ['select', 'insert', 'update', 'delete'];
},
},
};

View File

@ -22,4 +22,8 @@ export const mysql: Database = {
query: {
getTableRows: async () => Feature.NotImplemented,
},
config: {
getDefaultQueryRoot: async () => Feature.NotImplemented,
getSupportedQueryTypes: async () => Feature.NotImplemented,
},
};

View File

@ -39,5 +39,8 @@ export const postgres: Database = {
const { name, schema } = table as PostgresTable;
return schema === 'public' ? name : `${schema}_${name}`;
},
getSupportedQueryTypes: async () => {
return ['select', 'insert', 'update', 'delete'];
},
},
};

View File

@ -1,6 +1,10 @@
import { DataSource, Feature } from '@/features/DataSource';
import { DataTarget } from '@/features/Datasources';
import { Table } from '@/features/hasura-metadata-types';
import { useHttpClient } from '@/features/Network';
import type { QualifiedTable } from '@/metadata/types';
import { useQuery } from 'react-query';
import { MetadataSelector } from './metadataSelectors';
import { useMetadata } from './useMetadata';
@ -18,6 +22,26 @@ export const useRemoteDatabaseRelationships = (target: DataTarget) => {
);
};
export const useSupportedQueryTypes = ({
dataSourceName,
table,
}: {
table: Table;
dataSourceName: string;
}) => {
const httpClient = useHttpClient();
return useQuery({
queryKey: ['supported-query-types'],
queryFn: async () => {
return DataSource(httpClient).getSupportedQueryTypes({
dataSourceName,
table,
});
},
});
};
export const useRemoteSchemaRelationships = (
database: string,
table: QualifiedTable

View File

@ -5,6 +5,7 @@ export {
useMetadataTables,
useRemoteDatabaseRelationships,
useRemoteSchemaRelationships,
useSupportedQueryTypes,
} from './hooks/useMetadataTables';
export { useMetadataVersion } from './hooks/useMetadataVersion';
export { useMetadataTableComputedFields } from './hooks/useMetadataTableComputedFields';

View File

@ -2,11 +2,20 @@ import React from 'react';
import { useConsoleForm } from '@/new-components/Form';
import { Button } from '@/new-components/Button';
import { IndicatorCard } from '@/new-components/IndicatorCard';
import {
MetadataSelector,
useMetadata,
useRoles,
useSupportedQueryTypes,
} from '@/features/MetadataAPI';
import { getTableDisplayName } from '@/features/DatabaseRelationships';
import { PermissionsSchema, schema } from './../schema';
import { AccessType, QueryType } from '../types';
import {
AggregationSection,
BackendOnlySection,
ClonePermissionsSection,
ColumnPermissionsSection,
ColumnPresetsSection,
RowPermissionsSection,
@ -37,14 +46,24 @@ const Component = (props: ComponentProps) => {
data,
} = props;
const { data: metadataTables } = useMetadata(
MetadataSelector.getTables(dataSourceName)
);
const tables = metadataTables?.map(t => t.table) ?? [];
// functions fired when the form is submitted
const { updatePermissions, deletePermissions } = useUpdatePermissions({
dataSourceName,
table,
tables,
queryType,
roleName,
accessType,
});
const { data: roles } = useRoles();
const { data: supportedQueryTypes } = useSupportedQueryTypes({
dataSourceName,
table,
});
const onSubmit = async (formData: PermissionsSchema) => {
await updatePermissions.submit(formData);
@ -56,9 +75,6 @@ const Component = (props: ComponentProps) => {
handleClose();
};
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];
@ -74,12 +90,6 @@ const Component = (props: ComponentProps) => {
},
});
if (isSubmittingError) {
return (
<IndicatorCard status="negative">Error submitting form</IndicatorCard>
);
}
// allRowChecks relates to other queries and is for duplicating from others
const allRowChecks = defaultValues?.allRowChecks;
@ -159,14 +169,14 @@ const Component = (props: ComponentProps) => {
)}
<hr className="my-4" />
{/* {!!tableNames?.length && (
<ClonePermissionsSection
queryType={queryType}
tables={tableNames}
supportedQueryTypes={supportedQueries}
roles={allRoles}
/>
)} */}
<ClonePermissionsSection
queryType={queryType}
supportedQueryTypes={supportedQueryTypes}
tables={tables}
roles={roles}
/>
<div className="pt-2 flex gap-2">
<Button
type="submit"

View File

@ -3,10 +3,11 @@ import { allowedMetadataTypes } from '@/features/MetadataAPI';
import { AccessType, QueryType } from '../../types';
import { PermissionsSchema } from '../../schema';
import { createInsertArgs } from './utils';
import { Table } from '@/features/hasura-metadata-types';
interface CreateBodyArgs {
dataSourceName: string;
table: unknown;
table: Table;
roleName: string;
resourceVersion: number;
}
@ -105,6 +106,7 @@ interface CreateInsertBodyArgs extends CreateBodyArgs {
accessType: AccessType;
existingPermissions: any;
driver: string;
tables: Table[];
}
export interface InsertBodyResult {
@ -123,6 +125,7 @@ const createInsertBody = ({
resourceVersion,
existingPermissions,
driver,
tables,
}: CreateInsertBodyArgs): InsertBodyResult => {
const args = createInsertArgs({
driver,
@ -133,6 +136,7 @@ const createInsertBody = ({
formData,
accessType,
existingPermissions,
tables,
});
const formBody = {

View File

@ -7,6 +7,7 @@ const selectArgs: CreateInsertArgs = {
table: 'users',
queryType: 'insert',
role: 'user',
tables: [],
formData: {
queryType: 'select',
filterType: 'none',
@ -62,6 +63,7 @@ test('create select args object from form data', () => {
allow_aggregations: false,
columns: ['email', 'type'],
filter: {},
presets: [],
},
role: 'user',
source: 'default',

View File

@ -3,10 +3,14 @@ import produce from 'immer';
import { allowedMetadataTypes } from '@/features/MetadataAPI';
import { AccessType } from '../../types';
import { PermissionsSchema } from '../../schema';
import { PermissionsSchema, Presets } from '../../schema';
import { areTablesEqual } from '@/features/hasura-metadata-api';
import { Table } from '@/features/hasura-metadata-types';
import { getTableDisplayName } from '@/features/DatabaseRelationships';
type SelectPermissionMetadata = {
columns: string[];
presets: Presets;
filter: Record<string, any>;
allow_aggregations?: boolean;
limit?: number;
@ -39,6 +43,7 @@ const createSelectObject = (input: PermissionsSchema) => {
const permissionObject: SelectPermissionMetadata = {
columns,
filter,
presets: [],
allow_aggregations: input.aggregationEnabled,
};
@ -57,7 +62,7 @@ const createSelectObject = (input: PermissionsSchema) => {
return permissionObject;
}
return {};
throw new Error('Case not handled');
};
/**
@ -65,14 +70,14 @@ const createSelectObject = (input: PermissionsSchema) => {
*/
const createPermission = (formData: PermissionsSchema) => {
switch (formData.queryType) {
case 'insert':
return {};
case 'select':
return createSelectObject(formData);
case 'insert':
throw new Error('Case not handled');
case 'update':
return {};
throw new Error('Case not handled');
case 'delete':
return {};
throw new Error('Case not handled');
default:
throw new Error('Case not handled');
}
@ -81,6 +86,7 @@ const createPermission = (formData: PermissionsSchema) => {
export interface CreateInsertArgs {
dataSourceName: string;
table: unknown;
tables: Table[];
queryType: any;
role: string;
accessType: AccessType;
@ -107,6 +113,7 @@ export const createInsertArgs = ({
formData,
existingPermissions,
driver,
tables,
}: CreateInsertArgs) => {
const permission = createPermission(formData);
@ -127,7 +134,7 @@ export const createInsertArgs = ({
// determine if args from form already exist
const permissionExists = existingPermissions.find(
existingPermission =>
JSON.stringify(existingPermission.table) === JSON.stringify(table) &&
areTablesEqual(existingPermission.table, table) &&
existingPermission.role === role &&
existingPermission.queryType === queryType
);
@ -144,60 +151,59 @@ export const createInsertArgs = ({
} as (typeof initialArgs)[0]);
}
// this has been commented out as cloned permissions is not currently used
// it's been left in because it could be useful when clone permissions is added back in
// last item is always empty default
// const clonedPermissions = formData?.clonePermissions?.slice(0, -1);
const clonedPermissions = formData?.clonePermissions?.slice(0, -1);
// if (clonedPermissions?.length) {
// clonedPermissions.forEach(clonedPermission => {
// // if permissions are being applied to a different table
// // columns and presets should be blank
// const permissionWithColumnsAndPresetsRemoved = produce(
// permission,
// d => {
// if (clonedPermission.tableName !== table) {
// d.columns = [];
// d.presets = {};
// }
if (clonedPermissions?.length) {
clonedPermissions.forEach(clonedPermission => {
const clonedPermissionTable = tables.find(
t => getTableDisplayName(t) === clonedPermission.tableName
);
// if permissions are being applied to a different table
// columns and presets should be blank
const permissionWithColumnsAndPresetsRemoved = produce(
permission,
d => {
if (!areTablesEqual(clonedPermissionTable, table)) {
d.columns = [];
d.presets = [];
}
// return d;
// }
// );
// // add each closed permission to args
// draft.push({
// type: `${driver}_create_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
// args: {
// table: clonedPermission.tableName || '',
// role: clonedPermission.roleName || '',
// permission: permissionWithColumnsAndPresetsRemoved,
// source: dataSourceName,
// },
// });
return d;
}
);
// add each closed permission to args
draft.push({
type: `${driver}_create_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
args: {
table: clonedPermissionTable || '',
role: clonedPermission.roleName || '',
permission: permissionWithColumnsAndPresetsRemoved,
source: dataSourceName,
},
});
// // determined if the cloned permission already exists
// const clonedPermissionExists = existingPermissions.find(
// existingPermission =>
// JSON.stringify(existingPermission.table) ===
// JSON.stringify(clonedPermission.tableName) &&
// existingPermission.role === clonedPermission.roleName &&
// existingPermission.queryType === clonedPermission.queryType
// );
// determined if the cloned permission already exists
const clonedPermissionExists = existingPermissions.find(
existingPermission =>
areTablesEqual(existingPermission.table, clonedPermissionTable) &&
existingPermission.role === clonedPermission.roleName &&
existingPermission.queryType === clonedPermission.queryType
);
// // if it already exists drop it
// if (clonedPermissionExists) {
// draft.unshift({
// type: `${driver}_drop_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
// args: {
// table: clonedPermission.tableName,
// role: clonedPermission.roleName,
// source: dataSourceName,
// },
// } as typeof initialArgs[0]);
// }
// });
// }
// if it already exists drop it
if (clonedPermissionExists) {
draft.unshift({
type: `${driver}_drop_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
args: {
table: clonedPermissionTable,
role: clonedPermission.roleName,
source: dataSourceName,
},
} as (typeof initialArgs)[0]);
}
});
}
});
return args;

View File

@ -5,10 +5,14 @@ import { Button } from '@/new-components/Button';
import { Collapse } from '@/new-components/deprecated';
import { useIsDisabled } from '../hooks/useIsDisabled';
import { QueryType } from '../../types';
import { Permission } from '../../schema';
import { Feature } from '@/features/DataSource';
import { Table } from '@/features/hasura-metadata-types';
import { getTableDisplayName } from '@/features/DatabaseRelationships';
interface ClonePermissionsRowProps {
id: number;
tables: string[];
tables: Table[];
currentQueryType: QueryType;
queryTypes: string[];
roleNames: string[];
@ -29,7 +33,7 @@ export const ClonePermissionsRow: React.FC<ClonePermissionsRowProps> = ({
const { register, watch } = useFormContext();
const formKey = 'clonePermissions';
const watched: ClonePermission = watch(`${formKey}.${id}`);
const watched: Permission = watch(`${formKey}.${id}`);
const allDisabled = useIsDisabled(currentQueryType as QueryType);
@ -46,11 +50,14 @@ export const ClonePermissionsRow: React.FC<ClonePermissionsRowProps> = ({
Table Name
</option>
{tables?.map(tableName => (
<option key={tableName} value={tableName}>
{tableName}
</option>
))}
{tables?.map(table => {
const tableName = getTableDisplayName(table);
return (
<option key={tableName} value={tableName}>
{tableName}
</option>
);
})}
</select>
</div>
@ -105,20 +112,13 @@ export const ClonePermissionsRow: React.FC<ClonePermissionsRowProps> = ({
};
export interface ClonePermissionsSectionProps {
queryType: any;
tables: string[];
supportedQueryTypes: string[];
queryType: string;
tables: Table[];
supportedQueryTypes: QueryType[] | Feature | undefined;
roles: string[];
defaultOpen?: boolean;
}
export interface ClonePermission {
id: number;
tableName: string;
queryType: any;
roleName: string;
}
export const ClonePermissionsSection: React.FC<
ClonePermissionsSectionProps
> = ({ queryType, tables, supportedQueryTypes, roles, defaultOpen }) => {
@ -131,7 +131,7 @@ export const ClonePermissionsSection: React.FC<
name: 'clonePermissions',
});
const watched: ClonePermission[] = watch('clonePermissions');
const watched: Permission[] = watch('clonePermissions');
const controlledFields = fields.map((field, index) => {
return {
...field,
@ -152,9 +152,14 @@ export const ClonePermissionsSection: React.FC<
tableName: '',
queryType: '',
roleName: '',
} as ClonePermission);
} as Permission);
}
}, [controlledFields, append]);
const queryTypes =
supportedQueryTypes === Feature.NotImplemented ||
supportedQueryTypes === undefined
? []
: supportedQueryTypes;
return (
<Collapse defaultOpen={defaultOpen}>
@ -176,7 +181,7 @@ export const ClonePermissionsSection: React.FC<
id={index}
tables={tables}
currentQueryType={queryType as QueryType}
queryTypes={supportedQueryTypes}
queryTypes={queryTypes}
roleNames={roles}
remove={() => remove(index)}
/>

View File

@ -71,7 +71,6 @@ export const ColumnRootFieldPermissions: React.FC<
'query_root_fields',
'subscription_root_fields',
]);
console.log('table', table);
const disabled = filterType === 'none';
const { columns: tableColumns } = useListAllTableColumns(
dataSourceName,
@ -156,40 +155,49 @@ export const ColumnRootFieldPermissions: React.FC<
<FaQuestionCircle aria-hidden="true" />
</OverlayTrigger>
</div>
<div
className={`px-md ${clsx(
!isRootPermissionsSwitchedOn && 'hidden'
)}`}
<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>
}
>
<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>
<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>
</Collapse.Content>
</Collapse>

View File

@ -31,14 +31,21 @@ export const createOperatorsObject = ({
name: key,
typeName: key,
type: 'boolOperator',
[key]: value.map((each: Record<string, any>) =>
createOperatorsObject({
tableName,
schema,
existingPermission: each,
tableConfig,
})
),
[key]: Array.isArray(value)
? value.map((each: Record<string, any>) =>
createOperatorsObject({
tableName,
schema,
existingPermission: each,
tableConfig,
})
)
: createOperatorsObject({
tableName,
schema,
existingPermission: value,
tableConfig,
}),
};
}

View File

@ -9,10 +9,12 @@ import { AccessType, QueryType } from '../../../types';
import { api } from '../../api';
import { isPermission, keyToPermission } from '../../../utils';
import { PermissionsSchema } from '../../../schema';
import { Table } from '@/features/hasura-metadata-types';
export interface UseSubmitFormArgs {
dataSourceName: string;
table: unknown;
table: Table;
tables: Table[];
roleName: string;
queryType: QueryType;
accessType: AccessType;
@ -21,7 +23,7 @@ export interface UseSubmitFormArgs {
interface ExistingPermissions {
role: string;
queryType: QueryType;
table: unknown;
table: Table;
}
interface GetAllPermissionsArgs {
@ -62,7 +64,8 @@ const getAllPermissions = async ({
};
export const useSubmitForm = (args: UseSubmitFormArgs) => {
const { dataSourceName, table, roleName, queryType, accessType } = args;
const { dataSourceName, table, roleName, queryType, accessType, tables } =
args;
const queryClient = useQueryClient();
const httpClient = useHttpClient();
@ -92,6 +95,7 @@ export const useSubmitForm = (args: UseSubmitFormArgs) => {
dataSourceName,
driver: metadataSource.kind,
table,
tables,
roleName,
queryType,
accessType,

View File

@ -2,10 +2,12 @@ import { useSubmitForm } from './useSubmitForm';
import { useDeletePermission } from './useDeletePermission';
import { AccessType, QueryType } from '../../../types';
import { Table } from '@/features/hasura-metadata-types';
export interface UseUpdatePermissionsArgs {
dataSourceName: string;
table: unknown;
table: Table;
tables: Table[];
roleName: string;
queryType: QueryType;
accessType: AccessType;
@ -14,6 +16,7 @@ export interface UseUpdatePermissionsArgs {
export const useUpdatePermissions = ({
dataSourceName,
table,
tables,
roleName,
queryType,
accessType,
@ -21,6 +24,7 @@ export const useUpdatePermissions = ({
const updatePermissions = useSubmitForm({
dataSourceName,
table,
tables,
roleName,
queryType,
accessType,

View File

@ -1 +1,2 @@
export * from './PermissionsTab';
export * from './types';

View File

@ -11,26 +11,46 @@ const presets = z.optional(
)
);
export type Presets = z.infer<typeof presets>;
export type PermissionsSchema = z.infer<typeof schema>;
const queryType = z.union([
z.literal(''),
z.literal('insert'),
z.literal('select'),
z.literal('update'),
z.literal('delete'),
]);
export type Permission = z.infer<typeof permission>;
const permission = z.object({
tableName: z.string(),
queryType,
roleName: z.string(),
});
export const schema = z.discriminatedUnion('queryType', [
z.object({
queryType: z.literal('insert'),
checkType: z.string(),
filterType: z.string(),
check: z.any(),
columns,
presets,
backendOnly: z.boolean().optional(),
clonePermissions: z.array(z.any()).optional(),
clonePermissions: z.array(permission).optional(),
}),
z.object({
queryType: z.literal('select'),
filterType: z.string(),
filter: z.any(),
columns,
presets,
rowCount: z.string().optional(),
aggregationEnabled: z.boolean().optional(),
clonePermissions: z.array(z.any()).optional(),
clonePermissions: z.array(permission).optional(),
query_root_fields: z.array(z.string()).nullable().optional(),
subscription_root_fields: z.array(z.string()).nullable().optional(),
}),
@ -43,14 +63,14 @@ export const schema = z.discriminatedUnion('queryType', [
check: z.any(),
presets,
backendOnly: z.boolean().optional(),
clonePermissions: z.array(z.any()).optional(),
clonePermissions: z.array(permission).optional(),
}),
z.object({
queryType: z.literal('delete'),
filterType: z.string(),
filter: z.any(),
backendOnly: z.boolean().optional(),
clonePermissions: z.array(z.any()).optional(),
clonePermissions: z.array(permission).optional(),
}),
]);