mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
console: add permissions for GDC to console
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6592 Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com> GitOrigin-RevId: 67a72f9b76604c50f647113d170e6e1778c9f7b9
This commit is contained in:
parent
5278e4ed9f
commit
89f639b40b
@ -1,6 +1,7 @@
|
|||||||
import { BrowseRowsContainer } from '@/features/BrowseRows';
|
import { BrowseRowsContainer } from '@/features/BrowseRows';
|
||||||
import { DatabaseRelationshipsContainer } from '@/features/DataRelationships';
|
import { DatabaseRelationshipsContainer } from '@/features/DataRelationships';
|
||||||
import { getTableName } from '@/features/DataSource';
|
import { getTableName } from '@/features/DataSource';
|
||||||
|
import { PermissionsTab } from '@/features/PermissionsTab';
|
||||||
import { Table } from '@/features/MetadataAPI';
|
import { Table } from '@/features/MetadataAPI';
|
||||||
import { IndicatorCard } from '@/new-components/IndicatorCard';
|
import { IndicatorCard } from '@/new-components/IndicatorCard';
|
||||||
import { Tabs } from '@/new-components/Tabs';
|
import { Tabs } from '@/new-components/Tabs';
|
||||||
@ -19,16 +20,6 @@ export interface ManageTableProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const FeatureNotImplemented = () => {
|
|
||||||
return (
|
|
||||||
<div className="my-4">
|
|
||||||
<IndicatorCard headline="Feature is currently unavailable">
|
|
||||||
Feature not implemented
|
|
||||||
</IndicatorCard>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const availableTabs = (
|
const availableTabs = (
|
||||||
dataSourceName: string,
|
dataSourceName: string,
|
||||||
table: Table,
|
table: Table,
|
||||||
@ -65,7 +56,7 @@ const availableTabs = (
|
|||||||
{
|
{
|
||||||
value: 'permissions',
|
value: 'permissions',
|
||||||
label: 'Permissions',
|
label: 'Permissions',
|
||||||
content: <FeatureNotImplemented />,
|
content: <PermissionsTab dataSourceName={dataSourceName} table={table} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ export const Primary: Story<BulkDeleteProps> = args => {
|
|||||||
return <BulkDelete {...args} />;
|
return <BulkDelete {...args} />;
|
||||||
};
|
};
|
||||||
Primary.args = {
|
Primary.args = {
|
||||||
currentSource: 'postgres',
|
|
||||||
dataSourceName: 'default',
|
dataSourceName: 'default',
|
||||||
roles: ['user'],
|
roles: ['user'],
|
||||||
handleClose: () => {},
|
handleClose: () => {},
|
||||||
|
@ -4,7 +4,6 @@ import { Button } from '@/new-components/Button';
|
|||||||
import { useBulkDeletePermissions } from './hooks';
|
import { useBulkDeletePermissions } from './hooks';
|
||||||
|
|
||||||
export interface BulkDeleteProps {
|
export interface BulkDeleteProps {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
table: unknown;
|
table: unknown;
|
||||||
@ -12,14 +11,12 @@ export interface BulkDeleteProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BulkDelete: React.FC<BulkDeleteProps> = ({
|
export const BulkDelete: React.FC<BulkDeleteProps> = ({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
roles,
|
roles,
|
||||||
table,
|
table,
|
||||||
handleClose,
|
handleClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { submit, isLoading, isError } = useBulkDeletePermissions({
|
const { submit, isLoading, isError } = useBulkDeletePermissions({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ import { PermissionsForm, PermissionsFormProps } from './PermissionsForm';
|
|||||||
import { handlers } from './mocks/handlers.mock';
|
import { handlers } from './mocks/handlers.mock';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Form',
|
title: 'Features/Permissions Tab/Permissions Form/Form',
|
||||||
component: PermissionsForm,
|
component: PermissionsForm,
|
||||||
decorators: [ReactQueryDecorator()],
|
decorators: [ReactQueryDecorator()],
|
||||||
parameters: {
|
parameters: {
|
||||||
@ -20,7 +20,6 @@ export const Insert: Story<PermissionsFormProps> = args => (
|
|||||||
<PermissionsForm {...args} />
|
<PermissionsForm {...args} />
|
||||||
);
|
);
|
||||||
Insert.args = {
|
Insert.args = {
|
||||||
currentSource: 'postgres',
|
|
||||||
dataSourceName: 'default',
|
dataSourceName: 'default',
|
||||||
|
|
||||||
queryType: 'insert',
|
queryType: 'insert',
|
||||||
@ -39,20 +38,17 @@ Select.args = {
|
|||||||
...Insert.args,
|
...Insert.args,
|
||||||
queryType: 'select',
|
queryType: 'select',
|
||||||
};
|
};
|
||||||
Select.parameters = Insert.parameters;
|
|
||||||
|
|
||||||
export const GDCSelect: Story<PermissionsFormProps> = args => (
|
export const GDCSelect: Story<PermissionsFormProps> = args => (
|
||||||
<PermissionsForm {...args} />
|
<PermissionsForm {...args} />
|
||||||
);
|
);
|
||||||
GDCSelect.args = {
|
GDCSelect.args = {
|
||||||
currentSource: 'sqlite',
|
|
||||||
dataSourceName: 'sqlite',
|
dataSourceName: 'sqlite',
|
||||||
queryType: 'select',
|
queryType: 'select',
|
||||||
table: ['Artist'],
|
table: ['Artist'],
|
||||||
roleName,
|
roleName,
|
||||||
handleClose: () => {},
|
handleClose: () => {},
|
||||||
};
|
};
|
||||||
GDCSelect.parameters = Insert.parameters;
|
|
||||||
|
|
||||||
export const Update: Story<PermissionsFormProps> = args => (
|
export const Update: Story<PermissionsFormProps> = args => (
|
||||||
<PermissionsForm {...args} />
|
<PermissionsForm {...args} />
|
||||||
@ -61,7 +57,6 @@ Update.args = {
|
|||||||
...Insert.args,
|
...Insert.args,
|
||||||
queryType: 'update',
|
queryType: 'update',
|
||||||
};
|
};
|
||||||
Update.parameters = Insert.parameters;
|
|
||||||
|
|
||||||
export const Delete: Story<PermissionsFormProps> = args => (
|
export const Delete: Story<PermissionsFormProps> = args => (
|
||||||
<PermissionsForm {...args} />
|
<PermissionsForm {...args} />
|
||||||
@ -70,4 +65,3 @@ Delete.args = {
|
|||||||
...Insert.args,
|
...Insert.args,
|
||||||
queryType: 'delete',
|
queryType: 'delete',
|
||||||
};
|
};
|
||||||
Delete.parameters = Insert.parameters;
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Form } from '@/new-components/Form';
|
import { UpdatedForm as Form } from '@/new-components/Form';
|
||||||
import { Button } from '@/new-components/Button';
|
import { Button } from '@/new-components/Button';
|
||||||
|
import { IndicatorCard } from '@/new-components/IndicatorCard';
|
||||||
|
|
||||||
import { schema } from './utils/formSchema';
|
import { PermissionsSchema, schema } from './utils/formSchema';
|
||||||
|
|
||||||
import { AccessType, FormOutput, QueryType } from './types';
|
import { AccessType, QueryType } from './types';
|
||||||
import {
|
import {
|
||||||
AggregationSection,
|
AggregationSection,
|
||||||
BackendOnlySection,
|
BackendOnlySection,
|
||||||
@ -15,10 +16,9 @@ import {
|
|||||||
RowPermissionsSectionWrapper,
|
RowPermissionsSectionWrapper,
|
||||||
} from './components';
|
} from './components';
|
||||||
|
|
||||||
import { useFormData, useDefaultValues, useUpdatePermissions } from './hooks';
|
import { useFormData, useUpdatePermissions } from './hooks';
|
||||||
|
|
||||||
export interface PermissionsFormProps {
|
export interface PermissionsFormProps {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
queryType: QueryType;
|
queryType: QueryType;
|
||||||
@ -29,7 +29,6 @@ export interface PermissionsFormProps {
|
|||||||
|
|
||||||
export const PermissionsForm = (props: PermissionsFormProps) => {
|
export const PermissionsForm = (props: PermissionsFormProps) => {
|
||||||
const {
|
const {
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
queryType,
|
queryType,
|
||||||
@ -38,34 +37,15 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
|||||||
handleClose,
|
handleClose,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// loads all information about selected table
|
const { data, isError, isLoading } = useFormData({
|
||||||
// e.g. column names, supported queries etc.
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
isLoading: loadingFormData,
|
|
||||||
isError: formDataError,
|
|
||||||
} = useFormData({
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
queryType,
|
queryType,
|
||||||
roleName,
|
roleName,
|
||||||
});
|
});
|
||||||
|
|
||||||
// loads any existing permissions from the metadata
|
|
||||||
const {
|
|
||||||
data: defaultValues,
|
|
||||||
isLoading: defaultValuesLoading,
|
|
||||||
isError: defaultValuesError,
|
|
||||||
} = useDefaultValues({
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
roleName,
|
|
||||||
queryType,
|
|
||||||
});
|
|
||||||
|
|
||||||
// functions fired when the form is submitted
|
// functions fired when the form is submitted
|
||||||
const { updatePermissions, deletePermissions } = useUpdatePermissions({
|
const { updatePermissions, deletePermissions } = useUpdatePermissions({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
queryType,
|
queryType,
|
||||||
@ -73,8 +53,8 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
|||||||
accessType,
|
accessType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async (formData: Record<string, unknown>) => {
|
const handleSubmit = async (formData: PermissionsSchema) => {
|
||||||
await updatePermissions.submit(formData as FormOutput);
|
await updatePermissions.submit(formData);
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,43 +63,49 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
|||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isError = formDataError || defaultValuesError;
|
|
||||||
|
|
||||||
const isSubmittingError =
|
const isSubmittingError =
|
||||||
updatePermissions.isError || deletePermissions.isError;
|
updatePermissions.isError || deletePermissions.isError;
|
||||||
|
|
||||||
const isLoading = loadingFormData || defaultValuesLoading;
|
|
||||||
|
|
||||||
// allRowChecks relates to other queries and is for duplicating from others
|
|
||||||
const allRowChecks = defaultValues?.allRowChecks;
|
|
||||||
|
|
||||||
// for update it is possible to set pre update and post update row checks
|
// for update it is possible to set pre update and post update row checks
|
||||||
const rowPermissions = queryType === 'update' ? ['pre', 'post'] : [queryType];
|
const rowPermissions = queryType === 'update' ? ['pre', 'post'] : [queryType];
|
||||||
|
|
||||||
if (isSubmittingError) {
|
if (isSubmittingError) {
|
||||||
return <div>Error submitting form</div>;
|
return (
|
||||||
|
<IndicatorCard status="negative">Error submitting form</IndicatorCard>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// these will be replaced by components once spec is decided
|
// these will be replaced by components once spec is decided
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <div>Error loading form data</div>;
|
return (
|
||||||
|
<IndicatorCard status="negative">Error fetching form data</IndicatorCard>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// these will be replaced by components once spec is decided
|
// these will be replaced by components once spec is decided
|
||||||
if (isLoading) {
|
if (isLoading || !data) {
|
||||||
return <div>Loading...</div>;
|
return <IndicatorCard status="info">Loading...</IndicatorCard>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { formData, defaultValues } = data;
|
||||||
|
|
||||||
|
// allRowChecks relates to other queries and is for duplicating from others
|
||||||
|
const allRowChecks = defaultValues?.allRowChecks;
|
||||||
|
|
||||||
|
const key = `${JSON.stringify(table)}-${queryType}-${roleName}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
key={`${queryType}-${roleName}-${accessType}`}
|
key={key}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
options={{ defaultValues }}
|
options={{ defaultValues }}
|
||||||
>
|
>
|
||||||
{options => {
|
{options => {
|
||||||
console.log('form values---->', options.getValues());
|
const filterType = options.getValues('filterType');
|
||||||
console.log('form errors---->', options.formState.errors);
|
if (Object.keys(options.formState.errors)?.length) {
|
||||||
|
console.error('form errors:', options.formState.errors);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded p-md border border-gray-300">
|
<div className="bg-white rounded p-md border border-gray-300">
|
||||||
<div className="pb-4 flex items-center gap-4">
|
<div className="pb-4 flex items-center gap-4">
|
||||||
@ -158,6 +144,7 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
|||||||
queryType === 'update' ? permissionName : undefined
|
queryType === 'update' ? permissionName : undefined
|
||||||
}
|
}
|
||||||
allRowChecks={allRowChecks || []}
|
allRowChecks={allRowChecks || []}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
@ -167,14 +154,14 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
|||||||
<ColumnPermissionsSection
|
<ColumnPermissionsSection
|
||||||
roleName={roleName}
|
roleName={roleName}
|
||||||
queryType={queryType}
|
queryType={queryType}
|
||||||
columns={data?.columns}
|
columns={formData?.columns}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{['insert', 'update'].includes(queryType) && (
|
{['insert', 'update'].includes(queryType) && (
|
||||||
<ColumnPresetsSection
|
<ColumnPresetsSection
|
||||||
queryType={queryType}
|
queryType={queryType}
|
||||||
columns={data?.columns}
|
columns={formData?.columns}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -201,6 +188,12 @@ export const PermissionsForm = (props: PermissionsFormProps) => {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
mode="primary"
|
mode="primary"
|
||||||
|
title={
|
||||||
|
filterType === 'none'
|
||||||
|
? 'You must select an option for row permissions'
|
||||||
|
: 'Submit'
|
||||||
|
}
|
||||||
|
disabled={filterType === 'none'}
|
||||||
isLoading={updatePermissions.isLoading}
|
isLoading={updatePermissions.isLoading}
|
||||||
>
|
>
|
||||||
Save Permissions
|
Save Permissions
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { allowedMetadataTypes } from '@/features/MetadataAPI';
|
import { allowedMetadataTypes } from '@/features/MetadataAPI';
|
||||||
|
|
||||||
import { AccessType, FormOutput, QueryType } from '../types';
|
import { AccessType, QueryType } from '../types';
|
||||||
|
import { PermissionsSchema } from '../utils';
|
||||||
import { createInsertArgs } from './utils';
|
import { createInsertArgs } from './utils';
|
||||||
|
|
||||||
interface CreateBodyArgs {
|
interface CreateBodyArgs {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
@ -13,10 +13,11 @@ interface CreateBodyArgs {
|
|||||||
|
|
||||||
interface CreateDeleteBodyArgs extends CreateBodyArgs {
|
interface CreateDeleteBodyArgs extends CreateBodyArgs {
|
||||||
queries: QueryType[];
|
queries: QueryType[];
|
||||||
|
driver: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDeleteBody = ({
|
const createDeleteBody = ({
|
||||||
currentSource,
|
driver,
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
roleName,
|
roleName,
|
||||||
@ -28,12 +29,8 @@ const createDeleteBody = ({
|
|||||||
resource_version: number;
|
resource_version: number;
|
||||||
args: BulkArgs[];
|
args: BulkArgs[];
|
||||||
} => {
|
} => {
|
||||||
// if (!['postgres', 'mssql'].includes(currentSource)) {
|
|
||||||
// throw new Error(`${currentSource} not supported`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const args = queries.map(queryType => ({
|
const args = queries.map(queryType => ({
|
||||||
type: `${currentSource}_drop_${queryType}_permission` as allowedMetadataTypes,
|
type: `${driver}_drop_${queryType}_permission` as allowedMetadataTypes,
|
||||||
args: {
|
args: {
|
||||||
table,
|
table,
|
||||||
role: roleName,
|
role: roleName,
|
||||||
@ -52,11 +49,11 @@ const createDeleteBody = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface CreateBulkDeleteBodyArgs {
|
interface CreateBulkDeleteBodyArgs {
|
||||||
source: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
resourceVersion: number;
|
resourceVersion: number;
|
||||||
roleList?: Array<{ roleName: string; queries: string[] }>;
|
roleList?: Array<{ roleName: string; queries: string[] }>;
|
||||||
|
driver: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BulkArgs {
|
interface BulkArgs {
|
||||||
@ -65,8 +62,8 @@ interface BulkArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createBulkDeleteBody = ({
|
const createBulkDeleteBody = ({
|
||||||
source,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
|
driver,
|
||||||
table,
|
table,
|
||||||
resourceVersion,
|
resourceVersion,
|
||||||
roleList,
|
roleList,
|
||||||
@ -76,15 +73,11 @@ const createBulkDeleteBody = ({
|
|||||||
resource_version: number;
|
resource_version: number;
|
||||||
args: BulkArgs[];
|
args: BulkArgs[];
|
||||||
} => {
|
} => {
|
||||||
// if (!['postgres', 'mssql'].includes(source)) {
|
|
||||||
// throw new Error(`${dataSourceName} not supported`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const args =
|
const args =
|
||||||
roleList?.reduce<BulkArgs[]>((acc, role) => {
|
roleList?.reduce<BulkArgs[]>((acc, role) => {
|
||||||
role.queries.forEach(queryType => {
|
role.queries.forEach(queryType => {
|
||||||
acc.push({
|
acc.push({
|
||||||
type: `${source}_drop_${queryType}_permission` as allowedMetadataTypes,
|
type: `${driver}_drop_${queryType}_permission` as allowedMetadataTypes,
|
||||||
args: {
|
args: {
|
||||||
table,
|
table,
|
||||||
role: role.roleName,
|
role: role.roleName,
|
||||||
@ -108,9 +101,10 @@ const createBulkDeleteBody = ({
|
|||||||
|
|
||||||
interface CreateInsertBodyArgs extends CreateBodyArgs {
|
interface CreateInsertBodyArgs extends CreateBodyArgs {
|
||||||
queryType: QueryType;
|
queryType: QueryType;
|
||||||
formData: FormOutput;
|
formData: PermissionsSchema;
|
||||||
accessType: AccessType;
|
accessType: AccessType;
|
||||||
existingPermissions: any;
|
existingPermissions: any;
|
||||||
|
driver: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InsertBodyResult {
|
export interface InsertBodyResult {
|
||||||
@ -120,7 +114,6 @@ export interface InsertBodyResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createInsertBody = ({
|
const createInsertBody = ({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
queryType,
|
queryType,
|
||||||
@ -129,13 +122,10 @@ const createInsertBody = ({
|
|||||||
accessType,
|
accessType,
|
||||||
resourceVersion,
|
resourceVersion,
|
||||||
existingPermissions,
|
existingPermissions,
|
||||||
|
driver,
|
||||||
}: CreateInsertBodyArgs): InsertBodyResult => {
|
}: CreateInsertBodyArgs): InsertBodyResult => {
|
||||||
// if (!['postgres', 'mssql'].includes(currentSource)) {
|
|
||||||
// throw new Error(`${currentSource} not supported`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const args = createInsertArgs({
|
const args = createInsertArgs({
|
||||||
currentSource,
|
driver,
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
queryType,
|
queryType,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { CreateInsertArgs, createInsertArgs } from './utils';
|
import { CreateInsertArgs, createInsertArgs } from './utils';
|
||||||
|
|
||||||
const insertArgs: CreateInsertArgs = {
|
const insertArgs: CreateInsertArgs = {
|
||||||
currentSource: 'postgres',
|
driver: 'postgres',
|
||||||
dataSourceName: 'default',
|
dataSourceName: 'default',
|
||||||
accessType: 'fullAccess',
|
accessType: 'fullAccess',
|
||||||
table: 'users',
|
table: 'users',
|
||||||
@ -88,7 +88,7 @@ test('create insert args object from form data', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const insertArgsWithClonePermissions: CreateInsertArgs = {
|
const insertArgsWithClonePermissions: CreateInsertArgs = {
|
||||||
currentSource: 'postgres',
|
driver: 'postgres',
|
||||||
dataSourceName: 'default',
|
dataSourceName: 'default',
|
||||||
accessType: 'fullAccess',
|
accessType: 'fullAccess',
|
||||||
table: 'users',
|
table: 'users',
|
||||||
|
@ -2,23 +2,24 @@ import produce from 'immer';
|
|||||||
|
|
||||||
import { allowedMetadataTypes } from '@/features/MetadataAPI';
|
import { allowedMetadataTypes } from '@/features/MetadataAPI';
|
||||||
|
|
||||||
import { AccessType, FormOutput } from '../types';
|
import { AccessType } from '../types';
|
||||||
|
import { PermissionsSchema } from '../utils';
|
||||||
|
|
||||||
interface PermissionArgs {
|
interface PermissionArgs {
|
||||||
columns: string[];
|
columns: string[];
|
||||||
presets?: Record<string, string | number>;
|
presets?: Record<string, string | number>;
|
||||||
computed_fields: string[];
|
computed_fields: string[];
|
||||||
backend_only: boolean;
|
backend_only?: boolean;
|
||||||
allow_aggregations: boolean;
|
allow_aggregations?: boolean;
|
||||||
check: Record<string, unknown>;
|
check: Record<string, any>;
|
||||||
filter: Record<string, unknown>;
|
filter: Record<string, any>;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* creates the permissions object for the server
|
* creates the permissions object for the server
|
||||||
*/
|
*/
|
||||||
const createPermission = (formData: FormOutput) => {
|
const createPermission = (formData: PermissionsSchema) => {
|
||||||
// presets need reformatting for server
|
// presets need reformatting for server
|
||||||
const presets = formData.presets?.reduce((acc, preset) => {
|
const presets = formData.presets?.reduce((acc, preset) => {
|
||||||
if (preset.columnValue) {
|
if (preset.columnValue) {
|
||||||
@ -33,6 +34,21 @@ const createPermission = (formData: FormOutput) => {
|
|||||||
.filter(({ 1: value }) => value)
|
.filter(({ 1: value }) => value)
|
||||||
.map(([key]) => key);
|
.map(([key]) => key);
|
||||||
|
|
||||||
|
// in row permissions builder an extra input is rendered automatically
|
||||||
|
// this will always be empty and needs to be removed
|
||||||
|
const filter = Object.entries(formData.filter).reduce<Record<string, any>>(
|
||||||
|
(acc, [operator, value]) => {
|
||||||
|
if (operator === '_and' || operator === '_or') {
|
||||||
|
const newValue = (value as any[])?.slice(0, -1);
|
||||||
|
acc[operator] = newValue;
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[operator] = value;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
// return permissions object for args
|
// return permissions object for args
|
||||||
const permissionObject: PermissionArgs = {
|
const permissionObject: PermissionArgs = {
|
||||||
columns,
|
columns,
|
||||||
@ -41,7 +57,7 @@ const createPermission = (formData: FormOutput) => {
|
|||||||
backend_only: formData.backendOnly,
|
backend_only: formData.backendOnly,
|
||||||
allow_aggregations: formData.aggregationEnabled,
|
allow_aggregations: formData.aggregationEnabled,
|
||||||
check: formData.check,
|
check: formData.check,
|
||||||
filter: formData.filter,
|
filter,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (formData.rowCount && formData.rowCount !== '0') {
|
if (formData.rowCount && formData.rowCount !== '0') {
|
||||||
@ -52,14 +68,14 @@ const createPermission = (formData: FormOutput) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface CreateInsertArgs {
|
export interface CreateInsertArgs {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
queryType: string;
|
queryType: string;
|
||||||
role: string;
|
role: string;
|
||||||
accessType: AccessType;
|
accessType: AccessType;
|
||||||
formData: FormOutput;
|
formData: PermissionsSchema;
|
||||||
existingPermissions: ExistingPermission[];
|
existingPermissions: ExistingPermission[];
|
||||||
|
driver: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExistingPermission {
|
interface ExistingPermission {
|
||||||
@ -73,20 +89,20 @@ interface ExistingPermission {
|
|||||||
* and creates drop arguments where permissions already exist
|
* and creates drop arguments where permissions already exist
|
||||||
*/
|
*/
|
||||||
export const createInsertArgs = ({
|
export const createInsertArgs = ({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
queryType,
|
queryType,
|
||||||
role,
|
role,
|
||||||
formData,
|
formData,
|
||||||
existingPermissions,
|
existingPermissions,
|
||||||
|
driver,
|
||||||
}: CreateInsertArgs) => {
|
}: CreateInsertArgs) => {
|
||||||
const permission = createPermission(formData);
|
const permission = createPermission(formData);
|
||||||
|
|
||||||
// create args object with args from form
|
// create args object with args from form
|
||||||
const initialArgs = [
|
const initialArgs = [
|
||||||
{
|
{
|
||||||
type: `${currentSource}_create_${queryType}_permission` as allowedMetadataTypes,
|
type: `${driver}_create_${queryType}_permission` as allowedMetadataTypes,
|
||||||
args: {
|
args: {
|
||||||
table,
|
table,
|
||||||
role,
|
role,
|
||||||
@ -108,7 +124,7 @@ export const createInsertArgs = ({
|
|||||||
// if the permission already exists it needs to be dropped
|
// if the permission already exists it needs to be dropped
|
||||||
if (permissionExists) {
|
if (permissionExists) {
|
||||||
draft.unshift({
|
draft.unshift({
|
||||||
type: `${currentSource}_drop_${queryType}_permission` as allowedMetadataTypes,
|
type: `${driver}_drop_${queryType}_permission` as allowedMetadataTypes,
|
||||||
args: {
|
args: {
|
||||||
table,
|
table,
|
||||||
role,
|
role,
|
||||||
@ -137,7 +153,7 @@ export const createInsertArgs = ({
|
|||||||
);
|
);
|
||||||
// add each closed permission to args
|
// add each closed permission to args
|
||||||
draft.push({
|
draft.push({
|
||||||
type: `${currentSource}_create_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
|
type: `${driver}_create_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
|
||||||
args: {
|
args: {
|
||||||
table: clonedPermission.tableName || '',
|
table: clonedPermission.tableName || '',
|
||||||
role: clonedPermission.roleName || '',
|
role: clonedPermission.roleName || '',
|
||||||
@ -158,7 +174,7 @@ export const createInsertArgs = ({
|
|||||||
// if it already exists drop it
|
// if it already exists drop it
|
||||||
if (clonedPermissionExists) {
|
if (clonedPermissionExists) {
|
||||||
draft.unshift({
|
draft.unshift({
|
||||||
type: `${currentSource}_drop_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
|
type: `${driver}_drop_${clonedPermission.queryType}_permission` as allowedMetadataTypes,
|
||||||
args: {
|
args: {
|
||||||
table: clonedPermission.tableName,
|
table: clonedPermission.tableName,
|
||||||
role: clonedPermission.roleName,
|
role: clonedPermission.roleName,
|
||||||
|
@ -6,7 +6,8 @@ import { Form } from '@/new-components/Form';
|
|||||||
import { AggregationSection, AggregationProps } from './Aggregation';
|
import { AggregationSection, AggregationProps } from './Aggregation';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Components/Aggregation Section',
|
title:
|
||||||
|
'Features/Permissions Tab/Permissions Form/Components/Aggregation Section',
|
||||||
component: AggregationSection,
|
component: AggregationSection,
|
||||||
parameters: {
|
parameters: {
|
||||||
// Disable storybook for playground stories
|
// Disable storybook for playground stories
|
||||||
|
@ -46,11 +46,12 @@ export const AggregationSection: React.FC<AggregationProps> = ({
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
title={disabled ? 'Set row permissions first' : ''}
|
title={disabled ? 'Set row permissions first' : ''}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
className="m-0 mt-0 rounded shadow-sm border border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
|
||||||
{...register('aggregationEnabled')}
|
{...register('aggregationEnabled')}
|
||||||
/>
|
/>
|
||||||
<p>
|
<span>
|
||||||
Allow role <strong>{roleName}</strong> to make aggregation queries
|
Allow role <strong>{roleName}</strong> to make aggregation queries
|
||||||
</p>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</Collapse.Content>
|
</Collapse.Content>
|
||||||
|
@ -6,7 +6,8 @@ import { Form } from '@/new-components/Form';
|
|||||||
import { BackendOnlySection, BackEndOnlySectionProps } from './BackendOnly';
|
import { BackendOnlySection, BackEndOnlySectionProps } from './BackendOnly';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Components/Backend Only Section',
|
title:
|
||||||
|
'Features/Permissions Tab/Permissions Form/Components/Backend Only Section',
|
||||||
component: BackendOnlySection,
|
component: BackendOnlySection,
|
||||||
parameters: {
|
parameters: {
|
||||||
// Disable storybook for playground stories
|
// Disable storybook for playground stories
|
||||||
|
@ -10,7 +10,8 @@ import {
|
|||||||
} from './ClonePermissions';
|
} from './ClonePermissions';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Components/Clone Permissions',
|
title:
|
||||||
|
'Features/Permissions Tab/Permissions Form/Components/Clone Permissions',
|
||||||
component: ClonePermissionsSection,
|
component: ClonePermissionsSection,
|
||||||
decorators: [
|
decorators: [
|
||||||
(StoryComponent: React.FC) => (
|
(StoryComponent: React.FC) => (
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
const schema = z.object({ columns: z.record(z.optional(z.boolean())) });
|
const schema = z.object({ columns: z.record(z.optional(z.boolean())) });
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Components/Column Section',
|
title: 'Features/Permissions Tab/Permissions Form/Components/Column Section',
|
||||||
component: ColumnPermissionsSection,
|
component: ColumnPermissionsSection,
|
||||||
decorators: [
|
decorators: [
|
||||||
(StoryComponent: React.FC) => (
|
(StoryComponent: React.FC) => (
|
||||||
|
@ -99,14 +99,15 @@ export const ColumnPermissionsSection: React.FC<ColumnPermissionsSectionProps> =
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset className="flex gap-4">
|
<fieldset className="flex gap-4 flex-wrap">
|
||||||
{columns?.map(fieldName => (
|
{columns?.map(fieldName => (
|
||||||
<label key={fieldName} className="flex gap-2 items-center">
|
<label key={fieldName} className="flex gap-2 items-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
title={disabled ? 'Set a row permission first' : ''}
|
title={disabled ? 'Set a row permission first' : ''}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="mt-0 rounded shadow-sm border border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
|
style={{ marginTop: '0px !important' }}
|
||||||
|
className="rounded shadow-sm border border-gray-300 hover:border-gray-400 focus:ring-yellow-400"
|
||||||
{...register(`columns.${fieldName}`)}
|
{...register(`columns.${fieldName}`)}
|
||||||
/>
|
/>
|
||||||
<i>{fieldName}</i>
|
<i>{fieldName}</i>
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from './ColumnPresets';
|
} from './ColumnPresets';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Components/Presets Section',
|
title: 'Features/Permissions Tab/Permissions Form/Components/Presets Section',
|
||||||
component: ColumnPresetsSection,
|
component: ColumnPresetsSection,
|
||||||
decorators: [
|
decorators: [
|
||||||
(StoryComponent: React.FC) => (
|
(StoryComponent: React.FC) => (
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Story, Meta } from '@storybook/react';
|
|
||||||
|
|
||||||
import { JSONEditor, JSONEditorProps } from './JSONEditor';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Features/Permissions Form/Components/JSON Editor',
|
|
||||||
component: JSONEditor,
|
|
||||||
} as Meta;
|
|
||||||
|
|
||||||
export const Default: Story<JSONEditorProps> = args => <JSONEditor {...args} />;
|
|
||||||
Default.args = {
|
|
||||||
initData: '{"id":{"_eq":1}}',
|
|
||||||
};
|
|
@ -1,73 +0,0 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
|
||||||
import AceEditor, { IAnnotation } from 'react-ace';
|
|
||||||
|
|
||||||
import { isJsonString } from '@/components/Common/utils/jsUtils';
|
|
||||||
import { usePrevious } from '@/hooks/usePrevious';
|
|
||||||
|
|
||||||
export interface JSONEditorProps {
|
|
||||||
initData: string;
|
|
||||||
onChange: (v: string) => void;
|
|
||||||
data: string;
|
|
||||||
minLines?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JSONEditor: React.FC<JSONEditorProps> = ({
|
|
||||||
initData,
|
|
||||||
onChange,
|
|
||||||
data,
|
|
||||||
minLines,
|
|
||||||
}) => {
|
|
||||||
const [value, setValue] = useState(initData || data || '');
|
|
||||||
const [annotations, setAnnotations] = useState<IAnnotation[]>([]);
|
|
||||||
const prevData = usePrevious<string>(data);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// if the data prop is changed do nothing
|
|
||||||
if (prevData !== data) return;
|
|
||||||
// when state gets new data, trigger parent callback
|
|
||||||
if (value !== data) onChange(value);
|
|
||||||
}, [value, data, prevData]);
|
|
||||||
|
|
||||||
// check and set error message
|
|
||||||
useEffect(() => {
|
|
||||||
if (isJsonString(value)) {
|
|
||||||
setAnnotations([]);
|
|
||||||
} else {
|
|
||||||
setAnnotations([
|
|
||||||
{ row: 0, column: 0, text: 'Invalid JSON', type: 'error' },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
setAnnotations([]);
|
|
||||||
};
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// set data to editor only if the prop has a valid json string
|
|
||||||
// setting value from query editor will always have a valid json
|
|
||||||
// any invalid json means, the value is set from this component so no need to set that again
|
|
||||||
if (isJsonString(data)) setValue(data);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const onEditorValueChange = useCallback(
|
|
||||||
newVal => setValue(newVal),
|
|
||||||
[setValue]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AceEditor
|
|
||||||
mode="json"
|
|
||||||
onChange={onEditorValueChange}
|
|
||||||
theme="github"
|
|
||||||
height="5em"
|
|
||||||
minLines={minLines || 1}
|
|
||||||
maxLines={15}
|
|
||||||
width="100%"
|
|
||||||
showPrintMargin={false}
|
|
||||||
value={value}
|
|
||||||
annotations={annotations}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default JSONEditor;
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Story, Meta } from '@storybook/react';
|
import { Story, Meta } from '@storybook/react';
|
||||||
import { Form } from '@/new-components/Form';
|
import { Form } from '@/new-components/Form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||||
import {
|
import {
|
||||||
RowPermissionsSection,
|
RowPermissionsSection,
|
||||||
RowPermissionsProps,
|
RowPermissionsProps,
|
||||||
@ -10,11 +10,10 @@ import {
|
|||||||
RowPermissionsWrapperProps,
|
RowPermissionsWrapperProps,
|
||||||
} from './RowPermissions';
|
} from './RowPermissions';
|
||||||
|
|
||||||
// import { allSchemas, allFunctions } from '../mocks/mockData';
|
|
||||||
import { QueryType } from '../types';
|
import { QueryType } from '../types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Components/Row Section',
|
title: 'Features/Permissions Tab/Permissions Form/Components/Row Section',
|
||||||
component: RowPermissionsSection,
|
component: RowPermissionsSection,
|
||||||
decorators: [
|
decorators: [
|
||||||
(StoryComponent: React.FC) => (
|
(StoryComponent: React.FC) => (
|
||||||
@ -22,6 +21,7 @@ export default {
|
|||||||
{() => <StoryComponent />}
|
{() => <StoryComponent />}
|
||||||
</Form>
|
</Form>
|
||||||
),
|
),
|
||||||
|
ReactQueryDecorator(),
|
||||||
],
|
],
|
||||||
parameters: { chromatic: { disableSnapshot: true } },
|
parameters: { chromatic: { disableSnapshot: true } },
|
||||||
} as Meta;
|
} as Meta;
|
||||||
@ -57,6 +57,7 @@ Insert.args = {
|
|||||||
schema: 'public',
|
schema: 'public',
|
||||||
name: 'user',
|
name: 'user',
|
||||||
},
|
},
|
||||||
|
dataSourceName: 'chinook',
|
||||||
queryType: 'delete',
|
queryType: 'delete',
|
||||||
allRowChecks,
|
allRowChecks,
|
||||||
// allSchemas,
|
// allSchemas,
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import AceEditor from 'react-ace';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { Table } from '@/features/MetadataAPI';
|
||||||
import 'brace/mode/json';
|
import { useHttpClient } from '@/features/Network';
|
||||||
import 'brace/theme/github';
|
import { useQuery } from 'react-query';
|
||||||
|
import { exportMetadata } from '@/features/DataSource';
|
||||||
|
import { areTablesEqual } from '@/features/RelationshipsTable';
|
||||||
|
import { getTypeName } from '@/features/GraphQLUtils';
|
||||||
import { InputField } from '@/new-components/Form';
|
import { InputField } from '@/new-components/Form';
|
||||||
import { IconTooltip } from '@/new-components/Tooltip';
|
import { IconTooltip } from '@/new-components/Tooltip';
|
||||||
import { Collapse } from '@/new-components/deprecated';
|
import { Collapse } from '@/new-components/deprecated';
|
||||||
import { getIngForm } from '../../../components/Services/Data/utils';
|
import { getIngForm } from '../../../components/Services/Data/utils';
|
||||||
|
|
||||||
import JSONEditor from './JSONEditor';
|
|
||||||
import { RowPermissionBuilder } from './RowPermissionsBuilder';
|
import { RowPermissionBuilder } from './RowPermissionsBuilder';
|
||||||
|
|
||||||
import { QueryType } from '../types';
|
import { QueryType } from '../types';
|
||||||
@ -37,6 +39,7 @@ export interface RowPermissionsProps {
|
|||||||
queryType: QueryType;
|
queryType: QueryType;
|
||||||
subQueryType?: string;
|
subQueryType?: string;
|
||||||
allRowChecks: Array<{ queryType: QueryType; value: string }>;
|
allRowChecks: Array<{ queryType: QueryType; value: string }>;
|
||||||
|
dataSourceName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SelectedSection {
|
enum SelectedSection {
|
||||||
@ -80,26 +83,40 @@ const getRowPermissionCheckType = (
|
|||||||
return 'filterType';
|
return 'filterType';
|
||||||
};
|
};
|
||||||
|
|
||||||
const isGDCTable = (table: unknown): table is string[] => {
|
const useTypeName = ({
|
||||||
return Array.isArray(table);
|
table,
|
||||||
};
|
dataSourceName,
|
||||||
|
}: {
|
||||||
|
table: Table;
|
||||||
|
dataSourceName: string;
|
||||||
|
}) => {
|
||||||
|
const httpClient = useHttpClient();
|
||||||
|
|
||||||
const hasTableName = (table: unknown): table is { name: string } => {
|
return useQuery({
|
||||||
return typeof table === 'object' && 'name' in (table || {});
|
queryKey: ['gql_introspection', 'type_name', table, dataSourceName],
|
||||||
};
|
queryFn: async () => {
|
||||||
|
const { metadata } = await exportMetadata({ httpClient });
|
||||||
|
const metadataSource = metadata.sources.find(
|
||||||
|
s => s.name === dataSourceName
|
||||||
|
);
|
||||||
|
const metadataTable = metadataSource?.tables.find(t =>
|
||||||
|
areTablesEqual(t.table, table)
|
||||||
|
);
|
||||||
|
|
||||||
const getTableName = (table: unknown) => {
|
if (!metadataSource || !metadataTable)
|
||||||
const gdcTable = isGDCTable(table);
|
throw Error('unable to generate type name');
|
||||||
if (gdcTable) {
|
|
||||||
return table[table.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableName = hasTableName(table);
|
// This is very GDC specific. We have to move this to DAL later
|
||||||
if (tableName) {
|
const typeName = getTypeName({
|
||||||
return table.name;
|
defaultQueryRoot: (table as string[]).join('_'),
|
||||||
}
|
operation: 'select',
|
||||||
|
sourceCustomization: metadataSource?.customization,
|
||||||
|
configuration: metadataTable.configuration,
|
||||||
|
});
|
||||||
|
|
||||||
throw new Error('cannot read table');
|
return typeName;
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RowPermissionsSection: React.FC<RowPermissionsProps> = ({
|
export const RowPermissionsSection: React.FC<RowPermissionsProps> = ({
|
||||||
@ -107,8 +124,9 @@ export const RowPermissionsSection: React.FC<RowPermissionsProps> = ({
|
|||||||
queryType,
|
queryType,
|
||||||
subQueryType,
|
subQueryType,
|
||||||
allRowChecks,
|
allRowChecks,
|
||||||
|
dataSourceName,
|
||||||
}) => {
|
}) => {
|
||||||
const tableName = getTableName(table);
|
const { data: tableName, isLoading } = useTypeName({ table, dataSourceName });
|
||||||
const { register, watch, setValue } = useFormContext();
|
const { register, watch, setValue } = useFormContext();
|
||||||
// determines whether the inputs should be pointed at `check` or `filter`
|
// determines whether the inputs should be pointed at `check` or `filter`
|
||||||
const rowPermissions = getRowPermission(queryType, subQueryType);
|
const rowPermissions = getRowPermission(queryType, subQueryType);
|
||||||
@ -143,13 +161,20 @@ export const RowPermissionsSection: React.FC<RowPermissionsProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
{selectedSection === SelectedSection.NoChecks && (
|
{selectedSection === SelectedSection.NoChecks && (
|
||||||
<div className="pt-4">
|
<div className="mt-4 p-6 rounded-lg bg-white border border-gray-200 min-h-32 w-full">
|
||||||
<JSONEditor
|
<AceEditor
|
||||||
data="{}"
|
mode="json"
|
||||||
|
minLines={1}
|
||||||
|
fontSize={14}
|
||||||
|
height="18px"
|
||||||
|
width="100%"
|
||||||
|
theme="github"
|
||||||
|
name={`${tableName}-json-editor`}
|
||||||
|
value="{}"
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
setValue(rowPermissionsCheckType, SelectedSection.Custom)
|
setValue(rowPermissionsCheckType, SelectedSection.Custom)
|
||||||
}
|
}
|
||||||
initData="{}"
|
editorProps={{ $blockScrolling: true }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -175,14 +200,20 @@ export const RowPermissionsSection: React.FC<RowPermissionsProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
{selectedSection === query && (
|
{selectedSection === query && (
|
||||||
<div className="pt-4">
|
<div className="mt-4 p-6 rounded-lg bg-white border border-gray-200 min-h-32 w-full">
|
||||||
<JSONEditor
|
<AceEditor
|
||||||
data={value}
|
mode="json"
|
||||||
onChange={output => {
|
minLines={1}
|
||||||
setValue(rowPermissionsCheckType, SelectedSection.Custom);
|
fontSize={14}
|
||||||
setValue(rowPermissions, output);
|
height="18px"
|
||||||
}}
|
width="100%"
|
||||||
initData=""
|
theme="github"
|
||||||
|
name={`${tableName}-json-editor`}
|
||||||
|
value="{}"
|
||||||
|
onChange={() =>
|
||||||
|
setValue(rowPermissionsCheckType, SelectedSection.Custom)
|
||||||
|
}
|
||||||
|
editorProps={{ $blockScrolling: true }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -203,7 +234,16 @@ export const RowPermissionsSection: React.FC<RowPermissionsProps> = ({
|
|||||||
|
|
||||||
{selectedSection === SelectedSection.Custom && (
|
{selectedSection === SelectedSection.Custom && (
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<RowPermissionBuilder tableName={tableName} nesting={['filter']} />
|
{!isLoading && tableName ? (
|
||||||
|
<RowPermissionBuilder
|
||||||
|
tableName={tableName}
|
||||||
|
nesting={['filter']}
|
||||||
|
table={table}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>Loading...</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { ComponentStory, Meta } from '@storybook/react';
|
import { ComponentStory, Meta } from '@storybook/react';
|
||||||
|
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||||
import { UpdatedForm } from '@/new-components/Form';
|
import { UpdatedForm } from '@/new-components/Form';
|
||||||
|
|
||||||
import { RowPermissionBuilder } from './RowPermissionBuilder';
|
import { RowPermissionBuilder } from './RowPermissionBuilder';
|
||||||
@ -15,8 +16,10 @@ import {
|
|||||||
} from './mocks';
|
} from './mocks';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Form/Components/New Builder',
|
title:
|
||||||
|
'Features/Permissions Tab/Permissions Form/Components/Row Permissions Builder',
|
||||||
component: RowPermissionBuilder,
|
component: RowPermissionBuilder,
|
||||||
|
decorators: [ReactQueryDecorator()],
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: handlers(),
|
msw: handlers(),
|
||||||
},
|
},
|
||||||
@ -60,6 +63,7 @@ WithDefaults.decorators = [
|
|||||||
tableName: 'Album',
|
tableName: 'Album',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: simpleExample,
|
existingPermission: simpleExample,
|
||||||
|
tableConfig: {},
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
onSubmit={console.log}
|
onSubmit={console.log}
|
||||||
@ -92,6 +96,7 @@ WithDefaultsBool.decorators = [
|
|||||||
tableName: 'user',
|
tableName: 'user',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: exampleWithBoolOperator,
|
existingPermission: exampleWithBoolOperator,
|
||||||
|
tableConfig: {},
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
onSubmit={console.log}
|
onSubmit={console.log}
|
||||||
@ -124,6 +129,7 @@ WithDefaultsRelationship.decorators = [
|
|||||||
tableName: 'user',
|
tableName: 'user',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: exampleWithRelationship,
|
existingPermission: exampleWithRelationship,
|
||||||
|
tableConfig: {},
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
onSubmit={console.log}
|
onSubmit={console.log}
|
||||||
@ -157,6 +163,7 @@ WithPointlesslyComplicatedRelationship.decorators = [
|
|||||||
tableName: 'user',
|
tableName: 'user',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: complicatedExample,
|
existingPermission: complicatedExample,
|
||||||
|
tableConfig: {},
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
onSubmit={console.log}
|
onSubmit={console.log}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Table } from '@/features/MetadataAPI';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AceEditor from 'react-ace';
|
import AceEditor from 'react-ace';
|
||||||
|
|
||||||
@ -16,9 +17,16 @@ interface Props {
|
|||||||
* e.g. ['filter', 'Title', '_eq'] would be registered as 'filter.Title._eq'
|
* e.g. ['filter', 'Title', '_eq'] would be registered as 'filter.Title._eq'
|
||||||
*/
|
*/
|
||||||
nesting: string[];
|
nesting: string[];
|
||||||
|
table: Table;
|
||||||
|
dataSourceName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RowPermissionBuilder = ({ tableName, nesting }: Props) => {
|
export const RowPermissionBuilder = ({
|
||||||
|
tableName,
|
||||||
|
nesting,
|
||||||
|
table,
|
||||||
|
dataSourceName,
|
||||||
|
}: Props) => {
|
||||||
const { watch } = useFormContext();
|
const { watch } = useFormContext();
|
||||||
const { data: schema } = useIntrospectSchema();
|
const { data: schema } = useIntrospectSchema();
|
||||||
|
|
||||||
@ -26,13 +34,17 @@ export const RowPermissionBuilder = ({ tableName, nesting }: Props) => {
|
|||||||
// this value will always be 'filter' or 'check' depending on the query type
|
// this value will always be 'filter' or 'check' depending on the query type
|
||||||
const value = watch(nesting[0]);
|
const value = watch(nesting[0]);
|
||||||
const json = createDisplayJson(value || {});
|
const json = createDisplayJson(value || {});
|
||||||
|
// const { data: tableConfig } = useTableConfiguration({
|
||||||
|
// table,
|
||||||
|
// dataSourceName,
|
||||||
|
// });
|
||||||
|
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4 w-full">
|
<div key={tableName} className="flex flex-col space-y-4 w-full">
|
||||||
<div className="p-6 rounded-lg bg-white border border-gray-200 min-h-32 w-full">
|
<div className="p-6 rounded-lg bg-white border border-gray-200 min-h-32 w-full">
|
||||||
<AceEditor
|
<AceEditor
|
||||||
mode="json"
|
mode="json"
|
||||||
@ -49,7 +61,13 @@ export const RowPermissionBuilder = ({ tableName, nesting }: Props) => {
|
|||||||
<div className="p-6 rounded-lg bg-white border border-gray-200w-full">
|
<div className="p-6 rounded-lg bg-white border border-gray-200w-full">
|
||||||
<JsonItem text="{" />
|
<JsonItem text="{" />
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<Builder tableName={tableName} nesting={nesting} schema={schema} />
|
<Builder
|
||||||
|
tableName={tableName}
|
||||||
|
nesting={nesting}
|
||||||
|
schema={schema}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<JsonItem text="}" />
|
<JsonItem text="}" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { GraphQLSchema } from 'graphql';
|
import { GraphQLSchema } from 'graphql';
|
||||||
|
import { Table } from '@/features/MetadataAPI';
|
||||||
import { RenderFormElement } from './RenderFormElement';
|
import { RenderFormElement } from './RenderFormElement';
|
||||||
import { CustomField } from './Fields';
|
import { CustomField } from './Fields';
|
||||||
import { JsonItem } from './Elements';
|
import { JsonItem } from './Elements';
|
||||||
|
|
||||||
import { getColumnOperators } from '../utils';
|
import { getColumnOperators } from '../utils';
|
||||||
|
|
||||||
import { useData } from '../hooks';
|
import { useData } from '../hooks';
|
||||||
@ -110,15 +109,22 @@ interface Props {
|
|||||||
*/
|
*/
|
||||||
nesting: string[];
|
nesting: string[];
|
||||||
schema: GraphQLSchema;
|
schema: GraphQLSchema;
|
||||||
|
dataSourceName: string;
|
||||||
|
table: Table;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Builder = (props: Props) => {
|
export const Builder = (props: Props) => {
|
||||||
const { tableName, nesting, schema } = props;
|
const { tableName, nesting, schema, dataSourceName, table } = props;
|
||||||
|
|
||||||
const { data } = useData({ tableName, schema });
|
|
||||||
|
|
||||||
|
const { data, tableConfig } = useData({
|
||||||
|
tableName,
|
||||||
|
schema,
|
||||||
|
// we have to pass in table like this because if it is a relationship if will
|
||||||
|
// fetch the wrong table config otherwise
|
||||||
|
table,
|
||||||
|
dataSourceName,
|
||||||
|
});
|
||||||
const { unregister, setValue, getValues } = useFormContext();
|
const { unregister, setValue, getValues } = useFormContext();
|
||||||
|
|
||||||
// the selections from the dropdowns are stored on the form state under the key "operators"
|
// the selections from the dropdowns are stored on the form state under the key "operators"
|
||||||
// this will be removed for submitting the form
|
// this will be removed for submitting the form
|
||||||
// and is generated from the permissions object when rendering the form from existing data
|
// and is generated from the permissions object when rendering the form from existing data
|
||||||
@ -138,11 +144,12 @@ export const Builder = (props: Props) => {
|
|||||||
tableName,
|
tableName,
|
||||||
columnName: dropDownState.name,
|
columnName: dropDownState.name,
|
||||||
schema,
|
schema,
|
||||||
|
tableConfig,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}, [tableName, dropDownState, schema]);
|
}, [tableName, dropDownState, schema, tableConfig]);
|
||||||
|
|
||||||
const handleDropdownChange: React.ChangeEventHandler<HTMLSelectElement> =
|
const handleDropdownChange: React.ChangeEventHandler<HTMLSelectElement> =
|
||||||
e => {
|
e => {
|
||||||
@ -216,6 +223,8 @@ export const Builder = (props: Props) => {
|
|||||||
handleColumnChange={handleColumnChange}
|
handleColumnChange={handleColumnChange}
|
||||||
nesting={nesting}
|
nesting={nesting}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
table={table}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,9 +2,10 @@ import React from 'react';
|
|||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { GraphQLSchema } from 'graphql';
|
import { GraphQLSchema } from 'graphql';
|
||||||
|
import { Table } from '@/features/MetadataAPI';
|
||||||
import { Builder } from './Builder';
|
import { Builder } from './Builder';
|
||||||
import { JsonItem } from './Elements';
|
import { JsonItem } from './Elements';
|
||||||
|
// import { useTableConfiguration } from '../hooks';
|
||||||
|
|
||||||
interface FieldArrayElementProps {
|
interface FieldArrayElementProps {
|
||||||
index: number;
|
index: number;
|
||||||
@ -15,13 +16,27 @@ interface FieldArrayElementProps {
|
|||||||
fields: Field[];
|
fields: Field[];
|
||||||
append: ReturnType<typeof useFieldArray>['append'];
|
append: ReturnType<typeof useFieldArray>['append'];
|
||||||
schema: GraphQLSchema;
|
schema: GraphQLSchema;
|
||||||
|
dataSourceName: string;
|
||||||
|
table: Table;
|
||||||
|
// tableConfig: ReturnType<typeof useTableConfiguration>['data'];
|
||||||
}
|
}
|
||||||
|
|
||||||
type Field = Record<'id', string>;
|
type Field = Record<'id', string>;
|
||||||
|
|
||||||
export const FieldArrayElement = (props: FieldArrayElementProps) => {
|
export const FieldArrayElement = (props: FieldArrayElementProps) => {
|
||||||
const { index, arrayKey, tableName, field, nesting, fields, append, schema } =
|
const {
|
||||||
props;
|
index,
|
||||||
|
arrayKey,
|
||||||
|
tableName,
|
||||||
|
field,
|
||||||
|
nesting,
|
||||||
|
fields,
|
||||||
|
append,
|
||||||
|
schema,
|
||||||
|
table,
|
||||||
|
dataSourceName,
|
||||||
|
// tableConfig,
|
||||||
|
} = props;
|
||||||
const { watch } = useFormContext();
|
const { watch } = useFormContext();
|
||||||
|
|
||||||
// from this we can determine if the dropdown has been selected
|
// from this we can determine if the dropdown has been selected
|
||||||
@ -44,6 +59,9 @@ export const FieldArrayElement = (props: FieldArrayElementProps) => {
|
|||||||
tableName={tableName}
|
tableName={tableName}
|
||||||
nesting={[...nesting, index.toString()]}
|
nesting={[...nesting, index.toString()]}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
|
table={table}
|
||||||
|
// tableConfig={tableConfig}
|
||||||
/>
|
/>
|
||||||
<JsonItem text="}" />
|
<JsonItem text="}" />
|
||||||
</div>
|
</div>
|
||||||
@ -60,6 +78,9 @@ export const FieldArrayElement = (props: FieldArrayElementProps) => {
|
|||||||
tableName={tableName}
|
tableName={tableName}
|
||||||
nesting={[...nesting, index.toString()]}
|
nesting={[...nesting, index.toString()]}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
|
table={table}
|
||||||
|
// tableConfig={tableConfig}
|
||||||
/>
|
/>
|
||||||
<JsonItem text="}," />
|
<JsonItem text="}," />
|
||||||
</div>
|
</div>
|
||||||
@ -70,10 +91,20 @@ interface Props {
|
|||||||
tableName: string;
|
tableName: string;
|
||||||
nesting: string[];
|
nesting: string[];
|
||||||
schema: GraphQLSchema;
|
schema: GraphQLSchema;
|
||||||
|
dataSourceName: string;
|
||||||
|
table: Table;
|
||||||
|
// tableConfig: ReturnType<typeof useTableConfiguration>['data'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldArray = (props: Props) => {
|
export const FieldArray = (props: Props) => {
|
||||||
const { tableName, nesting, schema } = props;
|
const {
|
||||||
|
tableName,
|
||||||
|
nesting,
|
||||||
|
schema,
|
||||||
|
dataSourceName,
|
||||||
|
table,
|
||||||
|
// tableConfig
|
||||||
|
} = props;
|
||||||
const arrayKey = nesting.join('.');
|
const arrayKey = nesting.join('.');
|
||||||
|
|
||||||
const { fields, append } = useFieldArray({
|
const { fields, append } = useFieldArray({
|
||||||
@ -103,6 +134,9 @@ export const FieldArray = (props: Props) => {
|
|||||||
nesting={nesting}
|
nesting={nesting}
|
||||||
append={append}
|
append={append}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
|
table={table}
|
||||||
|
// tableConfig={tableConfig}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { GraphQLSchema } from 'graphql';
|
import { GraphQLSchema } from 'graphql';
|
||||||
|
import { Table } from '@/features/MetadataAPI';
|
||||||
import { CustomField } from './Fields';
|
import { CustomField } from './Fields';
|
||||||
import { FieldArray } from './FieldArray';
|
import { FieldArray } from './FieldArray';
|
||||||
import { Builder } from './Builder';
|
import { Builder } from './Builder';
|
||||||
@ -23,6 +22,8 @@ interface Props {
|
|||||||
*/
|
*/
|
||||||
nesting: string[];
|
nesting: string[];
|
||||||
schema: GraphQLSchema;
|
schema: GraphQLSchema;
|
||||||
|
table: Table;
|
||||||
|
dataSourceName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RenderFormElement = (props: Props) => {
|
export const RenderFormElement = (props: Props) => {
|
||||||
@ -34,6 +35,9 @@ export const RenderFormElement = (props: Props) => {
|
|||||||
handleColumnChange,
|
handleColumnChange,
|
||||||
nesting,
|
nesting,
|
||||||
schema,
|
schema,
|
||||||
|
table,
|
||||||
|
dataSourceName,
|
||||||
|
// tableConfig,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { register, setValue, watch } = useFormContext();
|
const { register, setValue, watch } = useFormContext();
|
||||||
@ -117,6 +121,8 @@ export const RenderFormElement = (props: Props) => {
|
|||||||
tableName={dropDownState.typeName}
|
tableName={dropDownState.typeName}
|
||||||
nesting={[...nesting, dropDownState.name]}
|
nesting={[...nesting, dropDownState.name]}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
table={table}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<JsonItem text="}" />
|
<JsonItem text="}" />
|
||||||
@ -134,6 +140,8 @@ export const RenderFormElement = (props: Props) => {
|
|||||||
tableName={tableName}
|
tableName={tableName}
|
||||||
nesting={[...nesting, dropDownState.name]}
|
nesting={[...nesting, dropDownState.name]}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
table={table}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<JsonItem text="}" />
|
<JsonItem text="}" />
|
||||||
@ -146,6 +154,8 @@ export const RenderFormElement = (props: Props) => {
|
|||||||
tableName={tableName}
|
tableName={tableName}
|
||||||
nesting={[...nesting, dropDownState.name]}
|
nesting={[...nesting, dropDownState.name]}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
table={table}
|
||||||
|
dataSourceName={dataSourceName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { buildClientSchema, GraphQLSchema, IntrospectionQuery } from 'graphql';
|
import { buildClientSchema, GraphQLSchema, IntrospectionQuery } from 'graphql';
|
||||||
import { useHttpClient } from '@/features/Network';
|
import { useHttpClient } from '@/features/Network';
|
||||||
import { runIntrospectionQuery } from '@/features/DataSource';
|
import { exportMetadata, runIntrospectionQuery } from '@/features/DataSource';
|
||||||
|
import { Table } from '@/features/MetadataAPI';
|
||||||
import { createDefaultValues, getAllColumnsAndOperators } from '../utils';
|
import { useQuery } from 'react-query';
|
||||||
|
import { areTablesEqual } from '@/features/RelationshipsTable';
|
||||||
|
import { getAllColumnsAndOperators } from '../utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -28,9 +30,33 @@ export const useIntrospectSchema = () => {
|
|||||||
return { data: schema };
|
return { data: schema };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useTableConfiguration = ({
|
||||||
|
dataSourceName,
|
||||||
|
table,
|
||||||
|
}: {
|
||||||
|
dataSourceName: string;
|
||||||
|
table: Table;
|
||||||
|
}) => {
|
||||||
|
const httpClient = useHttpClient();
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['export_metadata', dataSourceName, table, 'configuration'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { metadata } = await exportMetadata({ httpClient });
|
||||||
|
const metadataTable = metadata.sources
|
||||||
|
.find(s => s.name === dataSourceName)
|
||||||
|
?.tables.find(t => areTablesEqual(t.table, table));
|
||||||
|
if (!metadata) throw Error('Unable to find table in metadata');
|
||||||
|
|
||||||
|
return metadataTable?.configuration ?? {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
interface Args {
|
interface Args {
|
||||||
tableName: string;
|
tableName: string;
|
||||||
schema?: GraphQLSchema;
|
schema?: GraphQLSchema;
|
||||||
|
table: Table;
|
||||||
|
dataSourceName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +64,11 @@ interface Args {
|
|||||||
* get all boolOperators, columns and relationships
|
* get all boolOperators, columns and relationships
|
||||||
* and information about types for each
|
* and information about types for each
|
||||||
*/
|
*/
|
||||||
export const useData = ({ tableName, schema }: Args) => {
|
export const useData = ({ tableName, schema, table, dataSourceName }: Args) => {
|
||||||
|
const { data: tableConfig } = useTableConfiguration({
|
||||||
|
table,
|
||||||
|
dataSourceName,
|
||||||
|
});
|
||||||
if (!schema)
|
if (!schema)
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
@ -47,21 +77,7 @@ export const useData = ({ tableName, schema }: Args) => {
|
|||||||
relationships: [],
|
relationships: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const data = getAllColumnsAndOperators({ tableName, schema });
|
|
||||||
return { data };
|
const data = getAllColumnsAndOperators({ tableName, schema, tableConfig });
|
||||||
};
|
return { data, tableConfig };
|
||||||
|
|
||||||
interface A {
|
|
||||||
tableName: string;
|
|
||||||
existingPermission: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCreateRowPermissionsDefaults = () => {
|
|
||||||
const { data: schema } = useIntrospectSchema();
|
|
||||||
|
|
||||||
const fetchDefaults = async ({ tableName, existingPermission }: A) => {
|
|
||||||
createDefaultValues({ tableName, schema, existingPermission });
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetchDefaults;
|
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@ test('renders basic permission', () => {
|
|||||||
tableName: 'Album',
|
tableName: 'Album',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: simpleExample,
|
existingPermission: simpleExample,
|
||||||
|
tableConfig: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected: Expected = {
|
const expected: Expected = {
|
||||||
@ -43,6 +44,7 @@ test('renders bool operator permission', () => {
|
|||||||
tableName: 'Album',
|
tableName: 'Album',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: exampleWithBoolOperator,
|
existingPermission: exampleWithBoolOperator,
|
||||||
|
tableConfig: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
@ -78,6 +80,7 @@ test('renders permission with relationship', () => {
|
|||||||
tableName: 'Album',
|
tableName: 'Album',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: exampleWithRelationship,
|
existingPermission: exampleWithRelationship,
|
||||||
|
tableConfig: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected: Expected = {
|
const expected: Expected = {
|
||||||
@ -101,6 +104,7 @@ test('renders complex permission', () => {
|
|||||||
tableName: 'user',
|
tableName: 'user',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: complicatedExample,
|
existingPermission: complicatedExample,
|
||||||
|
tableConfig: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected: Expected = {
|
const expected: Expected = {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { MetadataTable } from '@/features/MetadataAPI';
|
||||||
import { GraphQLSchema } from 'graphql';
|
import { GraphQLSchema } from 'graphql';
|
||||||
import { getAllColumnsAndOperators } from '.';
|
import { getAllColumnsAndOperators } from '.';
|
||||||
|
|
||||||
@ -5,17 +6,19 @@ export interface CreateOperatorsArgs {
|
|||||||
tableName: string;
|
tableName: string;
|
||||||
schema?: GraphQLSchema;
|
schema?: GraphQLSchema;
|
||||||
existingPermission?: Record<string, any>;
|
existingPermission?: Record<string, any>;
|
||||||
|
tableConfig: MetadataTable['configuration'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createOperatorsObject = ({
|
export const createOperatorsObject = ({
|
||||||
tableName,
|
tableName,
|
||||||
schema,
|
schema,
|
||||||
existingPermission,
|
existingPermission,
|
||||||
|
tableConfig,
|
||||||
}: CreateOperatorsArgs): Record<string, any> => {
|
}: CreateOperatorsArgs): Record<string, any> => {
|
||||||
if (!existingPermission || !schema) {
|
if (!existingPermission || !schema) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const data = getAllColumnsAndOperators({ tableName, schema });
|
const data = getAllColumnsAndOperators({ tableName, schema, tableConfig });
|
||||||
|
|
||||||
const colNames = data.columns.map(col => col.name);
|
const colNames = data.columns.map(col => col.name);
|
||||||
const boolOperators = data.boolOperators.map(bo => bo.name);
|
const boolOperators = data.boolOperators.map(bo => bo.name);
|
||||||
@ -33,6 +36,7 @@ export const createOperatorsObject = ({
|
|||||||
tableName,
|
tableName,
|
||||||
schema,
|
schema,
|
||||||
existingPermission: each,
|
existingPermission: each,
|
||||||
|
tableConfig,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@ -50,6 +54,7 @@ export const createOperatorsObject = ({
|
|||||||
tableName: typeName || '',
|
tableName: typeName || '',
|
||||||
schema,
|
schema,
|
||||||
existingPermission: value,
|
existingPermission: value,
|
||||||
|
tableConfig,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -63,6 +68,7 @@ export const createOperatorsObject = ({
|
|||||||
tableName,
|
tableName,
|
||||||
schema,
|
schema,
|
||||||
existingPermission: value,
|
existingPermission: value,
|
||||||
|
tableConfig,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -79,10 +85,11 @@ export interface CreateDefaultsArgs {
|
|||||||
tableName: string;
|
tableName: string;
|
||||||
schema?: GraphQLSchema;
|
schema?: GraphQLSchema;
|
||||||
existingPermission?: Record<string, any>;
|
existingPermission?: Record<string, any>;
|
||||||
|
tableConfig: MetadataTable['configuration'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createDefaultValues = (props: CreateDefaultsArgs) => {
|
export const createDefaultValues = (props: CreateDefaultsArgs) => {
|
||||||
const { tableName, schema, existingPermission } = props;
|
const { tableName, schema, existingPermission, tableConfig } = props;
|
||||||
if (!existingPermission) {
|
if (!existingPermission) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -91,6 +98,7 @@ export const createDefaultValues = (props: CreateDefaultsArgs) => {
|
|||||||
tableName,
|
tableName,
|
||||||
schema,
|
schema,
|
||||||
existingPermission,
|
existingPermission,
|
||||||
|
tableConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -6,7 +6,11 @@ import {
|
|||||||
import { schema } from '../mocks';
|
import { schema } from '../mocks';
|
||||||
|
|
||||||
test('correctly fetches items for dropdown from schema', () => {
|
test('correctly fetches items for dropdown from schema', () => {
|
||||||
const result = getAllColumnsAndOperators({ tableName: 'user', schema });
|
const result = getAllColumnsAndOperators({
|
||||||
|
tableName: 'user',
|
||||||
|
schema,
|
||||||
|
tableConfig: {},
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.boolOperators.length).toBe(3);
|
expect(result.boolOperators.length).toBe(3);
|
||||||
expect(result.columns.length).toBe(4);
|
expect(result.columns.length).toBe(4);
|
||||||
@ -25,6 +29,7 @@ test('correctly fetches operators for a given column', () => {
|
|||||||
tableName: 'user',
|
tableName: 'user',
|
||||||
schema,
|
schema,
|
||||||
columnName: 'age',
|
columnName: 'age',
|
||||||
|
tableConfig: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected: ReturnType<typeof getColumnOperators> = [
|
const expected: ReturnType<typeof getColumnOperators> = [
|
||||||
@ -94,6 +99,7 @@ test('correctly fetches information about a column operator', () => {
|
|||||||
tableName: 'user',
|
tableName: 'user',
|
||||||
schema,
|
schema,
|
||||||
columnName: 'age',
|
columnName: 'age',
|
||||||
|
tableConfig: {},
|
||||||
});
|
});
|
||||||
const result = findColumnOperator({ columnKey: '_eq', columnOperators });
|
const result = findColumnOperator({ columnKey: '_eq', columnOperators });
|
||||||
const expected: ReturnType<typeof findColumnOperator> = {
|
const expected: ReturnType<typeof findColumnOperator> = {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { MetadataTable } from '@/features/MetadataAPI';
|
||||||
import {
|
import {
|
||||||
GraphQLFieldMap,
|
GraphQLFieldMap,
|
||||||
GraphQLInputFieldMap,
|
GraphQLInputFieldMap,
|
||||||
@ -71,16 +72,20 @@ interface GetColumnOperatorsArgs {
|
|||||||
tableName: string;
|
tableName: string;
|
||||||
columnName: string;
|
columnName: string;
|
||||||
schema: GraphQLSchema;
|
schema: GraphQLSchema;
|
||||||
|
tableConfig: MetadataTable['configuration'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getColumnOperators = ({
|
export const getColumnOperators = ({
|
||||||
tableName,
|
tableName,
|
||||||
columnName,
|
columnName,
|
||||||
schema,
|
schema,
|
||||||
|
tableConfig,
|
||||||
}: GetColumnOperatorsArgs) => {
|
}: GetColumnOperatorsArgs) => {
|
||||||
const fields = getFields(`${tableName}_bool_exp`, schema);
|
const fields = getFields(`${tableName}_bool_exp`, schema);
|
||||||
|
|
||||||
const col = fields?.[columnName];
|
const customName =
|
||||||
|
tableConfig?.column_config?.[columnName]?.custom_name ?? columnName;
|
||||||
|
const col = fields?.[customName];
|
||||||
|
|
||||||
if (col?.type && !isListType(col?.type)) {
|
if (col?.type && !isListType(col?.type)) {
|
||||||
const colType = schema.getType(col.type.name);
|
const colType = schema.getType(col.type.name);
|
||||||
@ -156,14 +161,32 @@ export const findColumnOperator = ({
|
|||||||
interface Args {
|
interface Args {
|
||||||
tableName: string;
|
tableName: string;
|
||||||
schema: GraphQLSchema;
|
schema: GraphQLSchema;
|
||||||
|
tableConfig: MetadataTable['configuration'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOriginalTableNameFromCustomName = (
|
||||||
|
tableConfig: MetadataTable['configuration'],
|
||||||
|
columnName: string
|
||||||
|
) => {
|
||||||
|
const columnConfig = tableConfig?.column_config;
|
||||||
|
|
||||||
|
const matchingEntry = Object.entries(columnConfig ?? {}).find(value => {
|
||||||
|
return value?.[1]?.custom_name === columnName;
|
||||||
|
});
|
||||||
|
|
||||||
|
return matchingEntry?.[0] ?? columnName;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Returns a list of all the boolOperators, columns and relationships for the selected table
|
* Returns a list of all the boolOperators, columns and relationships for the selected table
|
||||||
*/
|
*/
|
||||||
export const getAllColumnsAndOperators = ({ tableName, schema }: Args) => {
|
export const getAllColumnsAndOperators = ({
|
||||||
const fields = getFields(tableName, schema);
|
tableName,
|
||||||
|
schema,
|
||||||
|
tableConfig,
|
||||||
|
}: Args) => {
|
||||||
|
const metadataTableName = tableConfig?.custom_name ?? tableName;
|
||||||
|
const fields = getFields(metadataTableName, schema);
|
||||||
const boolOperators = getBoolOperators();
|
const boolOperators = getBoolOperators();
|
||||||
const columns = getColumns(fields);
|
const columns = getColumns(fields);
|
||||||
const relationships = getRelationships(fields);
|
const relationships = getRelationships(fields);
|
||||||
@ -174,7 +197,7 @@ export const getAllColumnsAndOperators = ({ tableName, schema }: Args) => {
|
|||||||
meta: null,
|
meta: null,
|
||||||
}));
|
}));
|
||||||
const colMap = columns.map(column => ({
|
const colMap = columns.map(column => ({
|
||||||
name: column.name,
|
name: getOriginalTableNameFromCustomName(tableConfig, column.name),
|
||||||
kind: 'column',
|
kind: 'column',
|
||||||
meta: column,
|
meta: column,
|
||||||
}));
|
}));
|
||||||
@ -183,6 +206,5 @@ export const getAllColumnsAndOperators = ({ tableName, schema }: Args) => {
|
|||||||
kind: 'relationship',
|
kind: 'relationship',
|
||||||
meta: relationship,
|
meta: relationship,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return { boolOperators: boolMap, columns: colMap, relationships: relMap };
|
return { boolOperators: boolMap, columns: colMap, relationships: relMap };
|
||||||
};
|
};
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export * from './useDefaultValues';
|
export * from './useFormData';
|
||||||
export * from './useFormData/useFormData';
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from './useDefaultValues';
|
|
@ -1,48 +0,0 @@
|
|||||||
import { createDefaultValues } from '../..';
|
|
||||||
import { schema } from '../../../../components/RowPermissionsBuilder/mocks';
|
|
||||||
|
|
||||||
export const input: Parameters<typeof createDefaultValues>[0] = {
|
|
||||||
queryType: 'select' as const,
|
|
||||||
roleName: 'user',
|
|
||||||
tableColumns: [
|
|
||||||
{
|
|
||||||
name: 'ArtistId',
|
|
||||||
dataType: 'number',
|
|
||||||
nullable: false,
|
|
||||||
isPrimaryKey: true,
|
|
||||||
graphQLProperties: {
|
|
||||||
name: 'ArtistId',
|
|
||||||
scalarType: 'decimal',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Name',
|
|
||||||
dataType: 'string',
|
|
||||||
nullable: true,
|
|
||||||
isPrimaryKey: false,
|
|
||||||
graphQLProperties: {
|
|
||||||
name: 'Name',
|
|
||||||
scalarType: 'String',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
selectedTable: {
|
|
||||||
table: ['Artist'],
|
|
||||||
select_permissions: [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
permission: {
|
|
||||||
columns: ['Name'],
|
|
||||||
filter: {
|
|
||||||
ArtistId: {
|
|
||||||
_gt: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
limit: 3,
|
|
||||||
allow_aggregations: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
schema,
|
|
||||||
};
|
|
@ -1,36 +0,0 @@
|
|||||||
import { createDefaultValues } from './useDefaultValues';
|
|
||||||
import { input } from './mock';
|
|
||||||
|
|
||||||
const mockResult: ReturnType<typeof createDefaultValues> = {
|
|
||||||
aggregationEnabled: true,
|
|
||||||
allRowChecks: [],
|
|
||||||
backendOnly: false,
|
|
||||||
check: {},
|
|
||||||
checkType: 'none',
|
|
||||||
clonePermissions: [],
|
|
||||||
columns: {
|
|
||||||
ArtistId: false,
|
|
||||||
Name: true,
|
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
ArtistId: {
|
|
||||||
_gt: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filterType: 'custom',
|
|
||||||
operators: {
|
|
||||||
filter: {
|
|
||||||
columnOperator: '_gt',
|
|
||||||
name: 'ArtistId',
|
|
||||||
type: 'column',
|
|
||||||
typeName: 'ArtistId',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
presets: [],
|
|
||||||
rowCount: '3',
|
|
||||||
};
|
|
||||||
|
|
||||||
test('use default values returns values correctly', () => {
|
|
||||||
const result = createDefaultValues(input);
|
|
||||||
expect(result).toEqual(mockResult);
|
|
||||||
});
|
|
@ -1,141 +0,0 @@
|
|||||||
import { buildClientSchema, GraphQLSchema } from 'graphql';
|
|
||||||
import { useQuery, UseQueryResult } from 'react-query';
|
|
||||||
import isEqual from 'lodash.isequal';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DataSource,
|
|
||||||
exportMetadata,
|
|
||||||
runIntrospectionQuery,
|
|
||||||
TableColumn,
|
|
||||||
} from '@/features/DataSource';
|
|
||||||
import { useHttpClient } from '@/features/Network';
|
|
||||||
import { Metadata, MetadataTable } from '@/features/MetadataAPI';
|
|
||||||
|
|
||||||
import { PermissionsSchema } from '../../../utils';
|
|
||||||
|
|
||||||
import type { QueryType } from '../../../types';
|
|
||||||
import {
|
|
||||||
createPermissionsObject,
|
|
||||||
getRowPermissionsForAllOtherQueriesMatchingSelectedRole,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
interface GetMetadataTableArgs {
|
|
||||||
dataSourceName: string;
|
|
||||||
table: unknown;
|
|
||||||
metadata: Metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMetadataTable = ({
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
metadata,
|
|
||||||
}: GetMetadataTableArgs) => {
|
|
||||||
const trackedTables = metadata.metadata?.sources?.find(
|
|
||||||
source => source.name === dataSourceName
|
|
||||||
)?.tables;
|
|
||||||
|
|
||||||
// find selected table
|
|
||||||
const currentTable = trackedTables?.find(trackedTable =>
|
|
||||||
isEqual(trackedTable.table, table)
|
|
||||||
);
|
|
||||||
|
|
||||||
return currentTable;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface CreateDefaultValuesArgs {
|
|
||||||
queryType: QueryType;
|
|
||||||
roleName: string;
|
|
||||||
selectedTable?: MetadataTable;
|
|
||||||
tableColumns: TableColumn[];
|
|
||||||
schema: GraphQLSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createDefaultValues = ({
|
|
||||||
queryType,
|
|
||||||
roleName,
|
|
||||||
selectedTable,
|
|
||||||
tableColumns,
|
|
||||||
schema,
|
|
||||||
}: CreateDefaultValuesArgs) => {
|
|
||||||
const allRowChecks = getRowPermissionsForAllOtherQueriesMatchingSelectedRole(
|
|
||||||
queryType,
|
|
||||||
roleName,
|
|
||||||
selectedTable
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseDefaultValues: DefaultValues = {
|
|
||||||
checkType: 'none',
|
|
||||||
filterType: 'none',
|
|
||||||
check: {},
|
|
||||||
filter: {},
|
|
||||||
columns: {},
|
|
||||||
presets: [],
|
|
||||||
backendOnly: false,
|
|
||||||
aggregationEnabled: false,
|
|
||||||
clonePermissions: [],
|
|
||||||
allRowChecks,
|
|
||||||
};
|
|
||||||
if (selectedTable) {
|
|
||||||
const permissionsObject = createPermissionsObject({
|
|
||||||
queryType,
|
|
||||||
selectedTable,
|
|
||||||
roleName,
|
|
||||||
tableColumns,
|
|
||||||
schema,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...baseDefaultValues, ...permissionsObject };
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseDefaultValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DefaultValues = PermissionsSchema & {
|
|
||||||
allRowChecks: { queryType: QueryType; value: string }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface Args {
|
|
||||||
dataSourceName: string;
|
|
||||||
table: unknown;
|
|
||||||
roleName: string;
|
|
||||||
queryType: QueryType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDefaultValues = ({
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
roleName,
|
|
||||||
queryType,
|
|
||||||
}: Args): UseQueryResult<DefaultValues> => {
|
|
||||||
const httpClient = useHttpClient();
|
|
||||||
return useQuery<any, Error>({
|
|
||||||
queryKey: [dataSourceName, 'permissionDefaultValues', roleName, queryType],
|
|
||||||
queryFn: async () => {
|
|
||||||
const introspectionResult = await runIntrospectionQuery({ httpClient });
|
|
||||||
const schema = buildClientSchema(introspectionResult.data);
|
|
||||||
|
|
||||||
const metadata = await exportMetadata({ httpClient });
|
|
||||||
|
|
||||||
// get table columns for metadata table from db introspection
|
|
||||||
const tableColumns = await DataSource(httpClient).getTableColumns({
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedTable = getMetadataTable({
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
metadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
return createDefaultValues({
|
|
||||||
queryType,
|
|
||||||
roleName,
|
|
||||||
selectedTable,
|
|
||||||
tableColumns,
|
|
||||||
schema,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
};
|
|
@ -0,0 +1,111 @@
|
|||||||
|
import { GraphQLSchema } from 'graphql';
|
||||||
|
|
||||||
|
import isEqual from 'lodash.isequal';
|
||||||
|
|
||||||
|
import { TableColumn } from '@/features/DataSource';
|
||||||
|
import { getTypeName } from '@/features/GraphQLUtils';
|
||||||
|
|
||||||
|
import { Metadata } from '@/features/MetadataAPI';
|
||||||
|
|
||||||
|
import { PermissionsSchema } from '../../../../utils';
|
||||||
|
|
||||||
|
import type { QueryType } from '../../../../types';
|
||||||
|
import {
|
||||||
|
createPermissionsObject,
|
||||||
|
getRowPermissionsForAllOtherQueriesMatchingSelectedRole,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
interface GetMetadataTableArgs {
|
||||||
|
dataSourceName: string;
|
||||||
|
table: unknown;
|
||||||
|
metadata: Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMetadataTable = ({
|
||||||
|
dataSourceName,
|
||||||
|
table,
|
||||||
|
metadata,
|
||||||
|
}: GetMetadataTableArgs) => {
|
||||||
|
const trackedTables = metadata.metadata?.sources?.find(
|
||||||
|
source => source.name === dataSourceName
|
||||||
|
)?.tables;
|
||||||
|
|
||||||
|
// find selected table
|
||||||
|
const currentTable = trackedTables?.find(trackedTable =>
|
||||||
|
isEqual(trackedTable.table, table)
|
||||||
|
);
|
||||||
|
|
||||||
|
return currentTable;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Args {
|
||||||
|
queryType: QueryType;
|
||||||
|
roleName: string;
|
||||||
|
table: unknown;
|
||||||
|
dataSourceName: string;
|
||||||
|
metadata: Metadata;
|
||||||
|
tableColumns: TableColumn[];
|
||||||
|
schema: GraphQLSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDefaultValues = ({
|
||||||
|
queryType,
|
||||||
|
roleName,
|
||||||
|
table,
|
||||||
|
dataSourceName,
|
||||||
|
metadata,
|
||||||
|
tableColumns,
|
||||||
|
schema,
|
||||||
|
}: Args) => {
|
||||||
|
const selectedTable = getMetadataTable({
|
||||||
|
dataSourceName,
|
||||||
|
table,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
const metadataSource = metadata.metadata.sources.find(
|
||||||
|
s => s.name === dataSourceName
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is GDC specific, we have to move this to DAL later
|
||||||
|
*/
|
||||||
|
const tableName = getTypeName({
|
||||||
|
defaultQueryRoot: (table as string[]).join('_'),
|
||||||
|
operation: 'select',
|
||||||
|
sourceCustomization: metadataSource?.customization,
|
||||||
|
configuration: selectedTable?.configuration,
|
||||||
|
});
|
||||||
|
|
||||||
|
const allRowChecks = getRowPermissionsForAllOtherQueriesMatchingSelectedRole(
|
||||||
|
queryType,
|
||||||
|
roleName,
|
||||||
|
selectedTable
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseDefaultValues: DefaultValues = {
|
||||||
|
checkType: 'none',
|
||||||
|
filterType: 'none',
|
||||||
|
columns: {},
|
||||||
|
allRowChecks,
|
||||||
|
};
|
||||||
|
if (selectedTable) {
|
||||||
|
const permissionsObject = createPermissionsObject({
|
||||||
|
queryType,
|
||||||
|
selectedTable,
|
||||||
|
roleName,
|
||||||
|
tableColumns,
|
||||||
|
schema,
|
||||||
|
tableName,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...baseDefaultValues, ...permissionsObject };
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseDefaultValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DefaultValues = PermissionsSchema & {
|
||||||
|
allRowChecks: { queryType: QueryType; value: string }[];
|
||||||
|
operators?: Record<string, unknown>;
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
import isEqual from 'lodash.isequal';
|
import isEqual from 'lodash.isequal';
|
||||||
|
import { GraphQLSchema } from 'graphql';
|
||||||
import { TableColumn } from '@/features/DataSource';
|
import { TableColumn } from '@/features/DataSource';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -10,10 +11,14 @@ import type {
|
|||||||
UpdatePermissionDefinition,
|
UpdatePermissionDefinition,
|
||||||
} from '@/features/MetadataAPI';
|
} from '@/features/MetadataAPI';
|
||||||
|
|
||||||
import { isPermission, keyToPermission, permissionToKey } from '../utils';
|
import {
|
||||||
import { createDefaultValues } from '../../../components/RowPermissionsBuilder';
|
isPermission,
|
||||||
|
keyToPermission,
|
||||||
|
permissionToKey,
|
||||||
|
} from '../../../../utils';
|
||||||
|
import { createDefaultValues } from '../../../../components/RowPermissionsBuilder';
|
||||||
|
|
||||||
import type { QueryType } from '../../../types';
|
import type { QueryType } from '../../../../types';
|
||||||
|
|
||||||
export const getCheckType = (
|
export const getCheckType = (
|
||||||
check?: Record<string, unknown> | null
|
check?: Record<string, unknown> | null
|
||||||
@ -150,12 +155,15 @@ export const createPermission = {
|
|||||||
select: (
|
select: (
|
||||||
permission: SelectPermissionDefinition,
|
permission: SelectPermissionDefinition,
|
||||||
tableColumns: TableColumn[],
|
tableColumns: TableColumn[],
|
||||||
schema: any
|
schema: GraphQLSchema,
|
||||||
|
tableName: string,
|
||||||
|
tableConfig: MetadataTable['configuration']
|
||||||
) => {
|
) => {
|
||||||
const { filter, operators } = createDefaultValues({
|
const { filter, operators } = createDefaultValues({
|
||||||
tableName: 'Artist',
|
tableName,
|
||||||
existingPermission: permission.filter,
|
existingPermission: permission.filter,
|
||||||
schema,
|
schema,
|
||||||
|
tableConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterType = getCheckType(permission?.filter);
|
const filterType = getCheckType(permission?.filter);
|
||||||
@ -254,6 +262,7 @@ interface ObjArgs {
|
|||||||
tableColumns: TableColumn[];
|
tableColumns: TableColumn[];
|
||||||
roleName: string;
|
roleName: string;
|
||||||
schema: any;
|
schema: any;
|
||||||
|
tableName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createPermissionsObject = ({
|
export const createPermissionsObject = ({
|
||||||
@ -262,6 +271,7 @@ export const createPermissionsObject = ({
|
|||||||
tableColumns,
|
tableColumns,
|
||||||
roleName,
|
roleName,
|
||||||
schema,
|
schema,
|
||||||
|
tableName,
|
||||||
}: ObjArgs) => {
|
}: ObjArgs) => {
|
||||||
const selectedPermission = getCurrentPermission({
|
const selectedPermission = getCurrentPermission({
|
||||||
table: selectedTable,
|
table: selectedTable,
|
||||||
@ -279,7 +289,9 @@ export const createPermissionsObject = ({
|
|||||||
return createPermission.select(
|
return createPermission.select(
|
||||||
selectedPermission.permission as SelectPermissionDefinition,
|
selectedPermission.permission as SelectPermissionDefinition,
|
||||||
tableColumns,
|
tableColumns,
|
||||||
schema
|
schema,
|
||||||
|
tableName,
|
||||||
|
selectedTable.configuration
|
||||||
);
|
);
|
||||||
case 'update':
|
case 'update':
|
||||||
return createPermission.update(
|
return createPermission.update(
|
@ -0,0 +1,93 @@
|
|||||||
|
import { TableColumn } from '@/features/DataSource';
|
||||||
|
|
||||||
|
import { Metadata, MetadataTable } from '@/features/MetadataAPI';
|
||||||
|
import { isPermission } from '../../../../utils';
|
||||||
|
|
||||||
|
type Operation = 'insert' | 'select' | 'update' | 'delete';
|
||||||
|
|
||||||
|
const supportedQueries: Operation[] = ['select'];
|
||||||
|
|
||||||
|
export const getAllowedFilterKeys = (
|
||||||
|
query: 'insert' | 'select' | 'update' | 'delete'
|
||||||
|
): ('check' | 'filter')[] => {
|
||||||
|
switch (query) {
|
||||||
|
case 'insert':
|
||||||
|
return ['check'];
|
||||||
|
case 'update':
|
||||||
|
return ['filter', 'check'];
|
||||||
|
default:
|
||||||
|
return ['filter'];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetMetadataTableArgs = {
|
||||||
|
dataSourceName: string;
|
||||||
|
table: unknown;
|
||||||
|
metadata: Metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMetadataTable = (args: GetMetadataTableArgs) => {
|
||||||
|
const { dataSourceName, table, metadata } = args;
|
||||||
|
|
||||||
|
const trackedTables = metadata.metadata?.sources?.find(
|
||||||
|
source => source.name === dataSourceName
|
||||||
|
)?.tables;
|
||||||
|
|
||||||
|
const selectedTable = trackedTables?.find(
|
||||||
|
trackedTable => JSON.stringify(trackedTable.table) === JSON.stringify(table)
|
||||||
|
);
|
||||||
|
|
||||||
|
// find selected table
|
||||||
|
return {
|
||||||
|
table: selectedTable,
|
||||||
|
tables: trackedTables,
|
||||||
|
// for gdc tables will be an array of strings so this needs updating
|
||||||
|
tableNames: trackedTables?.map(each => each.table),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoles = (metadataTables?: MetadataTable[]) => {
|
||||||
|
// go through all tracked tables
|
||||||
|
const res = metadataTables?.reduce<Set<string>>((acc, each) => {
|
||||||
|
// go through all permissions
|
||||||
|
Object.entries(each).forEach(([key, value]) => {
|
||||||
|
const props = { key, value };
|
||||||
|
// check object key of metadata is a permission
|
||||||
|
if (isPermission(props)) {
|
||||||
|
// add each role from each permission to the set
|
||||||
|
props.value.forEach(permission => {
|
||||||
|
acc.add(permission.role);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, new Set());
|
||||||
|
return Array.from(res || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Args {
|
||||||
|
dataSourceName: string;
|
||||||
|
table: unknown;
|
||||||
|
metadata: Metadata;
|
||||||
|
tableColumns: TableColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createFormData = (props: Args) => {
|
||||||
|
const { dataSourceName, table, metadata, tableColumns } = props;
|
||||||
|
// find the specific metadata table
|
||||||
|
const metadataTable = getMetadataTable({
|
||||||
|
dataSourceName,
|
||||||
|
table,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
const roles = getRoles(metadataTable.tables);
|
||||||
|
|
||||||
|
return {
|
||||||
|
roles,
|
||||||
|
supportedQueries,
|
||||||
|
tableNames: metadataTable.tableNames,
|
||||||
|
columns: tableColumns?.map(({ name }) => name),
|
||||||
|
};
|
||||||
|
};
|
@ -1 +1,2 @@
|
|||||||
export * from './useFormData';
|
export * from './useFormData';
|
||||||
|
export * from './createDefaultValues';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { TableColumn } from '@/features/DataSource';
|
import { TableColumn } from '@/features/DataSource';
|
||||||
import { Metadata } from '@/features/MetadataAPI';
|
import { Metadata } from '@/features/MetadataAPI';
|
||||||
|
import { createDefaultValues } from '../createDefaultValues';
|
||||||
|
import { schema } from '../../../../components/RowPermissionsBuilder/mocks';
|
||||||
|
|
||||||
interface Input {
|
interface Input {
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
@ -26,9 +28,14 @@ const metadata: Metadata = {
|
|||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
permission: {
|
permission: {
|
||||||
columns: ['ArtistId', 'Name'],
|
columns: ['Name'],
|
||||||
filter: {},
|
filter: {
|
||||||
|
ArtistId: {
|
||||||
|
_gt: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
allow_aggregations: true,
|
allow_aggregations: true,
|
||||||
|
limit: 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -54,7 +61,7 @@ const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const input: Input = {
|
export const formDataInput: Input = {
|
||||||
dataSourceName: 'sqlite',
|
dataSourceName: 'sqlite',
|
||||||
table: ['Artist'],
|
table: ['Artist'],
|
||||||
metadata,
|
metadata,
|
||||||
@ -81,3 +88,34 @@ export const input: Input = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultValuesInput: Parameters<typeof createDefaultValues>[0] = {
|
||||||
|
dataSourceName: 'sqlite',
|
||||||
|
table: ['Artist'],
|
||||||
|
metadata,
|
||||||
|
queryType: 'select' as const,
|
||||||
|
roleName: 'user',
|
||||||
|
tableColumns: [
|
||||||
|
{
|
||||||
|
name: 'ArtistId',
|
||||||
|
dataType: 'number',
|
||||||
|
nullable: false,
|
||||||
|
isPrimaryKey: true,
|
||||||
|
graphQLProperties: {
|
||||||
|
name: 'ArtistId',
|
||||||
|
scalarType: 'decimal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Name',
|
||||||
|
dataType: 'string',
|
||||||
|
nullable: true,
|
||||||
|
isPrimaryKey: false,
|
||||||
|
graphQLProperties: {
|
||||||
|
name: 'Name',
|
||||||
|
scalarType: 'String',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
schema,
|
||||||
|
};
|
||||||
|
@ -1,14 +1,46 @@
|
|||||||
import { createFormData } from './useFormData';
|
import { createFormData } from './createFormData';
|
||||||
import { input } from './mock';
|
import { createDefaultValues } from './createDefaultValues';
|
||||||
|
import { defaultValuesInput, formDataInput } from './mock';
|
||||||
|
|
||||||
const mockResult: ReturnType<typeof createFormData> = {
|
const formDataMockResult: ReturnType<typeof createFormData> = {
|
||||||
columns: ['ArtistId', 'Name'],
|
columns: ['ArtistId', 'Name'],
|
||||||
roles: ['user'],
|
roles: ['user'],
|
||||||
supportedQueries: ['insert', 'select', 'update', 'delete'],
|
supportedQueries: ['select'],
|
||||||
tableNames: [['Album'], ['Artist']],
|
tableNames: [['Album'], ['Artist']],
|
||||||
};
|
};
|
||||||
|
|
||||||
test('returns correctly formatted formData', () => {
|
test('returns correctly formatted formData', () => {
|
||||||
const result = createFormData(input);
|
const result = createFormData(formDataInput);
|
||||||
expect(result).toEqual(mockResult);
|
expect(result).toEqual(formDataMockResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultValuesMockResult: ReturnType<typeof createDefaultValues> = {
|
||||||
|
aggregationEnabled: true,
|
||||||
|
checkType: 'none',
|
||||||
|
allRowChecks: [],
|
||||||
|
columns: {
|
||||||
|
ArtistId: false,
|
||||||
|
Name: true,
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
ArtistId: {
|
||||||
|
_gt: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filterType: 'custom',
|
||||||
|
operators: {
|
||||||
|
filter: {
|
||||||
|
columnOperator: '_gt',
|
||||||
|
name: 'ArtistId',
|
||||||
|
type: 'column',
|
||||||
|
typeName: 'ArtistId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
presets: [],
|
||||||
|
rowCount: '3',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('use default values returns values correctly', () => {
|
||||||
|
const result = createDefaultValues(defaultValuesInput);
|
||||||
|
expect(result).toEqual(defaultValuesMockResult);
|
||||||
});
|
});
|
||||||
|
@ -1,102 +1,17 @@
|
|||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
import { buildClientSchema } from 'graphql';
|
||||||
|
|
||||||
import { DataSource, exportMetadata, TableColumn } from '@/features/DataSource';
|
import {
|
||||||
|
DataSource,
|
||||||
|
exportMetadata,
|
||||||
|
runIntrospectionQuery,
|
||||||
|
} from '@/features/DataSource';
|
||||||
import { useHttpClient } from '@/features/Network';
|
import { useHttpClient } from '@/features/Network';
|
||||||
|
|
||||||
import { Metadata, MetadataTable } from '@/features/MetadataAPI';
|
import { createDefaultValues } from './createDefaultValues';
|
||||||
import { isPermission } from '../utils';
|
import { createFormData } from './createFormData';
|
||||||
|
|
||||||
type Operation = 'insert' | 'select' | 'update' | 'delete';
|
export type Args = {
|
||||||
|
|
||||||
const supportedQueries: Operation[] = ['insert', 'select', 'update', 'delete'];
|
|
||||||
|
|
||||||
export const getAllowedFilterKeys = (
|
|
||||||
query: 'insert' | 'select' | 'update' | 'delete'
|
|
||||||
): ('check' | 'filter')[] => {
|
|
||||||
switch (query) {
|
|
||||||
case 'insert':
|
|
||||||
return ['check'];
|
|
||||||
case 'update':
|
|
||||||
return ['filter', 'check'];
|
|
||||||
default:
|
|
||||||
return ['filter'];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type GetMetadataTableArgs = {
|
|
||||||
dataSourceName: string;
|
|
||||||
table: unknown;
|
|
||||||
metadata: Metadata;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMetadataTable = (args: GetMetadataTableArgs) => {
|
|
||||||
const { dataSourceName, table, metadata } = args;
|
|
||||||
|
|
||||||
const trackedTables = metadata.metadata?.sources?.find(
|
|
||||||
source => source.name === dataSourceName
|
|
||||||
)?.tables;
|
|
||||||
|
|
||||||
const selectedTable = trackedTables?.find(
|
|
||||||
trackedTable => JSON.stringify(trackedTable.table) === JSON.stringify(table)
|
|
||||||
);
|
|
||||||
|
|
||||||
// find selected table
|
|
||||||
return {
|
|
||||||
table: selectedTable,
|
|
||||||
tables: trackedTables,
|
|
||||||
// for gdc tables will be an array of strings so this needs updating
|
|
||||||
tableNames: trackedTables?.map(each => each.table),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRoles = (metadataTables?: MetadataTable[]) => {
|
|
||||||
// go through all tracked tables
|
|
||||||
const res = metadataTables?.reduce<Set<string>>((acc, each) => {
|
|
||||||
// go through all permissions
|
|
||||||
Object.entries(each).forEach(([key, value]) => {
|
|
||||||
const props = { key, value };
|
|
||||||
// check object key of metadata is a permission
|
|
||||||
if (isPermission(props)) {
|
|
||||||
// add each role from each permission to the set
|
|
||||||
props.value.forEach(permission => {
|
|
||||||
acc.add(permission.role);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, new Set());
|
|
||||||
|
|
||||||
return Array.from(res || []);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface CreateFormDataArgs {
|
|
||||||
dataSourceName: string;
|
|
||||||
table: unknown;
|
|
||||||
metadata: Metadata;
|
|
||||||
tableColumns: TableColumn[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createFormData = (props: CreateFormDataArgs) => {
|
|
||||||
const { dataSourceName, table, metadata, tableColumns } = props;
|
|
||||||
// find the specific metadata table
|
|
||||||
const metadataTable = getMetadataTable({
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
metadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
const roles = getRoles(metadataTable.tables);
|
|
||||||
|
|
||||||
return {
|
|
||||||
roles,
|
|
||||||
supportedQueries,
|
|
||||||
tableNames: metadataTable.tableNames,
|
|
||||||
columns: tableColumns?.map(({ name }) => name),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UseFormDataArgs = {
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
@ -104,21 +19,32 @@ export type UseFormDataArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ReturnValue = {
|
type ReturnValue = {
|
||||||
roles: string[];
|
formData: ReturnType<typeof createFormData>;
|
||||||
supportedQueries: Operation[];
|
defaultValues: ReturnType<typeof createDefaultValues>;
|
||||||
tableNames: unknown;
|
|
||||||
columns: string[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* creates data for displaying in the form e.g. column names, roles etc.
|
* creates data for displaying in the form e.g. column names, roles etc.
|
||||||
|
* creates default values for form i.e. existing permissions from metadata
|
||||||
*/
|
*/
|
||||||
export const useFormData = ({ dataSourceName, table }: UseFormDataArgs) => {
|
export const useFormData = ({
|
||||||
|
dataSourceName,
|
||||||
|
table,
|
||||||
|
roleName,
|
||||||
|
queryType,
|
||||||
|
}: Args) => {
|
||||||
const httpClient = useHttpClient();
|
const httpClient = useHttpClient();
|
||||||
return useQuery<ReturnValue, Error>({
|
return useQuery<ReturnValue, Error>({
|
||||||
queryKey: [dataSourceName, 'permissionFormData'],
|
queryKey: [
|
||||||
|
dataSourceName,
|
||||||
|
'permissionFormData',
|
||||||
|
JSON.stringify(table),
|
||||||
|
roleName,
|
||||||
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
const introspectionResult = await runIntrospectionQuery({ httpClient });
|
||||||
|
const schema = buildClientSchema(introspectionResult.data);
|
||||||
const metadata = await exportMetadata({ httpClient });
|
const metadata = await exportMetadata({ httpClient });
|
||||||
|
|
||||||
// get table columns for metadata table from db introspection
|
// get table columns for metadata table from db introspection
|
||||||
@ -127,12 +53,24 @@ export const useFormData = ({ dataSourceName, table }: UseFormDataArgs) => {
|
|||||||
table,
|
table,
|
||||||
});
|
});
|
||||||
|
|
||||||
return createFormData({
|
const defaultValues = createDefaultValues({
|
||||||
|
queryType,
|
||||||
|
roleName,
|
||||||
|
dataSourceName,
|
||||||
|
metadata,
|
||||||
|
table,
|
||||||
|
tableColumns,
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = createFormData({
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
metadata,
|
metadata,
|
||||||
tableColumns,
|
tableColumns,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { formData, defaultValues };
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
export const permissionToKey = {
|
|
||||||
insert: 'insert_permissions',
|
|
||||||
select: 'select_permissions',
|
|
||||||
update: 'update_permissions',
|
|
||||||
delete: 'delete_permissions',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const metadataPermissionKeys = [
|
|
||||||
'insert_permissions',
|
|
||||||
'select_permissions',
|
|
||||||
'update_permissions',
|
|
||||||
'delete_permissions',
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const keyToPermission = {
|
|
||||||
insert_permissions: 'insert',
|
|
||||||
select_permissions: 'select',
|
|
||||||
update_permissions: 'update',
|
|
||||||
delete_permissions: 'delete',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const isPermission = (props: {
|
|
||||||
key: string;
|
|
||||||
value: any;
|
|
||||||
}): props is {
|
|
||||||
key: typeof metadataPermissionKeys[number];
|
|
||||||
// value: Permission[];
|
|
||||||
value: any[];
|
|
||||||
} => props.key in keyToPermission;
|
|
@ -3,6 +3,7 @@ import { AxiosInstance } from 'axios';
|
|||||||
import { exportMetadata } from '@/features/DataSource';
|
import { exportMetadata } from '@/features/DataSource';
|
||||||
import { useHttpClient } from '@/features/Network';
|
import { useHttpClient } from '@/features/Network';
|
||||||
import { Permission, useMetadataMigration } from '@/features/MetadataAPI';
|
import { Permission, useMetadataMigration } from '@/features/MetadataAPI';
|
||||||
|
import { useFireNotification } from '@/new-components/Notifications';
|
||||||
|
|
||||||
import { api } from '../../api';
|
import { api } from '../../api';
|
||||||
import { QueryType } from '../../types';
|
import { QueryType } from '../../types';
|
||||||
@ -38,6 +39,7 @@ const getMetadataTable = async ({
|
|||||||
JSON.stringify(trackedTable.table) === JSON.stringify(table)
|
JSON.stringify(trackedTable.table) === JSON.stringify(table)
|
||||||
),
|
),
|
||||||
resourceVersion: resource_version,
|
resourceVersion: resource_version,
|
||||||
|
driver: currentMetadataSource.kind,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,16 +71,11 @@ const isPermission = (props: {
|
|||||||
} => props.key in keyToPermission;
|
} => props.key in keyToPermission;
|
||||||
|
|
||||||
interface Args {
|
interface Args {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBulkDeletePermissions = ({
|
export const useBulkDeletePermissions = ({ dataSourceName, table }: Args) => {
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
}: Args) => {
|
|
||||||
const {
|
const {
|
||||||
mutateAsync,
|
mutateAsync,
|
||||||
isLoading: mutationLoading,
|
isLoading: mutationLoading,
|
||||||
@ -88,9 +85,10 @@ export const useBulkDeletePermissions = ({
|
|||||||
|
|
||||||
const httpClient = useHttpClient();
|
const httpClient = useHttpClient();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { fireNotification } = useFireNotification();
|
||||||
|
|
||||||
const submit = async (roles: string[]) => {
|
const submit = async (roles: string[]) => {
|
||||||
const { metadataTable, resourceVersion } = await getMetadataTable({
|
const { metadataTable, resourceVersion, driver } = await getMetadataTable({
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
httpClient,
|
httpClient,
|
||||||
@ -129,18 +127,48 @@ export const useBulkDeletePermissions = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const body = api.createBulkDeleteBody({
|
const body = api.createBulkDeleteBody({
|
||||||
source: currentSource,
|
driver,
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
resourceVersion,
|
resourceVersion,
|
||||||
roleList,
|
roleList,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mutateAsync({
|
await mutateAsync(
|
||||||
query: body,
|
{
|
||||||
});
|
query: body,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
fireNotification({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success!',
|
||||||
|
message: 'Permissions successfully deleted',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: err => {
|
||||||
|
fireNotification({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Error!',
|
||||||
|
message:
|
||||||
|
err?.message ?? 'Something went wrong while deleting permissions',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSettled: () => {
|
||||||
|
queryClient.invalidateQueries([
|
||||||
|
dataSourceName,
|
||||||
|
'permissionFormData',
|
||||||
|
JSON.stringify(table),
|
||||||
|
]);
|
||||||
|
|
||||||
queryClient.invalidateQueries([dataSourceName, 'permissionsTable']);
|
queryClient.invalidateQueries([
|
||||||
|
dataSourceName,
|
||||||
|
'permissionsTable',
|
||||||
|
JSON.stringify(table),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = mutationLoading;
|
const isLoading = mutationLoading;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { useMetadataMigration } from '@/features/MetadataAPI';
|
import { useMetadataMigration } from '@/features/MetadataAPI';
|
||||||
import { exportMetadata } from '@/features/DataSource';
|
import { exportMetadata } from '@/features/DataSource';
|
||||||
|
import { useFireNotification } from '@/new-components/Notifications';
|
||||||
|
|
||||||
import { useHttpClient } from '@/features/Network';
|
import { useHttpClient } from '@/features/Network';
|
||||||
|
|
||||||
@ -8,14 +9,12 @@ import { QueryType } from '../../types';
|
|||||||
import { api } from '../../api';
|
import { api } from '../../api';
|
||||||
|
|
||||||
export interface UseDeletePermissionArgs {
|
export interface UseDeletePermissionArgs {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDeletePermission = ({
|
export const useDeletePermission = ({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
roleName,
|
roleName,
|
||||||
@ -23,19 +22,25 @@ export const useDeletePermission = ({
|
|||||||
const mutate = useMetadataMigration();
|
const mutate = useMetadataMigration();
|
||||||
const httpClient = useHttpClient();
|
const httpClient = useHttpClient();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { fireNotification } = useFireNotification();
|
||||||
|
|
||||||
const submit = async (queries: QueryType[]) => {
|
const submit = async (queries: QueryType[]) => {
|
||||||
const { resource_version: resourceVersion } = await exportMetadata({
|
const { resource_version: resourceVersion, metadata } =
|
||||||
httpClient,
|
await exportMetadata({
|
||||||
});
|
httpClient,
|
||||||
|
});
|
||||||
|
|
||||||
if (!resourceVersion) {
|
if (!resourceVersion) {
|
||||||
console.error('No resource version');
|
console.error('No resource version');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const driver = metadata.sources.find(s => s.name === dataSourceName)?.kind;
|
||||||
|
|
||||||
|
if (!driver) throw Error('Unable to find driver in metadata');
|
||||||
|
|
||||||
const body = api.createDeleteBody({
|
const body = api.createDeleteBody({
|
||||||
currentSource,
|
driver,
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
roleName,
|
roleName,
|
||||||
@ -43,11 +48,41 @@ export const useDeletePermission = ({
|
|||||||
queries,
|
queries,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mutate.mutateAsync({
|
await mutate.mutateAsync(
|
||||||
query: body,
|
{
|
||||||
});
|
query: body,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
fireNotification({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success!',
|
||||||
|
message: 'Permissions successfully deleted',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: err => {
|
||||||
|
fireNotification({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Error!',
|
||||||
|
message:
|
||||||
|
err?.message ?? 'Something went wrong while deleting permissions',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSettled: async () => {
|
||||||
|
await queryClient.invalidateQueries([
|
||||||
|
dataSourceName,
|
||||||
|
'permissionFormData',
|
||||||
|
JSON.stringify(table),
|
||||||
|
]);
|
||||||
|
|
||||||
queryClient.invalidateQueries([dataSourceName, 'permissionsTable']);
|
await queryClient.invalidateQueries([
|
||||||
|
dataSourceName,
|
||||||
|
'permissionsTable',
|
||||||
|
JSON.stringify(table),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = mutate.isLoading;
|
const isLoading = mutate.isLoading;
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { AxiosInstance } from 'axios';
|
import { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
import {
|
import { useMetadataMigration } from '@/features/MetadataAPI';
|
||||||
useMetadataMigration,
|
|
||||||
useMetadataVersion,
|
|
||||||
} from '@/features/MetadataAPI';
|
|
||||||
import { exportMetadata } from '@/features/DataSource';
|
import { exportMetadata } from '@/features/DataSource';
|
||||||
import { useHttpClient } from '@/features/Network';
|
import { useHttpClient } from '@/features/Network';
|
||||||
|
import { useFireNotification } from '@/new-components/Notifications';
|
||||||
import { AccessType, FormOutput, QueryType } from '../../types';
|
import { AccessType, QueryType } from '../../types';
|
||||||
import { api } from '../../api';
|
import { api } from '../../api';
|
||||||
|
import { isPermission, keyToPermission, PermissionsSchema } from '../../utils';
|
||||||
|
|
||||||
export interface UseSubmitFormArgs {
|
export interface UseSubmitFormArgs {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
@ -20,29 +17,6 @@ export interface UseSubmitFormArgs {
|
|||||||
accessType: AccessType;
|
accessType: AccessType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadataPermissionKeys = [
|
|
||||||
'insert_permissions',
|
|
||||||
'select_permissions',
|
|
||||||
'update_permissions',
|
|
||||||
'delete_permissions',
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const keyToPermission = {
|
|
||||||
insert_permissions: 'insert',
|
|
||||||
select_permissions: 'select',
|
|
||||||
update_permissions: 'update',
|
|
||||||
delete_permissions: 'delete',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const isPermission = (props: {
|
|
||||||
key: string;
|
|
||||||
value: any;
|
|
||||||
}): props is {
|
|
||||||
key: typeof metadataPermissionKeys[number];
|
|
||||||
// value: Permission[];
|
|
||||||
value: any[];
|
|
||||||
} => props.key in keyToPermission;
|
|
||||||
|
|
||||||
interface ExistingPermissions {
|
interface ExistingPermissions {
|
||||||
role: string;
|
role: string;
|
||||||
queryType: QueryType;
|
queryType: QueryType;
|
||||||
@ -87,28 +61,24 @@ const getAllPermissions = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useSubmitForm = (args: UseSubmitFormArgs) => {
|
export const useSubmitForm = (args: UseSubmitFormArgs) => {
|
||||||
const {
|
const { dataSourceName, table, roleName, queryType, accessType } = args;
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
roleName,
|
|
||||||
queryType,
|
|
||||||
accessType,
|
|
||||||
} = args;
|
|
||||||
const {
|
|
||||||
data: resourceVersion,
|
|
||||||
isLoading: resourceVersionLoading,
|
|
||||||
isError: resourceVersionError,
|
|
||||||
} = useMetadataVersion();
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const httpClient = useHttpClient();
|
const httpClient = useHttpClient();
|
||||||
|
|
||||||
|
const { fireNotification } = useFireNotification();
|
||||||
|
|
||||||
const mutate = useMetadataMigration();
|
const mutate = useMetadataMigration();
|
||||||
|
|
||||||
const submit = async (formData: FormOutput) => {
|
const submit = async (formData: PermissionsSchema) => {
|
||||||
if (!resourceVersion) {
|
const { metadata, resource_version } = await exportMetadata({ httpClient });
|
||||||
console.error('No resource version');
|
|
||||||
|
const metadataSource = metadata?.sources.find(
|
||||||
|
s => s.name === dataSourceName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resource_version || !metadataSource) {
|
||||||
|
console.error('Something went wrong!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,32 +88,57 @@ export const useSubmitForm = (args: UseSubmitFormArgs) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const body = api.createInsertBody({
|
const body = api.createInsertBody({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
|
driver: metadataSource.kind,
|
||||||
table,
|
table,
|
||||||
roleName,
|
roleName,
|
||||||
queryType,
|
queryType,
|
||||||
accessType,
|
accessType,
|
||||||
resourceVersion,
|
resourceVersion: resource_version,
|
||||||
formData,
|
formData,
|
||||||
existingPermissions,
|
existingPermissions,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mutate.mutateAsync({
|
await mutate.mutateAsync(
|
||||||
query: body,
|
{
|
||||||
});
|
query: body,
|
||||||
|
},
|
||||||
await queryClient.invalidateQueries([
|
{
|
||||||
dataSourceName,
|
onSuccess: () => {
|
||||||
'permissionDefaultValues',
|
fireNotification({
|
||||||
roleName,
|
type: 'success',
|
||||||
queryType,
|
title: 'Success!',
|
||||||
]);
|
message: 'Permissions saved successfully!',
|
||||||
await queryClient.invalidateQueries([dataSourceName, 'permissionsTable']);
|
});
|
||||||
|
},
|
||||||
|
onError: err => {
|
||||||
|
fireNotification({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Error!',
|
||||||
|
message:
|
||||||
|
err?.message ?? 'Something went wrong while saving permissions',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSettled: () => {
|
||||||
|
queryClient.invalidateQueries(['export_metadata', 'roles']);
|
||||||
|
queryClient.invalidateQueries([
|
||||||
|
dataSourceName,
|
||||||
|
'permissionFormData',
|
||||||
|
JSON.stringify(table),
|
||||||
|
roleName,
|
||||||
|
]);
|
||||||
|
queryClient.invalidateQueries([
|
||||||
|
dataSourceName,
|
||||||
|
'permissionsTable',
|
||||||
|
JSON.stringify(table),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = mutate.isLoading || resourceVersionLoading;
|
const isLoading = mutate.isLoading;
|
||||||
const isError = mutate.isError || resourceVersionError;
|
const isError = mutate.isError;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
submit,
|
submit,
|
||||||
|
@ -4,7 +4,6 @@ import { useDeletePermission } from './useDeletePermission';
|
|||||||
import { AccessType, QueryType } from '../../types';
|
import { AccessType, QueryType } from '../../types';
|
||||||
|
|
||||||
export interface UseUpdatePermissionsArgs {
|
export interface UseUpdatePermissionsArgs {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
roleName: string;
|
roleName: string;
|
||||||
@ -13,7 +12,6 @@ export interface UseUpdatePermissionsArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useUpdatePermissions = ({
|
export const useUpdatePermissions = ({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
roleName,
|
roleName,
|
||||||
@ -21,7 +19,6 @@ export const useUpdatePermissions = ({
|
|||||||
accessType,
|
accessType,
|
||||||
}: UseUpdatePermissionsArgs) => {
|
}: UseUpdatePermissionsArgs) => {
|
||||||
const updatePermissions = useSubmitForm({
|
const updatePermissions = useSubmitForm({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
roleName,
|
roleName,
|
||||||
@ -30,7 +27,6 @@ export const useUpdatePermissions = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const deletePermissions = useDeletePermission({
|
const deletePermissions = useDeletePermission({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
roleName,
|
roleName,
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import * as z from 'zod';
|
|
||||||
import { schema } from '../utils/formSchema';
|
|
||||||
|
|
||||||
export type QueryType = 'insert' | 'select' | 'update' | 'delete';
|
export type QueryType = 'insert' | 'select' | 'update' | 'delete';
|
||||||
export type AccessType =
|
export type AccessType =
|
||||||
| 'fullAccess'
|
| 'fullAccess'
|
||||||
| 'noAccess'
|
| 'noAccess'
|
||||||
| 'partialAccess'
|
| 'partialAccess'
|
||||||
| 'partialAccessWarning';
|
| 'partialAccessWarning';
|
||||||
|
|
||||||
export type FormOutput = z.infer<typeof schema>;
|
|
||||||
|
@ -3,8 +3,8 @@ import * as z from 'zod';
|
|||||||
export const schema = z.object({
|
export const schema = z.object({
|
||||||
checkType: z.string(),
|
checkType: z.string(),
|
||||||
filterType: z.string(),
|
filterType: z.string(),
|
||||||
check: z.any(),
|
check: z.any().optional(),
|
||||||
filter: z.any(),
|
filter: z.any().optional(),
|
||||||
rowCount: z.string().optional(),
|
rowCount: z.string().optional(),
|
||||||
columns: z.record(z.optional(z.boolean())),
|
columns: z.record(z.optional(z.boolean())),
|
||||||
presets: z.optional(
|
presets: z.optional(
|
||||||
@ -16,8 +16,8 @@ export const schema = z.object({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aggregationEnabled: z.boolean(),
|
aggregationEnabled: z.boolean().optional(),
|
||||||
backendOnly: z.boolean(),
|
backendOnly: z.boolean().optional(),
|
||||||
clonePermissions: z.optional(
|
clonePermissions: z.optional(
|
||||||
z.array(
|
z.array(
|
||||||
z.object({
|
z.object({
|
||||||
|
@ -1,14 +1,30 @@
|
|||||||
import { Permission } from '@/dataSources/types';
|
import { Permission } from '@/features/MetadataAPI';
|
||||||
|
|
||||||
interface Args {
|
export const permissionToKey = {
|
||||||
permissions?: Permission[];
|
insert: 'insert_permissions',
|
||||||
roleName: string;
|
select: 'select_permissions',
|
||||||
}
|
update: 'update_permissions',
|
||||||
|
delete: 'delete_permissions',
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const getCurrentRole = ({ permissions, roleName }: Args) => {
|
export const metadataPermissionKeys = [
|
||||||
const rolePermissions = permissions?.find(
|
'insert_permissions',
|
||||||
({ role_name }) => role_name === roleName
|
'select_permissions',
|
||||||
);
|
'update_permissions',
|
||||||
|
'delete_permissions',
|
||||||
|
] as const;
|
||||||
|
|
||||||
return rolePermissions;
|
export const keyToPermission = {
|
||||||
};
|
insert_permissions: 'insert',
|
||||||
|
select_permissions: 'select',
|
||||||
|
update_permissions: 'update',
|
||||||
|
delete_permissions: 'delete',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const isPermission = (props: {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
}): props is {
|
||||||
|
key: typeof metadataPermissionKeys[number];
|
||||||
|
value: Permission[];
|
||||||
|
} => props.key in keyToPermission;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Story, Meta } from '@storybook/react';
|
import { Story, Meta } from '@storybook/react';
|
||||||
|
|
||||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||||
@ -16,7 +17,6 @@ export const Primary: Story<PermissionsTabProps> = args => (
|
|||||||
<PermissionsTab {...args} />
|
<PermissionsTab {...args} />
|
||||||
);
|
);
|
||||||
Primary.args = {
|
Primary.args = {
|
||||||
currentSource: 'postgres',
|
|
||||||
dataSourceName: 'default',
|
dataSourceName: 'default',
|
||||||
table: {
|
table: {
|
||||||
name: 'user',
|
name: 'user',
|
||||||
@ -28,7 +28,6 @@ export const GDC: Story<PermissionsTabProps> = args => (
|
|||||||
<PermissionsTab {...args} />
|
<PermissionsTab {...args} />
|
||||||
);
|
);
|
||||||
GDC.args = {
|
GDC.args = {
|
||||||
currentSource: 'sqlite',
|
|
||||||
dataSourceName: 'sqlite',
|
dataSourceName: 'sqlite',
|
||||||
table: ['Artist'],
|
table: ['Artist'],
|
||||||
};
|
};
|
||||||
@ -41,7 +40,6 @@ export const GDCNoMocks: Story<PermissionsTabProps> = args => (
|
|||||||
<PermissionsTab {...args} />
|
<PermissionsTab {...args} />
|
||||||
);
|
);
|
||||||
GDCNoMocks.args = {
|
GDCNoMocks.args = {
|
||||||
currentSource: 'sqlite',
|
|
||||||
dataSourceName: 'sqlite',
|
dataSourceName: 'sqlite',
|
||||||
table: ['Artist'],
|
table: ['Artist'],
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useTableMachine, PermissionsTable } from '../PermissionsTable';
|
import { useTableMachine, PermissionsTable } from '../PermissionsTable';
|
||||||
import { BulkDelete } from '../PermissionsForm';
|
import { BulkDelete } from '../PermissionsForm';
|
||||||
import { PermissionsForm } from '../PermissionsForm/PermissionsForm';
|
import { PermissionsForm } from '../PermissionsForm/PermissionsForm';
|
||||||
import { AccessType } from '../PermissionsForm/types';
|
import { AccessType } from '../PermissionsForm/types';
|
||||||
|
|
||||||
export interface PermissionsTabProps {
|
export interface PermissionsTabProps {
|
||||||
currentSource: string;
|
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PermissionsTab: React.FC<PermissionsTabProps> = ({
|
export const PermissionsTab: React.FC<PermissionsTabProps> = ({
|
||||||
currentSource,
|
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
}) => {
|
}) => {
|
||||||
@ -31,7 +30,6 @@ export const PermissionsTab: React.FC<PermissionsTabProps> = ({
|
|||||||
!!state.context.bulkSelections.length && (
|
!!state.context.bulkSelections.length && (
|
||||||
<BulkDelete
|
<BulkDelete
|
||||||
roles={state.context.bulkSelections}
|
roles={state.context.bulkSelections}
|
||||||
currentSource={currentSource}
|
|
||||||
dataSourceName={dataSourceName}
|
dataSourceName={dataSourceName}
|
||||||
table={table}
|
table={table}
|
||||||
handleClose={() => send('CLOSE')}
|
handleClose={() => send('CLOSE')}
|
||||||
@ -40,7 +38,6 @@ export const PermissionsTab: React.FC<PermissionsTabProps> = ({
|
|||||||
|
|
||||||
{state.value === 'formOpen' && (
|
{state.value === 'formOpen' && (
|
||||||
<PermissionsForm
|
<PermissionsForm
|
||||||
currentSource={currentSource}
|
|
||||||
dataSourceName={dataSourceName}
|
dataSourceName={dataSourceName}
|
||||||
table={table}
|
table={table}
|
||||||
roleName={state.context.selectedForm.roleName || ''}
|
roleName={state.context.selectedForm.roleName || ''}
|
||||||
|
@ -7,7 +7,7 @@ import { handlers } from '../PermissionsForm/mocks/handlers.mock';
|
|||||||
import { useTableMachine } from './hooks';
|
import { useTableMachine } from './hooks';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Features/Permissions Table/Table',
|
title: 'Features/Permissions Tab/Permissions Table/Table',
|
||||||
component: PermissionsTable,
|
component: PermissionsTable,
|
||||||
decorators: [ReactQueryDecorator()],
|
decorators: [ReactQueryDecorator()],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FaInfo } from 'react-icons/fa';
|
import { FaInfo } from 'react-icons/fa';
|
||||||
|
import Skeleton from 'react-loading-skeleton';
|
||||||
|
import { IndicatorCard } from '@/new-components/IndicatorCard';
|
||||||
import { useRolePermissions } from './hooks/usePermissions';
|
import { useRolePermissions } from './hooks/usePermissions';
|
||||||
import { PermissionsLegend } from './components/PermissionsLegend';
|
import { PermissionsLegend } from './components/PermissionsLegend';
|
||||||
import { EditableCell, InputCell } from './components/Cells';
|
import { EditableCell, InputCell } from './components/Cells';
|
||||||
@ -56,15 +57,28 @@ export const PermissionsTable: React.FC<PermissionsTableProps> = ({
|
|||||||
table,
|
table,
|
||||||
machine,
|
machine,
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = useRolePermissions({
|
const { data, isLoading } = useRolePermissions({
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [state, send] = machine;
|
const [state, send] = machine;
|
||||||
|
|
||||||
|
if (isLoading)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Skeleton count={5} height={30} className="my-1.5" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return (
|
||||||
|
<div>
|
||||||
|
<IndicatorCard status="negative" headline="Error">
|
||||||
|
Something went wrong while fetching permissions
|
||||||
|
</IndicatorCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { supportedQueries, rolePermissions } = data;
|
const { supportedQueries, rolePermissions } = data;
|
||||||
|
@ -4,9 +4,6 @@ import { PermissionsIcon } from './PermissionsIcons';
|
|||||||
|
|
||||||
export const PermissionsLegend: React.FC = () => (
|
export const PermissionsLegend: React.FC = () => (
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<p>
|
|
||||||
<strong>Permissions</strong>
|
|
||||||
</p>
|
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<span>
|
<span>
|
||||||
<PermissionsIcon type="fullAccess" />
|
<PermissionsIcon type="fullAccess" />
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { AxiosInstance } from 'axios';
|
|
||||||
import isEqual from 'lodash.isequal';
|
import isEqual from 'lodash.isequal';
|
||||||
import { DataSource, exportMetadata } from '@/features/DataSource';
|
import { DataSource, exportMetadata } from '@/features/DataSource';
|
||||||
import type { TableColumn } from '@/features/DataSource';
|
import type { TableColumn } from '@/features/DataSource';
|
||||||
|
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useHttpClient } from '@/features/Network';
|
import { useHttpClient } from '@/features/Network';
|
||||||
|
import { MetadataTable, Metadata } from '@/features/MetadataAPI';
|
||||||
|
|
||||||
interface RolePermission {
|
interface RolePermission {
|
||||||
roleName: string;
|
roleName: string;
|
||||||
@ -95,19 +95,16 @@ const getAccessType = ({
|
|||||||
type GetMetadataTableArgs = {
|
type GetMetadataTableArgs = {
|
||||||
dataSourceName: string;
|
dataSourceName: string;
|
||||||
table: unknown;
|
table: unknown;
|
||||||
httpClient: AxiosInstance;
|
metadata?: Metadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMetadataTable = async ({
|
const getMetadataTable = ({
|
||||||
httpClient,
|
metadata,
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
}: GetMetadataTableArgs) => {
|
}: GetMetadataTableArgs) => {
|
||||||
// get all metadata
|
|
||||||
const { metadata } = await exportMetadata({ httpClient });
|
|
||||||
|
|
||||||
// find current source
|
// find current source
|
||||||
const currentMetadataSource = metadata?.sources?.find(
|
const currentMetadataSource = metadata?.metadata?.sources?.find(
|
||||||
source => source.name === dataSourceName
|
source => source.name === dataSourceName
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -140,14 +137,16 @@ const isPermission = (props: {
|
|||||||
type CreateRoleTableDataArgs = {
|
type CreateRoleTableDataArgs = {
|
||||||
metadataTable: any;
|
metadataTable: any;
|
||||||
tableColumns?: TableColumn[];
|
tableColumns?: TableColumn[];
|
||||||
|
allRoles: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type RoleToPermissionsMap = Record<string, Partial<Record<QueryType, Access>>>;
|
type RoleToPermissionsMap = Record<string, Partial<Record<QueryType, Access>>>;
|
||||||
|
|
||||||
const createRoleTableData = async ({
|
const createRoleTableData = ({
|
||||||
metadataTable,
|
metadataTable,
|
||||||
tableColumns,
|
tableColumns,
|
||||||
}: CreateRoleTableDataArgs): Promise<RolePermission[]> => {
|
allRoles,
|
||||||
|
}: CreateRoleTableDataArgs): RolePermission[] => {
|
||||||
if (!metadataTable) return [];
|
if (!metadataTable) return [];
|
||||||
// create object with key of role
|
// create object with key of role
|
||||||
// and value describing permissions attached to that role
|
// and value describing permissions attached to that role
|
||||||
@ -178,8 +177,22 @@ const createRoleTableData = async ({
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
const allRolesToPermissionsMap = allRoles.reduce<RoleToPermissionsMap>(
|
||||||
|
(acc, role) => {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[role]: roleToPermissionsMap[role] ?? {
|
||||||
|
insert: 'noAccess',
|
||||||
|
select: 'noAccess',
|
||||||
|
update: 'noAccess',
|
||||||
|
delete: 'noAccess',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
// create the array that has the relevant information for each row of the table
|
// create the array that has the relevant information for each row of the table
|
||||||
const permissions = Object.entries(roleToPermissionsMap).map(
|
const permissions = Object.entries(allRolesToPermissionsMap).map(
|
||||||
([roleName, permission]) => {
|
([roleName, permission]) => {
|
||||||
const permissionEntries = Object.entries(permission) as [
|
const permissionEntries = Object.entries(permission) as [
|
||||||
QueryType,
|
QueryType,
|
||||||
@ -246,34 +259,113 @@ type UseRolePermissionsArgs = {
|
|||||||
table: unknown;
|
table: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PermKeys = Pick<
|
||||||
|
MetadataTable,
|
||||||
|
| 'update_permissions'
|
||||||
|
| 'select_permissions'
|
||||||
|
| 'delete_permissions'
|
||||||
|
| 'insert_permissions'
|
||||||
|
>;
|
||||||
|
const permKeys: Array<keyof PermKeys> = [
|
||||||
|
'insert_permissions',
|
||||||
|
'update_permissions',
|
||||||
|
'select_permissions',
|
||||||
|
'delete_permissions',
|
||||||
|
];
|
||||||
|
|
||||||
|
const getRoles = (m: Metadata) => {
|
||||||
|
if (!m) return null;
|
||||||
|
|
||||||
|
const { metadata } = m;
|
||||||
|
|
||||||
|
const actions = metadata.actions;
|
||||||
|
const tableEntries: MetadataTable[] = metadata.sources.reduce<
|
||||||
|
MetadataTable[]
|
||||||
|
>((acc, source) => {
|
||||||
|
return [...acc, ...source.tables];
|
||||||
|
}, []);
|
||||||
|
const inheritedRoles = metadata.inherited_roles;
|
||||||
|
const remoteSchemas = metadata.remote_schemas;
|
||||||
|
const allowlists = metadata.allowlist;
|
||||||
|
const securitySettings = {
|
||||||
|
api_limits: metadata.api_limits,
|
||||||
|
graphql_schema_introspection: metadata.graphql_schema_introspection,
|
||||||
|
};
|
||||||
|
const roleNames: string[] = [];
|
||||||
|
|
||||||
|
tableEntries?.forEach(table =>
|
||||||
|
permKeys.forEach(key =>
|
||||||
|
table[key]?.forEach(({ role }: { role: string }) => roleNames.push(role))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
actions?.forEach(action =>
|
||||||
|
action.permissions?.forEach(p => roleNames.push(p.role))
|
||||||
|
);
|
||||||
|
|
||||||
|
remoteSchemas?.forEach(remoteSchema => {
|
||||||
|
remoteSchema?.permissions?.forEach(p => roleNames.push(p.role));
|
||||||
|
});
|
||||||
|
|
||||||
|
allowlists?.forEach(allowlist => {
|
||||||
|
if (allowlist?.scope?.global === false) {
|
||||||
|
allowlist?.scope?.roles?.forEach(role => roleNames.push(role));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inheritedRoles?.forEach(role => roleNames.push(role.role_name));
|
||||||
|
|
||||||
|
Object.entries(securitySettings?.api_limits ?? {}).forEach(
|
||||||
|
([limit, value]) => {
|
||||||
|
if (limit !== 'disabled' && typeof value !== 'boolean') {
|
||||||
|
Object.keys(value?.per_role ?? {}).forEach(role =>
|
||||||
|
roleNames.push(role)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
securitySettings?.graphql_schema_introspection?.disabled_for_roles.forEach(
|
||||||
|
role => roleNames.push(role)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Array.from(new Set(roleNames));
|
||||||
|
};
|
||||||
|
|
||||||
export const useRolePermissions = ({
|
export const useRolePermissions = ({
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
}: UseRolePermissionsArgs) => {
|
}: UseRolePermissionsArgs) => {
|
||||||
const httpClient = useHttpClient();
|
const httpClient = useHttpClient();
|
||||||
|
|
||||||
return useQuery<
|
return useQuery<
|
||||||
{ supportedQueries: QueryType[]; rolePermissions: RolePermission[] },
|
{ supportedQueries: QueryType[]; rolePermissions: RolePermission[] },
|
||||||
Error
|
Error
|
||||||
>({
|
>({
|
||||||
queryKey: [dataSourceName, 'permissionsTable'],
|
queryKey: [dataSourceName, 'permissionsTable', JSON.stringify(table)],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
// find the specific metadata table
|
const metadata = await exportMetadata({ httpClient });
|
||||||
const metadataTable = await getMetadataTable({
|
|
||||||
httpClient,
|
|
||||||
dataSourceName,
|
|
||||||
table,
|
|
||||||
});
|
|
||||||
|
|
||||||
// get table columns for metadata table from db introspection
|
// get table columns for metadata table from db introspection
|
||||||
const tableColumns = await DataSource(httpClient).getTableColumns({
|
const tableColumns = await DataSource(httpClient).getTableColumns({
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
table,
|
table,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// find the specific metadata table
|
||||||
|
const metadataTable = getMetadataTable({
|
||||||
|
metadata,
|
||||||
|
dataSourceName,
|
||||||
|
table,
|
||||||
|
});
|
||||||
|
|
||||||
|
// get all roles
|
||||||
|
const roles = getRoles(metadata);
|
||||||
|
|
||||||
// // extract the permissions data in the format required for the table
|
// // extract the permissions data in the format required for the table
|
||||||
const rolePermissions = await createRoleTableData({
|
const rolePermissions = createRoleTableData({
|
||||||
metadataTable,
|
metadataTable,
|
||||||
tableColumns,
|
tableColumns,
|
||||||
|
allRoles: roles ?? [],
|
||||||
});
|
});
|
||||||
|
|
||||||
return { rolePermissions, supportedQueries };
|
return { rolePermissions, supportedQueries };
|
||||||
|
Loading…
Reference in New Issue
Block a user