Skip to content

Commit

Permalink
console: show only compatible postgres functions in computed fields s…
Browse files Browse the repository at this point in the history
…ection (close hasura#5155)

GITHUB_PR_NUMBER: 5978
GITHUB_PR_URL: hasura#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: 9399fae
  • Loading branch information
3 people committed Jan 26, 2021
1 parent 353859d commit 3cac1c3
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 33 additions & 0 deletions console/cypress/helpers/dataHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
71 changes: 61 additions & 10 deletions console/cypress/integration/data/modify/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,46 @@ import {
getTableName,
getColName,
getElementFromAlias,
createUntrackedFunctionSQL,
dropUntrackedFunctionSQL,
} from '../../../helpers/dataHelpers';

import {
setMetaData,
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`);
Expand All @@ -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);
};
Expand All @@ -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`
);
};

Expand Down Expand Up @@ -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`
);
};

Expand All @@ -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(
Expand All @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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),
Expand All @@ -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);
Expand All @@ -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);
};

Expand Down
5 changes: 5 additions & 0 deletions console/cypress/integration/data/modify/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
passModifyUniqueKey,
passRemoveUniqueKey,
passMTChangeDefaultValueForPKey,
passMTFunctionList,
} from './spec';

import { testMode } from '../../../helpers/common';
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
7 changes: 6 additions & 1 deletion console/src/dataSources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
35 changes: 33 additions & 2 deletions console/src/dataSources/services/postgresql/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
7 changes: 7 additions & 0 deletions console/src/dataSources/services/postgresql/types.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down

0 comments on commit 3cac1c3

Please sign in to comment.