console: fix fallback dataype operators and filterable input

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8716
GitOrigin-RevId: cc91c31353f079b078e59e8065e4a70f1b7ba0d4
This commit is contained in:
Erik Magnusson 2023-04-18 10:03:48 +02:00 committed by hasura-bot
parent 4ccfc3490b
commit 74fe1c3268
6 changed files with 2203 additions and 32 deletions

View File

@ -1,6 +1,7 @@
import { useContext } from 'react';
import { rowPermissionsContext } from './RowPermissionsProvider';
import { useOperators } from './utils/comparatorsFromSchema';
import Select from 'react-select';
export const Comparator = ({
comparator,
@ -16,20 +17,35 @@ export const Comparator = ({
const operators = useOperators({ path });
return (
<select
data-testid={comparatorLevelId}
className="border border-gray-200 rounded-md p-2"
value={comparator}
onChange={e => {
setKey({ path, key: e.target.value, type: 'comparator' });
<Select
inputId={`${comparatorLevelId}-select-value`}
isSearchable
aria-label={comparatorLevelId}
components={{ DropdownIndicator: null }}
options={operators.map(o => ({
value: o.name,
label: o.name,
}))}
onChange={option => {
const { value } = option as { value: string };
setKey({ path, key: value, type: 'comparator' });
}}
>
<option value="">-</option>
{operators.map((o, index) => (
<option key={index} value={o.name}>
{o.name}
</option>
))}
</select>
defaultValue={{
value: comparator,
label: comparator,
}}
value={{
value: comparator,
label: comparator,
}}
styles={{
control: base => ({
...base,
border: 0,
minHeight: 'auto',
}),
}}
className="w-32 border border-gray-200 rounded-md"
/>
);
};

View File

@ -304,7 +304,10 @@ export const BooleanArrayType: ComponentStory<
BooleanArrayType.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByTestId('Author-operator')).toBeInTheDocument();
expect(canvas.getByTestId('Author._ceq-comparator')).toBeInTheDocument();
const element = await canvas.getByLabelText('Author._ceq-comparator');
await expect(element.getAttribute('id')).toEqual(
'Author._ceq-comparator-select-value'
);
expect(
canvas.getByTestId('Author._ceq-column-comparator-entry')
).toBeInTheDocument();
@ -550,9 +553,9 @@ SetNotPermission.play = async ({ canvasElement }) => {
await userEvent.selectOptions(canvas.getByTestId('_not-operator'), 'Period');
await userEvent.selectOptions(
canvas.getByTestId('_not.Period._eq-comparator'),
'_neq'
const element = await canvas.getByLabelText('_not.Period._eq-comparator');
await expect(element.getAttribute('id')).toEqual(
'_not.Period._eq-comparator-select-value'
);
};
@ -641,9 +644,10 @@ JsonbColumns.play = async ({ canvasElement }) => {
timeout: 50000,
});
// Expect jason._contained_in-comparator to be in the document
expect(
canvas.getByTestId('jason._contained_in-comparator')
).toBeInTheDocument();
const element = await canvas.getByLabelText('jason._contained_in-comparator');
await expect(element.getAttribute('id')).toEqual(
'jason._contained_in-comparator-select-value'
);
// Expect jason._contained_in-value-input to have value "{"a": "b"}"
expect(canvas.getByTestId('jason._contained_in-value-input')).toHaveValue(
'{"a":"b"}'

View File

@ -1,6 +1,12 @@
import { comparatorsFromSchema } from './comparatorsFromSchema';
import {
comparatorsFromSchema,
getDataTypeOperators,
mapScalarDataType,
} from './comparatorsFromSchema';
import { NamedTypeNode, parseType, typeFromAST } from 'graphql';
import { schema } from '../__tests__/fixtures/graphql';
import { SourceDataTypes } from './sourceDataTypes';
import { mssqlRealColumnTypeInput } from '../__tests__/fixtures/getDataTypeOperators';
describe('comparatorsFromSchema', () => {
it('should return comparators from schema', () => {
@ -113,3 +119,20 @@ describe('comparatorsFromSchema', () => {
]);
});
});
describe('getDataTypeOperators', () => {
it('should fallback to use int for integer type fallback columns', () => {
const operators = getDataTypeOperators(mssqlRealColumnTypeInput);
expect(operators.length).toEqual(15);
});
});
describe('mapScalarDataType', () => {
describe('MSSQL', () => {
it('should return string for ntext type', () => {
const dataType = mapScalarDataType('mssql', 'ntext' as SourceDataTypes);
expect(dataType).toEqual('string');
});
});
});

View File

@ -10,6 +10,7 @@ import { areTablesEqual } from '../../../../../../hasura-metadata-api';
import { Table } from '../../../../../../hasura-metadata-types';
import { useContext } from 'react';
import { rowPermissionsContext } from '../RowPermissionsProvider';
import { sourceDataTypes, SourceDataTypes } from './sourceDataTypes';
function columnOperators(): Array<Operator> {
return Object.keys(columnOperatorsInfo).reduce((acc, key) => {
@ -144,9 +145,27 @@ const whitelist: Record<string, string[]> = {
],
};
type Sources =
| 'postgres'
| 'bigquery'
| 'mssql'
| 'citus'
| 'cockroach'
| 'alloy';
export const mapScalarDataType = (
dataSource: string | undefined,
dataType: SourceDataTypes
) => {
if (!dataSource) return dataType;
const dataTypes = sourceDataTypes[dataSource as Sources];
return dataTypes?.[dataType] || dataType;
};
export function useOperators({ path }: { path: string[] }) {
const { comparators, tables } = useContext(rowPermissionsContext);
const { columns, table } = useContext(tableContext);
const columnName = path[path.length - 2];
const column = columns.find(c => c.name === columnName);
let dataType = column?.dataType;
@ -168,19 +187,21 @@ export function useOperators({ path }: { path: string[] }) {
return operators;
}
function getDataTypeOperators({
comparators,
path,
columns,
tables,
table,
}: {
export type GetDataTypeOperatorsProps = {
comparators: Comparators;
path: string[];
columns: Columns;
tables: Tables;
table: Table;
}) {
};
export const getDataTypeOperators = ({
comparators,
path,
columns,
tables,
table,
}: GetDataTypeOperatorsProps) => {
const columnName = path[path.length - 2];
const column = columns.find(c => c.name === columnName);
const dataSourceKind = tables.find(t => areTablesEqual(t.table, table))
@ -191,10 +212,24 @@ function getDataTypeOperators({
const comparatorKey = column ? `${column.dataType}${comparatorSuffix}` : '';
const operators = comparators[comparatorKey]?.operators;
if (!operators) {
return allOperators;
const dataSource = tables?.[0]?.dataSource?.name;
const dataType = mapScalarDataType(
dataSource,
column?.dataType as SourceDataTypes
);
const fallbackComparatorKey = column
? `${dataType}${comparatorSuffix}`
: '';
const lowerCaseComparators = Object.fromEntries(
Object.entries(comparators).map(([k, v]) => [k.toLowerCase(), v])
);
const backupOperators =
lowerCaseComparators[fallbackComparatorKey]?.operators;
return backupOperators || allOperators;
}
return operators;
}
};
function hasWhitelistedOperators(dataType: string) {
return whitelist[dataType];

View File

@ -0,0 +1,257 @@
export const sourceDataTypes = {
mssql: {
int: 'int',
bigint: 'int',
smallint: 'int',
tinyint: 'int',
bit: 'boolean',
decimal: 'int',
numeric: 'int',
float: 'int',
real: 'int',
date: 'string',
datetime: 'string',
smalldatetime: 'string',
datetime2: 'string',
time: 'string',
char: 'string',
varchar: 'string',
text: 'string',
nchar: 'string',
nvarchar: 'string',
ntext: 'string',
binary: 'string',
varbinary: 'string',
image: 'string',
uniqueidentifier: 'string',
},
postgres: {
integer: 'int',
bigint: 'int',
smallint: 'int',
serial: 'int',
bigserial: 'int',
smallserial: 'int',
numeric: 'int',
decimal: 'int',
real: 'int',
boolean: 'boolean',
char: 'string',
varchar: 'string',
'character varying': 'string',
text: 'string',
date: 'string',
timestamp: 'string',
timestamptz: 'string',
time: 'string',
timetz: 'string',
interval: 'string',
uuid: 'string',
bytea: 'string',
bit: 'string',
json: 'object',
jsonb: 'object',
array: 'array',
hstore: 'object',
enum: 'string',
tsvector: 'string',
tsquery: 'string',
inet: 'string',
cidr: 'string',
macaddr: 'string',
macaddr8: 'string',
point: 'object',
line: 'object',
lseg: 'object',
box: 'object',
path: 'object',
polygon: 'object',
circle: 'object',
},
mysql: {
tinyint: 'integer',
smallint: 'integer',
mediumint: 'integer',
int: 'integer',
bigint: 'integer',
bit: 'boolean',
float: 'integer',
double: 'integer',
decimal: 'integer',
numeric: 'integer',
date: 'string',
datetime: 'string',
timestamp: 'string',
time: 'string',
year: 'integer',
char: 'string',
varchar: 'string',
tinytext: 'string',
text: 'string',
mediumtext: 'string',
longtext: 'string',
binary: 'string',
varbinary: 'string',
tinyblob: 'string',
blob: 'string',
mediumblob: 'string',
longblob: 'string',
enum: 'string',
set: 'string',
json: 'object',
geometry: 'object',
point: 'object',
linestring: 'object',
polygon: 'object',
multipoint: 'object',
multilinestring: 'object',
multipolygon: 'object',
geometrycollection: 'object',
},
citus: {
integer: 'integer',
bigint: 'integer',
smallint: 'integer',
serial: 'integer',
bigserial: 'integer',
smallserial: 'integer',
numeric: 'integer',
decimal: 'integer',
real: 'integer',
'double precision': 'integer',
boolean: 'boolean',
char: 'string',
varchar: 'string',
text: 'string',
date: 'string',
timestamp: 'string',
timestamptz: 'string',
time: 'string',
timetz: 'string',
interval: 'string',
uuid: 'string',
bytea: 'string',
bit: 'string',
'bit varying': 'string',
json: 'object',
jsonb: 'object',
array: 'array',
hstore: 'object',
enum: 'string',
tsvector: 'string',
tsquery: 'string',
inet: 'string',
cidr: 'string',
macaddr: 'string',
macaddr8: 'string',
point: 'object',
line: 'object',
lseg: 'object',
box: 'object',
path: 'object',
polygon: 'object',
circle: 'object',
},
alloy: {
integer: 'integer',
bigint: 'integer',
smallint: 'integer',
serial: 'integer',
bigserial: 'integer',
smallserial: 'integer',
numeric: 'integer',
decimal: 'integer',
real: 'integer',
boolean: 'boolean',
char: 'string',
varchar: 'string',
text: 'string',
date: 'string',
timestamp: 'string',
timestamptz: 'string',
time: 'string',
timetz: 'string',
interval: 'string',
uuid: 'string',
bytea: 'string',
bit: 'string',
json: 'object',
jsonb: 'object',
array: 'array',
hstore: 'object',
enum: 'string',
tsvector: 'string',
tsquery: 'string',
inet: 'string',
cidr: 'string',
macaddr: 'string',
macaddr8: 'string',
point: 'object',
line: 'object',
lseg: 'object',
box: 'object',
path: 'object',
polygon: 'object',
circle: 'object',
},
bigquery: {
INT64: 'integer',
FLOAT64: 'integer',
NUMERIC: 'integer',
BIGNUMERIC: 'integer',
BOOL: 'boolean',
STRING: 'string',
BYTES: 'string',
DATE: 'string',
DATETIME: 'string',
TIME: 'string',
TIMESTAMP: 'string',
GEOGRAPHY: 'string',
},
cockroach: {
INT: 'integer',
INTEGER: 'integer',
SMALLINT: 'integer',
BIGINT: 'integer',
SERIAL: 'integer',
SMALLSERIAL: 'integer',
BIGSERIAL: 'integer',
FLOAT: 'integer',
REAL: 'integer',
'DOUBLE PRECISION': 'integer',
DECIMAL: 'integer',
NUMERIC: 'integer',
BOOL: 'boolean',
BOOLEAN: 'boolean',
CHAR: 'string',
VARCHAR: 'string',
STRING: 'string',
TEXT: 'string',
BYTES: 'string',
DATE: 'string',
TIME: 'string',
TIMESTAMP: 'string',
INTERVAL: 'string',
UUID: 'string',
INET: 'string',
JSON: 'object',
JSONB: 'object',
ENUM: 'string',
},
};
export type MsSQLTypes = keyof (typeof sourceDataTypes)['mssql'];
export type PostgresTypes = keyof (typeof sourceDataTypes)['postgres'];
export type MySQLTypes = keyof (typeof sourceDataTypes)['mysql'];
export type CitusTypes = keyof (typeof sourceDataTypes)['citus'];
export type AlloyTypes = keyof (typeof sourceDataTypes)['alloy'];
export type CockroachTypes = keyof (typeof sourceDataTypes)['cockroach'];
export type BigQueryTypes = keyof (typeof sourceDataTypes)['bigquery'];
export type SourceDataTypes = MsSQLTypes &
PostgresTypes &
MySQLTypes &
CitusTypes &
AlloyTypes &
CockroachTypes &
BigQueryTypes;