Skip to content

Commit

Permalink
console: fix regression in editing permissions manually (fix hasura#4683
Browse files Browse the repository at this point in the history
  • Loading branch information
soorajshankar authored and stevefan1999-personal committed Sep 12, 2020
1 parent 68f8cbd commit 14fcd57
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 104 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState, useEffect, useCallback } from 'react';
import AceEditor, { IAnnotation } from 'react-ace';
import { isJsonString } from '../../../Common/utils/jsUtils';
import { usePrevious } from '../../../../hooks/usePrevious';

export interface JSONEditorProps {
initData: string;
onChange: (v: string) => void;
data: string;
}

const JSONEditor: React.FC<JSONEditorProps> = ({
initData,
onChange,
data,
}) => {
const [value, setValue] = useState(initData || data || '');
const [annotations, setAnnotations] = useState<IAnnotation[]>([]);
const prevData = usePrevious<string>(data);

useEffect(() => {
// if the data prop is changed do nothing
if (prevData !== data) return;
// when state gets new data, trigger parent callback
if (value !== data) onChange(value);
}, [value, data, prevData]);

// check and set error message
useEffect(() => {
if (isJsonString(value)) {
setAnnotations([]);
} else {
setAnnotations([
{ row: 0, column: 0, text: 'Invalid JSON', type: 'error' },
]);
}
return () => {
setAnnotations([]);
};
}, [value]);

useEffect(() => {
// set data to editor only if the prop has a valid json string
// setting value from query editor will always have a valid json
// any invalid json means, the value is set from this component so no need to set that again
if (isJsonString(data)) setValue(data);
}, [data]);

const onEditorValueChange = useCallback(newVal => setValue(newVal), [
setValue,
]);

return (
<AceEditor
mode="json"
onChange={onEditorValueChange}
theme="github"
height="5em"
maxLines={15}
width="100%"
showPrintMargin={false}
value={value}
annotations={annotations}
/>
);
};

export default JSONEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useCallback } from 'react';
import { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
import Button from '../../../Common/Button';
import { isJsonString, getConfirmation } from '../../../Common/utils/jsUtils';
import { FilterState } from './utils';
import { showErrorNotification } from '../../Common/Notification';
import { permChangePermissions, permChangeTypes } from './Actions';
import styles from '../../../Common/Permissions/PermissionStyles.scss';

interface PermButtonSectionProps {
readOnlyMode: string;
query: string;
localFilterString: FilterState;
dispatch: (d: ThunkDispatch<{}, {}, AnyAction>) => void;
permissionsState: FilterState;
permsChanged: string;
currQueryPermissions: string;
}

const PermButtonSection: React.FC<PermButtonSectionProps> = ({
readOnlyMode,
query,
localFilterString,
dispatch,
permissionsState,
permsChanged,
currQueryPermissions,
}) => {
if (readOnlyMode) {
return null;
}

const dispatchSavePermissions = useCallback(() => {
const isInvalid = Object.values(localFilterString).some(val => {
if (val && !isJsonString(val)) {
return true;
}
return false;
});

if (isInvalid) {
dispatch(
showErrorNotification(
'Saving permissions failed',
'Row permission is not a valid JSON'
)
);
return;
}

dispatch(permChangePermissions(permChangeTypes.save));
}, [readOnlyMode, query, localFilterString, dispatch]);

const dispatchRemoveAccess = useCallback(() => {
const confirmMessage =
'This will permanently delete the currently set permissions';
const isOk = getConfirmation(confirmMessage);
if (isOk) {
dispatch(permChangePermissions(permChangeTypes.delete));
}
}, [dispatch]);

return (
<div className={`${styles.add_mar_top} ${styles.add_pad_left}`}>
<Button
className={styles.add_mar_right}
color="yellow"
size="sm"
onClick={dispatchSavePermissions}
disabled={
permissionsState.applySamePermissions.length !== 0 || !permsChanged
}
title={!permsChanged ? 'No changes made' : ''}
data-test="Save-Permissions-button"
>
Save Permissions
</Button>
<Button
className={styles.add_mar_right}
color="red"
size="sm"
onClick={dispatchRemoveAccess}
disabled={!currQueryPermissions}
title={!currQueryPermissions ? 'No permissions set' : ''}
data-test="Delete-Permissions-button"
>
Delete Permissions
</Button>
</div>
);
};

export default PermButtonSection;
118 changes: 15 additions & 103 deletions console/src/components/Services/Data/TablePermissions/Permissions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AceEditor from 'react-ace';
import JSONEditor from './JSONEditor';
import Tooltip from 'react-bootstrap/lib/Tooltip';
import InputGroup from 'react-bootstrap/lib/InputGroup';
import OverlayTrigger from 'react-bootstrap/es/OverlayTrigger';
Expand All @@ -9,7 +9,6 @@ import 'brace/theme/github';

import { RESET } from '../TableModify/ModifyActions';
import {
permChangeTypes,
permOpenEdit,
permSetFilter,
permSetFilterSameAs,
Expand All @@ -18,7 +17,6 @@ import {
permAllowAll,
permCloseEdit,
permSetRoleName,
permChangePermissions,
// permToggleAllowUpsert,
permToggleAllowAggregation,
permToggleModifyLimit,
Expand Down Expand Up @@ -82,16 +80,15 @@ import {
getTableSupportedQueries,
QUERY_TYPES,
} from '../../../Common/utils/pgUtils';
import { showErrorNotification } from '../../Common/Notification';
import KnowMoreLink from '../../../Common/KnowMoreLink/KnowMoreLink';
import {
getFilterQueries,
replaceLegacyOperators,
getAllowedFilterKeys,
updateFilterTypeLabel,
getDefaultFilterType,
getUpdateTooltip,
} from './utils';
import PermButtonSection from './PermButtonsSection';

class Permissions extends Component {
constructor() {
Expand Down Expand Up @@ -659,8 +656,8 @@ class Permissions extends Component {

const dispatchFuncSetFilter = filter => {
this.setState(prev => ({
filterString: {
...prev,
localFilterString: {
...prev.localFilterString,
[filterType]: filter,
},
}));
Expand All @@ -687,15 +684,9 @@ class Permissions extends Component {
);

const selectedValue = (
<AceEditor
mode="json"
value={currentFilterString || filterString[filterType]}
<JSONEditor
data={filterString[filterType] || currentFilterString}
onChange={dispatchFuncSetFilter}
theme="github"
height="5em"
maxLines={15}
width="100%"
showPrintMargin={false}
key={-3}
/>
);
Expand Down Expand Up @@ -1741,93 +1732,6 @@ class Permissions extends Component {
return clonePermissionsHtml;
};

const getButtonsSection = () => {
if (readOnlyMode) {
return null;
}

const dispatchSavePermissions = () => {
const filterKeys = getAllowedFilterKeys(query);
const isInvalid = filterKeys.some(key => {
if (
localFilterString[key] &&
!isJsonString(localFilterString[key])
) {
return true;
}
return false;
});

if (isInvalid) {
dispatch(
showErrorNotification(
'Saving permissions failed',
'Row permission is not a valid JSON'
)
);
return;
}

dispatch(permChangePermissions(permChangeTypes.save));
};

const dispatchRemoveAccess = () => {
const confirmMessage =
'This will permanently delete the currently set permissions';
const isOk = getConfirmation(confirmMessage);
if (isOk) {
dispatch(permChangePermissions(permChangeTypes.delete));
}
};

const getPermActionButton = (
value,
color,
onClickFn,
disabled,
title
) => (
<Button
className={styles.add_mar_right}
color={color}
size="sm"
onClick={onClickFn}
disabled={disabled}
title={title}
data-test={`${value.split(' ').join('-')}-button`}
>
{value}
</Button>
);
const applySameSelected = permissionsState.applySamePermissions.length;

const disableSave = applySameSelected || !permsChanged;
const disableRemoveAccess = !currQueryPermissions;

const saveButton = getPermActionButton(
'Save Permissions',
'yellow',
dispatchSavePermissions,
disableSave,
!permsChanged ? 'No changes made' : ''
);

const removeAccessButton = getPermActionButton(
'Delete Permissions',
'red',
dispatchRemoveAccess,
disableRemoveAccess,
disableRemoveAccess ? 'No permissions set' : ''
);

return (
<div className={styles.add_mar_top + ' ' + styles.add_pad_left}>
{saveButton}
{removeAccessButton}
</div>
);
};

const getBackendOnlySection = () => {
if (!isQueryTypeBackendOnlyCompatible(permissionsState.query)) {
return null;
Expand Down Expand Up @@ -1901,7 +1805,15 @@ class Permissions extends Component {
{getPresetsSection('insert')}
{getPresetsSection('update')}
{getBackendOnlySection()}
{getButtonsSection()}
<PermButtonSection
readOnlyMode={readOnlyMode}
query={query}
localFilterString={localFilterString}
dispatch={dispatch}
permissionsState={permissionsState}
permsChanged={permsChanged}
currQueryPermissions={currQueryPermissions}
/>
{getClonePermsSection()}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { escapeRegExp } from '../utils';
import { UNSAFE_keys } from '../../../Common/utils/tsUtils';

type FilterType = 'check' | 'filter';
type BaseQueryType = 'select' | 'update' | 'insert' | 'delete';
export type BaseQueryType = 'select' | 'update' | 'insert' | 'delete';
export interface FilterState {
[key: string]: BaseQueryType;
}

type DisplayQueryType =
| Exclude<BaseQueryType, 'update'>
Expand Down
9 changes: 9 additions & 0 deletions console/src/hooks/usePrevious.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useRef, useEffect } from 'react';

export const usePrevious = <T>(value: T) => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};

0 comments on commit 14fcd57

Please sign in to comment.