mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
console: allow tracking of custom SQL functions having composite type (rowtype) input arguments
Closes: https://github.com/hasura/graphql-engine/issues/6858 Description: Following two things are done: 1. Show compatible functions in the Untracked custom functions list (i.e. even those with ROWTYPE arg) ![Screenshot from 2021-05-11 16-13-21](https://user-images.githubusercontent.com/68095256/117803100-dd606f80-b273-11eb-8d02-1ea55b31863d.png) 2. When a function with ROWTYPE argument is tracked, a confirmatory dialogue box is shown. The text reads `This function can be added as a root field or a computed field inside a table` and the buttons reflect the options: `Add as root field`, `Add as computed field` (this will take the user to the Modify -> Add a computed field section of the first-row type argument) ![Screenshot from 2021-06-03 17-28-28](https://user-images.githubusercontent.com/68095256/120641377-28dff500-c491-11eb-80ea-cc60e6f37f23.png) Affected Component: - [x] Console Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com> Co-authored-by: Abhijeet Khangarot <26903230+abhi40308@users.noreply.github.com> GitOrigin-RevId: 089944aadba73f7a77e220a49489846ff1cb9540
This commit is contained in:
parent
b83ba51fa3
commit
3f19e8a3a8
@ -5,6 +5,7 @@
|
||||
### Bug fixes and improvements
|
||||
(Add entries below in the order of server, console, cli, docs, others)
|
||||
- console: add foreign key CRUD functionality to ms sql server tables
|
||||
- console: allow tracking of custom SQL functions having composite type (rowtype) input arguments
|
||||
|
||||
|
||||
## v2.0.0-beta.1
|
||||
|
@ -64,6 +64,8 @@ export const trackFunction = () => {
|
||||
cy.get(
|
||||
getElementFromAlias(`add-track-function-${getCustomFunctionName(1)}`)
|
||||
).click();
|
||||
cy.get(getElementFromAlias(`track-as-mutation`)).should('exist');
|
||||
cy.get(getElementFromAlias(`track-as-mutation`)).click();
|
||||
cy.wait(5000);
|
||||
validateCFunc(getCustomFunctionName(1), getSchema(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
@ -148,7 +150,10 @@ export const deleteCustomFunction = () => {
|
||||
|
||||
export const trackVolatileFunction = () => {
|
||||
const fN = 'customVolatileFunc'.toLowerCase();
|
||||
dataRequest(dropTableIfExists({ name: 'text_result', schema: 'public'}), ResultType.SUCCESS);
|
||||
dataRequest(
|
||||
dropTableIfExists({ name: 'text_result', schema: 'public' }),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.wait(5000);
|
||||
dataRequest(createSampleTable(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
@ -168,7 +173,10 @@ export const trackVolatileFunction = () => {
|
||||
|
||||
export const trackVolatileFunctionAsQuery = () => {
|
||||
const fN = 'customVolatileFunc'.toLowerCase();
|
||||
dataRequest(dropTableIfExists({ name: 'text_result', schema: 'public'}), ResultType.SUCCESS);
|
||||
dataRequest(
|
||||
dropTableIfExists({ name: 'text_result', schema: 'public' }),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.wait(5000);
|
||||
dataRequest(createSampleTable(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
|
@ -1,47 +1,78 @@
|
||||
import React, { useReducer } from 'react';
|
||||
|
||||
import { isEmpty } from '../../../Common/utils/jsUtils';
|
||||
import { Dispatch } from '../../../../types';
|
||||
import { Dispatch, ReduxState } from '../../../../types';
|
||||
import { addExistingFunction } from '../Add/AddExistingTableViewActions';
|
||||
import Button from '../../../Common/Button';
|
||||
import RawSqlButton from '../Common/Components/RawSqlButton';
|
||||
import { Note } from '../../../Common/Note';
|
||||
import styles from './styles.scss';
|
||||
import { PGFunction } from '../../../../dataSources/services/postgresql/types';
|
||||
import { isTrackableAndComputedField } from './utils';
|
||||
import _push from '../push';
|
||||
import { Table } from '../../../../dataSources/types';
|
||||
|
||||
type VolatileFuncNoteProps = {
|
||||
showQueryNote: boolean;
|
||||
type FuncNoteProps = {
|
||||
showTrackVolatileNote?: boolean;
|
||||
onCancelClick: () => void;
|
||||
onTrackAsMutationClick: () => void;
|
||||
onTrackAsQueryClick: () => void;
|
||||
onTrackAsQueryConfirmClick: () => void;
|
||||
onClickMainAction: () => void;
|
||||
onClickSecondaryAction: () => void;
|
||||
showQueryNote?: boolean;
|
||||
onTrackAsQueryConfirmClick?: () => void;
|
||||
};
|
||||
const VolatileFuncNote: React.FC<VolatileFuncNoteProps> = ({
|
||||
showQueryNote,
|
||||
const FuncNote: React.FC<FuncNoteProps> = ({
|
||||
showQueryNote = false,
|
||||
showTrackVolatileNote = false,
|
||||
onCancelClick,
|
||||
onTrackAsMutationClick,
|
||||
onTrackAsQueryClick,
|
||||
onClickMainAction,
|
||||
onClickSecondaryAction,
|
||||
onTrackAsQueryConfirmClick,
|
||||
}) => {
|
||||
const mainButtonText = showTrackVolatileNote
|
||||
? 'Track As Mutation'
|
||||
: 'Add As Root Field';
|
||||
const secondaryActionText = showTrackVolatileNote
|
||||
? 'Track As Query'
|
||||
: 'Cancel';
|
||||
const thirdActionText = showTrackVolatileNote
|
||||
? 'Cancel'
|
||||
: 'Add As Computed Field';
|
||||
return (
|
||||
<Note type="warn">
|
||||
This function will be tracked as a <b>Mutation</b> because the function is{' '}
|
||||
<b>VOLATILE</b>
|
||||
{showTrackVolatileNote ? (
|
||||
<>
|
||||
This function will be tracked as a <b>Mutation</b> because the
|
||||
function is <b>VOLATILE</b>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
This function can be added as a <b>root field</b> or a{' '}
|
||||
<b>computed field</b> inside a table.
|
||||
</>
|
||||
)}
|
||||
<div className={styles.buttonsSection}>
|
||||
<Button
|
||||
color="yellow"
|
||||
onClick={onTrackAsMutationClick}
|
||||
onClick={onClickMainAction}
|
||||
data-test="track-as-mutation"
|
||||
>
|
||||
Track as Mutation
|
||||
{mainButtonText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={
|
||||
showTrackVolatileNote ? onCancelClick : onClickSecondaryAction
|
||||
}
|
||||
>
|
||||
{thirdActionText}
|
||||
</Button>
|
||||
<Button onClick={onCancelClick}>Cancel</Button>
|
||||
<button
|
||||
className={`${styles.btnBlank} ${styles.queryButton}`}
|
||||
onClick={onTrackAsQueryClick}
|
||||
onClick={
|
||||
showTrackVolatileNote ? onClickSecondaryAction : onCancelClick
|
||||
}
|
||||
data-test="track-as-query"
|
||||
>
|
||||
Track as Query
|
||||
{secondaryActionText}
|
||||
</button>
|
||||
</div>
|
||||
{showQueryNote ? (
|
||||
@ -65,18 +96,21 @@ const VolatileFuncNote: React.FC<VolatileFuncNoteProps> = ({
|
||||
type State = {
|
||||
volatileNoteOpen: boolean;
|
||||
queryWarningOpen: boolean;
|
||||
stableImmutableNoteOpen: boolean;
|
||||
};
|
||||
|
||||
const defaultState: State = {
|
||||
volatileNoteOpen: false,
|
||||
queryWarningOpen: false,
|
||||
stableImmutableNoteOpen: false,
|
||||
};
|
||||
|
||||
type Action =
|
||||
| 'click-track-volatile-func'
|
||||
| 'click-track-volatile-func-as-query'
|
||||
| 'track-volatile-func'
|
||||
| 'cancel';
|
||||
| 'cancel'
|
||||
| 'click-track-stable-immutable-func';
|
||||
|
||||
const reducer = (state: State, action: Action): State => {
|
||||
switch (action) {
|
||||
@ -85,6 +119,11 @@ const reducer = (state: State, action: Action): State => {
|
||||
...state,
|
||||
volatileNoteOpen: true,
|
||||
};
|
||||
case 'click-track-stable-immutable-func':
|
||||
return {
|
||||
...state,
|
||||
stableImmutableNoteOpen: true,
|
||||
};
|
||||
case 'click-track-volatile-func-as-query':
|
||||
return {
|
||||
...state,
|
||||
@ -107,6 +146,7 @@ type TrackableEntryProps = {
|
||||
func: PGFunction;
|
||||
index: number;
|
||||
source: string;
|
||||
allSchemas: Table[];
|
||||
};
|
||||
const TrackableEntry: React.FC<TrackableEntryProps> = ({
|
||||
readOnlyMode,
|
||||
@ -114,14 +154,18 @@ const TrackableEntry: React.FC<TrackableEntryProps> = ({
|
||||
func,
|
||||
index,
|
||||
source,
|
||||
allSchemas,
|
||||
}) => {
|
||||
const [state, dispatchR] = useReducer(reducer, defaultState);
|
||||
const isVolatile = func.function_type.toLowerCase() === 'volatile';
|
||||
const isTrackableAndUseInComputedField = isTrackableAndComputedField(func);
|
||||
|
||||
const handleTrackFn = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
if (isVolatile) {
|
||||
dispatchR('click-track-volatile-func');
|
||||
} else if (isTrackableAndUseInComputedField) {
|
||||
dispatchR('click-track-stable-immutable-func');
|
||||
} else {
|
||||
dispatch(addExistingFunction(func.function_name));
|
||||
}
|
||||
@ -164,13 +208,47 @@ const TrackableEntry: React.FC<TrackableEntryProps> = ({
|
||||
<span>{func.function_name}</span>
|
||||
</div>
|
||||
|
||||
{isTrackableAndUseInComputedField && state.stableImmutableNoteOpen && (
|
||||
<div className={styles.volatileNote}>
|
||||
<FuncNote
|
||||
onCancelClick={() => dispatchR('cancel')}
|
||||
onClickMainAction={() =>
|
||||
dispatch(addExistingFunction(func.function_name))
|
||||
}
|
||||
onClickSecondaryAction={() => {
|
||||
const currentFuncInfo = func.input_arg_types?.find(
|
||||
fn => fn.type === 'c'
|
||||
);
|
||||
if (!currentFuncInfo) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!allSchemas?.find(
|
||||
schemaInfo =>
|
||||
schemaInfo?.table_name === currentFuncInfo.name &&
|
||||
schemaInfo?.table_schema === currentFuncInfo.schema
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
_push(
|
||||
`/data/${source}/schema/${currentFuncInfo.schema}/tables/${currentFuncInfo.name}/modify`
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isVolatile && state.volatileNoteOpen && (
|
||||
<div className={styles.volatileNote}>
|
||||
<VolatileFuncNote
|
||||
<FuncNote
|
||||
showTrackVolatileNote
|
||||
showQueryNote={state.queryWarningOpen}
|
||||
onCancelClick={() => dispatchR('cancel')}
|
||||
onTrackAsMutationClick={() => handleTrackFunction('mutation')}
|
||||
onTrackAsQueryClick={() =>
|
||||
onClickMainAction={() => handleTrackFunction('mutation')}
|
||||
onClickSecondaryAction={() =>
|
||||
dispatchR('click-track-volatile-func-as-query')
|
||||
}
|
||||
onTrackAsQueryConfirmClick={() => handleTrackFunction('query')}
|
||||
@ -186,13 +264,13 @@ type FunctionsListProps = {
|
||||
readOnlyMode: boolean;
|
||||
dispatch: Dispatch;
|
||||
source: string;
|
||||
allSchemas: ReduxState['tables']['allSchemas'];
|
||||
};
|
||||
export const TrackableFunctionsList: React.FC<FunctionsListProps> = ({
|
||||
funcs,
|
||||
...props
|
||||
}) => {
|
||||
const noTrackableFunctions = isEmpty(funcs);
|
||||
|
||||
if (noTrackableFunctions) {
|
||||
return (
|
||||
<div key="no-untracked-fns">
|
||||
|
@ -225,7 +225,6 @@ class Schema extends Component {
|
||||
trackedFunctions,
|
||||
currentDataSource,
|
||||
} = this.props;
|
||||
|
||||
const getSectionHeading = (headingText, tooltip, actionElement = null) => {
|
||||
return (
|
||||
<div>
|
||||
@ -572,6 +571,7 @@ class Schema extends Component {
|
||||
funcs={trackableFuncs}
|
||||
readOnlyMode={readOnlyMode}
|
||||
source={currentDataSource}
|
||||
allSchemas={schema}
|
||||
/>
|
||||
) : (
|
||||
`Currently unsupported for ${(
|
||||
|
@ -1,23 +1,19 @@
|
||||
import {
|
||||
ArgType,
|
||||
PGFunction,
|
||||
PGInputArgType,
|
||||
} from '../../../../dataSources/services/postgresql/types';
|
||||
import { PGFunction } from '../../../../dataSources/services/postgresql/types';
|
||||
|
||||
export const getTrackableFunctions = (
|
||||
functionsList: PGFunction[],
|
||||
trackedFunctions: PGFunction[]
|
||||
): PGFunction[] => {
|
||||
const trackedFuncNames = trackedFunctions.map(fn => fn.function_name);
|
||||
const containsTableArgs = (arg: PGInputArgType): boolean =>
|
||||
arg.type.toLowerCase() === ArgType.CompositeType;
|
||||
// Assuming schema for both function and tables are same
|
||||
// return function which are tracked
|
||||
const filterCondition = (func: PGFunction) => {
|
||||
return (
|
||||
!trackedFuncNames.includes(func.function_name) &&
|
||||
!func.input_arg_types?.some(containsTableArgs)
|
||||
);
|
||||
};
|
||||
const filterCondition = (func: PGFunction) =>
|
||||
!trackedFuncNames.includes(func.function_name);
|
||||
return functionsList.filter(filterCondition);
|
||||
};
|
||||
|
||||
export const isTrackableAndComputedField = (func: PGFunction) => {
|
||||
return (
|
||||
func.return_type_type === 'c' &&
|
||||
(func.function_type === 'STABLE' || func.function_type === 'IMMUTABLE') &&
|
||||
func.returns_set
|
||||
);
|
||||
};
|
||||
|
@ -97,7 +97,7 @@ export const getFetchTablesListQuery = (options: {
|
||||
COALESCE(json_agg(DISTINCT row_to_json(ist) :: jsonb || jsonb_build_object('comment', obj_description(pgt.oid))) filter (WHERE ist.trigger_name IS NOT NULL), '[]' :: json) AS triggers,
|
||||
row_to_json(isv) AS view_info
|
||||
|
||||
FROM partitions, pg_class as pgc
|
||||
FROM pg_class as pgc
|
||||
INNER JOIN pg_namespace as pgn
|
||||
ON pgc.relnamespace = pgn.oid
|
||||
|
||||
@ -179,7 +179,6 @@ export const getFetchTablesListQuery = (options: {
|
||||
|
||||
WHERE
|
||||
pgc.relkind IN ('r', 'v', 'f', 'm', 'p')
|
||||
and NOT (pgc.relname = ANY (partitions.names))
|
||||
${whereQuery}
|
||||
GROUP BY pgc.oid, pgn.nspname, pgc.relname, table_type, isv.*
|
||||
) AS info;
|
||||
@ -400,7 +399,7 @@ export const getCreateTableQueries = (
|
||||
|
||||
// add comment
|
||||
if (tableComment && tableComment !== '') {
|
||||
sqlCreateTable += `COMMENT ON TABLE "${currentSchema}"."${tableName}" IS ${sqlEscapeText(
|
||||
sqlCreateTable += `COMMENT ON TABLE "${currentSchema}".${tableName} IS ${sqlEscapeText(
|
||||
tableComment
|
||||
)};`;
|
||||
}
|
||||
@ -797,8 +796,6 @@ export const getCreatePkSql = ({
|
||||
primary key (${selectedPkColumns.map(pkc => `"${pkc}"`).join(', ')});`;
|
||||
};
|
||||
|
||||
export const getFunctionsWhereQuery = () => {};
|
||||
|
||||
const trackableFunctionsWhere = `
|
||||
AND has_variadic = FALSE
|
||||
AND returns_set = TRUE
|
||||
|
@ -17,6 +17,7 @@ export type PGFunction = {
|
||||
return_type_type: string;
|
||||
function_type: string;
|
||||
input_arg_types?: PGInputArgType[];
|
||||
returns_set: boolean;
|
||||
};
|
||||
|
||||
export interface PostgresTable {
|
||||
|
Loading…
Reference in New Issue
Block a user