mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-11-10 10:29:12 +03:00
console: display collection names and queries from all collections in allowlist
This PR * Gets all queries from all collections present in allowlist and displays those. (Earlier we were just displaying queries present in "allowed-queries" collection. * Adds collection names to allow-list section ### Description fix [4138](https://github.com/hasura/graphql-engine/issues/4138) ### Affected components - [x] Console ### Solution and Design <img width="828" alt="Screenshot 2021-01-04 at 12 11 01 PM" src="https://user-images.githubusercontent.com/26903230/103507774-eb495280-4e85-11eb-9ef7-95871fb03edd.png"> ### Changelog - [x] `CHANGELOG.md` is updated with user-facing content relevant to this PR. If no changelog is required, then add the `no-changelog-required` label. Co-authored-by: Aleksandra Sikora <9019397+beerose@users.noreply.github.com> GitOrigin-RevId: d96d2aadebeabc00073e028d514db429ee18f187
This commit is contained in:
parent
0870ceda0d
commit
c597efb65e
@ -12,6 +12,7 @@
|
||||
- server: fix action custom types failing to parse when mutually recursive
|
||||
- server: fix MSSQL table name descriptions
|
||||
- console: allow editing rest endpoints queries and misc ui improvements
|
||||
- console: display collection names and queries from all collections in allowlist
|
||||
- cli: match ordering of keys in project metadata files with server metadata
|
||||
|
||||
## v2.0.0-alpha.5
|
||||
|
@ -1,173 +0,0 @@
|
||||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import styles from './AllowedQueries.scss';
|
||||
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import Tooltip from '../../../Common/Tooltip/Tooltip';
|
||||
|
||||
import { readFile, parseQueryString } from './utils';
|
||||
import { showErrorNotification } from '../../Common/Notification';
|
||||
import { addAllowedQueries } from '../../../../metadata/actions';
|
||||
|
||||
class AddAllowedQuery extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
manualQuery: {},
|
||||
graphqlFile: null,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dispatch } = this.props;
|
||||
const { manualQuery, graphqlFile } = this.state;
|
||||
|
||||
const handleManualCollapse = () => {
|
||||
this.setState({ manualQuery: {} });
|
||||
};
|
||||
|
||||
const handleManualSubmit = toggle => {
|
||||
dispatch(addAllowedQueries([manualQuery], toggle));
|
||||
};
|
||||
|
||||
const handleFileUploadCollapse = () => {};
|
||||
|
||||
function handleFileUploadSubmit(toggle) {
|
||||
const addFileQueries = content => {
|
||||
try {
|
||||
const fileQueries = parseQueryString(content);
|
||||
dispatch(addAllowedQueries(fileQueries, toggle));
|
||||
} catch (error) {
|
||||
dispatch(
|
||||
showErrorNotification('Uploading operations failed', error.message)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
readFile(graphqlFile, addFileQueries);
|
||||
}
|
||||
|
||||
const getManualQueryInput = () => {
|
||||
const getNameInput = () => {
|
||||
const handleNameChange = e => {
|
||||
this.setState({
|
||||
manualQuery: {
|
||||
...manualQuery,
|
||||
name: e.target.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation name:</b>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={'form-control input-sm ' + styles.inline_block}
|
||||
placeholder={'operation_name'}
|
||||
value={manualQuery.name}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getQueryInput = () => {
|
||||
const handleQueryChange = val => {
|
||||
this.setState({
|
||||
manualQuery: {
|
||||
...manualQuery,
|
||||
query: val,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation:</b>
|
||||
</div>
|
||||
<AceEditor
|
||||
data-test="allowed_operation_add"
|
||||
mode="graphql"
|
||||
theme="github"
|
||||
name="allowed_operation_add"
|
||||
value={manualQuery.query}
|
||||
minLines={8}
|
||||
maxLines={100}
|
||||
width="100%"
|
||||
showPrintMargin={false}
|
||||
onChange={handleQueryChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{getNameInput()}</div>
|
||||
<div className={styles.add_mar_top}>{getQueryInput()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getFileUploadInput = () => {
|
||||
const handleFileUpload = e => {
|
||||
const files = e.target.files;
|
||||
this.setState({ graphqlFile: files[0] });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Graphql File:</b>
|
||||
<Tooltip message={'.graphql file with operations'} />
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
className={'form-control input-sm ' + styles.inline_block}
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className={styles.subheading_text}>
|
||||
Add new operations to allow-list
|
||||
</h4>
|
||||
<div className={styles.subsection}>
|
||||
<div>
|
||||
<ExpandableEditor
|
||||
expandButtonText="Add operation manually"
|
||||
editorExpanded={getManualQueryInput}
|
||||
collapseCallback={handleManualCollapse}
|
||||
property="add-allowed-operation"
|
||||
service="add-allowed-operation"
|
||||
saveButtonText="Add"
|
||||
saveFunc={handleManualSubmit}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>OR</div>
|
||||
<div className={styles.add_mar_top}>
|
||||
<ExpandableEditor
|
||||
expandButtonText="Upload graphql file"
|
||||
editorExpanded={getFileUploadInput}
|
||||
collapseCallback={handleFileUploadCollapse}
|
||||
property="upload-allowed-operations"
|
||||
service="upload-allowed-operations"
|
||||
saveButtonText="Upload"
|
||||
saveFunc={handleFileUploadSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddAllowedQuery;
|
@ -0,0 +1,161 @@
|
||||
import React, { useState } from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import styles from './AllowedQueries.scss';
|
||||
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import Tooltip from '../../../Common/Tooltip/Tooltip';
|
||||
|
||||
import { readFile, parseQueryString } from './utils';
|
||||
import { showErrorNotification } from '../../Common/Notification';
|
||||
import { addAllowedQueries } from '../../../../metadata/actions';
|
||||
import { allowedQueriesCollection } from '../../../../metadata/utils';
|
||||
import { AllowedQueriesCollection } from '../../../../metadata/reducer';
|
||||
import { Dispatch } from '../../../../types';
|
||||
|
||||
const defaultManualQuery: AllowedQueriesCollection = {
|
||||
name: '',
|
||||
query: '',
|
||||
collection: allowedQueriesCollection,
|
||||
};
|
||||
|
||||
type AddAllowedQueryProps = {
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
|
||||
const AddAllowedQuery: React.FC<AddAllowedQueryProps> = props => {
|
||||
const { dispatch } = props;
|
||||
|
||||
const [manualQuery, setManualQuery] = useState<AllowedQueriesCollection>(
|
||||
defaultManualQuery
|
||||
);
|
||||
const [graphqlFile, setGraphqlFile] = useState<File | null>(null);
|
||||
|
||||
const handleManualCollapse = () => {
|
||||
setManualQuery(defaultManualQuery);
|
||||
};
|
||||
|
||||
const handleManualSubmit = (toggle: () => void) => {
|
||||
dispatch(addAllowedQueries([manualQuery], toggle));
|
||||
};
|
||||
|
||||
const handleFileUploadCollapse = () => {};
|
||||
|
||||
const handleFileUploadSubmit = (toggle: () => void) => {
|
||||
const addFileQueries = (content: string) => {
|
||||
try {
|
||||
const fileQueries = parseQueryString(content);
|
||||
dispatch(addAllowedQueries(fileQueries, toggle));
|
||||
} catch (error) {
|
||||
dispatch(
|
||||
showErrorNotification('Uploading operations failed', error.message)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
readFile(graphqlFile, addFileQueries);
|
||||
};
|
||||
|
||||
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setManualQuery({
|
||||
...manualQuery,
|
||||
name: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleQueryChange = (val: string) => {
|
||||
setManualQuery({
|
||||
...manualQuery,
|
||||
query: val,
|
||||
});
|
||||
};
|
||||
|
||||
const manualQueryInput = () => (
|
||||
<div>
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation name:</b>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`form-control input-sm ${styles.inline_block}`}
|
||||
placeholder="operation_name"
|
||||
value={manualQuery.name}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation:</b>
|
||||
</div>
|
||||
<AceEditor
|
||||
data-test="allowed_operation_add"
|
||||
mode="graphql"
|
||||
theme="github"
|
||||
name="allowed_operation_add"
|
||||
value={manualQuery.query}
|
||||
minLines={8}
|
||||
maxLines={100}
|
||||
width="100%"
|
||||
showPrintMargin={false}
|
||||
onChange={handleQueryChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
setGraphqlFile(files![0]);
|
||||
};
|
||||
|
||||
const fileUploadInput = () => (
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Graphql File:</b>
|
||||
<Tooltip message=".graphql file with operations" />
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
className={`form-control input-sm ${styles.inline_block}`}
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className={styles.subheading_text}>
|
||||
Add new operations to allow-list
|
||||
</h4>
|
||||
<div className={styles.subsection}>
|
||||
<div>
|
||||
<ExpandableEditor
|
||||
expandButtonText="Add operation manually"
|
||||
editorExpanded={manualQueryInput}
|
||||
collapseCallback={handleManualCollapse}
|
||||
property="add-allowed-operation"
|
||||
service="add-allowed-operation"
|
||||
saveButtonText="Add"
|
||||
saveFunc={handleManualSubmit}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>OR</div>
|
||||
<div className={styles.add_mar_top}>
|
||||
<ExpandableEditor
|
||||
expandButtonText="Upload graphql file"
|
||||
editorExpanded={fileUploadInput}
|
||||
collapseCallback={handleFileUploadCollapse}
|
||||
property="upload-allowed-operations"
|
||||
service="upload-allowed-operations"
|
||||
saveButtonText="Upload"
|
||||
saveFunc={handleFileUploadSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddAllowedQuery;
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import AllowedQueriesNotes from './AllowedQueriesNotes';
|
||||
import AddAllowedQuery from './AddAllowedQuery';
|
||||
import AllowedQueriesList from './AllowedQueriesList';
|
||||
|
||||
import styles from './AllowedQueries.scss';
|
||||
import { getAllowedQueries } from '../../../../metadata/selector';
|
||||
|
||||
class AllowedQueries extends React.Component {
|
||||
render() {
|
||||
const { dispatch, allowedQueries } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.clear_fix} ${styles.padd_left} ${styles.padd_top} ${styles.metadata_wrapper} container-fluid`}
|
||||
>
|
||||
<div className={styles.subHeader}>
|
||||
<h2 className={styles.headerText}>Allow List</h2>
|
||||
<div className={styles.add_mar_top + ' ' + styles.wd60}>
|
||||
<AllowedQueriesNotes />
|
||||
<hr />
|
||||
<AddAllowedQuery dispatch={dispatch} />
|
||||
<hr />
|
||||
<AllowedQueriesList
|
||||
dispatch={dispatch}
|
||||
allowedQueries={allowedQueries}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
allowedQueries: getAllowedQueries(state),
|
||||
};
|
||||
};
|
||||
|
||||
const allowedQueriesConnector = connect =>
|
||||
connect(mapStateToProps)(AllowedQueries);
|
||||
|
||||
export default allowedQueriesConnector;
|
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
import AllowedQueriesNotes from './AllowedQueriesNotes';
|
||||
import AddAllowedQuery from './AddAllowedQuery';
|
||||
import AllowedQueriesList from './AllowedQueriesList';
|
||||
|
||||
import styles from './AllowedQueries.scss';
|
||||
import { getAllowedQueries } from '../../../../metadata/selector';
|
||||
import { Dispatch, ReduxState } from '../../../../types';
|
||||
import { mapDispatchToPropsEmpty } from '../../../Common/utils/reactUtils';
|
||||
import { AllowedQueriesCollection } from '../../../../metadata/reducer';
|
||||
|
||||
interface Props {
|
||||
dispatch: Dispatch;
|
||||
allowedQueries: AllowedQueriesCollection[];
|
||||
}
|
||||
|
||||
const AllowedQueries: React.FC<Props> = props => {
|
||||
const { dispatch, allowedQueries } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.clear_fix} ${styles.padd_left} ${styles.padd_top} ${styles.metadata_wrapper} container-fluid`}
|
||||
>
|
||||
<div className={styles.subHeader}>
|
||||
<h2 className={styles.headerText}>Allow List</h2>
|
||||
<div className={`${styles.add_mar_top} ${styles.wd60}`}>
|
||||
<AllowedQueriesNotes />
|
||||
<hr />
|
||||
<AddAllowedQuery dispatch={dispatch} />
|
||||
<hr />
|
||||
<AllowedQueriesList
|
||||
dispatch={dispatch}
|
||||
allowedQueries={allowedQueries}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: ReduxState) => {
|
||||
return {
|
||||
allowedQueries: getAllowedQueries(state),
|
||||
};
|
||||
};
|
||||
|
||||
const allowedQueriesConnector = (connect: any) =>
|
||||
connect(mapStateToProps, mapDispatchToPropsEmpty)(AllowedQueries);
|
||||
|
||||
export default allowedQueriesConnector;
|
@ -1,177 +0,0 @@
|
||||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import styles from './AllowedQueries.scss';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
import {
|
||||
updateAllowedQuery,
|
||||
deleteAllowedQuery,
|
||||
deleteAllowList,
|
||||
} from '../../../../metadata/actions';
|
||||
|
||||
class AllowedQueriesList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
modifiedQueries: {},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { allowedQueries, dispatch } = this.props;
|
||||
const { modifiedQueries } = this.state;
|
||||
|
||||
const getQueryList = () => {
|
||||
if (allowedQueries.length === 0) {
|
||||
return <div>No operations in allow-list yet</div>;
|
||||
}
|
||||
|
||||
return allowedQueries.map((query, i) => {
|
||||
const queryName = query.name;
|
||||
|
||||
const collapsedLabel = () => (
|
||||
<div>
|
||||
<b>{queryName}</b>
|
||||
</div>
|
||||
);
|
||||
|
||||
const expandedLabel = collapsedLabel;
|
||||
|
||||
const queryEditorExpanded = () => {
|
||||
const modifiedQuery = modifiedQueries[queryName] || { ...query };
|
||||
|
||||
const handleNameChange = e => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
newModifiedQueries[queryName].name = e.target.value;
|
||||
|
||||
this.setState({ modifiedQueries: newModifiedQueries });
|
||||
};
|
||||
|
||||
const handleQueryChange = val => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
newModifiedQueries[queryName].query = val;
|
||||
|
||||
this.setState({ modifiedQueries: newModifiedQueries });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation name:</b>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={'form-control input-sm ' + styles.inline_block}
|
||||
value={modifiedQuery.name}
|
||||
placeholder={'operation_name'}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation:</b>
|
||||
</div>
|
||||
<AceEditor
|
||||
data-test="allowed_operation_editor"
|
||||
mode="graphql"
|
||||
theme="github"
|
||||
name="allowed_operation_editor"
|
||||
value={modifiedQuery.query}
|
||||
minLines={8}
|
||||
maxLines={100}
|
||||
width="100%"
|
||||
showPrintMargin={false}
|
||||
onChange={handleQueryChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const editorExpandCallback = () => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
newModifiedQueries[queryName] = { ...query };
|
||||
|
||||
this.setState({ modifiedQueries: newModifiedQueries });
|
||||
};
|
||||
|
||||
const editorCollapseCallback = () => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
delete newModifiedQueries[queryName];
|
||||
|
||||
this.setState({ modifiedQueries: newModifiedQueries });
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
dispatch(updateAllowedQuery(queryName, modifiedQueries[queryName]));
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
const confirmMessage = `This will delete the operation "${queryName}" from the allow-list`;
|
||||
const isOk = getConfirmation(confirmMessage);
|
||||
if (isOk) {
|
||||
const isLastQuery = allowedQueries.length === 1;
|
||||
|
||||
dispatch(deleteAllowedQuery(queryName, isLastQuery));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={queryName}>
|
||||
<ExpandableEditor
|
||||
editorExpanded={queryEditorExpanded}
|
||||
property={`query-${i}`}
|
||||
service="modify-allowed-operation"
|
||||
saveFunc={onSubmit}
|
||||
removeFunc={onDelete}
|
||||
collapsedClass={styles.display_flex}
|
||||
expandedLabel={expandedLabel}
|
||||
collapsedLabel={collapsedLabel}
|
||||
expandCallback={editorExpandCallback}
|
||||
collapseCallback={editorCollapseCallback}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getDeleteAllBtn = () => {
|
||||
const handleDeleteAll = () => {
|
||||
const confirmMessage =
|
||||
'This will delete all operations from the allow-list';
|
||||
const isOk = getConfirmation(confirmMessage, true);
|
||||
if (isOk) {
|
||||
dispatch(deleteAllowList());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="xs"
|
||||
onClick={handleDeleteAll}
|
||||
disabled={allowedQueries.length === 0}
|
||||
>
|
||||
Delete all
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className={styles.subheading_text}>
|
||||
Allow List
|
||||
<span className={styles.add_mar_left}>{getDeleteAllBtn()}</span>
|
||||
</h4>
|
||||
|
||||
<div className={styles.subsection}>{getQueryList()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AllowedQueriesList;
|
@ -0,0 +1,177 @@
|
||||
import React, { useState } from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import styles from './AllowedQueries.scss';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
import {
|
||||
updateAllowedQuery,
|
||||
deleteAllowedQuery,
|
||||
deleteAllowList,
|
||||
} from '../../../../metadata/actions';
|
||||
import { AllowedQueriesCollection } from '../../../../metadata/reducer';
|
||||
import { Dispatch } from '../../../../types';
|
||||
import { getCollectionNames, checkLastQuery } from './utils';
|
||||
|
||||
type AllowedQueriesListProps = {
|
||||
dispatch: Dispatch;
|
||||
allowedQueries: AllowedQueriesCollection[];
|
||||
};
|
||||
|
||||
type ModifiedQuery = Record<string, AllowedQueriesCollection>;
|
||||
|
||||
const AllowedQueriesList: React.FC<AllowedQueriesListProps> = props => {
|
||||
const [modifiedQueries, setModifiedQueries] = useState<ModifiedQuery>({});
|
||||
const { allowedQueries, dispatch } = props;
|
||||
|
||||
const getQueryList = () => {
|
||||
if (allowedQueries.length === 0) {
|
||||
return <div>No operations in allow-list yet</div>;
|
||||
}
|
||||
|
||||
return allowedQueries.map((query, i) => {
|
||||
const queryName = query.name;
|
||||
const collectionName = query.collection;
|
||||
|
||||
const collapsedLabel = () => (
|
||||
<div>
|
||||
<b>{queryName} </b>
|
||||
<i>- {collectionName}</i>
|
||||
</div>
|
||||
);
|
||||
|
||||
const expandedLabel = collapsedLabel;
|
||||
|
||||
const queryEditorExpanded = () => {
|
||||
const modifiedQuery = modifiedQueries[queryName] || { ...query };
|
||||
|
||||
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
newModifiedQueries[queryName].name = e.target.value;
|
||||
setModifiedQueries(newModifiedQueries);
|
||||
};
|
||||
|
||||
const handleQueryChange = (val: string) => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
newModifiedQueries[queryName].query = val;
|
||||
setModifiedQueries(newModifiedQueries);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation name:</b>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`form-control input-sm ${styles.inline_block}`}
|
||||
value={modifiedQuery.name}
|
||||
placeholder="operation_name"
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Operation:</b>
|
||||
</div>
|
||||
<AceEditor
|
||||
data-test="allowed_operation_editor"
|
||||
mode="graphql"
|
||||
theme="github"
|
||||
name="allowed_operation_editor"
|
||||
value={modifiedQuery.query}
|
||||
minLines={8}
|
||||
maxLines={100}
|
||||
width="100%"
|
||||
showPrintMargin={false}
|
||||
onChange={handleQueryChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const editorExpandCallback = () => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
newModifiedQueries[queryName] = { ...query };
|
||||
setModifiedQueries(newModifiedQueries);
|
||||
};
|
||||
|
||||
const editorCollapseCallback = () => {
|
||||
const newModifiedQueries = { ...modifiedQueries };
|
||||
delete newModifiedQueries[queryName];
|
||||
setModifiedQueries(newModifiedQueries);
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
dispatch(
|
||||
updateAllowedQuery(
|
||||
queryName,
|
||||
modifiedQueries[queryName],
|
||||
collectionName
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
const confirmMessage = `This will delete the operation "${queryName}" from the allow-list`;
|
||||
const isOk = getConfirmation(confirmMessage);
|
||||
if (isOk) {
|
||||
const isLastQuery = checkLastQuery(collectionName, allowedQueries);
|
||||
dispatch(deleteAllowedQuery(queryName, isLastQuery, collectionName));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={queryName}>
|
||||
<ExpandableEditor
|
||||
editorExpanded={queryEditorExpanded}
|
||||
property={`query-${i}`}
|
||||
service="modify-allowed-operation"
|
||||
saveFunc={onSubmit}
|
||||
removeFunc={onDelete}
|
||||
collapsedClass={styles.display_flex}
|
||||
expandedLabel={expandedLabel}
|
||||
collapsedLabel={collapsedLabel}
|
||||
expandCallback={editorExpandCallback}
|
||||
collapseCallback={editorCollapseCallback}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteAll = () => {
|
||||
const confirmMessage =
|
||||
'This will delete all operations from the allow-list';
|
||||
const isOk = getConfirmation(confirmMessage, true);
|
||||
const collectionNames = getCollectionNames(allowedQueries);
|
||||
if (isOk) {
|
||||
dispatch(deleteAllowList(collectionNames));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className={styles.subheading_text}>
|
||||
Allow List
|
||||
<span className={styles.add_mar_left}>
|
||||
<Button
|
||||
size="xs"
|
||||
onClick={handleDeleteAll}
|
||||
disabled={allowedQueries.length === 0}
|
||||
>
|
||||
Delete all
|
||||
</Button>
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
<div className={styles.subsection}>{getQueryList()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllowedQueriesList;
|
@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
import styles from './AllowedQueries.scss';
|
||||
|
||||
class AllowedQueriesNotes extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
If GraphQL Engine is started with the{' '}
|
||||
<code>HASURA_GRAPHQL_ENABLE_ALLOWLIST</code> env var or the{' '}
|
||||
<code>--enable-allowlist</code> flag set to <i>true</i>, only
|
||||
operations added to the allow-list will be allowed to be
|
||||
executed.
|
||||
<a
|
||||
href="https://hasura.io/docs/latest/graphql/core/deployment/allow-list.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<i>(Read more)</i>
|
||||
</a>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>
|
||||
<b>Notes</b>
|
||||
<div className={styles.subsection}>
|
||||
<ul
|
||||
className={styles.ul_left_small + ' ' + styles.add_mar_top_small}
|
||||
>
|
||||
<li>
|
||||
All allowed operations need to have a unique name for reference
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AllowedQueriesNotes;
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import styles from './AllowedQueries.scss';
|
||||
|
||||
const AllowedQueriesNotes: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
If GraphQL Engine is started with the{' '}
|
||||
<code>HASURA_GRAPHQL_ENABLE_ALLOWLIST</code> env var or the{' '}
|
||||
<code>--enable-allowlist</code> flag set to <i>true</i>, only operations
|
||||
added to the allow-list will be allowed to be executed.
|
||||
<a
|
||||
href="https://hasura.io/docs/latest/graphql/core/deployment/allow-list.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<i>(Read more)</i>
|
||||
</a>
|
||||
</div>
|
||||
<div className={styles.add_mar_top}>
|
||||
<b>Notes</b>
|
||||
<div className={styles.subsection}>
|
||||
<ul className={`${styles.ul_left_small} ${styles.add_mar_top_small}`}>
|
||||
<li>
|
||||
All allowed operations need to have a unique name for reference
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllowedQueriesNotes;
|
@ -1,99 +0,0 @@
|
||||
import { parse, print, visit } from 'graphql';
|
||||
|
||||
export const readFile = (file, callback) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = event => {
|
||||
const content = event.target.result;
|
||||
callback(content);
|
||||
};
|
||||
|
||||
reader.onerror = event => {
|
||||
console.error('File could not be read! Code ' + event.target.error.code);
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
function recurQueryDef(queryDef, fragments, definitionHash) {
|
||||
visit(queryDef, {
|
||||
FragmentSpread(node) {
|
||||
fragments.add(node.name.value);
|
||||
recurQueryDef(definitionHash[node.name.value], fragments, definitionHash);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const getQueryFragments = (queryDef, definitionHash = {}) => {
|
||||
const fragments = new Set();
|
||||
recurQueryDef(queryDef, fragments, definitionHash);
|
||||
return [...fragments];
|
||||
};
|
||||
|
||||
const getQueryString = (queryDef, fragmentDefs, definitionHash = {}) => {
|
||||
let queryString = print(queryDef);
|
||||
|
||||
const queryFragments = getQueryFragments(queryDef, definitionHash);
|
||||
|
||||
queryFragments.forEach(qf => {
|
||||
const fragmentDef = fragmentDefs.find(fd => fd.name.value === qf);
|
||||
|
||||
if (fragmentDef) {
|
||||
queryString += '\n\n' + print(fragmentDef);
|
||||
}
|
||||
});
|
||||
|
||||
return queryString;
|
||||
};
|
||||
|
||||
export const parseQueryString = queryString => {
|
||||
const queries = [];
|
||||
|
||||
let parsedQueryString;
|
||||
|
||||
try {
|
||||
parsedQueryString = parse(queryString);
|
||||
} catch (ex) {
|
||||
throw new Error('Parsing operation failed');
|
||||
}
|
||||
|
||||
const definitionHash = (parsedQueryString.definitions || []).reduce(
|
||||
(defObj, queryObj) => {
|
||||
defObj[queryObj.name.value] = queryObj;
|
||||
return defObj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const queryDefs = parsedQueryString.definitions.filter(
|
||||
def => def.kind === 'OperationDefinition'
|
||||
);
|
||||
|
||||
const fragmentDefs = parsedQueryString.definitions.filter(
|
||||
def => def.kind === 'FragmentDefinition'
|
||||
);
|
||||
|
||||
queryDefs.forEach(queryDef => {
|
||||
if (!queryDef.name) {
|
||||
throw new Error(`Operation without name found: ${print(queryDef)}`);
|
||||
}
|
||||
|
||||
const query = {
|
||||
name: queryDef.name.value,
|
||||
query: getQueryString(queryDef, fragmentDefs, definitionHash),
|
||||
};
|
||||
|
||||
queries.push(query);
|
||||
});
|
||||
|
||||
const queryNames = queries.map(q => q.name);
|
||||
const duplicateNames = queryNames.filter(
|
||||
(q, i) => queryNames.indexOf(q) !== i
|
||||
);
|
||||
if (duplicateNames.length > 0) {
|
||||
throw new Error(
|
||||
`Operations with duplicate names found: ${duplicateNames.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
return queries;
|
||||
};
|
150
console/src/components/Services/Settings/AllowedQueries/utils.ts
Normal file
150
console/src/components/Services/Settings/AllowedQueries/utils.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { parse, print, visit, DefinitionNode } from 'graphql';
|
||||
import { AllowedQueriesCollection } from '../../../../metadata/reducer';
|
||||
|
||||
export type NewDefinitionNode = DefinitionNode & {
|
||||
name?: {
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const readFile = (
|
||||
file: File | null,
|
||||
callback: (content: string) => void
|
||||
) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = event => {
|
||||
const content = event.target!.result as string;
|
||||
callback(content);
|
||||
};
|
||||
|
||||
reader.onerror = event => {
|
||||
console.error(`File could not be read! Code ${event.target!.error!.code}`);
|
||||
};
|
||||
|
||||
if (file) reader.readAsText(file);
|
||||
};
|
||||
|
||||
const recurQueryDef = (
|
||||
queryDef: NewDefinitionNode,
|
||||
fragments: Set<string>,
|
||||
definitionHash: Record<string, any>
|
||||
) => {
|
||||
visit(queryDef, {
|
||||
FragmentSpread(node) {
|
||||
fragments.add(node.name.value);
|
||||
recurQueryDef(definitionHash[node.name.value], fragments, definitionHash);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getQueryFragments = (
|
||||
queryDef: NewDefinitionNode,
|
||||
definitionHash: Record<string, any> = {}
|
||||
) => {
|
||||
const fragments = new Set<string>();
|
||||
recurQueryDef(queryDef, fragments, definitionHash);
|
||||
return [...Array.from(fragments)];
|
||||
};
|
||||
|
||||
const getQueryString = (
|
||||
queryDef: NewDefinitionNode,
|
||||
fragmentDefs: NewDefinitionNode[],
|
||||
definitionHash: Record<string, any> = {}
|
||||
) => {
|
||||
let queryString = print(queryDef);
|
||||
|
||||
const queryFragments = getQueryFragments(queryDef, definitionHash);
|
||||
|
||||
queryFragments.forEach(qf => {
|
||||
// eslint-disable-next-line array-callback-return
|
||||
const fragmentDef = fragmentDefs.find(fd => {
|
||||
if (fd.name) return fd.name.value === qf;
|
||||
});
|
||||
|
||||
if (fragmentDef) {
|
||||
queryString += `\n\n${print(fragmentDef)}`;
|
||||
}
|
||||
});
|
||||
|
||||
return queryString;
|
||||
};
|
||||
|
||||
export const parseQueryString = (queryString: string) => {
|
||||
const queries: { name: string; query: string }[] = [];
|
||||
|
||||
let parsedQueryString;
|
||||
|
||||
try {
|
||||
parsedQueryString = parse(queryString);
|
||||
} catch (ex) {
|
||||
throw new Error('Parsing operation failed');
|
||||
}
|
||||
|
||||
const definitions: NewDefinitionNode[] = [...parsedQueryString.definitions];
|
||||
|
||||
const definitionHash = (definitions || []).reduce(
|
||||
(defObj: Record<string, NewDefinitionNode>, queryObj) => {
|
||||
if (queryObj.name) defObj[queryObj.name.value] = queryObj;
|
||||
return defObj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const queryDefs = definitions.filter(
|
||||
def => def.kind === 'OperationDefinition'
|
||||
);
|
||||
|
||||
const fragmentDefs = definitions.filter(
|
||||
def => def.kind === 'FragmentDefinition'
|
||||
);
|
||||
|
||||
queryDefs.forEach(queryDef => {
|
||||
if (!queryDef.name) {
|
||||
throw new Error(`Operation without name found: ${print(queryDef)}`);
|
||||
}
|
||||
|
||||
const query = {
|
||||
name: queryDef.name.value,
|
||||
query: getQueryString(queryDef, fragmentDefs, definitionHash),
|
||||
};
|
||||
|
||||
queries.push(query);
|
||||
});
|
||||
|
||||
const queryNames = queries.map(q => q.name);
|
||||
const duplicateNames = queryNames.filter(
|
||||
(q, i) => queryNames.indexOf(q) !== i
|
||||
);
|
||||
if (duplicateNames.length > 0) {
|
||||
throw new Error(
|
||||
`Operations with duplicate names found: ${duplicateNames.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
return queries;
|
||||
};
|
||||
|
||||
export const getQueriesInCollection = (
|
||||
collectionName: string,
|
||||
allowedQueries: AllowedQueriesCollection[]
|
||||
) => {
|
||||
const queries: AllowedQueriesCollection[] = [];
|
||||
allowedQueries.forEach(query => {
|
||||
if (query.collection === collectionName) {
|
||||
queries.push(query);
|
||||
}
|
||||
});
|
||||
return queries;
|
||||
};
|
||||
|
||||
export const checkLastQuery = (
|
||||
collectionName: string,
|
||||
queries: AllowedQueriesCollection[]
|
||||
) => {
|
||||
return getQueriesInCollection(collectionName, queries).length === 1;
|
||||
};
|
||||
|
||||
// Missing feature in typescript https://stackoverflow.com/questions/33464504/using-spread-syntax-and-new-set-with-typescript/33464709
|
||||
export const getCollectionNames = (queries: AllowedQueriesCollection[]) => {
|
||||
return Array.from(new Set(queries.map(query => query.collection)));
|
||||
};
|
@ -91,7 +91,7 @@ export interface UpdateAllowedQuery {
|
||||
type: 'Metadata/UPDATE_ALLOWED_QUERY';
|
||||
data: {
|
||||
queryName: string;
|
||||
newQuery: { name: string; query: string };
|
||||
newQuery: { name: string; query: string; collection: string };
|
||||
};
|
||||
}
|
||||
export interface DeleteAllowedQuery {
|
||||
@ -704,20 +704,27 @@ export const dropInconsistentObjects = (
|
||||
|
||||
export const updateAllowedQuery = (
|
||||
queryName: string,
|
||||
newQuery: { name: string; query: string }
|
||||
newQuery: { name: string; query: string },
|
||||
collectionName: string
|
||||
): Thunk<void, MetadataActions> => {
|
||||
return (dispatch, getState) => {
|
||||
const upQuery = updateAllowedQueryQuery(queryName, newQuery);
|
||||
const upQuery = updateAllowedQueryQuery(
|
||||
queryName,
|
||||
newQuery,
|
||||
collectionName
|
||||
);
|
||||
|
||||
const migrationName = `update_allowed_query`;
|
||||
const requestMsg = 'Updating allowed query...';
|
||||
const successMsg = 'Updated allow-list query';
|
||||
const errorMsg = 'Updating allow-list query failed';
|
||||
|
||||
const updatedQuery = { ...newQuery, collection: collectionName };
|
||||
|
||||
const onSuccess = () => {
|
||||
dispatch({
|
||||
type: 'Metadata/UPDATE_ALLOWED_QUERY',
|
||||
data: { queryName, newQuery },
|
||||
data: { queryName, newQuery: updatedQuery },
|
||||
});
|
||||
};
|
||||
|
||||
@ -740,12 +747,13 @@ export const updateAllowedQuery = (
|
||||
|
||||
export const deleteAllowedQuery = (
|
||||
queryName: string,
|
||||
isLastQuery: boolean
|
||||
isLastQuery: boolean,
|
||||
collectionName: string
|
||||
): Thunk<void, MetadataActions> => {
|
||||
return (dispatch, getState) => {
|
||||
const upQuery = isLastQuery
|
||||
? deleteAllowListQuery()
|
||||
: deleteAllowedQueryQuery(queryName);
|
||||
? deleteAllowListQuery(collectionName)
|
||||
: deleteAllowedQueryQuery(queryName, collectionName);
|
||||
|
||||
const migrationName = `delete_allowed_query`;
|
||||
const requestMsg = 'Deleting allowed query...';
|
||||
@ -773,9 +781,19 @@ export const deleteAllowedQuery = (
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteAllowList = (): Thunk<void, MetadataActions> => {
|
||||
export const deleteAllowList = (
|
||||
collectionNames: string[]
|
||||
): Thunk<void, MetadataActions> => {
|
||||
return (dispatch, getState) => {
|
||||
const upQuery = deleteAllowListQuery();
|
||||
const upQueries: {
|
||||
type: string;
|
||||
args: { collection: string; cascade: boolean };
|
||||
}[] = [];
|
||||
|
||||
collectionNames.forEach(collectionName => {
|
||||
upQueries.push(deleteAllowListQuery(collectionName));
|
||||
});
|
||||
|
||||
const migrationName = 'delete_allow_list';
|
||||
const requestMsg = 'Deleting allow list...';
|
||||
const successMsg = 'Deleted all queries from allow-list';
|
||||
@ -790,7 +808,7 @@ export const deleteAllowList = (): Thunk<void, MetadataActions> => {
|
||||
makeMigrationCall(
|
||||
dispatch,
|
||||
getState,
|
||||
[upQuery],
|
||||
upQueries,
|
||||
undefined,
|
||||
migrationName,
|
||||
onSuccess,
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { MetadataActions } from './actions';
|
||||
import { QueryCollection, HasuraMetadataV3, InheritedRole } from './types';
|
||||
import { allowedQueriesCollection } from './utils';
|
||||
import { HasuraMetadataV3, CollectionName, InheritedRole } from './types';
|
||||
import { setAllowedQueries } from './utils';
|
||||
|
||||
export type AllowedQueriesCollection = {
|
||||
name: string;
|
||||
query: string;
|
||||
collection: CollectionName;
|
||||
};
|
||||
|
||||
type MetadataState = {
|
||||
metadataObject: null | HasuraMetadataV3;
|
||||
@ -8,7 +14,7 @@ type MetadataState = {
|
||||
loading: boolean;
|
||||
inconsistentObjects: any[];
|
||||
ongoingRequest: boolean; // deprecate
|
||||
allowedQueries: QueryCollection[];
|
||||
allowedQueries: AllowedQueriesCollection[];
|
||||
inheritedRoles: InheritedRole[];
|
||||
};
|
||||
|
||||
@ -31,10 +37,10 @@ export const metadataReducer = (
|
||||
return {
|
||||
...state,
|
||||
metadataObject: action.data,
|
||||
allowedQueries:
|
||||
action.data?.query_collections?.find(
|
||||
query => query.name === allowedQueriesCollection
|
||||
)?.definition.queries || [],
|
||||
allowedQueries: setAllowedQueries(
|
||||
action.data?.query_collections,
|
||||
action.data?.allowlist
|
||||
),
|
||||
inheritedRoles: action.data?.inherited_roles,
|
||||
loading: false,
|
||||
error: null,
|
||||
|
@ -923,7 +923,8 @@ export interface HasuraMetadataV3 {
|
||||
actions?: Action[];
|
||||
custom_types?: CustomTypes;
|
||||
cron_triggers?: CronTrigger[];
|
||||
query_collections: QueryCollectionEntry[];
|
||||
query_collections?: QueryCollectionEntry[];
|
||||
allowlist?: AllowList[];
|
||||
inherited_roles: InheritedRole[];
|
||||
rest_endpoints?: RestEndpointEntry[];
|
||||
}
|
||||
|
@ -3,22 +3,56 @@ import {
|
||||
getReloadMetadataQuery,
|
||||
getReloadRemoteSchemaCacheQuery,
|
||||
} from './queryUtils';
|
||||
import { HasuraMetadataV3 } from './types';
|
||||
import { AllowList, QueryCollectionEntry, HasuraMetadataV3 } from './types';
|
||||
import { AllowedQueriesCollection } from './reducer';
|
||||
|
||||
export const allowedQueriesCollection = 'allowed-queries';
|
||||
|
||||
export const deleteAllowedQueryQuery = (queryName: string) => ({
|
||||
export const findAllowedQueryCollections = (
|
||||
collectionName: string,
|
||||
allowList: AllowList[]
|
||||
) => {
|
||||
return allowList.find(
|
||||
allowedCollection => collectionName === allowedCollection.collection
|
||||
);
|
||||
};
|
||||
|
||||
export const setAllowedQueries = (
|
||||
allQueryCollections?: QueryCollectionEntry[],
|
||||
allowlist?: AllowList[]
|
||||
): AllowedQueriesCollection[] => {
|
||||
if (!allQueryCollections || !allowlist) return [];
|
||||
const allowedQueryCollections = allQueryCollections.filter(query =>
|
||||
findAllowedQueryCollections(query.name, allowlist)
|
||||
);
|
||||
|
||||
const allowedQueries: AllowedQueriesCollection[] = [];
|
||||
allowedQueryCollections.forEach(collection => {
|
||||
collection.definition.queries.forEach(query => {
|
||||
allowedQueries.push({ ...query, collection: collection.name });
|
||||
});
|
||||
});
|
||||
return allowedQueries;
|
||||
};
|
||||
|
||||
export const deleteAllowedQueryQuery = (
|
||||
queryName: string,
|
||||
collectionName = allowedQueriesCollection
|
||||
) => ({
|
||||
type: 'drop_query_from_collection',
|
||||
args: {
|
||||
collection_name: allowedQueriesCollection,
|
||||
collection_name: collectionName,
|
||||
query_name: queryName,
|
||||
},
|
||||
});
|
||||
|
||||
export const addAllowedQuery = (query: { name: string; query: string }) => ({
|
||||
export const addAllowedQuery = (
|
||||
query: { name: string; query: string },
|
||||
collectionName = allowedQueriesCollection
|
||||
) => ({
|
||||
type: 'add_query_to_collection',
|
||||
args: {
|
||||
collection_name: allowedQueriesCollection,
|
||||
collection_name: collectionName,
|
||||
query_name: query.name,
|
||||
query: query.query,
|
||||
},
|
||||
@ -26,16 +60,22 @@ export const addAllowedQuery = (query: { name: string; query: string }) => ({
|
||||
|
||||
export const updateAllowedQueryQuery = (
|
||||
queryName: string,
|
||||
newQuery: { name: string; query: string }
|
||||
newQuery: { name: string; query: string },
|
||||
collectionName = allowedQueriesCollection
|
||||
) => ({
|
||||
type: 'bulk',
|
||||
args: [deleteAllowedQueryQuery(queryName), addAllowedQuery(newQuery)],
|
||||
args: [
|
||||
deleteAllowedQueryQuery(queryName, collectionName),
|
||||
addAllowedQuery(newQuery, collectionName),
|
||||
],
|
||||
});
|
||||
|
||||
export const deleteAllowListQuery = () => ({
|
||||
export const deleteAllowListQuery = (
|
||||
collectionName = allowedQueriesCollection
|
||||
) => ({
|
||||
type: 'drop_query_collection',
|
||||
args: {
|
||||
collection: allowedQueriesCollection,
|
||||
collection: collectionName,
|
||||
cascade: true,
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user