add console support for exists operator in permissions (close #2837) (#2878)

This commit is contained in:
Rikin Kachhia 2019-09-23 17:23:12 +05:30 committed by GitHub
parent eacda7cad5
commit ddf27c1768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 627 additions and 423 deletions

View File

@ -149,16 +149,10 @@
display: initial !important;
.tableIcon, .functionIcon {
//display: inline;
margin-right: 5px;
font-size: 12px;
}
.functionIcon {
width: 12px;
img {
width: 100%;
}
}
}
}
@ -174,7 +168,7 @@
}
}
.activeTable {
.activeLink {
a {
// border-left: 4px solid #FFC627;
color: #FD9540!important;

View File

@ -14,7 +14,9 @@ const WarningSymbol = ({
return (
<div className={styles.display_inline}>
<OverlayTrigger placement={tooltipPlacement} overlay={tooltip}>
<WarningIcon customStyle={customStyle} />
<span>
<WarningIcon customStyle={customStyle} />
</span>
</OverlayTrigger>
</div>
);

View File

@ -37,9 +37,22 @@ export const isEqual = (value1, value2) => {
if (typeof value1 === typeof value2) {
if (isArray(value1)) {
// TODO
} else if (isObject(value2)) {
_isEqual = JSON.stringify(value1) === JSON.stringify(value2);
} else if (isObject(value2)) {
const value1Keys = Object.keys(value1);
const value2Keys = Object.keys(value2);
if (value1Keys.length === value2Keys.length) {
_isEqual = true;
for (let i = 0; i < value1Keys.length; i++) {
const key = value1Keys[i];
if (!isEqual(value1[key], value2[key])) {
_isEqual = false;
break;
}
}
}
} else {
_isEqual = value1 === value2;
}
@ -48,6 +61,54 @@ export const isEqual = (value1, value2) => {
return _isEqual;
};
export function isJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
export function getAllJsonPaths(json, leafKeys = [], prefix = '') {
const _paths = [];
const addPrefix = subPath => {
return prefix + (prefix && subPath ? '.' : '') + subPath;
};
const handleSubJson = (subJson, newPrefix) => {
const subPaths = getAllJsonPaths(subJson, leafKeys, newPrefix);
subPaths.forEach(subPath => {
_paths.push(subPath);
});
if (!subPaths.length) {
_paths.push(newPrefix);
}
};
if (isArray(json)) {
json.forEach((subJson, i) => {
handleSubJson(subJson, addPrefix(i.toString()));
});
} else if (isObject(json)) {
Object.keys(json).forEach(key => {
if (leafKeys.includes(key)) {
_paths.push({ [addPrefix(key)]: json[key] });
} else {
handleSubJson(json[key], addPrefix(key));
}
});
} else {
_paths.push(addPrefix(json));
}
return _paths;
}
// use browser confirm and prompt to get user confirmation for actions
export const getConfirmation = (
message = '',

View File

@ -1,10 +1,8 @@
import React from 'react';
import { isEqual } from './jsUtils';
import { isEqual, isString } from './jsUtils';
/*** Table/View utils ***/
// TODO: figure out better pattern for overloading fns
export const getTableName = table => {
return table.table_name;
};
@ -13,6 +11,7 @@ export const getTableSchema = table => {
return table.table_schema;
};
// TODO: figure out better pattern for overloading fns
// tableName and tableNameWithSchema are either/or arguments
export const generateTableDef = (
tableName,
@ -34,15 +33,12 @@ export const getTableDef = table => {
return generateTableDef(getTableName(table), getTableSchema(table));
};
// table and tableDef are either/or arguments
export const getTableNameWithSchema = (
table,
wrapDoubleQuotes = true,
tableDef = null
) => {
let _fullTableName;
export const getQualifiedTableDef = tableDef => {
return isString(tableDef) ? generateTableDef(tableDef) : tableDef;
};
tableDef = tableDef || getTableDef(table);
export const getTableNameWithSchema = (tableDef, wrapDoubleQuotes = false) => {
let _fullTableName;
if (wrapDoubleQuotes) {
_fullTableName =
@ -69,14 +65,40 @@ export const findTable = (allTables, tableDef) => {
return allTables.find(t => isEqual(getTableDef(t), tableDef));
};
export const getSchemaTables = (allTables, tableSchema) => {
return allTables.filter(t => getTableSchema(t) === tableSchema);
};
export const getTrackedTables = tables => {
return tables.filter(t => t.is_table_tracked);
};
/*** Table/View column utils ***/
export const getTableColumns = table => {
return table.columns;
};
export const getColumnName = column => {
return column.column_name;
};
export const getTableColumnNames = table => {
return getTableColumns(table).map(c => getColumnName(c));
};
export const getTableColumn = (table, columnName) => {
return getTableColumns(table).find(
column => getColumnName(column) === columnName
);
};
export const getColumnType = column => {
let _columnType = column.data_type;
if (_columnType === 'USER-DEFINED') {
_columnType = column.udt_name;
}
return _columnType;
};
export const isColumnAutoIncrement = column => {
const columnDefault = column.column_default;
@ -88,7 +110,79 @@ export const isColumnAutoIncrement = column => {
);
};
/*** Table/View relationship utils ***/
export const getTableRelationships = table => {
return table.relationships;
};
export const getRelationshipName = relationship => {
return relationship.rel_name;
};
export const getRelationshipDef = relationship => {
return relationship.rel_def;
};
export const getRelationshipType = relationship => {
return relationship.rel_type;
};
export const getTableRelationshipNames = table => {
return getTableRelationships(table).map(r => getRelationshipName(r));
};
export function getTableRelationship(table, relationshipName) {
return getTableRelationships(table).find(
relationship => getRelationshipName(relationship) === relationshipName
);
}
export function getRelationshipRefTable(table, relationship) {
let _refTable = null;
const relationshipDef = getRelationshipDef(relationship);
const relationshipType = getRelationshipType(relationship);
// if manual relationship
if (relationshipDef.manual_configuration) {
_refTable = relationshipDef.manual_configuration.remote_table;
}
// if foreign-key based relationship
if (relationshipDef.foreign_key_constraint_on) {
// if array relationship
if (relationshipType === 'array') {
_refTable = relationshipDef.foreign_key_constraint_on.table;
}
// if object relationship
if (relationshipType === 'object') {
const fkCol = relationshipDef.foreign_key_constraint_on;
for (let i = 0; i < table.foreign_key_constraints.length; i++) {
const fkConstraint = table.foreign_key_constraints[i];
const fkConstraintCol = Object.keys(fkConstraint.column_mapping)[0];
if (fkCol === fkConstraintCol) {
_refTable = generateTableDef(
fkConstraint.ref_table,
fkConstraint.ref_table_table_schema
);
break;
}
}
}
}
if (typeof _refTable === 'string') {
_refTable = generateTableDef(_refTable);
}
return _refTable;
}
/*** Table/View permissions utils ***/
export const getTablePermissions = (table, role = null, action = null) => {
let tablePermissions = table.permissions;
@ -112,3 +206,17 @@ export const getFunctionSchema = pgFunction => {
export const getFunctionName = pgFunction => {
return pgFunction.function_name;
};
/*** Schema utils ***/
export const getSchemaName = schema => {
return schema.schema_name;
};
export const getSchemaTables = (allTables, tableSchema) => {
return allTables.filter(t => getTableSchema(t) === tableSchema);
};
export const getSchemaTableNames = (allTables, tableSchema) => {
return getSchemaTables(allTables, tableSchema).map(t => getTableName(t));
};

View File

@ -12,7 +12,6 @@ import { NotFoundError } from '../../Error/PageNotFound';
const sectionPrefix = '/data';
const DataPageContainer = ({
schema,
currentSchema,
schemaList,
children,
@ -81,12 +80,7 @@ const DataPageContainer = ({
</div>
</div>
</Link>
<DataSubSidebar
location={location}
schema={schema}
currentSchema={currentSchema}
dispatch={dispatch}
/>
<DataSubSidebar location={location} />
</li>
<li
role="presentation"
@ -117,7 +111,6 @@ const DataPageContainer = ({
const mapStateToProps = state => {
return {
schema: state.tables.allSchemas,
schemaList: state.tables.schemaList,
currentSchema: state.tables.currentSchema,
};

View File

@ -33,7 +33,6 @@ const defaultPermissionsState = {
limitEnabled: true,
bulkSelect: [],
applySamePermissions: [],
tableSchemas: [],
};
const defaultPresetsState = {

View File

@ -8,6 +8,7 @@ import GqlCompatibilityWarning from '../../Common/GqlCompatibilityWarning/GqlCom
import {
displayTableName,
getFunctionName,
getSchemaTables,
getTableName,
} from '../../Common/utils/pgUtils';
import {
@ -20,23 +21,11 @@ class DataSubSidebar extends React.Component {
constructor() {
super();
this.tableSearch = this.tableSearch.bind(this);
this.state = {
trackedTables: [],
searchInput: '',
};
}
static getDerivedStateFromProps(props) {
const { currentSchema, schema } = props;
const trackedTables = schema.filter(
table => table.is_table_tracked && table.table_schema === currentSchema
);
return {
trackedTables: trackedTables,
};
this.tableSearch = this.tableSearch.bind(this);
}
shouldComponentUpdate(nextProps) {
@ -60,23 +49,27 @@ class DataSubSidebar extends React.Component {
const functionSymbol = require('../../Common/Layout/LeftSubSidebar/function.svg');
const functionSymbolActive = require('../../Common/Layout/LeftSubSidebar/function_active.svg');
const {
functionsList,
currentTable,
currentSchema,
migrationMode,
location,
currentFunction,
trackedFunctions,
allSchemas,
} = this.props;
const { trackedTables, searchInput } = this.state;
const { searchInput } = this.state;
const trackedTablesLength = trackedTables.length;
const trackedTablesInSchema = getSchemaTables(
allSchemas,
currentSchema
).filter(table => table.is_table_tracked);
const tableList = trackedTables.filter(t =>
const filteredTableList = trackedTablesInSchema.filter(t =>
getTableName(t).includes(searchInput)
);
const listedFunctions = functionsList.filter(f =>
const filteredFunctionsList = trackedFunctions.filter(f =>
getFunctionName(f).includes(searchInput)
);
@ -93,57 +86,56 @@ class DataSubSidebar extends React.Component {
};
const getChildList = () => {
let tableLinks = [
<li className={styles.noChildren} key="no-tables-1">
<i>No tables/views available</i>
</li>,
];
const tables = {};
tableList.map(t => {
if (t.is_table_tracked) {
tables[getTableName(t)] = t;
}
});
let childList;
const currentLocation = location.pathname;
if (tableList && tableList.length) {
tableLinks = Object.keys(tables)
.sort()
.map((tableName, i) => {
const table = tables[tableName];
let tableLinks;
if (filteredTableList && filteredTableList.length) {
const filteredTablesObject = {};
filteredTableList.forEach(t => {
filteredTablesObject[getTableName(t)] = t;
});
let activeTableClass = '';
if (
tableName === currentTable &&
currentLocation.indexOf(currentTable) !== -1
) {
activeTableClass = styles.activeTable;
}
const sortedTableNames = Object.keys(filteredTablesObject).sort();
let gqlCompatibilityWarning = null;
if (!gqlPattern.test(tableName)) {
gqlCompatibilityWarning = (
<span className={styles.add_mar_left_mid}>
<GqlCompatibilityWarning />
</span>
);
}
tableLinks = sortedTableNames.map((tableName, i) => {
const table = filteredTablesObject[tableName];
return (
<li className={activeTableClass} key={i}>
<Link to={getTableBrowseRoute(table)} data-test={tableName}>
<i
className={styles.tableIcon + ' fa fa-table'}
aria-hidden="true"
/>
{displayTableName(table)}
</Link>
{gqlCompatibilityWarning}
</li>
const isActive =
tableName === currentTable && currentLocation.includes(tableName);
let gqlCompatibilityWarning = null;
if (!gqlPattern.test(tableName)) {
gqlCompatibilityWarning = (
<span className={styles.add_mar_left_mid}>
<GqlCompatibilityWarning />
</span>
);
});
}
return (
<li
className={isActive ? styles.activeLink : ''}
key={'table ' + i}
>
<Link to={getTableBrowseRoute(table)} data-test={tableName}>
<i
className={styles.tableIcon + ' fa fa-table'}
aria-hidden="true"
/>
{displayTableName(table)}
</Link>
{gqlCompatibilityWarning}
</li>
);
});
} else {
tableLinks = [
<li className={styles.noChildren} key="no-tables-1">
<i>No tables/views available</i>
</li>,
];
}
const dividerHr = [
@ -152,48 +144,57 @@ class DataSubSidebar extends React.Component {
</li>,
];
// If the listedFunctions is non empty
if (listedFunctions.length > 0) {
const functionHtml = listedFunctions.map((func, i) => {
const funcName = getFunctionName(func);
const isActive = funcName === currentFunction;
if (filteredFunctionsList && filteredFunctionsList.length > 0) {
const filteredFunctionsObject = {};
filteredFunctionsList.forEach(f => {
filteredFunctionsObject[getFunctionName(f)] = f;
});
const sortedFunctionNames = Object.keys(filteredFunctionsObject).sort();
const functionLinks = sortedFunctionNames.map((funcName, i) => {
const func = filteredFunctionsObject[funcName];
const isActive =
funcName === currentFunction && currentLocation.includes(funcName);
return (
<li className={isActive ? styles.activeTable : ''} key={'fn ' + i}>
<li className={isActive ? styles.activeLink : ''} key={'fn ' + i}>
<Link to={getFunctionModifyRoute(func)} data-test={funcName}>
<div
className={styles.display_inline + ' ' + styles.functionIcon}
>
<img src={isActive ? functionSymbolActive : functionSymbol} />
</div>
{getFunctionName(func)}
<img
src={isActive ? functionSymbolActive : functionSymbol}
className={styles.functionIcon}
/>
<span>{funcName}</span>
</Link>
</li>
);
});
tableLinks = [...tableLinks, ...dividerHr, ...functionHtml];
childList = [...tableLinks, ...dividerHr, ...functionLinks];
} else if (
functionsList.length !== listedFunctions.length &&
listedFunctions.length === 0
trackedFunctions.length > 0 &&
filteredFunctionsList.length === 0
) {
const noFunctionResult = [
<li className={styles.noChildren}>
const noFunctionsMsg = [
<li className={styles.noChildren} key="no-fns-1">
<i>No matching functions available</i>
</li>,
];
tableLinks = [...tableLinks, ...dividerHr, ...noFunctionResult];
childList = [...tableLinks, ...dividerHr, ...noFunctionsMsg];
} else {
childList = [...tableLinks];
}
return tableLinks;
return childList;
};
return (
<LeftSubSidebar
showAddBtn={migrationMode}
searchInput={getSearchInput()}
heading={`Tables (${trackedTablesLength})`}
heading={`Tables (${trackedTablesInSchema.length})`}
addLink={getSchemaAddTableRoute(currentSchema)}
addLabel={'Add Table'}
addTestString={'sidebar-add-table'}
@ -207,10 +208,12 @@ class DataSubSidebar extends React.Component {
const mapStateToProps = state => {
return {
currentTable: state.tables.currentTable,
migrationMode: state.main.migrationMode,
functionsList: state.tables.trackedFunctions,
trackedFunctions: state.tables.trackedFunctions,
currentFunction: state.functions.functionName,
allSchemas: state.tables.allSchemas,
currentTable: state.tables.currentTable,
currentSchema: state.tables.currentSchema,
serverVersion: state.main.serverVersion ? state.main.serverVersion : '',
metadata: state.metadata,
};

View File

@ -262,7 +262,7 @@ class PermissionsSummary extends Component {
...copyState,
copyFromRole: role,
copyFromTable: currTable
? getTableNameWithSchema(null, false, currTable)
? getTableNameWithSchema(currTable)
: 'all',
copyFromAction: currRole ? 'all' : currAction,
},
@ -803,7 +803,7 @@ class PermissionsSummary extends Component {
const getFromTableOptions = () => {
return currSchemaTrackedTables.map(table => {
const tableName = getTableName(table);
const tableValue = getTableNameWithSchema(table, false);
const tableValue = getTableNameWithSchema(getTableDef(table));
return (
<option key={tableValue} value={tableValue}>

View File

@ -46,7 +46,6 @@ import {
import {
PERM_OPEN_EDIT,
PERM_ADD_TABLE_SCHEMAS,
PERM_SET_FILTER,
PERM_SET_FILTER_SAME_AS,
PERM_TOGGLE_COLUMN,
@ -260,25 +259,12 @@ const modifyReducer = (tableName, schemas, modifyStateOrig, action) => {
...modifyState,
permissionsState: {
...permState,
tableSchemas: schemas,
},
prevPermissionState: {
...permState,
},
};
case PERM_ADD_TABLE_SCHEMAS:
return {
...modifyState,
permissionsState: {
...modifyState.permissionsState,
tableSchemas: [
...modifyState.permissionsState.tableSchemas,
...action.schemas,
],
},
};
case PERM_CLOSE_EDIT:
return {
...modifyState,

View File

@ -5,10 +5,6 @@ import {
} from '../DataState';
import { getEdForm, getIngForm } from '../utils';
import { makeMigrationCall } from '../DataActions';
import dataHeaders from '../Common/Headers';
import { globalCookiePolicy } from '../../../../Endpoints';
import requestAction from '../../../../utils/requestAction';
import Endpoints from '../../../../Endpoints';
import {
findTable,
generateTableDef,
@ -21,7 +17,6 @@ import {
getDropPermissionQuery,
} from '../../../Common/utils/v1QueryUtils';
export const PERM_ADD_TABLE_SCHEMAS = 'ModifyTable/PERM_ADD_TABLE_SCHEMAS';
export const PERM_OPEN_EDIT = 'ModifyTable/PERM_OPEN_EDIT';
export const PERM_SET_FILTER = 'ModifyTable/PERM_SET_FILTER';
export const PERM_SET_FILTER_SAME_AS = 'ModifyTable/PERM_SET_FILTER_SAME_AS';
@ -177,45 +172,6 @@ const getBasePermissionsState = (tableSchema, role, query) => {
return _permissions;
};
const permAddTableSchemas = schemaNames => {
return (dispatch, getState) => {
const url = Endpoints.getSchema;
const options = {
credentials: globalCookiePolicy,
method: 'POST',
headers: dataHeaders(getState),
body: JSON.stringify({
type: 'select',
args: {
table: {
name: 'hdb_table',
schema: 'hdb_catalog',
},
columns: [
'*.*',
{
name: 'columns',
columns: ['*.*'],
order_by: [{ column: 'column_name', type: 'asc', nulls: 'last' }],
},
],
where: { table_schema: { $in: schemaNames } },
order_by: [{ column: 'table_name', type: 'asc', nulls: 'last' }],
},
}),
};
return dispatch(requestAction(url, options)).then(
data => {
dispatch({ type: PERM_ADD_TABLE_SCHEMAS, schemas: data });
},
error => {
console.error('Failed to load table schemas: ' + JSON.stringify(error));
}
);
};
};
const updatePermissionsState = (permissions, key, value) => {
const _permissions = JSON.parse(JSON.stringify(permissions));
@ -801,7 +757,6 @@ const permChangePermissions = changeType => {
export {
permChangeTypes,
permAddTableSchemas,
permOpenEdit,
permSetFilter,
permSetFilterSameAs,

View File

@ -3,17 +3,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import QueryBuilderJson from '../../../../Common/QueryBuilderJson/QueryBuilderJson';
import {
addToPrefix,
getRefTable,
getTableColumnNames,
getTableRelationshipNames,
getTableRelationship,
getTableDef,
getTableSchema,
getColumnType,
isJsonString,
getAllJsonPaths,
isArrayBoolOperator,
isBoolOperator,
isArrayColumnOperator,
@ -23,23 +16,50 @@ import {
boolOperators,
PGTypes,
PGTypesOperators,
existOperators,
isExistOperator,
TABLE_KEY,
WHERE_KEY,
} from './utils';
import QueryBuilderJson from '../../../../Common/QueryBuilderJson/QueryBuilderJson';
import {
findTable,
generateTableDef,
getSchemaName,
getTrackedTables,
getColumnType,
getTableColumn,
getRelationshipRefTable,
getTableColumnNames,
getTableRelationshipNames,
getTableRelationship,
getTableSchema,
getQualifiedTableDef,
getSchemaTableNames,
} from '../../../../Common/utils/pgUtils';
import {
isJsonString,
getAllJsonPaths,
isObject,
} from '../../../../Common/utils/jsUtils';
import { EXISTS_PERMISSION_SUPPORT } from '../../../../../helpers/versionUtils';
import globals from '../../../../../Globals';
class PermissionBuilder extends React.Component {
static propTypes = {
allTableSchemas: PropTypes.array.isRequired,
schemaList: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired,
dispatchFuncSetFilter: PropTypes.func.isRequired,
dispatchFuncAddTableSchemas: PropTypes.func.isRequired,
loadSchemasFunc: PropTypes.func.isRequired,
filter: PropTypes.string,
tableName: PropTypes.string,
schemaName: PropTypes.string,
tableDef: PropTypes.object.isRequired,
};
componentDidMount() {
this.fetchMissingSchemas();
this.loadMissingSchemas();
}
componentDidUpdate(prevProps) {
@ -49,50 +69,79 @@ class PermissionBuilder extends React.Component {
this.props.filter !== prevProps.filter ||
this.props.allTableSchemas.length !== prevProps.allTableSchemas.length
) {
this.fetchMissingSchemas();
this.loadMissingSchemas();
}
}
fetchMissingSchemas() {
const {
dispatch,
tableName,
schemaName,
dispatchFuncAddTableSchemas,
filter,
} = this.props;
loadMissingSchemas(
tableDef = this.props.tableDef,
filter = this.props.filter
) {
const { loadSchemasFunc } = this.props;
const findMissingSchemas = (path, currTable) => {
let _missingSchemas = [];
let value;
if (isObject(path)) {
value = Object.values(path)[0];
path = Object.keys(path)[0];
}
const getNewPath = newPath => {
return value ? { [newPath]: value } : newPath;
};
const pathSplit = path.split('.');
const operator = pathSplit[0];
if (isArrayBoolOperator(operator)) {
const newPath = pathSplit.slice(2).join('.');
const newPath = getNewPath(pathSplit.slice(2).join('.'));
_missingSchemas = findMissingSchemas(newPath, currTable);
} else if (isBoolOperator(operator)) {
const newPath = pathSplit.slice(1).join('.');
const newPath = getNewPath(pathSplit.slice(1).join('.'));
_missingSchemas = findMissingSchemas(newPath, currTable);
} else if (isExistOperator(operator)) {
const existTableDef = getQualifiedTableDef(value[TABLE_KEY]);
const existTableSchema = existTableDef.schema;
const existWhere = value[WHERE_KEY];
if (existTableSchema) {
const { allTableSchemas } = this.props;
const allSchemaNames = allTableSchemas.map(t => getTableSchema(t));
if (!allSchemaNames.includes(existTableSchema)) {
_missingSchemas.push(existTableSchema);
}
}
this.loadMissingSchemas(existTableDef, JSON.stringify(existWhere));
} else if (isColumnOperator(operator)) {
// no missing schemas
} else {
const { allTableSchemas } = this.props;
const tableSchema = getTableSchema(allTableSchemas, currTable);
const tableRelationships = getTableRelationshipNames(tableSchema);
let tableRelationshipNames = [];
if (tableRelationships.includes(operator)) {
const rel = getTableRelationship(tableSchema, operator);
const refTable = getRefTable(rel, tableSchema);
const tableSchema = findTable(allTableSchemas, currTable);
const refTableSchema = getTableSchema(allTableSchemas, refTable);
if (tableSchema) {
tableRelationshipNames = getTableRelationshipNames(tableSchema);
}
if (tableRelationshipNames.includes(operator)) {
const relationship = getTableRelationship(tableSchema, operator);
const refTable = getRelationshipRefTable(tableSchema, relationship);
const refTableSchema = findTable(allTableSchemas, refTable);
if (!refTableSchema) {
_missingSchemas.push(refTable.schema);
}
const newPath = pathSplit.slice(1).join('.');
const newPath = getNewPath(pathSplit.slice(1).join('.'));
_missingSchemas.push(...findMissingSchemas(newPath, refTable));
} else {
// no missing schemas
@ -102,21 +151,17 @@ class PermissionBuilder extends React.Component {
return _missingSchemas;
};
const table = getTableDef(tableName, schemaName);
const missingSchemas = [];
const paths = getAllJsonPaths(JSON.parse(filter || '{}'));
const paths = getAllJsonPaths(JSON.parse(filter || '{}'), existOperators);
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
const subMissingSchemas = findMissingSchemas(path, table);
paths.forEach(path => {
const subMissingSchemas = findMissingSchemas(path, tableDef);
missingSchemas.push(...subMissingSchemas);
}
});
if (missingSchemas.length > 0) {
dispatch(dispatchFuncAddTableSchemas(missingSchemas));
loadSchemasFunc(missingSchemas);
}
}
@ -135,8 +180,8 @@ class PermissionBuilder extends React.Component {
/********************************/
const getFilter = (conditions, prefix, value = '') => {
let _where = {};
const getFilter = (defaultSchema, conditions, prefix, value = '') => {
let _boolExp = {};
const getArrayBoolOperatorFilter = (
operator,
@ -157,6 +202,7 @@ class PermissionBuilder extends React.Component {
_filter[operator] = opConditions;
_filter[operator][position] = getFilter(
defaultSchema,
opConditions[position],
newPrefix,
opValue
@ -181,7 +227,12 @@ class PermissionBuilder extends React.Component {
if (isLast) {
_filter[operator] = {};
} else {
_filter[operator] = getFilter(opConditions, opPrefix, opValue);
_filter[operator] = getFilter(
defaultSchema,
opConditions,
opPrefix,
opValue
);
}
return _filter;
@ -221,6 +272,39 @@ class PermissionBuilder extends React.Component {
return _filter;
};
const getExistsOperatorFilter = (
operator,
opValue,
opConditions,
opPrefix,
isLast
) => {
const _filter = {
[operator]: opConditions,
};
if (isLast) {
_filter[operator] = {
[TABLE_KEY]: generateTableDef('', defaultSchema),
[WHERE_KEY]: {},
};
} else if (opPrefix === TABLE_KEY) {
_filter[operator] = {
[TABLE_KEY]: opValue,
[WHERE_KEY]: {},
};
} else if (opPrefix === WHERE_KEY) {
_filter[operator][WHERE_KEY] = getFilter(
defaultSchema,
opConditions[opPrefix],
opValue.prefix,
opValue.value
);
}
return _filter;
};
const getColumnFilter = (
operator,
opValue,
@ -233,7 +317,12 @@ class PermissionBuilder extends React.Component {
if (isLast) {
_filter[operator] = {};
} else {
_filter[operator] = getFilter(opConditions, opPrefix, opValue);
_filter[operator] = getFilter(
defaultSchema,
opConditions,
opPrefix,
opValue
);
}
return _filter;
@ -249,9 +338,9 @@ class PermissionBuilder extends React.Component {
const opConditions = isLast ? null : conditions[operator];
if (operator === '') {
// blank where
// blank bool exp
} else if (isArrayBoolOperator(operator)) {
_where = getArrayBoolOperatorFilter(
_boolExp = getArrayBoolOperatorFilter(
operator,
value,
opConditions,
@ -259,7 +348,7 @@ class PermissionBuilder extends React.Component {
isLast
);
} else if (isBoolOperator(operator)) {
_where = getBoolOperatorFilter(
_boolExp = getBoolOperatorFilter(
operator,
value,
opConditions,
@ -267,7 +356,7 @@ class PermissionBuilder extends React.Component {
isLast
);
} else if (isArrayColumnOperator(operator)) {
_where = getArrayColumnOperatorFilter(
_boolExp = getArrayColumnOperatorFilter(
operator,
value,
opConditions,
@ -275,9 +364,17 @@ class PermissionBuilder extends React.Component {
isLast
);
} else if (isColumnOperator(operator)) {
_where = getColumnOperatorFilter(operator, value);
_boolExp = getColumnOperatorFilter(operator, value);
} else if (isExistOperator(operator)) {
_boolExp = getExistsOperatorFilter(
operator,
value,
opConditions,
newPrefix,
isLast
);
} else {
_where = getColumnFilter(
_boolExp = getColumnFilter(
operator,
value,
opConditions,
@ -286,13 +383,14 @@ class PermissionBuilder extends React.Component {
);
}
return _where;
return _boolExp;
};
const _dispatchFunc = data => {
const { dispatch, filter, dispatchFuncSetFilter } = this.props;
const { dispatch, filter, dispatchFuncSetFilter, tableDef } = this.props;
const newFilter = getFilter(
tableDef.schema,
JSON.parse(filter || '{}'),
data.prefix,
data.value
@ -590,53 +688,157 @@ class PermissionBuilder extends React.Component {
const renderColumnExp = (
dispatchFunc,
column,
columnName,
expression,
table,
tableDef,
tableSchemas,
schemaList,
prefix
) => {
let tableColumns = [];
let tableRelationships = [];
let tableColumnNames = [];
let tableRelationshipNames = [];
let tableSchema;
if (table) {
tableSchema = getTableSchema(tableSchemas, table);
tableColumns = getTableColumnNames(tableSchema);
tableRelationships = getTableRelationshipNames(tableSchema);
if (tableDef) {
tableSchema = findTable(tableSchemas, tableDef);
if (tableSchema) {
tableColumnNames = getTableColumnNames(tableSchema);
tableRelationshipNames = getTableRelationshipNames(tableSchema);
}
}
let _columnExp = '';
if (tableRelationships.includes(column)) {
const rel = getTableRelationship(tableSchema, column);
const refTable = getRefTable(rel, tableSchema);
if (tableRelationshipNames.includes(columnName)) {
const relationship = getTableRelationship(tableSchema, columnName);
const refTable = getRelationshipRefTable(tableSchema, relationship);
_columnExp = renderBoolExp(
dispatchFunc,
expression,
refTable,
tableSchemas,
schemaList,
prefix
); // eslint-disable-line no-use-before-define
);
} else {
const columnType = getColumnType(column, tableSchema);
let columnType = '';
if (tableSchema && columnName) {
const column = getTableColumn(tableSchema, columnName);
columnType = getColumnType(column);
}
_columnExp = renderOperatorExp(
dispatchFunc,
expression,
prefix,
columnType,
tableColumns
tableColumnNames
);
}
return _columnExp;
};
const renderTableSelect = (
dispatchFunc,
tableDef,
tableSchemas,
schemaList,
defaultSchema
) => {
const selectedSchema = tableDef ? tableDef.schema : defaultSchema;
const selectedTable = tableDef ? tableDef.name : '';
const schemaSelectDispatchFunc = val => {
dispatchFunc(generateTableDef('', val));
};
const tableSelectDispatchFunc = val => {
dispatchFunc(generateTableDef(val, selectedSchema));
};
const tableNames = getSchemaTableNames(tableSchemas, selectedSchema);
const schemaNames = schemaList.map(s => getSchemaName(s));
const schemaSelect = wrapDoubleQuotes(
renderSelect(schemaSelectDispatchFunc, selectedSchema, schemaNames)
);
const tableSelect = wrapDoubleQuotes(
renderSelect(tableSelectDispatchFunc, selectedTable, tableNames)
);
const _tableExp = [
{ key: 'schema', value: schemaSelect },
{ key: 'table', value: tableSelect },
];
return <QueryBuilderJson element={_tableExp} />;
};
const renderExistsExp = (
dispatchFunc,
operation,
expression,
tableDef,
tableSchemas,
schemaList,
prefix
) => {
const dispatchTableSelect = val => {
dispatchFunc({ prefix: addToPrefix(prefix, TABLE_KEY), value: val });
};
const dispatchWhereOperatorSelect = val => {
dispatchFunc({ prefix: addToPrefix(prefix, WHERE_KEY), value: val });
};
const existsOpTable = getQualifiedTableDef(expression[TABLE_KEY]);
const existsOpWhere = expression[WHERE_KEY];
const tableSelect = renderTableSelect(
dispatchTableSelect,
existsOpTable,
tableSchemas,
schemaList,
tableDef.schema
);
let whereSelect = {};
if (existsOpTable) {
whereSelect = renderBoolExp(
dispatchWhereOperatorSelect,
existsOpWhere,
existsOpTable,
tableSchemas,
schemaList
);
}
const _existsArgsJsonObject = {
[TABLE_KEY]: tableSelect,
[WHERE_KEY]: whereSelect,
};
const unselectedElements = [];
if (!existsOpTable.name) {
unselectedElements.push(WHERE_KEY);
}
return (
<QueryBuilderJson
element={_existsArgsJsonObject}
unselectedElements={unselectedElements}
/>
);
};
const renderBoolExpArray = (
dispatchFunc,
expressions,
table,
tableDef,
tableSchemas,
schemaList,
prefix
) => {
const _boolExpArray = [];
@ -645,10 +847,11 @@ class PermissionBuilder extends React.Component {
const _boolExp = renderBoolExp(
dispatchFunc,
expression,
table,
tableDef,
tableSchemas,
schemaList,
addToPrefix(prefix, i)
); // eslint-disable-line no-use-before-define
);
_boolExpArray.push(_boolExp);
});
@ -665,8 +868,9 @@ class PermissionBuilder extends React.Component {
const renderBoolExp = (
dispatchFunc,
expression,
table,
tableDef,
tableSchemas,
schemaList,
prefix = ''
) => {
const dispatchOperationSelect = val => {
@ -678,19 +882,32 @@ class PermissionBuilder extends React.Component {
operation = Object.keys(expression)[0];
}
let tableColumns = [];
let tableRelationships = [];
if (table) {
const tableSchema = getTableSchema(tableSchemas, table);
tableColumns = getTableColumnNames(tableSchema);
tableRelationships = getTableRelationshipNames(tableSchema);
let tableColumnNames = [];
let tableRelationshipNames = [];
if (tableDef) {
const tableSchema = findTable(tableSchemas, tableDef);
if (tableSchema) {
tableColumnNames = getTableColumnNames(tableSchema);
tableRelationshipNames = getTableRelationshipNames(tableSchema);
}
}
const columnOptions = tableColumns.concat(tableRelationships);
const columnOptions = tableColumnNames.concat(tableRelationshipNames);
const operatorOptions = boolOperators
.concat(['---'])
.concat(columnOptions);
const existsSupported =
globals.featuresCompatibility &&
globals.featuresCompatibility[EXISTS_PERMISSION_SUPPORT];
let operatorOptions;
if (existsSupported) {
operatorOptions = boolOperators
.concat(['---'])
.concat(existOperators)
.concat(['---'])
.concat(columnOptions);
} else {
operatorOptions = boolOperators.concat(['---']).concat(columnOptions);
}
const _boolExpKey = renderSelect(
dispatchOperationSelect,
@ -707,16 +924,28 @@ class PermissionBuilder extends React.Component {
_boolExpValue = renderBoolExpArray(
dispatchFunc,
expression[operation],
table,
tableDef,
tableSchemas,
schemaList,
newPrefix
);
} else if (isBoolOperator(operation)) {
_boolExpValue = renderBoolExp(
dispatchFunc,
expression[operation],
table,
tableDef,
tableSchemas,
schemaList,
newPrefix
);
} else if (isExistOperator(operation)) {
_boolExpValue = renderExistsExp(
dispatchFunc,
operation,
expression[operation],
tableDef,
tableSchemas,
schemaList,
newPrefix
);
} else {
@ -724,8 +953,9 @@ class PermissionBuilder extends React.Component {
dispatchFunc,
operation,
expression[operation],
table,
tableDef,
tableSchemas,
schemaList,
newPrefix
);
}
@ -749,15 +979,16 @@ class PermissionBuilder extends React.Component {
/********************************/
const showPermissionBuilder = () => {
const { tableName, schemaName, filter, allTableSchemas } = this.props;
const { tableDef, filter, allTableSchemas, schemaList } = this.props;
const table = getTableDef(tableName, schemaName);
const trackedTables = getTrackedTables(allTableSchemas);
return renderBoolExp(
_dispatchFunc,
JSON.parse(filter || '{}'),
table,
allTableSchemas
tableDef,
trackedTables,
schemaList
);
};

View File

@ -216,7 +216,14 @@ export const boolOperators = Object.keys(boolOperatorsInfo);
const columnOperators = Object.keys(columnOperatorsInfo);
export const allOperators = boolOperators.concat(columnOperators);
export const existOperators = ['_exists'];
export const allOperators = boolOperators
.concat(columnOperators)
.concat(existOperators);
export const TABLE_KEY = '_table';
export const WHERE_KEY = '_where';
/* Util functions */
@ -224,6 +231,10 @@ export const isBoolOperator = operator => {
return boolOperators.includes(operator);
};
export const isExistOperator = operator => {
return existOperators.includes(operator);
};
export const isArrayBoolOperator = operator => {
const arrayBoolOperators = Object.keys(boolOperatorsInfo).filter(
op => boolOperatorsInfo[op].type === 'array'
@ -284,147 +295,3 @@ export function addToPrefix(prefix, value) {
return _newPrefix;
}
export function getTableSchema(allSchemas, table) {
return allSchemas.find(
tableSchema =>
tableSchema.table_name === table.name &&
tableSchema.table_schema === table.schema
);
}
export function getTableColumnNames(tableSchema) {
if (!tableSchema) {
return [];
}
return tableSchema.columns.map(c => c.column_name);
}
export function getTableRelationshipNames(tableSchema) {
if (!tableSchema) {
return [];
}
return tableSchema.relationships.map(r => r.rel_name);
}
export function getTableRelationship(tableSchema, relName) {
if (!tableSchema) {
return {};
}
const relIndex = getTableRelationshipNames(tableSchema).indexOf(relName);
return tableSchema.relationships[relIndex];
}
export function getTableDef(tableName, schema) {
return { name: tableName, schema: schema };
}
export function getRefTable(rel, tableSchema) {
let _refTable = null;
// if manual relationship
if (rel.rel_def.manual_configuration) {
_refTable = rel.rel_def.manual_configuration.remote_table;
}
// if foreign-key based relationship
if (rel.rel_def.foreign_key_constraint_on) {
// if array relationship
if (rel.rel_type === 'array') {
_refTable = rel.rel_def.foreign_key_constraint_on.table;
}
// if object relationship
if (rel.rel_type === 'object') {
const fkCol = rel.rel_def.foreign_key_constraint_on;
for (let i = 0; i < tableSchema.foreign_key_constraints.length; i++) {
const fkConstraint = tableSchema.foreign_key_constraints[i];
const fkConstraintCol = Object.keys(fkConstraint.column_mapping)[0];
if (fkCol === fkConstraintCol) {
_refTable = getTableDef(
fkConstraint.ref_table,
fkConstraint.ref_table_table_schema
);
break;
}
}
}
}
if (typeof _refTable === 'string') {
_refTable = getTableDef(_refTable, 'public');
}
return _refTable;
}
export function getColumnType(columnName, tableSchema) {
let _columnType = '';
if (!tableSchema || !columnName) {
return _columnType;
}
const columnSchema = tableSchema.columns.find(
_columnSchema => _columnSchema.column_name === columnName
);
if (columnSchema) {
_columnType = columnSchema.data_type;
if (_columnType === 'USER-DEFINED') {
_columnType = columnSchema.udt_name;
}
}
return _columnType;
}
export function isJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
export function getAllJsonPaths(json, prefix = '') {
const _paths = [];
const addPrefix = subPath => {
return prefix + (prefix && subPath ? '.' : '') + subPath;
};
const handleSubJson = (subJson, newPrefix) => {
const subPaths = getAllJsonPaths(subJson, newPrefix);
subPaths.forEach(subPath => {
_paths.push(subPath);
});
if (!subPaths.length) {
_paths.push(newPrefix);
}
};
if (json instanceof Array) {
json.forEach((subJson, i) => {
handleSubJson(subJson, addPrefix(i.toString()));
});
} else if (json instanceof Object) {
Object.keys(json).forEach(key => {
handleSubJson(json[key], addPrefix(key));
});
} else {
_paths.push(addPrefix(json));
}
return _paths;
}

View File

@ -11,7 +11,6 @@ import { RESET } from '../TableModify/ModifyActions';
import {
permChangeTypes,
permOpenEdit,
permAddTableSchemas,
permSetFilter,
permSetFilterSameAs,
permToggleColumn,
@ -41,7 +40,7 @@ import TableHeader from '../TableCommon/TableHeader';
import CollapsibleToggle from '../../../Common/CollapsibleToggle/CollapsibleToggle';
import EnhancedInput from '../../../Common/InputChecker/InputChecker';
import { setTable } from '../DataActions';
import { setTable, updateSchemaInfo } from '../DataActions';
import { getIngForm, getEdForm, escapeRegExp } from '../utils';
import { allOperators, getLegacyOperator } from './PermissionBuilder/utils';
import {
@ -57,6 +56,7 @@ import { defaultPresetsState } from '../DataState';
import { NotFoundError } from '../../../Error/PageNotFound';
import { getConfirmation } from '../../../Common/utils/jsUtils';
import { generateTableDef } from '../../../Common/utils/pgUtils';
class Permissions extends Component {
constructor() {
@ -111,6 +111,7 @@ class Permissions extends Component {
tableName,
tableType,
allSchemas,
schemaList,
ongoingRequest,
lastError,
lastFormError,
@ -711,8 +712,9 @@ class Permissions extends Component {
const dispatchFuncSetFilter = filter =>
permSetFilter(JSON.parse(filter));
const dispatchFuncAddTableSchemas = schemaNames =>
permAddTableSchemas(schemaNames);
const loadSchemasFunc = schemaNames => {
dispatch(updateSchemaInfo({ schemas: schemaNames }));
};
const isUniqueFilter =
filterString !== '' &&
@ -753,10 +755,10 @@ class Permissions extends Component {
_filterOptionsSection.push(
<PermissionBuilder
dispatchFuncSetFilter={dispatchFuncSetFilter}
dispatchFuncAddTableSchemas={dispatchFuncAddTableSchemas}
tableName={tableName}
schemaName={currentSchema}
allTableSchemas={permissionsState.tableSchemas}
loadSchemasFunc={loadSchemasFunc}
tableDef={generateTableDef(tableName, currentSchema)}
allTableSchemas={allSchemas}
schemaList={schemaList}
filter={filterString}
dispatch={dispatch}
key={-4}
@ -1811,6 +1813,7 @@ const mapStateToProps = (state, ownProps) => ({
tableName: ownProps.params.table,
tableType: ownProps.route.tableType,
allSchemas: state.tables.allSchemas,
schemaList: state.tables.schemaList,
migrationMode: state.main.migrationMode,
currentSchema: state.tables.currentSchema,
serverVersion: state.main.serverVersion ? state.main.serverVersion : '',

View File

@ -67,7 +67,7 @@ const EventSubSidebar = ({
trigger === currentTrigger &&
currentLocation.indexOf(currentTrigger) !== -1
) {
activeTableClass = styles.activeTable;
activeTableClass = styles.activeLink;
}
return (

View File

@ -51,7 +51,7 @@ const RemoteSchemaSubSidebar = ({
d.name === viewRemoteSchema &&
location.pathname.includes(viewRemoteSchema)
) {
activeTableClass = styles.activeTable;
activeTableClass = styles.activeLink;
}
return (

View File

@ -5,6 +5,7 @@ export const RELOAD_METADATA_API_CHANGE = 'reloadMetaDataApiChange';
export const REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT =
'remoteSchemaTimeoutConfSupport';
export const TABLE_ENUMS_SUPPORT = 'tableEnumsSupport';
export const EXISTS_PERMISSION_SUPPORT = 'existsPermissionSupport';
// list of feature launch versions
const featureLaunchVersions = {
@ -13,6 +14,7 @@ const featureLaunchVersions = {
[FT_JWT_ANALYZER]: 'v1.0.0-beta.3',
[REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT]: 'v1.0.0-beta.5',
[TABLE_ENUMS_SUPPORT]: 'v1.0.0-beta.6',
[EXISTS_PERMISSION_SUPPORT]: 'v1.0.0-beta.7',
};
export const getFeaturesCompatibility = serverVersion => {