diff --git a/frontend/libs/console/legacy-ce/src/lib/features/BrowseRows/hooks/useRows.tsx b/frontend/libs/console/legacy-ce/src/lib/features/BrowseRows/hooks/useRows.tsx index e395930ae57..2ab5d120b16 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/BrowseRows/hooks/useRows.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/BrowseRows/hooks/useRows.tsx @@ -75,7 +75,6 @@ export const useRows = ({ columns, options, }); - console.log({ queryKey }); return useQuery({ queryKey, diff --git a/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.test.ts b/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.test.ts index 6615f26426f..f99d0f72482 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.test.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.test.ts @@ -26,9 +26,14 @@ describe('getTableDisplayName', () => { }); describe('when table is object and includes "name"', () => { - it('returns .name', () => { + it('returns .name if object has a schema key (Postgres)', () => { expect(getTableDisplayName({ name: 'aName' })).toBe('aName'); }); + it('returns name and other keys concatenated (non Postgres DBs)', () => { + expect(getTableDisplayName({ name: 'aName', dataset: 'aDataset' })).toBe( + 'aDataset.aName' + ); + }); }); describe('when table is object without name', () => { diff --git a/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.ts b/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.ts index 3f41cd5bfd1..ba578b176ca 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/DatabaseRelationships/utils/helpers.ts @@ -1,5 +1,6 @@ import { Table } from '@/features/hasura-metadata-types'; import isObject from 'lodash.isobject'; +import { isSchemaTable } from '../components/RelationshipForm/utils'; /* this function isn't entirely generic but it will hold for the current set of native DBs we have & GDC as well @@ -17,8 +18,8 @@ export const getTableDisplayName = (table: Table): string => { return table; } - if (typeof table === 'object' && 'name' in table) { - return (table as { name: string }).name; + if (typeof table === 'object' && isSchemaTable(table)) { + return table.name; } if (isObject(table)) { diff --git a/frontend/libs/console/legacy-ce/src/lib/features/GraphQLUtils/common/getTypeName.ts b/frontend/libs/console/legacy-ce/src/lib/features/GraphQLUtils/common/getTypeName.ts index 25d85fe4552..9a66b6d4fb2 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/GraphQLUtils/common/getTypeName.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/GraphQLUtils/common/getTypeName.ts @@ -1,5 +1,6 @@ import { MetadataTable, Source } from '@/features/hasura-metadata-types'; import { AllowedQueryOperation } from '../query'; +import { TableEntry } from '../../../metadata/types'; export const getTypeName = ({ defaultQueryRoot, @@ -8,7 +9,7 @@ export const getTypeName = ({ sourceCustomization, }: { defaultQueryRoot: string | never[]; - configuration?: MetadataTable['configuration']; + configuration?: TableEntry['configuration'] | MetadataTable['configuration']; operation: AllowedQueryOperation; sourceCustomization?: Source['customization']; defaultSchema?: string; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/PermissionsForm.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/PermissionsForm.tsx index cea06568547..ccadbccccc8 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/PermissionsForm.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/PermissionsForm.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useConsoleForm } from '@/new-components/Form'; import { Button } from '@/new-components/Button'; import { IndicatorCard } from '@/new-components/IndicatorCard'; @@ -8,7 +8,6 @@ import { useRoles, useSupportedQueryTypes, } from '@/features/MetadataAPI'; -import { getTableDisplayName } from '@/features/DatabaseRelationships'; import { PermissionsSchema, schema } from './../schema'; import { AccessType, QueryType } from '../types'; @@ -24,6 +23,8 @@ import { import { useFormData, useUpdatePermissions } from './hooks'; import ColumnRootFieldPermissions from './components/RootFieldPermissions/RootFieldPermissions'; +import { useListAllTableColumns } from '@/features/Data'; +import { useMetadataSource } from '@/features/MetadataAPI'; export interface ComponentProps { dataSourceName: string; @@ -79,9 +80,8 @@ const Component = (props: ComponentProps) => { const rowPermissions = queryType === 'update' ? ['pre', 'post'] : [queryType]; const { formData, defaultValues } = data || {}; - const { - methods: { getValues }, + methods: { getValues, reset }, Form, } = useConsoleForm({ schema, @@ -90,6 +90,13 @@ const Component = (props: ComponentProps) => { }, }); + // Reset form when default values change + // E.g. when switching tables + useEffect(() => { + const newValues = getValues(); + reset({ ...newValues, ...defaultValues }); + }, [roleName, defaultValues]); + // allRowChecks relates to other queries and is for duplicating from others const allRowChecks = defaultValues?.allRowChecks; @@ -134,6 +141,9 @@ const Component = (props: ComponentProps) => { } allRowChecks={allRowChecks || []} dataSourceName={dataSourceName} + supportedOperators={ + data?.defaultValues?.supportedOperators ?? [] + } /> ))} @@ -220,11 +230,18 @@ export interface PermissionsFormProps { export const PermissionsForm = (props: PermissionsFormProps) => { const { dataSourceName, table, queryType, roleName } = props; + const { columns: tableColumns, isLoading: isLoadingTables } = + useListAllTableColumns(dataSourceName, table); + + const { data: metadataSource } = useMetadataSource(dataSourceName); + const { data, isError, isLoading } = useFormData({ dataSourceName, table, queryType, roleName, + tableColumns, + metadataSource, }); if (isError) { @@ -233,7 +250,15 @@ export const PermissionsForm = (props: PermissionsFormProps) => { ); } - if (isLoading || !data) { + if ( + isLoading || + !data || + isLoadingTables || + !tableColumns || + tableColumns?.length === 0 || + !metadataSource || + !data.defaultValues + ) { return Loading...; } diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/PermissionsForm.utils.ts b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/PermissionsForm.utils.ts new file mode 100644 index 00000000000..7fe2eaa97f3 --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/PermissionsForm.utils.ts @@ -0,0 +1,163 @@ +import { TableColumn } from '@/features/DataSource'; +import { RelationshipType } from '../../RelationshipsTable/types'; +import { MetadataDataSource } from '../../../metadata/types'; +import { ManualObjectRelationship } from '@/features/hasura-metadata-types'; + +const boolOperators = ['_and', '_or', '_not']; +export const getBoolOperators = () => { + const boolMap = boolOperators.map(boolOperator => ({ + name: boolOperator, + kind: 'boolOperator', + meta: null, + })); + return boolMap; +}; + +const getExistOperators = () => { + return ['_exists']; +}; + +export const formatTableColumns = (columns: TableColumn[]) => { + if (!columns) return []; + return columns?.map(column => { + return { + kind: 'column', + name: column.name, + meta: { name: column.name, type: column.consoleDataType }, + }; + }); +}; + +export const formatTableRelationships = ( + metadataTables: MetadataDataSource['tables'] +) => { + if (!metadataTables) return []; + const met = metadataTables.reduce( + (tally: RelationshipType[], curr: RelationshipType) => { + const object_relationships = curr.object_relationships; + if (!object_relationships) return tally; + const relations = object_relationships + .map( + (relationship: { + using: { + manual_configuration: { + remote_table: { dataset: string; name: string }; + }; + }; + name: string; + }) => { + if (!relationship?.using) return undefined; + return { + kind: 'relationship', + name: relationship?.name, + meta: { + name: relationship?.name, + type: `${relationship?.using?.manual_configuration?.remote_table?.dataset}_${relationship?.using?.manual_configuration?.remote_table?.name}`, + isObject: true, + }, + }; + } + ) + .filter(Boolean); + return [...tally, ...relations]; + }, + [] + ); + return met; +}; + +export interface CreateOperatorsArgs { + tableName: string; + existingPermission?: Record; + tableColumns: TableColumn[]; + sourceMetadataTables: MetadataDataSource['tables'] | undefined; +} + +export const createOperatorsObject = ({ + tableName = '', + existingPermission, + tableColumns, + sourceMetadataTables, +}: CreateOperatorsArgs): Record => { + if (!existingPermission) { + return {}; + } + + const data = { + boolOperators: boolOperators, + existOperators: getExistOperators(), + columns: formatTableColumns(tableColumns), + relationships: sourceMetadataTables + ? formatTableRelationships(sourceMetadataTables) + : [], + }; + + const colNames = data.columns.map(col => col.name); + const relationships = data.relationships.map( + (rel: ManualObjectRelationship) => rel.name + ); + + const operators = Object.entries(existingPermission).reduce( + (_acc, [key, value]) => { + if (boolOperators.includes(key)) { + return { + name: key, + typeName: key, + type: 'boolOperator', + [key]: Array.isArray(value) + ? value.map((each: Record) => + createOperatorsObject({ + tableName, + tableColumns, + existingPermission: each, + sourceMetadataTables, + }) + ) + : createOperatorsObject({ + tableName, + tableColumns, + existingPermission: value, + sourceMetadataTables, + }), + }; + } + if (relationships.includes(key)) { + const rel = data.relationships.find( + (relationship: ManualObjectRelationship) => key === relationship.name + ); + const typeName = rel?.meta?.type?.type; + + return { + name: key, + typeName, + type: 'relationship', + [key]: createOperatorsObject({ + tableName, + existingPermission: value, + tableColumns, + sourceMetadataTables, + }), + }; + } + + if (colNames.includes(key)) { + return { + name: key, + typeName: key, + type: 'column', + columnOperator: createOperatorsObject({ + tableName, + existingPermission: value, + tableColumns, + sourceMetadataTables, + }), + }; + } + + return key; + }, + {} + ); + + return operators; +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/api.ts b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/api.ts index e0e3b4325e8..c722252dc6b 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/api.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/api.ts @@ -2,7 +2,7 @@ import { allowedMetadataTypes } from '@/features/MetadataAPI'; import { AccessType, QueryType } from '../../types'; import { PermissionsSchema } from '../../schema'; -import { createInsertArgs } from './utils'; +import { createInsertArgs, ExistingPermission } from './utils'; import { Table } from '@/features/hasura-metadata-types'; interface CreateBodyArgs { @@ -104,7 +104,7 @@ interface CreateInsertBodyArgs extends CreateBodyArgs { queryType: QueryType; formData: PermissionsSchema; accessType: AccessType; - existingPermissions: any; + existingPermissions: ExistingPermission[]; driver: string; tables: Table[]; } diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/utils.ts b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/utils.ts index 21b63baf75e..8e22a41a944 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/utils.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/api/utils.ts @@ -14,8 +14,8 @@ type SelectPermissionMetadata = { filter: Record; allow_aggregations?: boolean; limit?: number; - query_root_fields?: any[]; - subscription_root_fields?: any[]; + query_root_fields?: string[]; + subscription_root_fields?: string[]; }; const createSelectObject = (input: PermissionsSchema) => { @@ -26,11 +26,14 @@ const createSelectObject = (input: PermissionsSchema) => { // in row permissions builder an extra input is rendered automatically // this will always be empty and needs to be removed + const filter = Object.entries(input.filter).reduce>( (acc, [operator, value]) => { if (operator === '_and' || operator === '_or') { - const newValue = (value as any[])?.slice(0, -1); - acc[operator] = newValue; + const filteredEmptyObjects = (value as any[]).filter( + p => Object.keys(p).length !== 0 + ); + acc[operator] = filteredEmptyObjects; return acc; } @@ -95,10 +98,10 @@ export interface CreateInsertArgs { driver: string; } -interface ExistingPermission { +export interface ExistingPermission { table: unknown; role: string; - queryType: any; + queryType: string; } /** * creates the insert arguments to update permissions diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/ColumnPermissions.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/ColumnPermissions.tsx index a1c2dc2dbb6..e13fe49dc75 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/ColumnPermissions.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/ColumnPermissions.tsx @@ -19,7 +19,7 @@ import { QueryRootPermissionType, } from './RootFieldPermissions/types'; -const getAccessText = (queryType: any) => { +const getAccessText = (queryType: string) => { if (queryType === 'insert') { return 'to set input for'; } @@ -94,6 +94,8 @@ export const ColumnPermissionsSection: React.FC< const [showConfirmation, setShowConfirmationModal] = useState( null ); + const all = watch(); + const [selectedColumns, queryRootFields, subscriptionRootFields] = watch([ 'columns', 'query_root_fields', diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissions.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissions.tsx index 26a33a8c1d5..b17e2690678 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissions.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissions.tsx @@ -4,7 +4,7 @@ import { useFormContext } from 'react-hook-form'; import { Table } from '@/features/hasura-metadata-types'; import { useHttpClient } from '@/features/Network'; import { useQuery } from 'react-query'; -import { DataSource, exportMetadata } from '@/features/DataSource'; +import { DataSource, exportMetadata, Operator } from '@/features/DataSource'; import { areTablesEqual } from '@/features/RelationshipsTable'; import { getTypeName } from '@/features/GraphQLUtils'; import { InputField } from '@/new-components/Form'; @@ -40,6 +40,7 @@ export interface RowPermissionsProps { subQueryType?: string; allRowChecks: Array<{ queryType: QueryType; value: string }>; dataSourceName: string; + supportedOperators: Operator[]; } enum SelectedSection { @@ -132,6 +133,7 @@ export const RowPermissionsSection: React.FC = ({ subQueryType, allRowChecks, dataSourceName, + supportedOperators, }) => { const { data: tableName, isLoading } = useTypeName({ table, dataSourceName }); const { register, watch, setValue } = useFormContext(); @@ -245,7 +247,6 @@ export const RowPermissionsSection: React.FC = ({
{!isLoading && tableName ? ( = args => ; + +WithDefaultsNot.args = { + tableName: 'user', + nesting: ['filter'], +}; + +WithDefaultsNot.decorators = [ + Component => { + return ( +
+ + + +
+ ); + }, +]; + export const WithDefaultsRelationship: ComponentStory< typeof RowPermissionBuilder > = args => ; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx index 9eb3971488c..f14d67efef8 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/RowPermissionBuilder.tsx @@ -1,73 +1,49 @@ import { Table } from '@/features/hasura-metadata-types'; -import React from 'react'; -import AceEditor from 'react-ace'; import { useFormContext } from 'react-hook-form'; - -import { Builder, JsonItem } from './components'; -import { useIntrospectSchema } from './hooks'; -import { createDisplayJson } from './utils'; +import { RowPermissionsInput } from './components'; +import { usePermissionTables } from './hooks/usePermissionTables'; +import { usePermissionComparators } from './hooks/usePermissionComparators'; +import { useMetadataTable } from '../../../../hasura-metadata-api/metadataHooks'; +import { getMetadataTableCustomName } from './utils/getMetadataTableCustomName'; interface Props { - tableName: string; - /** - * The builder is a recursive structure - * the nesting describes the level of the structure - * so react hook form can correctly register the fields - * e.g. ['filter', 'Title', '_eq'] would be registered as 'filter.Title._eq' - */ nesting: string[]; table: Table; dataSourceName: string; } export const RowPermissionBuilder = ({ - tableName, nesting, table, dataSourceName, }: Props) => { - const { watch } = useFormContext(); - const { data: schema } = useIntrospectSchema(); + const { watch, setValue } = useFormContext(); // by watching the top level of nesting we can get the values for the whole builder // this value will always be 'filter' or 'check' depending on the query type - const value = watch(nesting[0]); - const json = createDisplayJson(value || {}); + const permissionsKey = nesting[0]; - if (!schema) { - return null; - } + const value = watch(permissionsKey); + const { data: tableConfig } = useMetadataTable(dataSourceName, table); + const tables = usePermissionTables({ + dataSourceName, + tableCustomName: getMetadataTableCustomName(tableConfig), + }); + + const comparators = usePermissionComparators(); + + if (!tables) return <>Loading; return ( -
-
- -
-
- -
- -
- -
-
+ { + setValue(permissionsKey, permissions); + }} + table={table} + tables={tables} + permissions={value} + comparators={comparators} + /> ); }; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Builder.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Builder.tsx deleted file mode 100644 index 8f6f4bc9040..00000000000 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Builder.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import React from 'react'; -import { useFormContext } from 'react-hook-form'; -import { GraphQLSchema } from 'graphql'; -import { Table } from '@/features/hasura-metadata-types'; -import { RenderFormElement } from './RenderFormElement'; -import { CustomField } from './Fields'; -import { JsonItem } from './Elements'; -import { getColumnOperators } from '../utils'; - -import { useData } from '../hooks'; - -const createKey = (inputs: string[]) => inputs.filter(Boolean).join('.'); - -interface Args { - value: string; - data: ReturnType['data']; -} - -/** - * return value to be set for dropdown state - */ -const getNewValues = ({ value, data }: Args) => { - const allItemsArray = [ - ...data.boolOperators, - ...data.columns, - ...data.relationships, - ]; - const selectedItem = allItemsArray.find(item => item.name === value); - - switch (selectedItem?.kind) { - case 'boolOperator': - return { - name: selectedItem.name, - typeName: selectedItem.name, - type: 'boolOperator', - columnOperator: '_eq', - }; - case 'column': - return { - name: selectedItem.name, - typeName: selectedItem.name, - type: 'column', - columnOperator: '_eq', - }; - case 'relationship': - return { - name: selectedItem.name, - // for relationships the type name will be different from the name - // for example if the relationship name is things the type name will be thing - // therefore we need both to - // 1. correctly display the relationship name in the permission i.e. things - // 2. find the information needed about the type from the schema using the type name i.e. thing - typeName: selectedItem?.meta?.type?.type, - type: 'relationship', - columnOperator: '_eq', - }; - default: - throw new Error('Case not handled'); - } -}; - -interface RenderJsonDisplayProps { - dropDownState: { name: string; type: string }; -} - -/** - * - * needed to render the next level of the form differently depending on which item is selected - */ -const RenderJsonDisplay = (props: RenderJsonDisplayProps) => { - const { dropDownState } = props; - - const isObjectType = - dropDownState?.type === 'column' || - dropDownState?.type === 'relationship' || - dropDownState?.name === '_not'; - - // if nothing is selected render a disabled input - if (!dropDownState?.type) { - return ; - } - - if (isObjectType) { - return ( - <> - - - ); - } - - if (dropDownState?.name === '_and' || dropDownState?.name === '_or') { - return ( - <> - - - ); - } - - return null; -}; - -interface Props { - tableName: string; - /** - * The builder is a recursive structure - * the nesting describes the level of the structure - * so react hook form can correctly register the fields - * e.g. ['filter', 'Title', '_eq'] would be registered as 'filter.Title._eq' - */ - nesting: string[]; - schema: GraphQLSchema; - dataSourceName: string; - table: Table; -} - -export const Builder = (props: Props) => { - const { tableName, nesting, schema, dataSourceName, table } = props; - - 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(); - // the selections from the dropdowns are stored on the form state under the key "operators" - // this will be removed for submitting the form - // and is generated from the permissions object when rendering the form from existing data - const operatorsKey = createKey(['operators', ...nesting]); - const dropDownState = getValues(operatorsKey); - - const permissionsKey = createKey([...nesting, dropDownState?.name]); - const columnKey = createKey([ - ...nesting, - dropDownState?.name, - dropDownState?.columnOperator || '_eq', - ]); - - const columnOperators = React.useMemo(() => { - if (dropDownState?.name && dropDownState?.type === 'column' && schema) { - return getColumnOperators({ - tableName, - columnName: dropDownState.name, - schema, - tableConfig, - }); - } - - return []; - }, [tableName, dropDownState, schema, tableConfig]); - - const handleDropdownChange: React.ChangeEventHandler< - HTMLSelectElement - > = e => { - const value = e.target.value; - - // as the form is populated a json object is built up - // when the dropdown changes at a specific level - // everything below that level needs to be removed - // set value undefined is necessary to remove field arrays - if (dropDownState?.name === '_and' || dropDownState?.name === '_or') { - setValue(permissionsKey, undefined); - } - // when the dropdown changes both the permissions object - // and operators object need to be unregistered below this level - unregister(permissionsKey); - unregister(operatorsKey); - - const newValue = getNewValues({ value, data }); - return setValue(operatorsKey, newValue); - }; - - const handleColumnChange: React.ChangeEventHandler = e => { - const target = e.target.value; - - // when the dropdown value changes the previous field needs to be unregistered - // so it is removed from the form state - unregister(columnKey); - setValue(operatorsKey, { - ...dropDownState, - columnOperator: target, - }); - }; - - return ( -
-
- - - {Object.entries(data).map(([section, list]) => { - return ( - - {list.map(item => ( - - ))} - - ); - })} - - - - - -
- - {/* depending on the selection from the drop down different form elements need to render */} - {/* for example if "_and" is selected a field array needs to render */} - -
- ); -}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Comparator.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Comparator.tsx new file mode 100644 index 00000000000..4e98027796e --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Comparator.tsx @@ -0,0 +1,48 @@ +import { useContext } from 'react'; +import { allOperators } from '@/components/Common/FilterQuery/utils'; +import { rowPermissionsContext } from './RowPermissionsProvider'; +import { tableContext } from './TableProvider'; + +const defaultOperators = allOperators.map(o => ({ + name: o.name, + operator: o.alias, +})); + +export const Comparator = ({ + comparator, + path, + noValue, +}: { + comparator: string; + path: string[]; + noValue?: boolean; +}) => { + const { setKey, comparators } = useContext(rowPermissionsContext); + const comparatorLevelId = `${path?.join('.')}-select${ + noValue ? '-is-empty' : '' + }`; + const { columns } = useContext(tableContext); + const columnName = path[path.length - 2]; + const column = columns.find(c => c.name === columnName); + const operators = + column?.type && comparators[column.type]?.operators + ? comparators[column.type].operators + : defaultOperators; + + return ( + + ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Elements.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Elements.tsx deleted file mode 100644 index 1bd62068316..00000000000 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Elements.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; - -interface Props extends React.ComponentProps<'div'> { - text: JsonOptions; -} - -type JsonOptions = '{' | '}' | '[' | ']' | '},' | ',' | '"' | ':'; - -const chooseColor = (text: JsonOptions) => { - switch (text) { - case '{': - case '}': - case '},': - return 'text-blue-800'; - case '[': - case ']': - return 'text-yellow-500'; - default: - return 'text-black'; - } -}; - -export const JsonItem = (props: Props) => { - const color = chooseColor(props.text); - - return ( - - {props.text} - - ); -}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EmptyEntry.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EmptyEntry.tsx new file mode 100644 index 00000000000..b4b7519933f --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/EmptyEntry.tsx @@ -0,0 +1,15 @@ +import { Key } from './Key'; +import { ValueInput } from './ValueInput'; + +export const EmptyEntry = ({ path }: { path: string[] }) => { + return ( +
+
+ + + + +
+
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Entry.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Entry.tsx new file mode 100644 index 00000000000..a42ba88bdec --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Entry.tsx @@ -0,0 +1,94 @@ +import { Fragment, useContext, ReactNode } from 'react'; +import { get, isEmpty, isPlainObject } from 'lodash'; +import { isComparator, isPrimitive } from './utils/helpers'; +import { tableContext, TableProvider } from './TableProvider'; +import { typesContext } from './TypesProvider'; +import { Key } from './Key'; +import { Token } from './Token'; +import { PermissionsInput } from './PermissionsInput'; +import { EmptyEntry } from './EmptyEntry'; + +export const Entry = ({ + k, + v, + path, +}: { + k: string; + v: any; + path: string[]; +}) => { + const { table } = useContext(tableContext); + const isDisabled = k === '_where' && isEmpty(table); + const { types } = useContext(typesContext); + const { relationships } = useContext(tableContext); + let Wrapper: any = Fragment; + const type = get(types, path)?.type; + + if (type === 'relationship') { + const relationship = relationships.find( + r => r.name === path[path.length - 1] + ); + if (relationship) { + const relationshipTable = relationship.table; + Wrapper = ({ children }: { children?: ReactNode | undefined }) => ( + {children} + ); + } + } + + return ( +
+
+ + + : + {Array.isArray(v) ? ( + + ) : isPrimitive(v) ? null : ( + + )} + + {k === '_exists' ? ( + + + + + ) : ( + +
+ + {Array.isArray(v) && k !== '_table' ? ( +
+ + + +
+ ) : null} + {isEmpty(v) && isPlainObject(v) && k !== '_table' ? ( + + ) : null} +
+
+ )} + {Array.isArray(v) ? ( +
+ + +
+ ) : isPrimitive(v) ? null : ( +
+ + +
+ )} +
+
+ ); +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/FieldArray.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/FieldArray.tsx deleted file mode 100644 index df5118dc4ad..00000000000 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/FieldArray.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React from 'react'; -import { useFieldArray, useFormContext } from 'react-hook-form'; - -import { GraphQLSchema } from 'graphql'; -import { Table } from '@/features/hasura-metadata-types'; -import { Builder } from './Builder'; -import { JsonItem } from './Elements'; - -interface FieldArrayElementProps { - index: number; - arrayKey: string; - tableName: string; - nesting: string[]; - field: Field; - fields: Field[]; - append: ReturnType['append']; - schema: GraphQLSchema; - dataSourceName: string; - table: Table; - // tableConfig: ReturnType['data']; -} - -type Field = Record<'id', string>; - -export const FieldArrayElement = (props: FieldArrayElementProps) => { - const { - index, - arrayKey, - tableName, - field, - nesting, - fields, - append, - schema, - table, - dataSourceName, - // tableConfig, - } = props; - const { watch } = useFormContext(); - - // from this we can determine if the dropdown has been selected - // if it has and the element is the final field - // another element needs to be appended - const currentField = watch(`operators.${arrayKey}.${index}`); - const isFinalField = fields.length - 1 === index; - - if (currentField && isFinalField) { - append({}); - } - - if (isFinalField) { - return ( - <> -
- - - -
- - - ); - } - - return ( -
- - - -
- ); -}; - -interface Props { - tableName: string; - nesting: string[]; - schema: GraphQLSchema; - dataSourceName: string; - table: Table; - // tableConfig: ReturnType['data']; -} - -export const FieldArray = (props: Props) => { - const { - tableName, - nesting, - schema, - dataSourceName, - table, - // tableConfig - } = props; - const arrayKey = nesting.join('.'); - - const { fields, append } = useFieldArray({ - name: arrayKey, - }); - - // automatically append a new field when the array is empty - // necessary to render an element - if (fields.length === 0) { - append({}); - } - - return ( -
-
- {fields.map((field, index) => ( - - ))} -
-
- ); -}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Fields.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Fields.tsx deleted file mode 100644 index e0d4fa42e64..00000000000 --- a/frontend/libs/console/legacy-ce/src/lib/features/Permissions/PermissionsForm/components/RowPermissionsBuilder/components/Fields.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { JsonItem } from './Elements'; - -const Input = React.forwardRef>( - (props, ref) => { - return ( -
- {props.type === 'text' && } - - {props.type === 'text' && } -
- ); - } -); - -const Select = (props: React.ComponentProps<'select'>) => { - return ( -
- -