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:
Erik Magnusson 2022-11-03 11:13:20 +02:00 committed by hasura-bot
parent d17b27e91f
commit a9515cdb68
12 changed files with 310 additions and 67 deletions

View File

@ -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;

View File

@ -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':

View File

@ -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(
getTableInsertRowRoute(
currentSchema,
currentSource,
curTableName,
true
)
)
push({
pathname:
urlPrefix +
getTableInsertRowRoute(
currentSchema,
currentSource,
curTableName,
true
),
state: { row },
})
);
};

View File

@ -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 };

View File

@ -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}

View File

@ -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>

View File

@ -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,
});
};

View File

@ -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,
});
};

View File

@ -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,
});
};

View File

@ -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;
};

View File

@ -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;

View File

@ -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();