mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
console: replace redux fetching logic with react query for insert row tab
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6644 GitOrigin-RevId: ae7809e702edeafa84075bcad1ba37236bdb1d1a
This commit is contained in:
parent
d17b27e91f
commit
a9515cdb68
@ -70,7 +70,7 @@ export interface TableRowProps {
|
||||
refName: 'valueNode' | 'nullNode' | 'defaultNode' | 'radioNode',
|
||||
node: HTMLInputElement | null
|
||||
) => void;
|
||||
enumOptions: Record<string, any>;
|
||||
enumOptions: string[];
|
||||
index: string;
|
||||
clone?: Record<string, any>;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>, val: unknown) => void;
|
||||
|
@ -178,6 +178,7 @@ const loadSchema = (configOptions = {}) => {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const body = {
|
||||
type: 'bulk',
|
||||
source,
|
||||
@ -223,7 +224,6 @@ const loadSchema = (configOptions = {}) => {
|
||||
return dispatch(requestAction(url, options)).then(
|
||||
data => {
|
||||
if (!data || !data[0] || !data[0].result) return;
|
||||
|
||||
let mergedData = [];
|
||||
switch (currentDriver) {
|
||||
case 'postgres':
|
||||
|
@ -49,7 +49,6 @@ import { ordinalColSort } from '../utils';
|
||||
import Spinner from '../../../Common/Spinner/Spinner';
|
||||
|
||||
import { E_SET_EDITITEM } from '../TableEditItem/EditActions';
|
||||
import { I_SET_CLONE } from '../TableInsertItem/InsertActions';
|
||||
import {
|
||||
getTableInsertRowRoute,
|
||||
getTableEditRowRoute,
|
||||
@ -70,6 +69,8 @@ import {
|
||||
getPersistedColumnsOrder,
|
||||
} from './tableUtils';
|
||||
import { compareRows, isTableWithPK } from './utils';
|
||||
import { push } from 'react-router-redux';
|
||||
import globals from '@/Globals';
|
||||
|
||||
const ViewRows = props => {
|
||||
const {
|
||||
@ -412,16 +413,19 @@ const ViewRows = props => {
|
||||
const cloneIcon = <FaClone />;
|
||||
|
||||
const handleCloneClick = () => {
|
||||
dispatch({ type: I_SET_CLONE, clone: row });
|
||||
const urlPrefix = globals.urlPrefix;
|
||||
dispatch(
|
||||
_push(
|
||||
push({
|
||||
pathname:
|
||||
urlPrefix +
|
||||
getTableInsertRowRoute(
|
||||
currentSchema,
|
||||
currentSource,
|
||||
curTableName,
|
||||
true
|
||||
)
|
||||
)
|
||||
),
|
||||
state: { row },
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -23,7 +23,6 @@ import { removeAll } from 'react-notification-system-redux';
|
||||
import { getNotificationDetails } from '../../Common/Notification';
|
||||
import { getTableConfiguration } from '../TableBrowseRows/utils';
|
||||
|
||||
const I_SET_CLONE = 'InsertItem/I_SET_CLONE';
|
||||
const I_RESET = 'InsertItem/I_RESET';
|
||||
const I_ONGOING_REQ = 'InsertItem/I_ONGOING_REQ';
|
||||
const I_REQUEST_SUCCESS = 'InsertItem/I_REQUEST_SUCCESS';
|
||||
@ -302,14 +301,6 @@ const insertReducer = (tableName, state, action) => {
|
||||
lastSuccess: null,
|
||||
enumOptions: null,
|
||||
};
|
||||
case I_SET_CLONE:
|
||||
return {
|
||||
clone: action.clone,
|
||||
ongoingRequest: false,
|
||||
lastError: null,
|
||||
lastSuccess: null,
|
||||
enumOptions: null,
|
||||
};
|
||||
case I_ONGOING_REQ:
|
||||
return {
|
||||
...state,
|
||||
@ -360,4 +351,4 @@ const insertReducer = (tableName, state, action) => {
|
||||
};
|
||||
|
||||
export default insertReducer;
|
||||
export { fetchEnumOptions, insertItem, I_SET_CLONE, I_RESET, Open, Close };
|
||||
export { fetchEnumOptions, insertItem, I_RESET, Open, Close };
|
||||
|
@ -1,10 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '@/store';
|
||||
import { setTable } from '../DataActions';
|
||||
import { fetchEnumOptions, I_RESET, insertItem } from './InsertActions';
|
||||
import { useMigrationMode, useReadOnlyMode } from '@/hooks';
|
||||
import { useMetadata } from '@/features/MetadataAPI';
|
||||
import { HasuraMetadataV3 } from '@/metadata/types';
|
||||
import { insertItem } from './InsertActions';
|
||||
import { ColumnName, RowValues } from '../TableCommon/DataTableRowItem.types';
|
||||
import { DataTableRowItemProps } from '../TableCommon/DataTableRowItem';
|
||||
import { TableInsertItems } from './TableInsertItems';
|
||||
import {
|
||||
useTableEnums,
|
||||
UseTableEnumsResponseArrayType,
|
||||
} from './hooks/useTableEnums';
|
||||
import { useSchemas } from './hooks/useSchemas';
|
||||
import { TableObject } from './types';
|
||||
|
||||
type GetButtonTextArgs = {
|
||||
insertedRows: number;
|
||||
@ -23,25 +31,93 @@ const getButtonText = ({ insertedRows, ongoingRequest }: GetButtonTextArgs) => {
|
||||
return 'Save';
|
||||
};
|
||||
|
||||
const getTableWithEnumRelations = (
|
||||
source: string,
|
||||
schema: string,
|
||||
metadata: HasuraMetadataV3 | undefined
|
||||
) => {
|
||||
return metadata
|
||||
? (metadata?.sources
|
||||
?.find(s => s.name === source)
|
||||
?.tables.filter((t: any) => {
|
||||
return t?.is_enum && t?.table?.schema === schema;
|
||||
})
|
||||
?.map(t => t?.table) as TableObject[])
|
||||
: [];
|
||||
};
|
||||
|
||||
const formatEnumOptions = (
|
||||
tableEnums: UseTableEnumsResponseArrayType | undefined
|
||||
) =>
|
||||
tableEnums
|
||||
? tableEnums?.reduce((tally, curr) => {
|
||||
return {
|
||||
...tally,
|
||||
[curr.from]: curr.values,
|
||||
};
|
||||
}, {})
|
||||
: [];
|
||||
|
||||
const getTableMetadata = (
|
||||
source: string,
|
||||
table: string,
|
||||
metadata: HasuraMetadataV3 | undefined
|
||||
) => {
|
||||
return metadata?.sources
|
||||
?.find((s: { name: string }) => s.name === source)
|
||||
?.tables.filter(
|
||||
(t: { table: { name: string } }) => t?.table?.name === table
|
||||
)?.[0];
|
||||
};
|
||||
|
||||
type TableInsertItemContainerContainer = {
|
||||
params: {
|
||||
schema: string;
|
||||
source: string;
|
||||
table: string;
|
||||
};
|
||||
router: { location: { state: any } };
|
||||
};
|
||||
|
||||
export const TableInsertItemContainer = (
|
||||
props: TableInsertItemContainerContainer
|
||||
) => {
|
||||
const { table: tableName } = props.params;
|
||||
|
||||
const {
|
||||
table: tableName,
|
||||
source: dataSourceName,
|
||||
schema: schemaName,
|
||||
} = props.params;
|
||||
const currentRow = props.router?.location?.state?.row;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isMigration, setIsMigration] = useState(false);
|
||||
const [insertedRows, setInsertedRows] = useState(0);
|
||||
const [values, setValues] = useState<Record<ColumnName, RowValues>>({});
|
||||
|
||||
const { data: metadata } = useMetadata();
|
||||
const tableMetadata = getTableMetadata(
|
||||
dataSourceName,
|
||||
tableName,
|
||||
metadata?.metadata
|
||||
);
|
||||
|
||||
const { data: migrationMode } = useMigrationMode();
|
||||
const { data: readOnlyMode } = useReadOnlyMode();
|
||||
const { data: schemas, isLoading: schemasIsLoading } = useSchemas({
|
||||
dataSourceName,
|
||||
schemaName,
|
||||
});
|
||||
|
||||
const tablesWithEnumRelations = getTableWithEnumRelations(
|
||||
dataSourceName,
|
||||
schemaName,
|
||||
metadata?.metadata
|
||||
);
|
||||
|
||||
const { data: tableEnums } = useTableEnums({
|
||||
tables: tablesWithEnumRelations,
|
||||
dataSourceName,
|
||||
});
|
||||
const onColumnUpdate: DataTableRowItemProps['onColumnUpdate'] = (
|
||||
columnName,
|
||||
rowValues
|
||||
@ -59,38 +135,9 @@ export const TableInsertItemContainer = (
|
||||
setValues(newValues);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setTable(tableName));
|
||||
dispatch(fetchEnumOptions());
|
||||
|
||||
return () => {
|
||||
dispatch({ type: I_RESET });
|
||||
};
|
||||
}, [tableName]);
|
||||
|
||||
const nextInsert = () =>
|
||||
setInsertedRows(prevInsertedRows => prevInsertedRows + 1);
|
||||
|
||||
const toggleMigrationCheckBox = () =>
|
||||
setIsMigration(prevIsMigration => !prevIsMigration);
|
||||
|
||||
const insert = useAppSelector(store => store.tables.insert);
|
||||
const allSchemas = useAppSelector(store => store.tables.allSchemas);
|
||||
const tablesView = useAppSelector(store => store.tables.view);
|
||||
const currentSchema = useAppSelector(store => store.tables.currentSchema);
|
||||
const currentDataSource = useAppSelector(
|
||||
store => store.tables.currentDataSource
|
||||
);
|
||||
const migrationMode = useAppSelector(store => store.main.migrationMode);
|
||||
const readOnlyMode = useAppSelector(store => store.main.readOnlyMode);
|
||||
|
||||
const { count } = tablesView;
|
||||
const { ongoingRequest, lastError, lastSuccess, clone, enumOptions } = insert;
|
||||
const buttonText = getButtonText({
|
||||
insertedRows,
|
||||
ongoingRequest,
|
||||
});
|
||||
|
||||
const onClickClear = () => {
|
||||
const form = document.getElementById('insertForm');
|
||||
if (!form) {
|
||||
@ -111,6 +158,22 @@ export const TableInsertItemContainer = (
|
||||
});
|
||||
};
|
||||
|
||||
const enumOptions = formatEnumOptions(tableEnums);
|
||||
|
||||
// Refactor in next iteration: --- Insert section start ---
|
||||
const insert = useAppSelector(
|
||||
(store: { tables: { insert: any } }) => store.tables.insert
|
||||
);
|
||||
const { ongoingRequest, lastError, lastSuccess } = insert;
|
||||
|
||||
const nextInsert = () =>
|
||||
setInsertedRows(prevInsertedRows => prevInsertedRows + 1);
|
||||
|
||||
const buttonText = getButtonText({
|
||||
insertedRows,
|
||||
ongoingRequest,
|
||||
});
|
||||
|
||||
const onClickSave: React.MouseEventHandler = e => {
|
||||
e.preventDefault();
|
||||
const inputValues = Object.keys(values).reduce<
|
||||
@ -133,7 +196,7 @@ export const TableInsertItemContainer = (
|
||||
dispatch(
|
||||
insertItem(
|
||||
tableName,
|
||||
clone ? { ...clone, ...inputValues } : inputValues,
|
||||
currentRow ? { ...currentRow, ...inputValues } : inputValues,
|
||||
isMigration
|
||||
)
|
||||
).then(() => {
|
||||
@ -141,21 +204,26 @@ export const TableInsertItemContainer = (
|
||||
});
|
||||
};
|
||||
|
||||
// --- Insert section end ---
|
||||
|
||||
if (schemasIsLoading) return <p>Loading...</p>;
|
||||
|
||||
return (
|
||||
<TableInsertItems
|
||||
isEnum={!!tableMetadata?.is_enum}
|
||||
toggleMigrationCheckBox={toggleMigrationCheckBox}
|
||||
onColumnUpdate={onColumnUpdate}
|
||||
isMigration={isMigration}
|
||||
dispatch={dispatch}
|
||||
tableName={tableName}
|
||||
currentSchema={currentSchema}
|
||||
clone={clone}
|
||||
schemas={allSchemas}
|
||||
migrationMode={migrationMode}
|
||||
readOnlyMode={readOnlyMode}
|
||||
count={count}
|
||||
currentSchema={schemaName}
|
||||
clone={currentRow}
|
||||
schemas={schemas}
|
||||
migrationMode={!!migrationMode}
|
||||
readOnlyMode={!!readOnlyMode}
|
||||
count={0}
|
||||
enumOptions={enumOptions}
|
||||
currentSource={currentDataSource}
|
||||
currentSource={dataSourceName}
|
||||
onClickSave={onClickSave}
|
||||
onClickClear={onClickClear}
|
||||
lastError={lastError}
|
||||
|
@ -45,6 +45,7 @@ const Alert = ({ lastError, lastSuccess }: AlertProps) => {
|
||||
};
|
||||
|
||||
type TableInsertItemsProps = {
|
||||
isEnum: boolean;
|
||||
tableName: string;
|
||||
currentSchema: string;
|
||||
clone: Record<string, unknown>;
|
||||
@ -66,6 +67,7 @@ type TableInsertItemsProps = {
|
||||
};
|
||||
|
||||
export const TableInsertItems = ({
|
||||
isEnum,
|
||||
tableName,
|
||||
currentSchema,
|
||||
clone,
|
||||
@ -161,7 +163,7 @@ export const TableInsertItems = ({
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
{currentTable.is_enum ? (
|
||||
{isEnum ? (
|
||||
<ReloadEnumValuesButton dispatch={dispatch} />
|
||||
) : null}
|
||||
</div>
|
||||
|
@ -0,0 +1,41 @@
|
||||
import { getRunSqlQuery } from '@/components/Common/utils/v1QueryUtils';
|
||||
import { dataSource } from '@/dataSources';
|
||||
import Endpoints from '@/Endpoints';
|
||||
import { useHttpClient } from '@/features/Network';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
export type UseTableRelationsType = {
|
||||
dataSourceName: string;
|
||||
schemaName: string;
|
||||
};
|
||||
|
||||
export const useSchemas = ({
|
||||
dataSourceName,
|
||||
schemaName,
|
||||
}: UseTableRelationsType) => {
|
||||
const httpClient = useHttpClient();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['tables-schema', schemaName, dataSourceName],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
// Will always fetch all tables on schema
|
||||
const runSql = dataSource?.getFetchTablesListQuery({
|
||||
schemas: [schemaName],
|
||||
});
|
||||
const url = Endpoints.query;
|
||||
const query = getRunSqlQuery(runSql, dataSourceName, false, true);
|
||||
const response = await httpClient.post(url, {
|
||||
type: 'bulk',
|
||||
source: dataSourceName,
|
||||
args: [query],
|
||||
});
|
||||
|
||||
return JSON.parse(response.data?.[0]?.result?.[1]?.[0]);
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
};
|
@ -0,0 +1,81 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
import Endpoints from '@/Endpoints';
|
||||
import { useHttpClient } from '@/features/Network';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { useTablesForeignKeys } from './useTableForeignKeys';
|
||||
import { TableObject, ForeignKeyMapping } from '../types';
|
||||
|
||||
export type UseTableEnumOptionsProps = {
|
||||
tables: TableObject[];
|
||||
dataSourceName: string;
|
||||
};
|
||||
|
||||
type UseTableEnumsResponseType = {
|
||||
from: string;
|
||||
to: string;
|
||||
column: string[];
|
||||
values: string[];
|
||||
};
|
||||
|
||||
export type UseTableEnumsResponseArrayType = UseTableEnumsResponseType[];
|
||||
|
||||
export const useTableEnums = ({
|
||||
tables,
|
||||
dataSourceName,
|
||||
}: UseTableEnumOptionsProps): UseQueryResult<UseTableEnumsResponseArrayType> => {
|
||||
const httpClient = useHttpClient();
|
||||
|
||||
const { data: foreignKeys } = useTablesForeignKeys({
|
||||
tables,
|
||||
dataSourceName,
|
||||
});
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['tables-enum', JSON.stringify(tables), dataSourceName],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const enums = [];
|
||||
if (!foreignKeys) return [];
|
||||
/* eslint-disable no-await-in-loop */
|
||||
for (const table of tables) {
|
||||
const relation = foreignKeys.reduce(
|
||||
(tally: ForeignKeyMapping | null, fk: ForeignKeyMapping[]) => {
|
||||
const found = fk.find(f => f.to.table === table.name);
|
||||
if (!found) return tally;
|
||||
return found;
|
||||
},
|
||||
null
|
||||
);
|
||||
if (!relation) return [];
|
||||
const body = {
|
||||
type: 'select',
|
||||
args: {
|
||||
source: dataSourceName,
|
||||
columns: relation.to.column,
|
||||
table,
|
||||
},
|
||||
};
|
||||
|
||||
const url = Endpoints.query;
|
||||
const response = await httpClient.post(url, JSON.stringify(body));
|
||||
const column = Object.keys(response?.data?.[0])?.[0];
|
||||
const values = response?.data?.map(
|
||||
(v: Record<string, string>) => v[column]
|
||||
);
|
||||
|
||||
enums.push({
|
||||
from: relation.from.column[0],
|
||||
to: relation.to.table,
|
||||
column,
|
||||
values,
|
||||
});
|
||||
}
|
||||
return enums;
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
},
|
||||
enabled: !!foreignKeys,
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
};
|
@ -0,0 +1,41 @@
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { DataSource } from '@/features/DataSource';
|
||||
import { useHttpClient } from '@/features/Network';
|
||||
|
||||
import { TableObject, ForeignKeyMapping } from '../types';
|
||||
|
||||
export type UseTableForeignKeysProps = {
|
||||
tables: TableObject[];
|
||||
dataSourceName: string;
|
||||
};
|
||||
|
||||
export const useTablesForeignKeys = ({
|
||||
tables,
|
||||
dataSourceName,
|
||||
}: UseTableForeignKeysProps): UseQueryResult<ForeignKeyMapping[][]> => {
|
||||
const httpClient = useHttpClient();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['tables-fk', JSON.stringify(tables), dataSourceName],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const foreignKeys = [];
|
||||
/* eslint-disable no-await-in-loop */
|
||||
for (const table of tables) {
|
||||
const response = await DataSource(httpClient).getTableFkRelationships(
|
||||
{
|
||||
dataSourceName,
|
||||
table,
|
||||
}
|
||||
);
|
||||
foreignKeys.push(response);
|
||||
}
|
||||
|
||||
return foreignKeys;
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
export type TableObject = {
|
||||
name: string;
|
||||
schema: string;
|
||||
};
|
||||
|
||||
export type ForeignKeyMap = {
|
||||
table: string;
|
||||
column: string[];
|
||||
};
|
||||
|
||||
export type ForeignKeyMapping = {
|
||||
to: ForeignKeyMap;
|
||||
from: ForeignKeyMap;
|
||||
};
|
@ -373,6 +373,7 @@ export const mergeLoadSchemaDataPostgres = (
|
||||
Table['foreign_key_constraints'][0],
|
||||
'is_table_tracked' | 'is_ref_table_tracked'
|
||||
>[];
|
||||
|
||||
const primaryKeys = JSON.parse(data[2].result[1]) as Table['primary_key'][];
|
||||
const uniqueKeys = JSON.parse(data[3].result[1]) as any;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SERVER_CONSOLE_MODE } from '@/constants';
|
||||
import Endpoints from '@/Endpoints';
|
||||
import { useQuery, UseQueryOptions } from 'react-query';
|
||||
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||
import { useAppSelector } from '../store';
|
||||
import { Api } from './apiUtils';
|
||||
import { useConsoleConfig } from './useEnvVars';
|
||||
@ -12,7 +12,7 @@ export interface Config {
|
||||
|
||||
export function useMigrationMode(
|
||||
queryOptions?: UseQueryOptions<{ migration_mode: boolean }, Error, boolean>
|
||||
) {
|
||||
): UseQueryResult<boolean> {
|
||||
const headers = useAppSelector(s => s.tables.dataHeaders);
|
||||
const migrationUrl = Endpoints.hasuractlMigrateSettings;
|
||||
const { mode } = useConsoleConfig();
|
||||
|
Loading…
Reference in New Issue
Block a user