mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-13 11:07:11 +03:00
console: Support computed fields in new permissions UI DSF-426
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10493 GitOrigin-RevId: 6c326ccc2ebb636feb8f1fa73eb6f20b0757832b
This commit is contained in:
parent
73e413e1ee
commit
1b7f2451ca
@ -4,7 +4,7 @@ import { Button } from '../../../new-components/Button';
|
||||
import { IndicatorCard } from '../../../new-components/IndicatorCard';
|
||||
import {
|
||||
MetadataSelector,
|
||||
useMetadata,
|
||||
useMetadata as useLegacyMetadata,
|
||||
useRoles,
|
||||
useSupportedQueryTypes,
|
||||
} from '../../MetadataAPI';
|
||||
@ -41,6 +41,7 @@ import {
|
||||
inputValidationEnabledSchema,
|
||||
} from '../../../components/Services/Data/TablePermissions/InputValidation/InputValidation';
|
||||
import { z } from 'zod';
|
||||
import { MetadataSelectors, useMetadata } from '../../hasura-metadata-api';
|
||||
|
||||
export interface ComponentProps {
|
||||
dataSourceName: string;
|
||||
@ -70,7 +71,7 @@ const Component = (props: ComponentProps) => {
|
||||
|
||||
useScrollIntoView(permissionSectionRef, [roleName], { behavior: 'smooth' });
|
||||
|
||||
const { data: metadataTables } = useMetadata(
|
||||
const { data: metadataTables } = useLegacyMetadata(
|
||||
MetadataSelector.getTables(dataSourceName)
|
||||
);
|
||||
const tables = metadataTables?.map(t => t.table) ?? [];
|
||||
@ -197,6 +198,7 @@ const Component = (props: ComponentProps) => {
|
||||
roleName={roleName}
|
||||
queryType={queryType}
|
||||
columns={formData?.columns}
|
||||
computedFields={formData?.computed_fields}
|
||||
table={table}
|
||||
dataSourceName={dataSourceName}
|
||||
/>
|
||||
@ -281,6 +283,11 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
||||
const { columns: tableColumns, isLoading: isLoadingTables } =
|
||||
useListAllTableColumns(dataSourceName, table);
|
||||
|
||||
const metadataTableResult = useMetadata(
|
||||
MetadataSelectors.findTable(dataSourceName, table)
|
||||
);
|
||||
const computedFields = metadataTableResult.data?.computed_fields ?? [];
|
||||
|
||||
const { data: metadataSource } = useMetadataSource(dataSourceName);
|
||||
|
||||
const { data, isError, isLoading } = useFormData({
|
||||
@ -328,6 +335,7 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
||||
metadata: data?.metadata,
|
||||
table,
|
||||
tableColumns,
|
||||
tableComputedFields: computedFields,
|
||||
defaultQueryRoot: data.defaultQueryRoot,
|
||||
metadataSource,
|
||||
supportedOperators: data.supportedOperators,
|
||||
@ -357,6 +365,7 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
||||
table,
|
||||
metadata: data.metadata,
|
||||
tableColumns,
|
||||
computedFields,
|
||||
trackedTables: metadataSource.tables,
|
||||
metadataSource,
|
||||
validateInput: {
|
||||
|
@ -17,11 +17,13 @@ test('create select args object from form data', () => {
|
||||
args: {
|
||||
table: ['Album'],
|
||||
role: 'user',
|
||||
comment: '',
|
||||
permission: {
|
||||
columns: ['AlbumId', 'Title', 'ArtistId'],
|
||||
filter: { _not: { AlbumId: { _eq: 'X-Hasura-User-Id' } } },
|
||||
set: {},
|
||||
allow_aggregations: false,
|
||||
computed_fields: [],
|
||||
},
|
||||
source: 'Chinook',
|
||||
},
|
||||
@ -42,6 +44,7 @@ test('create delete args object from form data', () => {
|
||||
args: {
|
||||
table: ['Album'],
|
||||
role: 'user',
|
||||
comment: '',
|
||||
permission: { backend_only: false, filter: { Title: { _eq: 'Test' } } },
|
||||
source: 'Chinook',
|
||||
},
|
||||
@ -58,6 +61,7 @@ test('create insert args object from form data', () => {
|
||||
args: {
|
||||
table: ['Album'],
|
||||
role: 'user',
|
||||
comment: '',
|
||||
permission: {
|
||||
columns: [],
|
||||
check: {
|
||||
@ -69,6 +73,7 @@ test('create insert args object from form data', () => {
|
||||
},
|
||||
allow_upsert: true,
|
||||
set: {},
|
||||
validate_input: undefined,
|
||||
backend_only: false,
|
||||
},
|
||||
source: 'Chinook',
|
||||
|
@ -30,6 +30,7 @@ const formatFilterValues = (formFilter: Record<string, any>[] = []) => {
|
||||
|
||||
type SelectPermissionMetadata = {
|
||||
columns: string[];
|
||||
computed_fields: string[];
|
||||
set: Record<string, any>;
|
||||
filter: Record<string, any>;
|
||||
allow_aggregations?: boolean;
|
||||
@ -43,12 +44,16 @@ const createSelectObject = (input: PermissionsSchema) => {
|
||||
const columns = Object.entries(input.columns)
|
||||
.filter(({ 1: value }) => value)
|
||||
.map(([key]) => key);
|
||||
const computed_fields = Object.entries(input.computed_fields)
|
||||
.filter(({ 1: value }) => value)
|
||||
.map(([key]) => key);
|
||||
|
||||
// Input may be undefined
|
||||
const filter = formatFilterValues(input.filter);
|
||||
|
||||
const permissionObject: SelectPermissionMetadata = {
|
||||
columns,
|
||||
computed_fields,
|
||||
filter,
|
||||
set: {},
|
||||
allow_aggregations: input.aggregationEnabled,
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
SubscriptionRootPermissionType,
|
||||
QueryRootPermissionType,
|
||||
} from './RootFieldPermissions/types';
|
||||
import { MetadataSelectors, useMetadata } from '../../../hasura-metadata-api';
|
||||
|
||||
const getAccessText = (queryType: string) => {
|
||||
if (queryType === 'insert') {
|
||||
@ -35,6 +36,7 @@ export interface ColumnPermissionsSectionProps {
|
||||
queryType: QueryType;
|
||||
roleName: string;
|
||||
columns?: string[];
|
||||
computedFields?: string[];
|
||||
table: unknown;
|
||||
dataSourceName: string;
|
||||
}
|
||||
@ -85,19 +87,30 @@ const checkIfConfirmationIsNeeded = (
|
||||
);
|
||||
};
|
||||
|
||||
// @todo
|
||||
// this hasn't been fully implemented, it still needs computed columns adding
|
||||
export const ColumnPermissionsSection: React.FC<
|
||||
ColumnPermissionsSectionProps
|
||||
> = ({ roleName, queryType, columns, table, dataSourceName }) => {
|
||||
> = ({
|
||||
roleName,
|
||||
queryType,
|
||||
columns,
|
||||
table,
|
||||
computedFields,
|
||||
dataSourceName,
|
||||
}) => {
|
||||
const { setValue, watch } = useFormContext();
|
||||
const [showConfirmation, setShowConfirmationModal] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
watch();
|
||||
|
||||
const [selectedColumns, queryRootFields, subscriptionRootFields] = watch([
|
||||
const [
|
||||
selectedColumns,
|
||||
selectedComputedFields,
|
||||
queryRootFields,
|
||||
subscriptionRootFields,
|
||||
] = watch([
|
||||
'columns',
|
||||
'computed_fields',
|
||||
'query_root_fields',
|
||||
'subscription_root_fields',
|
||||
]);
|
||||
@ -112,6 +125,13 @@ export const ColumnPermissionsSection: React.FC<
|
||||
table
|
||||
);
|
||||
|
||||
const metadataTableResult = useMetadata(
|
||||
MetadataSelectors.findTable(dataSourceName, table)
|
||||
);
|
||||
const tableComputedFields = metadataTableResult.data?.computed_fields?.map(
|
||||
({ name }) => name
|
||||
);
|
||||
|
||||
const onClick = () => {
|
||||
columns?.forEach(column => {
|
||||
const toggleAllOn = status !== 'All columns';
|
||||
@ -119,6 +139,12 @@ export const ColumnPermissionsSection: React.FC<
|
||||
// otherwise toggle all off
|
||||
setValue(`columns.${column}`, toggleAllOn);
|
||||
});
|
||||
computedFields?.forEach(field => {
|
||||
const toggleAllOn = status !== 'All columns';
|
||||
// if status is not all columns: toggle all on
|
||||
// otherwise toggle all off
|
||||
setValue(`computed_fields.${field}`, toggleAllOn);
|
||||
});
|
||||
};
|
||||
|
||||
if (isError) {
|
||||
@ -206,6 +232,26 @@ export const ColumnPermissionsSection: React.FC<
|
||||
<i>{fieldName}</i>
|
||||
</label>
|
||||
))}
|
||||
{queryType === 'select' &&
|
||||
tableComputedFields?.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={selectedComputedFields[fieldName]}
|
||||
onChange={() => {
|
||||
setValue(
|
||||
`computed_fields.${fieldName}`,
|
||||
!selectedComputedFields[fieldName]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<i>{fieldName}</i>
|
||||
</label>
|
||||
))}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
|
@ -21,7 +21,8 @@ export const Operator = ({
|
||||
rowPermissionsContext
|
||||
);
|
||||
const { tables } = useContext(rootTableContext);
|
||||
const { columns, table, relationships } = useContext(tableContext);
|
||||
const { columns, table, relationships, computedFields } =
|
||||
useContext(tableContext);
|
||||
const { rootLogicalModel } = useContext(logicalModelContext);
|
||||
const parent = path[path.length - 1];
|
||||
const operatorLevelId =
|
||||
@ -73,6 +74,19 @@ export const Operator = ({
|
||||
))}
|
||||
</optgroup>
|
||||
) : null}
|
||||
{computedFields.length ? (
|
||||
<optgroup label="Computed fields">
|
||||
{computedFields.map((field, index) => (
|
||||
<option
|
||||
data-type="computedField"
|
||||
key={'computedField' + index}
|
||||
value={field.name}
|
||||
>
|
||||
{field.name}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
) : null}
|
||||
{rootLogicalModel?.fields.length ? (
|
||||
<optgroup label="Columns">
|
||||
{rootLogicalModel?.fields.map((field, index) => (
|
||||
|
@ -5,6 +5,7 @@ import { rootTableContext } from './RootTableProvider';
|
||||
import { areTablesEqual } from '../../../../../hasura-metadata-api';
|
||||
import { fieldsToColumns } from './utils/nestedObjects';
|
||||
import { rowPermissionsContext } from './RowPermissionsProvider';
|
||||
import { ComputedField } from '../../../../../../metadata/types';
|
||||
|
||||
export const tableContext = createContext<TableContext>({
|
||||
table: {},
|
||||
@ -13,6 +14,8 @@ export const tableContext = createContext<TableContext>({
|
||||
setComparator: () => {},
|
||||
columns: [],
|
||||
setColumns: () => {},
|
||||
computedFields: [],
|
||||
setComputedFields: () => {},
|
||||
relationships: [],
|
||||
setRelationships: () => {},
|
||||
});
|
||||
@ -29,6 +32,7 @@ export const TableProvider = ({
|
||||
const [table, setTableName] = useState<Table>(defaultTable || {});
|
||||
const [comparator, setComparator] = useState<string | undefined>();
|
||||
const [columns, setColumns] = useState<Columns>([]);
|
||||
const [computedFields, setComputedFields] = useState<ComputedField[]>([]);
|
||||
const [relationships, setRelationships] = useState<Relationships>([]);
|
||||
const { tables, rootTable } = useContext(rootTableContext);
|
||||
const { loadRelationships } = useContext(rowPermissionsContext);
|
||||
@ -50,6 +54,7 @@ export const TableProvider = ({
|
||||
const foundTable = tables.find(t => areTablesEqual(t.table, table));
|
||||
if (foundTable) {
|
||||
setColumns(foundTable.columns);
|
||||
setComputedFields(foundTable.computedFields);
|
||||
if (foundTable?.dataSource?.name !== rootTable?.dataSource?.name) return;
|
||||
setRelationships(
|
||||
foundTable.relationships.filter(rel => {
|
||||
@ -82,6 +87,8 @@ export const TableProvider = ({
|
||||
setRelationships,
|
||||
objectPath,
|
||||
loadRelationships,
|
||||
computedFields,
|
||||
setComputedFields,
|
||||
]);
|
||||
|
||||
return (
|
||||
@ -89,6 +96,8 @@ export const TableProvider = ({
|
||||
value={{
|
||||
columns,
|
||||
setColumns,
|
||||
computedFields,
|
||||
setComputedFields,
|
||||
table,
|
||||
setTable: setTableName,
|
||||
relationships,
|
||||
|
@ -19,6 +19,7 @@ export const tables: Tables = [
|
||||
},
|
||||
],
|
||||
relationships: [],
|
||||
computedFields: [],
|
||||
},
|
||||
{
|
||||
table: ['Artist'],
|
||||
@ -38,6 +39,7 @@ export const tables: Tables = [
|
||||
},
|
||||
],
|
||||
relationships: [],
|
||||
computedFields: [],
|
||||
},
|
||||
{
|
||||
table: ['Album'],
|
||||
@ -80,12 +82,14 @@ export const tables: Tables = [
|
||||
},
|
||||
},
|
||||
],
|
||||
computedFields: [],
|
||||
},
|
||||
{
|
||||
table: ['Customer'],
|
||||
dataSource: { name: 'SQLite', kind: 'SQLite' },
|
||||
columns: [],
|
||||
relationships: [],
|
||||
computedFields: [],
|
||||
},
|
||||
{
|
||||
table: { dataset: 'bigquery_sample', name: 'sample_table' },
|
||||
@ -149,6 +153,7 @@ export const tables: Tables = [
|
||||
},
|
||||
],
|
||||
relationships: [],
|
||||
computedFields: [],
|
||||
},
|
||||
];
|
||||
|
||||
@ -189,6 +194,7 @@ export const tableWithGeolocationSupport = [
|
||||
},
|
||||
},
|
||||
relationships: [],
|
||||
computedFields: [],
|
||||
columns: [
|
||||
{
|
||||
name: 'user_id',
|
||||
|
@ -2,6 +2,7 @@ import { Source, Table } from '../../../../../hasura-metadata-types';
|
||||
import { GraphQLType } from 'graphql';
|
||||
import { Relationship } from '../../../../../DatabaseRelationships';
|
||||
import { TableColumn } from '../../../../../DataSource';
|
||||
import { ComputedField } from '../../../../../../metadata/types';
|
||||
|
||||
export type Operators = Record<
|
||||
string,
|
||||
@ -22,6 +23,7 @@ export type Tables = Array<{
|
||||
columns: Columns;
|
||||
relationships: Relationships;
|
||||
dataSource: Pick<Source, 'kind' | 'name'> | undefined;
|
||||
computedFields: ComputedField[];
|
||||
}>;
|
||||
|
||||
export type Operator = {
|
||||
@ -40,6 +42,7 @@ export type Comparators = Record<string, Comparator>;
|
||||
|
||||
export type PermissionType =
|
||||
| 'column'
|
||||
| 'computedField'
|
||||
| 'exist'
|
||||
| 'relationship'
|
||||
| 'object'
|
||||
@ -77,6 +80,8 @@ export type TableContext = {
|
||||
setComparator: (comparator: string | undefined) => void;
|
||||
columns: Columns;
|
||||
setColumns: (columns: Columns) => void;
|
||||
computedFields: ComputedField[];
|
||||
setComputedFields: (computedFields: ComputedField[]) => void;
|
||||
relationships: Relationships;
|
||||
setRelationships: (relationships: Relationships) => void;
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ import { rowPermissionsContext } from '../RowPermissionsProvider';
|
||||
import { sourceDataTypes, SourceDataTypes } from './sourceDataTypes';
|
||||
import { rootTableContext } from '../RootTableProvider';
|
||||
import { columnDataType } from '../../../../../../DataSource/utils';
|
||||
import { ComputedField } from '../../../../../../../metadata/types';
|
||||
|
||||
function columnOperators(): Array<Operator> {
|
||||
return Object.keys(columnOperatorsInfo).reduce((acc, key) => {
|
||||
@ -152,7 +153,7 @@ export const mapScalarDataType = (
|
||||
export function useOperators({ path }: { path: string[] }) {
|
||||
const { comparators } = useContext(rowPermissionsContext);
|
||||
const { tables } = useContext(rootTableContext);
|
||||
const { columns, table } = useContext(tableContext);
|
||||
const { columns, table, computedFields } = useContext(tableContext);
|
||||
|
||||
const columnName = path[path.length - 2];
|
||||
const column = columns.find(c => c.name === columnName);
|
||||
@ -166,6 +167,7 @@ export function useOperators({ path }: { path: string[] }) {
|
||||
comparators,
|
||||
path,
|
||||
columns,
|
||||
computedFields,
|
||||
tables,
|
||||
table,
|
||||
});
|
||||
@ -181,6 +183,7 @@ export type GetDataTypeOperatorsProps = {
|
||||
comparators: Comparators;
|
||||
path: string[];
|
||||
columns: Columns;
|
||||
computedFields: ComputedField[];
|
||||
tables: Tables;
|
||||
table: Table;
|
||||
};
|
||||
|
@ -92,6 +92,7 @@ const getInitialValue = (key: string, type?: PermissionType) => {
|
||||
|
||||
switch (type) {
|
||||
case 'column':
|
||||
case 'computedField':
|
||||
// Depends on column type
|
||||
return { _eq: '' };
|
||||
case 'comparator':
|
||||
|
@ -39,6 +39,7 @@ export const usePermissionTables = ({
|
||||
suggestedRelationships
|
||||
),
|
||||
columns,
|
||||
computedFields: metadataTable.computed_fields ?? [],
|
||||
};
|
||||
}) ?? [],
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ import { SourceCustomization } from '../../../../../../hasura-metadata-types/sou
|
||||
import { Operator } from '../../../../../../DataSource/types';
|
||||
|
||||
import {
|
||||
ComputedField,
|
||||
MetadataDataSource,
|
||||
TableEntry,
|
||||
} from '../../../../../../../metadata/types';
|
||||
@ -41,6 +42,7 @@ export interface CreateDefaultValuesArgs {
|
||||
dataSourceName: string;
|
||||
metadata: Metadata | undefined;
|
||||
tableColumns: TableColumn[];
|
||||
tableComputedFields: ComputedField[];
|
||||
defaultQueryRoot: string | never[];
|
||||
metadataSource: MetadataDataSource | undefined;
|
||||
supportedOperators: Operator[];
|
||||
@ -52,6 +54,7 @@ export const createDefaultValues = ({
|
||||
roleName,
|
||||
table,
|
||||
tableColumns,
|
||||
tableComputedFields,
|
||||
defaultQueryRoot,
|
||||
metadataSource,
|
||||
supportedOperators,
|
||||
@ -74,6 +77,7 @@ export const createDefaultValues = ({
|
||||
comment: '',
|
||||
filterType: 'none',
|
||||
columns: {},
|
||||
computed_fields: {},
|
||||
supportedOperators,
|
||||
validateInput,
|
||||
};
|
||||
@ -84,6 +88,7 @@ export const createDefaultValues = ({
|
||||
selectedTable,
|
||||
roleName,
|
||||
tableColumns,
|
||||
tableComputedFields,
|
||||
tableName,
|
||||
metadataSource,
|
||||
});
|
||||
|
@ -14,6 +14,7 @@ import { createDefaultValues } from '../../../../components/RowPermissionsBuilde
|
||||
|
||||
import type { QueryType } from '../../../../../types';
|
||||
import {
|
||||
ComputedField,
|
||||
MetadataDataSource,
|
||||
TableEntry,
|
||||
} from '../../../../../../../metadata/types';
|
||||
@ -83,6 +84,17 @@ const getColumns = (
|
||||
}, {});
|
||||
};
|
||||
|
||||
const getComputedFields = (
|
||||
permissionComputedFields: string[],
|
||||
tableComputedFields: ComputedField[]
|
||||
) => {
|
||||
return tableComputedFields.reduce<Record<string, boolean>>((acc, each) => {
|
||||
const computedFieldIncluded = permissionComputedFields?.includes(each.name);
|
||||
acc[each.name] = !!computedFieldIncluded;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const createPermission = {
|
||||
insert: (
|
||||
permission: InsertPermissionDefinition,
|
||||
@ -110,6 +122,7 @@ export const createPermission = {
|
||||
select: (
|
||||
permission: SelectPermissionDefinition,
|
||||
tableColumns: TableColumn[],
|
||||
tableComputedFields: ComputedField[],
|
||||
tableName: string,
|
||||
metadataSource: MetadataDataSource | undefined
|
||||
) => {
|
||||
@ -123,6 +136,10 @@ export const createPermission = {
|
||||
const filterType = getCheckType(permission?.filter);
|
||||
|
||||
const columns = getColumns(permission?.columns || [], tableColumns);
|
||||
const computed_fields = getComputedFields(
|
||||
permission?.computed_fields || [],
|
||||
tableComputedFields
|
||||
);
|
||||
|
||||
const rowCount = getRowCount({
|
||||
currentQueryPermissions: permission,
|
||||
@ -135,6 +152,7 @@ export const createPermission = {
|
||||
filter,
|
||||
filterType,
|
||||
columns,
|
||||
computed_fields,
|
||||
rowCount,
|
||||
aggregationEnabled,
|
||||
operators: ops,
|
||||
@ -238,6 +256,7 @@ interface ObjArgs {
|
||||
queryType: QueryType;
|
||||
selectedTable: TableEntry;
|
||||
tableColumns: TableColumn[];
|
||||
tableComputedFields: ComputedField[];
|
||||
roleName: string;
|
||||
tableName: string;
|
||||
metadataSource: MetadataDataSource | undefined;
|
||||
@ -247,6 +266,7 @@ export const createPermissionsObject = ({
|
||||
queryType,
|
||||
selectedTable,
|
||||
tableColumns,
|
||||
tableComputedFields,
|
||||
roleName,
|
||||
tableName,
|
||||
metadataSource,
|
||||
@ -267,6 +287,7 @@ export const createPermissionsObject = ({
|
||||
return createPermission.select(
|
||||
selectedPermission.permission as SelectPermissionDefinition,
|
||||
tableColumns,
|
||||
tableComputedFields,
|
||||
tableName,
|
||||
// selectedTable.configuration,
|
||||
metadataSource
|
||||
|
@ -2,6 +2,7 @@ import { TableColumn } from '../../../../../../DataSource';
|
||||
import { Metadata } from '../../../../../../hasura-metadata-types';
|
||||
import { isPermission } from '../../../../../utils';
|
||||
import {
|
||||
ComputedField,
|
||||
MetadataDataSource,
|
||||
TableEntry,
|
||||
} from '../../../../../../../metadata/types';
|
||||
@ -75,10 +76,12 @@ export interface CreateFormDataArgs {
|
||||
metadataSource: MetadataDataSource;
|
||||
trackedTables: TableEntry[];
|
||||
validateInput: z.infer<typeof inputValidationSchema>;
|
||||
computedFields: ComputedField[];
|
||||
}
|
||||
|
||||
export const createFormData = (props: CreateFormDataArgs) => {
|
||||
const { dataSourceName, table, tableColumns, trackedTables } = props;
|
||||
const { dataSourceName, table, tableColumns, trackedTables, computedFields } =
|
||||
props;
|
||||
// find the specific metadata table
|
||||
const metadataTable = getMetadataTable({
|
||||
dataSourceName,
|
||||
@ -93,5 +96,6 @@ export const createFormData = (props: CreateFormDataArgs) => {
|
||||
supportedQueries,
|
||||
tableNames: metadataTable.tableNames,
|
||||
columns: tableColumns?.map(({ name }) => name),
|
||||
computed_fields: computedFields.map(({ name }) => name),
|
||||
};
|
||||
};
|
||||
|
@ -75,6 +75,7 @@ export const useFormDataCreateDefaultValuesMock = {
|
||||
role: 'asdf',
|
||||
permission: {
|
||||
columns: ['id', 'teacher'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_exists: {
|
||||
_table: { name: 'testing', schema: 'public' },
|
||||
@ -165,6 +166,7 @@ export const useFormDataCreateDefaultValuesMock = {
|
||||
role: 'new',
|
||||
permission: {
|
||||
columns: ['class', 'id'],
|
||||
computed_fields: [],
|
||||
filter: {},
|
||||
allow_aggregations: true,
|
||||
query_root_fields: [
|
||||
@ -213,6 +215,7 @@ export const useFormDataCreateDefaultValuesMock = {
|
||||
role: 'user',
|
||||
permission: {
|
||||
columns: ['deleted_at', 'id', 'metadata'],
|
||||
computed_fields: [],
|
||||
filter: { deleted_at: { _is_null: true } },
|
||||
allow_aggregations: true,
|
||||
},
|
||||
@ -738,6 +741,7 @@ export const useFormDataCreateDefaultValuesMock = {
|
||||
{ name: 'like', value: '_like', defaultValue: '%%' },
|
||||
{ name: 'not like', value: '_nlike', defaultValue: '%%' },
|
||||
],
|
||||
tableComputedFields: [],
|
||||
} as any;
|
||||
|
||||
export const createFormDataMock = {
|
||||
@ -816,6 +820,7 @@ export const createFormDataMock = {
|
||||
role: 'asdf',
|
||||
permission: {
|
||||
columns: ['id', 'teacher'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_exists: {
|
||||
_table: { name: 'testing', schema: 'public' },
|
||||
@ -833,6 +838,7 @@ export const createFormDataMock = {
|
||||
role: 'new',
|
||||
permission: {
|
||||
columns: ['id', 'teacher'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_exists: {
|
||||
_table: { name: 'testing', schema: 'public' },
|
||||
@ -845,6 +851,7 @@ export const createFormDataMock = {
|
||||
role: 'sdfsf',
|
||||
permission: {
|
||||
columns: ['id'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_exists: {
|
||||
_table: { name: 'class_student', schema: 'public' },
|
||||
@ -859,6 +866,7 @@ export const createFormDataMock = {
|
||||
role: 'testrole',
|
||||
permission: {
|
||||
columns: ['id', 'teacher'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
class_students: { class: { _eq: 'X-Hasura-User-Id' } },
|
||||
},
|
||||
@ -868,6 +876,7 @@ export const createFormDataMock = {
|
||||
role: 'user',
|
||||
permission: {
|
||||
columns: ['id'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_exists: {
|
||||
_table: { name: 'class', schema: 'public' },
|
||||
@ -906,6 +915,7 @@ export const createFormDataMock = {
|
||||
role: 'new',
|
||||
permission: {
|
||||
columns: ['class', 'id'],
|
||||
computed_fields: [],
|
||||
filter: {},
|
||||
allow_aggregations: true,
|
||||
query_root_fields: [
|
||||
@ -924,6 +934,7 @@ export const createFormDataMock = {
|
||||
role: 'user',
|
||||
permission: {
|
||||
columns: ['class', 'id', 'student_id'],
|
||||
computed_fields: [],
|
||||
filter: {},
|
||||
allow_aggregations: true,
|
||||
},
|
||||
@ -939,6 +950,7 @@ export const createFormDataMock = {
|
||||
permission: {
|
||||
check: {},
|
||||
columns: ['id', 'metadata', 'deleted_at'],
|
||||
computed_fields: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -947,6 +959,7 @@ export const createFormDataMock = {
|
||||
role: 'asdf',
|
||||
permission: {
|
||||
columns: ['id', 'metadata', 'deleted_at'],
|
||||
computed_fields: [],
|
||||
filter: {},
|
||||
},
|
||||
},
|
||||
@ -954,6 +967,7 @@ export const createFormDataMock = {
|
||||
role: 'user',
|
||||
permission: {
|
||||
columns: ['deleted_at', 'id', 'metadata'],
|
||||
computed_fields: [],
|
||||
filter: { deleted_at: { _is_null: true } },
|
||||
allow_aggregations: true,
|
||||
},
|
||||
@ -964,6 +978,7 @@ export const createFormDataMock = {
|
||||
role: 'user',
|
||||
permission: {
|
||||
columns: ['id', 'metadata'],
|
||||
computed_fields: [],
|
||||
filter: {},
|
||||
check: {},
|
||||
},
|
||||
@ -989,6 +1004,7 @@ export const createFormDataMock = {
|
||||
role: 'sdfsf',
|
||||
permission: {
|
||||
columns: ['id', 'name', 'deleted_at'],
|
||||
computed_fields: [],
|
||||
filter: { _or: [] },
|
||||
query_root_fields: ['select', 'select_by_pk'],
|
||||
subscription_root_fields: ['select', 'select_by_pk'],
|
||||
@ -998,6 +1014,7 @@ export const createFormDataMock = {
|
||||
role: 'user',
|
||||
permission: {
|
||||
columns: ['deleted_at', 'id', 'name'],
|
||||
computed_fields: [],
|
||||
filter: { deleted_at: { _is_null: true } },
|
||||
query_root_fields: ['select', 'select_by_pk'],
|
||||
subscription_root_fields: ['select', 'select_by_pk'],
|
||||
@ -1049,6 +1066,7 @@ export const createFormDataMock = {
|
||||
role: 'asdf',
|
||||
permission: {
|
||||
columns: ['AlbumId'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_or: [{ AlbumId: { _eq: 'X-Hasura-User-Id' } }],
|
||||
},
|
||||
@ -1058,6 +1076,7 @@ export const createFormDataMock = {
|
||||
role: 'new',
|
||||
permission: {
|
||||
columns: ['AlbumId'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_or: [{ AlbumId: { _eq: 'X-Hasura-User-Id' } }],
|
||||
},
|
||||
@ -1067,6 +1086,7 @@ export const createFormDataMock = {
|
||||
role: 'sdfsf',
|
||||
permission: {
|
||||
columns: ['AlbumId', 'Title', 'ArtistId'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_and: [{ AlbumId: { _eq: 'X-Hasura-User-Id' } }],
|
||||
},
|
||||
@ -1076,6 +1096,7 @@ export const createFormDataMock = {
|
||||
role: 'testrole',
|
||||
permission: {
|
||||
columns: ['AlbumId', 'Title'],
|
||||
computed_fields: [],
|
||||
filter: { _and: [{ AlbumId: { _eq: 'X-Hasura-User' } }] },
|
||||
},
|
||||
},
|
||||
@ -1094,6 +1115,7 @@ export const createFormDataMock = {
|
||||
role: 'testrole',
|
||||
permission: {
|
||||
columns: [],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_exists: {
|
||||
_table: ['Album'],
|
||||
@ -1155,6 +1177,7 @@ export const createFormDataMock = {
|
||||
role: 'asdf',
|
||||
permission: {
|
||||
columns: [],
|
||||
computed_fields: [],
|
||||
filter: { _not: { Data_value: { _eq: 1337 } } },
|
||||
},
|
||||
},
|
||||
@ -1162,6 +1185,7 @@ export const createFormDataMock = {
|
||||
role: 'new',
|
||||
permission: {
|
||||
columns: ['Series_reference', 'Period'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_and: [
|
||||
{ Data_value: { _eq: 'X-Hasura-User-Id' } },
|
||||
@ -1176,6 +1200,7 @@ export const createFormDataMock = {
|
||||
{
|
||||
role: 'sdfsf',
|
||||
permission: {
|
||||
computed_fields: [],
|
||||
columns: [
|
||||
'Series_reference',
|
||||
'Period',
|
||||
@ -1201,6 +1226,7 @@ export const createFormDataMock = {
|
||||
role: 'testrole',
|
||||
permission: {
|
||||
columns: [],
|
||||
computed_fields: [],
|
||||
filter: { Magnitude: { _eq: '123' } },
|
||||
},
|
||||
},
|
||||
@ -1223,6 +1249,7 @@ export const createFormDataMock = {
|
||||
'Series_title_4',
|
||||
'Series_title_5',
|
||||
],
|
||||
computed_fields: [],
|
||||
filter: {},
|
||||
},
|
||||
},
|
||||
@ -1382,6 +1409,7 @@ export const createFormDataMock = {
|
||||
nullable: false,
|
||||
},
|
||||
],
|
||||
computedFields: [],
|
||||
trackedTables: [
|
||||
{
|
||||
table: { dataset: 'bigquery_sample', name: 'sample_table' },
|
||||
@ -1425,6 +1453,7 @@ export const createFormDataMock = {
|
||||
role: 'new',
|
||||
permission: {
|
||||
columns: ['Series_reference', 'Period'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_and: [
|
||||
{ Data_value: { _eq: 'X-Hasura-User-Id' } },
|
||||
@ -1439,6 +1468,7 @@ export const createFormDataMock = {
|
||||
{
|
||||
role: 'sdfsf',
|
||||
permission: {
|
||||
computed_fields: [],
|
||||
columns: [
|
||||
'Series_reference',
|
||||
'Period',
|
||||
@ -1462,11 +1492,16 @@ export const createFormDataMock = {
|
||||
},
|
||||
{
|
||||
role: 'testrole',
|
||||
permission: { columns: [], filter: { Magnitude: { _eq: '123' } } },
|
||||
permission: {
|
||||
columns: [],
|
||||
computed_fields: [],
|
||||
filter: { Magnitude: { _eq: '123' } },
|
||||
},
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
permission: {
|
||||
computed_fields: [],
|
||||
columns: [
|
||||
'Series_reference',
|
||||
'Period',
|
||||
@ -1528,6 +1563,7 @@ export const createFormDataMock = {
|
||||
role: 'asdf',
|
||||
permission: {
|
||||
columns: [],
|
||||
computed_fields: [],
|
||||
filter: { _not: { Data_value: { _eq: 1337 } } },
|
||||
},
|
||||
},
|
||||
@ -1535,6 +1571,7 @@ export const createFormDataMock = {
|
||||
role: 'new',
|
||||
permission: {
|
||||
columns: ['Series_reference', 'Period'],
|
||||
computed_fields: [],
|
||||
filter: {
|
||||
_and: [
|
||||
{ Data_value: { _eq: 'X-Hasura-User-Id' } },
|
||||
@ -1549,6 +1586,7 @@ export const createFormDataMock = {
|
||||
{
|
||||
role: 'sdfsf',
|
||||
permission: {
|
||||
computed_fields: [],
|
||||
columns: [
|
||||
'Series_reference',
|
||||
'Period',
|
||||
@ -1572,11 +1610,16 @@ export const createFormDataMock = {
|
||||
},
|
||||
{
|
||||
role: 'testrole',
|
||||
permission: { columns: [], filter: { Magnitude: { _eq: '123' } } },
|
||||
permission: {
|
||||
columns: [],
|
||||
computed_fields: [],
|
||||
filter: { Magnitude: { _eq: '123' } },
|
||||
},
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
permission: {
|
||||
computed_fields: [],
|
||||
columns: [
|
||||
'Series_reference',
|
||||
'Period',
|
||||
|
@ -22,6 +22,7 @@ const formDataMockResult = {
|
||||
'Series_title_4',
|
||||
'Series_title_5',
|
||||
],
|
||||
computed_fields: [],
|
||||
};
|
||||
|
||||
test('returns correctly formatted formData', () => {
|
||||
@ -79,6 +80,7 @@ const defaultValuesMockResult: ReturnType<typeof createDefaultValues> = {
|
||||
},
|
||||
query_root_fields: null,
|
||||
subscription_root_fields: null,
|
||||
computed_fields: {},
|
||||
};
|
||||
|
||||
test('use default values returns values correctly', () => {
|
||||
|
@ -11,6 +11,8 @@ export const selectArgs: CreateInsertArgs = {
|
||||
filterType: 'custom',
|
||||
filter: { _not: { AlbumId: { _eq: 'X-Hasura-User-Id' } } },
|
||||
columns: { AlbumId: true, Title: true, ArtistId: true },
|
||||
computed_fields: {},
|
||||
comment: '',
|
||||
presets: [],
|
||||
rowCount: '0',
|
||||
aggregationEnabled: false,
|
||||
@ -60,6 +62,7 @@ export const deleteArgs: CreateInsertArgs = {
|
||||
{ name: '<=', value: '_lte' },
|
||||
],
|
||||
clonePermissions: [{ tableName: '', queryType: '', roleName: '' }],
|
||||
comment: '',
|
||||
},
|
||||
accessType: 'partialAccess',
|
||||
existingPermissions: [
|
||||
@ -94,6 +97,8 @@ export const insertArgs: CreateInsertArgs = {
|
||||
],
|
||||
},
|
||||
columns: { AlbumId: false, Title: false, ArtistId: false },
|
||||
computed_fields: {},
|
||||
comment: '',
|
||||
presets: [{ columnName: 'default', presetType: 'static', columnValue: '' }],
|
||||
backendOnly: false,
|
||||
supportedOperators: [
|
||||
|
@ -2,6 +2,7 @@ import * as z from 'zod';
|
||||
import { inputValidationSchema } from '../../../components/Services/Data/TablePermissions/InputValidation/InputValidation';
|
||||
|
||||
const columns = z.record(z.optional(z.boolean()));
|
||||
const computed_fields = z.record(z.optional(z.boolean()));
|
||||
const presets = z.optional(
|
||||
z.array(
|
||||
z.object({
|
||||
@ -40,6 +41,7 @@ export const schema = z.discriminatedUnion('queryType', [
|
||||
comment: z.string(),
|
||||
check: z.any(),
|
||||
columns,
|
||||
computed_fields,
|
||||
presets,
|
||||
backendOnly: z.boolean().optional(),
|
||||
supportedOperators: z.array(z.any()),
|
||||
@ -52,6 +54,7 @@ export const schema = z.discriminatedUnion('queryType', [
|
||||
comment: z.string(),
|
||||
filter: z.any(),
|
||||
columns,
|
||||
computed_fields,
|
||||
presets,
|
||||
rowCount: z.string().optional(),
|
||||
aggregationEnabled: z.boolean().optional(),
|
||||
@ -64,6 +67,7 @@ export const schema = z.discriminatedUnion('queryType', [
|
||||
z.object({
|
||||
queryType: z.literal('update'),
|
||||
columns,
|
||||
computed_fields,
|
||||
filterType: z.string(),
|
||||
comment: z.string(),
|
||||
filter: z.any(),
|
||||
|
@ -30,6 +30,7 @@ export interface SelectPermission extends BasePermission {
|
||||
}
|
||||
export interface SelectPermissionDefinition {
|
||||
columns?: string[];
|
||||
computed_fields?: string[];
|
||||
filter?: Record<string, unknown>;
|
||||
allow_aggregations?: boolean;
|
||||
query_root_fields?: string[] | null;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { QualifiedFunction } from '../../../metadata/types';
|
||||
import {
|
||||
InsertPermission,
|
||||
SelectPermission,
|
||||
@ -91,4 +92,11 @@ export type MetadataTable = {
|
||||
apollo_federation_config?: {
|
||||
enable: 'v1';
|
||||
} | null;
|
||||
|
||||
computed_fields?: {
|
||||
name: string;
|
||||
definition: {
|
||||
function: QualifiedFunction;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user