console: show only compatible postgres functions in computed fields section (close #5155)

GITHUB_PR_NUMBER: 5978
GITHUB_PR_URL: https://github.com/hasura/graphql-engine/pull/5978

Co-authored-by: Dmitry Grachikov <696824+GrizliK1988@users.noreply.github.com>
Co-authored-by: Aleksandra Sikora <9019397+beerose@users.noreply.github.com>
GitOrigin-RevId: 9399fae17ab3985fa0dd0339b6a36f0ac57997fa
This commit is contained in:
hasura-bot 2021-01-26 23:42:04 +05:30
parent 353859db09
commit 3cac1c30c0
8 changed files with 156 additions and 15 deletions

View File

@ -108,6 +108,7 @@ and be accessible according to the permissions that were configured for the role
- console: misc bug fixes (close #4785, #6330, #6288)
- console: allow setting table custom name (#212)
- console: support tracking VOLATILE functions as mutations or queries (close #6228)
- console: show only compatible postgres functions in computed fields section (close #5155) (#5978)
- console: added export data option on browse rows page (close #1438 #5158)
- cli: add missing global flags for seed command (#5565)
- cli: allow seeds as alias for seed command (#5693)

View File

@ -146,6 +146,39 @@ export const testCustomFunctionSQLWithSessArg = (
};
};
export const createUntrackedFunctionSQL = (
fnName: string,
tableName: string
) => {
return {
type: 'run_sql',
args: {
sql: `
CREATE OR REPLACE FUNCTION ${fnName}(table_row "${tableName}")
RETURNS int
LANGUAGE sql
STABLE
AS $function$
SELECT table_row.id
$function$
`,
cascade: false,
},
};
};
export const dropUntrackedFunctionSQL = (fnName: string) => {
return {
type: 'run_sql',
args: {
sql: `
DROP FUNCTION public.${fnName};
`,
cascade: false,
},
};
};
export const getTrackFnPayload = (name = 'customfunctionwithsessionarg') => ({
type: 'pg_track_function',
args: {

View File

@ -4,6 +4,8 @@ import {
getTableName,
getColName,
getElementFromAlias,
createUntrackedFunctionSQL,
dropUntrackedFunctionSQL,
} from '../../../helpers/dataHelpers';
import {
@ -11,11 +13,37 @@ import {
validateCT,
validateColumn,
ResultType,
dataRequest,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
const testName = 'mod';
export const passMTFunctionList = () => {
const tableName = getTableName(0, testName);
dataRequest(
createUntrackedFunctionSQL(`${tableName}_id_fn`, tableName),
ResultType.SUCCESS
);
cy.wait(5000);
cy.get(getElementFromAlias('modify-table-edit-computed-field-0')).click();
cy.get(getElementFromAlias('functions-dropdown')).click();
cy.get('[data-test^="data_test_column_type_value_"]').should(
'have.length',
1
);
cy.get('[data-test^="data_test_column_type_value_"]')
.first()
.should('have.text', `${getTableName(0, testName)}_id_fn`.toLowerCase());
dataRequest(
dropUntrackedFunctionSQL(`${tableName}_id_fn`),
ResultType.SUCCESS
);
};
export const passMTCreateTable = () => {
cy.get(getElementFromAlias('data-create-table')).click();
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
@ -30,7 +58,10 @@ export const passMTCreateTable = () => {
cy.wait(7000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
};
@ -41,7 +72,10 @@ export const passMTCheckRoute = () => {
// Match the URL
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
};
@ -92,7 +126,10 @@ export const passMTMoveToTable = () => {
cy.get(getElementFromAlias(getTableName(0, testName))).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/browse`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/browse`
);
};
@ -101,7 +138,10 @@ export const failMTWithoutColName = () => {
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
@ -116,7 +156,10 @@ export const failMTWithoutColType = () => {
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
getTableName(0, testName),
@ -137,7 +180,10 @@ export const Addcolumnnullable = () => {
cy.wait(2500);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
getTableName(0, testName),
@ -266,7 +312,10 @@ export const passMTDeleteCol = () => {
cy.wait(5000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
getTableName(0, testName),
@ -281,7 +330,10 @@ export const passMTDeleteTableCancel = () => {
cy.window().its('prompt').should('be.called');
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
@ -292,8 +344,7 @@ export const passMTDeleteTable = () => {
cy.get(getElementFromAlias('delete-table')).click();
cy.window().its('prompt').should('be.called');
cy.wait(5000);
// FIXME: change this later
// cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
validateCT(getTableName(0, testName), ResultType.FAILURE);
};

View File

@ -19,6 +19,7 @@ import {
passModifyUniqueKey,
passRemoveUniqueKey,
passMTChangeDefaultValueForPKey,
passMTFunctionList,
} from './spec';
import { testMode } from '../../../helpers/common';
@ -42,6 +43,10 @@ export const runModifyTableTests = () => {
it('Creating a table', passMTCreateTable);
it('Moving to the table', passMTMoveToTable);
it('Modify table button opens the correct route', passMTCheckRoute);
it(
'Can create computed field with compatible functions',
passMTFunctionList
);
it('Pass renaming table', passMTRenameTable);
it('Pass renaming column', passMTRenameColumn);
it('Fails to add column without column name', failMTWithoutColName);

View File

@ -311,10 +311,18 @@ const ComputedFieldsEditor = ({
Create new
</RawSqlButton>
</div>
<div className={styles.wd50percent}>
<div
className={styles.wd50percent}
data-test={'functions-dropdown'}
>
<SearchableSelectBox
options={dataSource
.getSchemaFunctions(functions, computedFieldFunctionSchema)
.getSchemaFunctions(
functions,
computedFieldFunctionSchema,
table.table_name,
table.table_schema
)
.map(fn => fn.function_name)}
onChange={handleFnNameChange}
value={computedFieldFunctionName}

View File

@ -31,7 +31,12 @@ export interface DataSourcesAPI {
// todo: replace with function type
getFunctionSchema(func: PGFunction): string;
getFunctionDefinition(func: PGFunction): string;
getSchemaFunctions(func: PGFunction[], schemaName: string): PGFunction[];
getSchemaFunctions(
func: PGFunction[],
schemaName: string,
tableName: string,
tableSchema: string
): PGFunction[];
findFunction(
func: PGFunction[],
fName: string,

View File

@ -135,11 +135,42 @@ export const getFunctionDefinition = (pgFunction: PGFunction) => {
return pgFunction.function_definition;
};
export const isFunctionCompatibleToTable = (
pgFunction: PGFunction,
tableName: string,
tableSchema: string
) => {
const inputArgTypes = pgFunction?.input_arg_types || [];
let hasTableRowInArguments = false;
let hasUnsupportedArguments = false;
inputArgTypes.forEach(inputArgType => {
if (!hasTableRowInArguments) {
hasTableRowInArguments =
inputArgType.name === tableName && inputArgType.schema === tableSchema;
}
if (!hasUnsupportedArguments) {
hasUnsupportedArguments =
inputArgType.type !== 'c' && inputArgType.type !== 'b';
}
});
return hasTableRowInArguments && !hasUnsupportedArguments;
};
export const getSchemaFunctions = (
allFunctions: PGFunction[],
fnSchema: string
fnSchema: string,
tableName: string,
tableSchema: string
) => {
return allFunctions.filter(fn => getFunctionSchema(fn) === fnSchema);
return allFunctions.filter(
fn =>
getFunctionSchema(fn) === fnSchema &&
isFunctionCompatibleToTable(fn, tableName, tableSchema)
);
};
export const findFunction = (

View File

@ -1,9 +1,16 @@
export type PGInputArgType = {
schema: string;
name: string;
type: string;
};
export type PGFunction = {
function_name: string;
function_schema: string;
function_definition: string;
return_type_type: string;
function_type: string;
input_arg_types?: PGInputArgType[];
};
export interface PostgresTable {