mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
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:
parent
58edd357b8
commit
bc7225fd1a
@ -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>
|
||||
|
@ -404,6 +404,7 @@ export const DataGrid = (props: DataGridProps) => {
|
||||
onRowsSelect={onRowsSelect}
|
||||
onRowDelete={deleteRow}
|
||||
isRowsSelectionEnabled={primaryKeys.length > 0}
|
||||
tableColumns={tableColumnQueryResult?.columns ?? []}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -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 (
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user