console: fix issue with JSON columns breaking new browse rows UI

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8455
Co-authored-by: Luca Restagno <59067245+lucarestagno@users.noreply.github.com>
GitOrigin-RevId: a9c4e111cfbd311fdd13ce52d78f6b0c352e636b
This commit is contained in:
Vijay Prasanna 2023-03-28 18:19:40 +05:30 committed by hasura-bot
parent 58edd357b8
commit bc7225fd1a
5 changed files with 205 additions and 102 deletions

View File

@ -87,10 +87,11 @@ const DataSubSidebar = props => {
const [schemaLoading, setSchemaLoading] = useState(false);
const [isFetching, setIsFetching] = useState(false);
const [preLoadState, setPreLoadState] = useState(true);
const { enabled: isBigQueryEnabled } = useIsFeatureFlagEnabled(
const { enabled: isBigQueryEnabled, isLoading } = useIsFeatureFlagEnabled(
availableFeatureFlagIds.enabledNewUIForBigQuery
);
console.log({ isBigQueryEnabled, isLoading });
const onDatabaseChange = newSourceName => {
if (newSourceName === currentDataSource) {
dispatch(_push(`/data/${newSourceName}/`));
@ -129,84 +130,84 @@ const DataSubSidebar = props => {
const getItems = (schemaInfo = null) => {
let sourceItems = [];
sources
.filter(source => !isBigQueryEnabled || source.kind !== 'bigquery')
.forEach(source => {
if (isInconsistentSource(source.name, inconsistentObjects)) return;
sources.forEach(source => {
if (source.kind === 'bigquery' && isBigQueryEnabled === true) return;
const sourceItem = { name: source.name, type: 'database' };
const sourceTables = !source.tables
? []
: source.tables.map(data => {
const is_enum = data.is_enum ? true : false;
return {
name: data.table.name,
schema: data.table.schema,
type: 'table',
is_enum: is_enum,
};
});
const sourceFunctions = !source.functions
? []
: source.functions.map(data => ({
name: data.function.name,
schema: data.function.schema,
type: 'function',
}));
if (isInconsistentSource(source.name, inconsistentObjects)) return;
const schemaGroups = groupByKey(
[...sourceTables, ...sourceFunctions],
'schema'
);
// Find out the difference between schemas from metadata and SchemaList from state
const schemasFromMetadata = Array.from(
new Set([
...sourceTables.map(i => i.schema),
...sourceFunctions.map(i => i.schema),
])
);
const missingSchemas = schemaList.filter(
x => !schemasFromMetadata.includes(x)
);
let schemaItems = [];
Object.keys(schemaGroups).forEach(schema => {
const schemaItem = { name: schema, type: 'schema' };
const tableItems = [];
schemaGroups[schema].forEach(table => {
const is_view =
schemaInfo?.[source.name]?.[schema]?.[table.name]?.table_type ===
'view' ||
schemaInfo?.[source.name]?.[schema]?.[table.name]?.table_type ===
'materialized_view';
let type = table.type;
if (is_view) type = 'view';
if (table.is_enum) type = 'enum';
tableItems.push({
name: table.name,
type: type,
});
const sourceItem = { name: source.name, type: 'database' };
const sourceTables = !source.tables
? []
: source.tables.map(data => {
const is_enum = data.is_enum ? true : false;
return {
name: data.table.name,
schema: data.table.schema,
type: 'table',
is_enum: is_enum,
};
});
const sourceFunctions = !source.functions
? []
: source.functions.map(data => ({
name: data.function.name,
schema: data.function.schema,
type: 'function',
}));
const schemaGroups = groupByKey(
[...sourceTables, ...sourceFunctions],
'schema'
);
// Find out the difference between schemas from metadata and SchemaList from state
const schemasFromMetadata = Array.from(
new Set([
...sourceTables.map(i => i.schema),
...sourceFunctions.map(i => i.schema),
])
);
const missingSchemas = schemaList.filter(
x => !schemasFromMetadata.includes(x)
);
let schemaItems = [];
Object.keys(schemaGroups).forEach(schema => {
const schemaItem = { name: schema, type: 'schema' };
const tableItems = [];
schemaGroups[schema].forEach(table => {
const is_view =
schemaInfo?.[source.name]?.[schema]?.[table.name]?.table_type ===
'view' ||
schemaInfo?.[source.name]?.[schema]?.[table.name]?.table_type ===
'materialized_view';
let type = table.type;
if (is_view) type = 'view';
if (table.is_enum) type = 'enum';
tableItems.push({
name: table.name,
type: type,
});
schemaItem.children = tableItems;
schemaItems = [...schemaItems, schemaItem];
});
sourceItem.children = schemaItems;
if (source.name === currentDataSource) {
sourceItem.children = [
...missingSchemas.map(schemaName => ({
name: schemaName,
type: 'schema',
children: [],
})),
...sourceItem.children,
];
}
sourceItems = [...sourceItems, sourceItem];
schemaItem.children = tableItems;
schemaItems = [...schemaItems, schemaItem];
});
sourceItem.children = schemaItems;
if (source.name === currentDataSource) {
sourceItem.children = [
...missingSchemas.map(schemaName => ({
name: schemaName,
type: 'schema',
children: [],
})),
...sourceItem.children,
];
}
sourceItems = [...sourceItems, sourceItem];
});
return sourceItems;
};
@ -250,7 +251,15 @@ const DataSubSidebar = props => {
setTreeViewItems(newItems);
}
);
}, [sources.length, tables, functions, enums, schemaList, currentTable]);
}, [
sources.length,
tables,
functions,
enums,
schemaList,
currentTable,
isLoading,
]);
const databasesCount = metadataSources.length;
@ -293,18 +302,20 @@ const DataSubSidebar = props => {
</div>
<ul className={styles.subSidebarListUL} data-test="table-links">
<div style={{ pointerEvents: 'auto' }}>
<TreeView
items={treeViewItems}
onDatabaseChange={onDatabaseChange}
onSchemaChange={onSchemaChange}
currentDataSource={currentDataSource}
currentSchema={currentSchema}
pathname={pathname}
databaseLoading={databaseLoading}
schemaLoading={schemaLoading}
preLoadState={preLoadState}
gdcItemClick={handleClick}
/>
{!isLoading && (
<TreeView
items={treeViewItems}
onDatabaseChange={onDatabaseChange}
onSchemaChange={onSchemaChange}
currentDataSource={currentDataSource}
currentSchema={currentSchema}
pathname={pathname}
databaseLoading={databaseLoading}
schemaLoading={schemaLoading}
preLoadState={preLoadState}
gdcItemClick={handleClick}
/>
)}
</div>
</ul>
</div>

View File

@ -404,6 +404,7 @@ export const DataGrid = (props: DataGridProps) => {
onRowsSelect={onRowsSelect}
onRowDelete={deleteRow}
isRowsSelectionEnabled={primaryKeys.length > 0}
tableColumns={tableColumnQueryResult?.columns ?? []}
/>
)}

View File

@ -1,4 +1,4 @@
import { TableRow } from '../../../../DataSource';
import { TableColumn, TableRow } from '../../../../DataSource';
import { CardedTable } from '../../../../../new-components/CardedTable';
import {
FaCaretDown,
@ -36,8 +36,15 @@ interface ReactTableWrapperProps {
sorting: ColumnSort[];
setSorting: React.Dispatch<React.SetStateAction<ColumnSort[]>>;
};
tableColumns: TableColumn[];
}
const renderColumnData = (data: any) => {
if (['bigint', 'string', 'number'].includes(typeof data)) return data;
return JSON.stringify(data);
};
export const ReactTableWrapper: React.VFC<ReactTableWrapperProps> = ({
isRowsSelectionEnabled,
onRowsSelect,
@ -45,6 +52,7 @@ export const ReactTableWrapper: React.VFC<ReactTableWrapperProps> = ({
relationships,
rows,
sort,
tableColumns: _tableColumns,
}) => {
const [currentActiveRow, setCurrentActiveRow] = useState<Record<
string,
@ -61,7 +69,7 @@ export const ReactTableWrapper: React.VFC<ReactTableWrapperProps> = ({
header: () => <span key={column}>{column}</span>,
enableSorting: true,
enableMultiSort: true,
cell: (info: any) => info.getValue() ?? '',
cell: (info: any) => <>{renderColumnData(info.getValue())}</> ?? '',
})
);
@ -269,6 +277,7 @@ export const ReactTableWrapper: React.VFC<ReactTableWrapperProps> = ({
<RowDialog
row={currentActiveRow}
onClose={() => setCurrentActiveRow(null)}
columns={_tableColumns}
/>
)}
</>

View File

@ -6,31 +6,43 @@ import {
Textarea,
InputField,
SimpleForm,
CodeEditorField,
} from '../../../../../new-components/Form';
import { TableColumn } from '../../../../DataSource';
interface RowDialogProps {
row: Record<string, any>;
onClose: () => void;
columns: TableColumn[];
}
const schema = z.object({});
export const RowDialog = ({ onClose, row }: RowDialogProps) => {
export const RowDialog = ({ onClose, row, columns }: RowDialogProps) => {
// Add submitting and schema validation when we work on editing columns
// const onSubmit = (values: Record<string, unknown>) => {};
const rowSections = Object.entries(row).map(([key, value]) => {
switch (typeof value) {
case 'string':
return <Textarea disabled name={key} label={key} />;
const columnDataType = columns.find(
column => column.name === key
)?.consoleDataType;
default:
return (
<div>
<InputField disabled type="text" name={key} label={key} />
</div>
);
}
if (columnDataType === 'json')
return <CodeEditorField name={key} label={key} disabled />;
if (columnDataType === 'string')
return <InputField disabled type="text" name={key} label={key} />;
if (columnDataType === 'number' || columnDataType === 'float')
return <InputField disabled type="number" name={key} label={key} />;
if (columnDataType === 'boolean')
return <Textarea disabled name={key} label={key} />;
if (columnDataType === 'text')
return <Textarea disabled name={key} label={key} />;
return <Textarea disabled name={key} label={key} />;
});
return (

View File

@ -1,7 +1,15 @@
import { runSQL, RunSQLResponse } from '../../api';
import {
exportMetadata,
runIntrospectionQuery,
runSQL,
RunSQLResponse,
} from '../../api';
import { BigQueryTable } from '..';
import { GetTableColumnsProps, TableColumn } from '../../types';
import { adaptSQLDataType } from './utils';
import { areTablesEqual } from '../../../hasura-metadata-api';
import { getScalarType, getTypeName } from '../../../GraphQLUtils';
import { GraphQLType } from 'graphql';
const adaptTableColumns = (result: RunSQLResponse['result']): TableColumn[] => {
if (!result) return [];
@ -32,5 +40,67 @@ export const getTableColumns = async ({
httpClient,
});
return adaptTableColumns(tables.result);
const sqlResult = adaptTableColumns(tables.result);
const introspectionResult = await runIntrospectionQuery({ httpClient });
const { metadata } = await exportMetadata({ httpClient });
if (!metadata) throw Error('Metadata could not be retrieved');
const metadataSource = metadata.sources.find(s => s.name === dataSourceName);
const metadataTable = metadataSource?.tables.find(t =>
areTablesEqual(t.table, table)
);
if (!metadataTable) throw Error('No table found in metadata');
const queryRoot = getTypeName({
defaultQueryRoot: `${dataset}_${name}`,
operation: 'select',
sourceCustomization: metadataSource?.customization,
configuration: metadataTable.configuration,
});
const graphQLFields =
// eslint-disable-next-line no-underscore-dangle
introspectionResult.data.__schema.types.find(
(t: any) => t.name === queryRoot
)?.fields ?? [];
const scalarTypes = graphQLFields
.map(({ name: _name, type }: { name: string; type: GraphQLType }) => {
try {
return { name: _name, type: getScalarType(type) };
} catch {
return null;
}
})
.filter(Boolean);
const result = sqlResult.map<TableColumn>(column => {
const graphqlFieldName =
metadataTable.configuration?.column_config?.[column.name]?.custom_name ??
column.name;
const scalarType =
scalarTypes.find(
({ name: _name }: { name: string }) => _name === graphqlFieldName
) ?? null;
return {
name: column.name,
dataType: column.dataType,
consoleDataType: column.consoleDataType,
nullable: column.nullable,
isPrimaryKey: false,
graphQLProperties: {
name: graphqlFieldName,
scalarType: scalarType?.type ?? null,
},
};
});
return result;
};