display untrackable functions on schema page (close #2249) (#2773)

This commit is contained in:
Anne Ogborn 2019-11-15 20:23:57 +05:30 committed by Rikin Kachhia
parent 5eb9d8887e
commit dc1748b539
7 changed files with 184 additions and 107 deletions

View File

@ -53,6 +53,7 @@ export const unTrackFunction = () => {
};
export const trackFunction = () => {
cy.get(getElementFromAlias('toggle-trackable-functions')).click();
cy.get(
getElementFromAlias(`add-track-function-${getCustomFunctionName(1)}`)
).should('exist');

View File

@ -69,6 +69,10 @@ export const getTrackedTables = tables => {
return tables.filter(t => t.is_table_tracked);
};
export const getUntrackedTables = tables => {
return tables.filter(t => !t.is_table_tracked);
};
export const getOnlyTables = tablesOrViews => {
return tablesOrViews.filter(t => checkIfTable(t));
};

View File

@ -101,8 +101,9 @@ const initQueries = {
schema: 'hdb_catalog',
},
columns: ['function_name', 'function_schema', 'is_system_defined'],
order_by: [{ column: 'function_name', type: 'asc', nulls: 'last' }],
where: {
function_schema: '',
function_schema: '', // needs to be set later
},
},
},
@ -128,8 +129,9 @@ const initQueries = {
columns: ['table_schema', 'table_name'],
},
],
order_by: [{ column: 'function_name', type: 'asc', nulls: 'last' }],
where: {
function_schema: '',
function_schema: '', // needs to be set later
has_variadic: false,
returns_set: true,
return_type_type: compositeFnCheck, // COMPOSITE type
@ -165,15 +167,30 @@ const initQueries = {
'return_type_name',
'return_type_type',
'returns_set',
{
name: 'return_table_info',
columns: ['table_schema', 'table_name'],
},
],
order_by: [{ column: 'function_name', type: 'asc', nulls: 'last' }],
where: {
// TODO: set correct where
function_schema: '',
has_variadic: false,
returns_set: true,
return_type_type: compositeFnCheck, // COMPOSITE type
function_type: {
$ilike: '%volatile%',
function_schema: '', // needs to be set later
$not: {
has_variadic: false,
returns_set: true,
return_type_type: compositeFnCheck, // COMPOSITE type
$or: [
{
function_type: {
$ilike: '%stable%',
},
},
{
function_type: {
$ilike: '%immutable%',
},
},
],
},
},
},
@ -389,10 +406,12 @@ const fetchFunctionInit = () => (dispatch, getState) => {
headers: dataHeaders(getState),
body: JSON.stringify(body),
};
return dispatch(requestAction(url, options)).then(
data => {
dispatch({ type: LOAD_FUNCTIONS, data: data[0] });
dispatch({ type: LOAD_NON_TRACKABLE_FUNCTIONS, data: data[1] });
let consistentFunctions = data[2];
const { inconsistentObjects } = getState().metadata;
if (inconsistentObjects.length > 0) {

View File

@ -37,7 +37,15 @@ import { createNewSchema, deleteCurrentSchema } from './Actions';
import CollapsibleToggle from '../../../Common/CollapsibleToggle/CollapsibleToggle';
import gqlPattern from '../Common/GraphQLValidation';
import GqlCompatibilityWarning from '../../../Common/GqlCompatibilityWarning/GqlCompatibilityWarning';
import { displayTableName } from '../../../Common/utils/pgUtils';
import {
displayTableName,
getFunctionName,
getSchemaTables,
getUntrackedTables,
} from '../../../Common/utils/pgUtils';
import { SET_SQL } from '../RawSQL/Actions';
import _push from '../push';
import { isEmpty } from '../../../Common/utils/jsUtils';
import { getConfirmation } from '../../../Common/utils/jsUtils';
class Schema extends Component {
@ -77,15 +85,15 @@ class Schema extends Component {
/***********/
const getTrackableFunctions = () => {
const trackedFuncNames = trackedFunctions.map(t => t.function_name);
const _getTrackableFunctions = () => {
const trackedFuncNames = trackedFunctions.map(fn => getFunctionName(fn));
// Assuming schema for both function and tables are same
// return function which are tracked && function name whose
// set of tables are tracked
const filterCondition = func => {
return (
!trackedFuncNames.includes(func.function_name) &&
!trackedFuncNames.includes(getFunctionName(func)) &&
!!func.return_table_info
);
};
@ -93,25 +101,6 @@ class Schema extends Component {
return functionsList.filter(filterCondition);
};
const getUntrackedTables = () => {
const tableSortFunc = (a, b) => {
return a.table_name === b.table_name
? 0
: +(a.table_name > b.table_name) || -1;
};
const _untrackedTables = schema.filter(
table => !table.is_table_tracked && table.table_schema === currentSchema
);
// update tableInfo with graphql compatibility
_untrackedTables.forEach(t => {
t.isGQLCompatible = gqlPattern.test(t.table_name);
});
return _untrackedTables.sort(tableSortFunc);
};
const getSectionHeading = (headingText, tooltip, actionBtn = null) => {
return (
<div>
@ -132,8 +121,29 @@ class Schema extends Component {
/***********/
const allUntrackedTables = getUntrackedTables();
const trackableFuncs = getTrackableFunctions();
const allUntrackedTables = getUntrackedTables(
getSchemaTables(schema, currentSchema)
);
const trackableFuncs = _getTrackableFunctions();
const getTrackableFunctionsRequirementsMessage = () => {
const requirementsLink = (
<a
href="https://docs.hasura.io/1.0/graphql/manual/queries/custom-functions.html#supported-sql-functions"
target="_blank"
rel="noopener noreferrer"
>
requirements
</a>
);
return (
<i>
See {requirementsLink} for functions to be exposed over the GraphQL
API
</i>
);
};
const getCreateBtn = () => {
let createBtn = null;
@ -359,7 +369,8 @@ class Schema extends Component {
dispatch(addExistingTableSql());
};
const gqlCompatibilityWarning = !table.isGQLCompatible ? (
const isGQLCompatible = gqlPattern.test(table.table_name);
const gqlCompatibilityWarning = !isGQLCompatible ? (
<span className={styles.add_mar_left_mid}>
<GqlCompatibilityWarning />
</span>
@ -523,55 +534,69 @@ class Schema extends Component {
};
const getUntrackedFunctionsSection = () => {
let trackableFunctionList = null;
const noTrackableFunctions = isEmpty(trackableFuncs);
if (trackableFuncs.length > 0) {
const heading = getSectionHeading(
'Untracked custom functions',
trackableFunctionsTip
);
const getTrackableFunctionsList = () => {
const trackableFunctionList = [];
trackableFunctionList = (
<div className={styles.add_mar_top} key={'custom-functions-content'}>
<CollapsibleToggle title={heading} isOpen>
<div className={`${styles.padd_left_remove} col-xs-12`}>
{trackableFuncs.map((p, i) => (
<div
className={styles.padd_bottom}
key={`${i}untracked-function`}
>
<div
className={`${styles.display_inline} ${
styles.add_mar_right
}`}
>
<Button
data-test={`add-track-function-${p.function_name}`}
className={`${
styles.display_inline
} btn btn-xs btn-default`}
onClick={e => {
e.preventDefault();
trackableFuncs.forEach((p, i) => {
trackableFunctionList.push(
<div className={styles.padd_bottom} key={`untracked-function-${i}`}>
<div
className={`${styles.display_inline} ${styles.add_mar_right}`}
>
<Button
data-test={`add-track-function-${p.function_name}`}
className={`${styles.display_inline} btn btn-xs btn-default`}
onClick={e => {
e.preventDefault();
dispatch(addExistingFunction(p.function_name));
}}
>
Track
</Button>
</div>
<div className={styles.display_inline}>
<span>{p.function_name}</span>
</div>
</div>
))}
dispatch(addExistingFunction(p.function_name));
}}
>
Track
</Button>
</div>
<div className={styles.clear_fix} />
</CollapsibleToggle>
</div>
);
}
<div className={styles.display_inline}>
<span>{p.function_name}</span>
</div>
</div>
);
});
return trackableFunctionList;
if (noTrackableFunctions) {
trackableFunctionList.push(
<div key="no-untracked-fns">
<div>There are no untracked functions</div>
<div className={styles.add_mar_top}>
{getTrackableFunctionsRequirementsMessage()}
</div>
</div>
);
}
return trackableFunctionList;
};
const heading = getSectionHeading(
'Untracked custom functions',
trackableFunctionsTip
);
return (
<div className={styles.add_mar_top} key={'custom-functions-content'}>
<CollapsibleToggle
title={heading}
isOpen={!noTrackableFunctions}
testId={'toggle-trackable-functions'}
>
<div className={`${styles.padd_left_remove} col-xs-12`}>
{getTrackableFunctionsList()}
</div>
<div className={styles.clear_fix} />
</CollapsibleToggle>
</div>
);
};
const getNonTrackableFunctionsSection = () => {
@ -579,7 +604,7 @@ class Schema extends Component {
if (nonTrackableFunctions.length > 0) {
const heading = getSectionHeading(
'Non trackable custom functions',
'Non trackable functions',
nonTrackableFunctionsTip
);
@ -588,18 +613,41 @@ class Schema extends Component {
className={styles.add_mar_top}
key={'non-trackable-custom-functions'}
>
<CollapsibleToggle title={heading} isOpen>
<CollapsibleToggle title={heading} isOpen={false}>
<div className={`${styles.padd_left_remove} col-xs-12`}>
<div className={styles.add_mar_bottom}>
{getTrackableFunctionsRequirementsMessage()}
</div>
{nonTrackableFunctions.map((p, i) => (
<div
className={styles.padd_bottom}
key={`${i}untracked-function`}
key={`untracked-function-${i}`}
>
<div
className={`${styles.padd_right} ${
styles.display_inline
className={`${styles.display_inline} ${
styles.add_mar_right
}`}
>
<Button
data-test={`view-function-${p.function_name}`}
className={`${
styles.display_inline
} btn btn-xs btn-default`}
onClick={e => {
e.preventDefault();
dispatch(_push('/data/sql'));
dispatch({
type: SET_SQL,
data: p.function_definition,
});
}}
>
View
</Button>
</div>
<div className={styles.display_inline}>
{p.function_name}
</div>
</div>
@ -642,7 +690,7 @@ class Schema extends Component {
{getUntrackedTablesSection()}
{getUntrackedRelationsSection()}
{getUntrackedFunctionsSection()}
{false && getNonTrackableFunctionsSection()}
{getNonTrackableFunctionsSection()}
<hr />
{getPermissionsSummaryLink()}
</div>

View File

@ -22,6 +22,7 @@ export const trackableFunctionsTip = (
export const nonTrackableFunctionsTip = (
<Tooltip id="tooltip-functions-untrackable">
Custom functions that do not conform to Hasura requirements
Functions that do not conform to Hasura requirements to be exposed over the
GraphQL API.
</Tooltip>
);

View File

@ -39,7 +39,11 @@ class EditItem extends Component {
if (!oldItem) {
dispatch(
replace(
`${globals.urlPrefix || ''}${getTableBrowseRoute(currentSchema, tableName, true)}`
`${globals.urlPrefix || ''}${getTableBrowseRoute(
currentSchema,
tableName,
true
)}`
)
);
return null;

View File

@ -470,14 +470,14 @@ const saveForeignKeys = (index, tableSchema, columns) => {
alter table "${schemaName}"."${tableName}" drop constraint "${generatedConstraintName}",
add constraint "${constraintName}"
foreign key (${Object.keys(oldConstraint.column_mapping)
.map(lc => `"${lc}"`)
.join(', ')})
.map(lc => `"${lc}"`)
.join(', ')})
references "${oldConstraint.ref_table_table_schema}"."${
oldConstraint.ref_table
}"
oldConstraint.ref_table
}"
(${Object.values(oldConstraint.column_mapping)
.map(rc => `"${rc}"`)
.join(', ')})
.map(rc => `"${rc}"`)
.join(', ')})
on update ${pgConfTypes[oldConstraint.on_update]}
on delete ${pgConfTypes[oldConstraint.on_delete]};
`;
@ -725,8 +725,8 @@ const deleteTrigger = (trigger, table) => {
downMigrationSql += `CREATE TRIGGER "${triggerName}"
${trigger.action_timing} ${
trigger.event_manipulation
} ON "${tableSchema}"."${tableName}"
trigger.event_manipulation
} ON "${tableSchema}"."${tableName}"
FOR EACH ${trigger.action_orientation} ${trigger.action_statement};`;
if (trigger.comment) {
@ -1586,24 +1586,24 @@ const saveColumnChangesSql = (colName, column, onSuccess) => {
const schemaChangesUp =
originalColType !== colType
? [
{
type: 'run_sql',
args: {
sql: columnChangesUpQuery,
},
{
type: 'run_sql',
args: {
sql: columnChangesUpQuery,
},
]
},
]
: [];
const schemaChangesDown =
originalColType !== colType
? [
{
type: 'run_sql',
args: {
sql: columnChangesDownQuery,
},
{
type: 'run_sql',
args: {
sql: columnChangesDownQuery,
},
]
},
]
: [];
/* column custom field up/down migration*/