diff --git a/CHANGELOG.md b/CHANGELOG.md index f5a76c6f379..fd51735159d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/console/cypress/integration/data/functions/spec.ts b/console/cypress/integration/data/functions/spec.ts index 0383e8e9f88..1deba169180 100644 --- a/console/cypress/integration/data/functions/spec.ts +++ b/console/cypress/integration/data/functions/spec.ts @@ -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); diff --git a/console/src/components/Services/Data/Schema/FunctionsList.tsx b/console/src/components/Services/Data/Schema/FunctionsList.tsx index f37770c7901..85ee1130cc7 100644 --- a/console/src/components/Services/Data/Schema/FunctionsList.tsx +++ b/console/src/components/Services/Data/Schema/FunctionsList.tsx @@ -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 = ({ - showQueryNote, +const FuncNote: React.FC = ({ + 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 ( - This function will be tracked as a Mutation because the function is{' '} - VOLATILE + {showTrackVolatileNote ? ( + <> + This function will be tracked as a Mutation because the + function is VOLATILE + + ) : ( + <> + This function can be added as a root field or a{' '} + computed field inside a table. + + )}
+ -
{showQueryNote ? ( @@ -65,18 +96,21 @@ const VolatileFuncNote: React.FC = ({ 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 = ({ readOnlyMode, @@ -114,14 +154,18 @@ const TrackableEntry: React.FC = ({ 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 = ({ {func.function_name} + {isTrackableAndUseInComputedField && state.stableImmutableNoteOpen && ( +
+ 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` + ) + ); + }} + /> +
+ )} + {isVolatile && state.volatileNoteOpen && (
- 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 = ({ funcs, ...props }) => { const noTrackableFunctions = isEmpty(funcs); - if (noTrackableFunctions) { return (
diff --git a/console/src/components/Services/Data/Schema/Schema.js b/console/src/components/Services/Data/Schema/Schema.js index c1455af00eb..65063017690 100644 --- a/console/src/components/Services/Data/Schema/Schema.js +++ b/console/src/components/Services/Data/Schema/Schema.js @@ -225,7 +225,6 @@ class Schema extends Component { trackedFunctions, currentDataSource, } = this.props; - const getSectionHeading = (headingText, tooltip, actionElement = null) => { return (
@@ -572,6 +571,7 @@ class Schema extends Component { funcs={trackableFuncs} readOnlyMode={readOnlyMode} source={currentDataSource} + allSchemas={schema} /> ) : ( `Currently unsupported for ${( diff --git a/console/src/components/Services/Data/Schema/utils.ts b/console/src/components/Services/Data/Schema/utils.ts index de269e3513e..c8f220f3439 100644 --- a/console/src/components/Services/Data/Schema/utils.ts +++ b/console/src/components/Services/Data/Schema/utils.ts @@ -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 + ); +}; diff --git a/console/src/dataSources/services/postgresql/sqlUtils.ts b/console/src/dataSources/services/postgresql/sqlUtils.ts index 344c0942f16..75d6682b699 100644 --- a/console/src/dataSources/services/postgresql/sqlUtils.ts +++ b/console/src/dataSources/services/postgresql/sqlUtils.ts @@ -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 diff --git a/console/src/dataSources/services/postgresql/types.ts b/console/src/dataSources/services/postgresql/types.ts index e064a309f7c..e9c5cf16460 100644 --- a/console/src/dataSources/services/postgresql/types.ts +++ b/console/src/dataSources/services/postgresql/types.ts @@ -17,6 +17,7 @@ export type PGFunction = { return_type_type: string; function_type: string; input_arg_types?: PGInputArgType[]; + returns_set: boolean; }; export interface PostgresTable {