diff --git a/CHANGELOG.md b/CHANGELOG.md index 2726206e1e9..05279f644e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - server: support EdDSA algorithm and key type for JWT - server: fix GraphQL type for single-row returning functions (close #7109) - console: add support for creation of indexes for Postgres data sources +- console: allow same named queries and unnamed queries on allowlist file upload ## v2.0.6 diff --git a/console/src/components/Services/Settings/AllowedQueries/AddAllowedQuery.tsx b/console/src/components/Services/Settings/AllowedQueries/AddAllowedQuery.tsx index 8c311681c72..21e6f342cfa 100644 --- a/console/src/components/Services/Settings/AllowedQueries/AddAllowedQuery.tsx +++ b/console/src/components/Services/Settings/AllowedQueries/AddAllowedQuery.tsx @@ -5,7 +5,7 @@ import styles from './AllowedQueries.scss'; import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor'; import Tooltip from '../../../Common/Tooltip/Tooltip'; -import { readFile, parseQueryString } from './utils'; +import { readFile, parseQueryString, renameDuplicates } from './utils'; import { showErrorNotification } from '../../Common/Notification'; import { addAllowedQueries } from '../../../../metadata/actions'; import { allowedQueriesCollection } from '../../../../metadata/utils'; @@ -20,10 +20,11 @@ const defaultManualQuery: AllowedQueriesCollection = { type AddAllowedQueryProps = { dispatch: Dispatch; + allowedQueries: AllowedQueriesCollection[]; }; const AddAllowedQuery: React.FC = props => { - const { dispatch } = props; + const { dispatch, allowedQueries } = props; const [manualQuery, setManualQuery] = useState( defaultManualQuery @@ -44,7 +45,8 @@ const AddAllowedQuery: React.FC = props => { const addFileQueries = (content: string) => { try { const fileQueries = parseQueryString(content); - dispatch(addAllowedQueries(fileQueries, toggle)); + const updatedQueries = renameDuplicates(fileQueries, allowedQueries); + dispatch(addAllowedQueries(updatedQueries, toggle)); } catch (error) { dispatch( showErrorNotification('Uploading operations failed', error.message) @@ -73,7 +75,11 @@ const AddAllowedQuery: React.FC = props => {
- Operation name: + Query name: +
= props => {

- +
= props => { return allowedQueries.map((query, i) => { const queryName = query.name; const collectionName = query.collection; + const queryId = `${queryName}_${collectionName}_${i}`; const collapsedLabel = () => (
@@ -45,17 +47,17 @@ const AllowedQueriesList: React.FC = props => { const expandedLabel = collapsedLabel; const queryEditorExpanded = () => { - const modifiedQuery = modifiedQueries[queryName] || { ...query }; + const modifiedQuery = modifiedQueries[queryId] || { ...query }; const handleNameChange = (e: React.ChangeEvent) => { const newModifiedQueries = { ...modifiedQueries }; - newModifiedQueries[queryName].name = e.target.value; + newModifiedQueries[queryId].name = e.target.value; setModifiedQueries(newModifiedQueries); }; const handleQueryChange = (val: string) => { const newModifiedQueries = { ...modifiedQueries }; - newModifiedQueries[queryName].query = val; + newModifiedQueries[queryId].query = val; setModifiedQueries(newModifiedQueries); }; @@ -63,7 +65,11 @@ const AllowedQueriesList: React.FC = props => {
- Operation name: + Query name: +
= props => { const editorExpandCallback = () => { const newModifiedQueries = { ...modifiedQueries }; - newModifiedQueries[queryName] = { ...query }; + newModifiedQueries[queryId] = { ...query }; setModifiedQueries(newModifiedQueries); }; const editorCollapseCallback = () => { const newModifiedQueries = { ...modifiedQueries }; - delete newModifiedQueries[queryName]; + delete newModifiedQueries[queryId]; setModifiedQueries(newModifiedQueries); }; @@ -110,7 +116,7 @@ const AllowedQueriesList: React.FC = props => { dispatch( updateAllowedQuery( queryName, - modifiedQueries[queryName], + modifiedQueries[queryId], collectionName ) ); @@ -126,7 +132,7 @@ const AllowedQueriesList: React.FC = props => { }; return ( -
+
{ const queries: { name: string; query: string }[] = []; @@ -99,28 +101,16 @@ export const parseQueryString = (queryString: string) => { ); queryDefs.forEach(queryDef => { - if (!queryDef.name) { - throw new Error(`Operation without name found: ${print(queryDef)}`); - } + const queryName = queryDef.name ? queryDef.name.value : `unnamed`; const query = { - name: queryDef.name.value, + name: queryName, 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; }; @@ -137,6 +127,35 @@ export const getQueriesInCollection = ( return queries; }; +// check if the uploaded queries have same names within the file, or among the already present queries +export const renameDuplicates = ( + fileQueries: { name: string; query: string }[], + allQueries: AllowedQueriesCollection[] +) => { + // we only allow addition to allowedQueriesCollection from console atm + const allowListQueries = getQueriesInCollection( + allowedQueriesCollection, + allQueries + ); + + const queryNames = new Set(); + allowListQueries.forEach(query => queryNames.add(query.name)); + + const updatedQueries = fileQueries.map(query => { + let queryName = query.name; + if (queryNames.has(queryName)) { + let num = 1; + while (queryNames.has(queryName)) { + queryName = `${query.name}_${num++}`; + } + } + queryNames.add(queryName); + return { name: queryName, query: query.query }; + }); + + return updatedQueries; +}; + export const checkLastQuery = ( collectionName: string, queries: AllowedQueriesCollection[] diff --git a/console/src/components/Services/Settings/__tests__/__snapshots__/settings.test.ts.snap b/console/src/components/Services/Settings/__tests__/__snapshots__/settings.test.ts.snap new file mode 100644 index 00000000000..b52a0f9c261 --- /dev/null +++ b/console/src/components/Services/Settings/__tests__/__snapshots__/settings.test.ts.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AllowedQueries_Utils.ts renameDuplicates should rename duplicate queries 1`] = ` +Array [ + Object { + "name": "getAuthors_1", + "query": "query getAuthors { + author { + id + name + } +}", + }, + Object { + "name": "getAuthors_2", + "query": "query getAuthors { + author { + id + name + address + } +}", + }, + Object { + "name": "getAuthors_3", + "query": "query getAuthors { + author { + id + name + age + } +}", + }, + Object { + "name": "unnamed", + "query": "{ + student { + id + name + age + } +}", + }, + Object { + "name": "unnamed_1", + "query": "{ + student { + id + name + roll + } +}", + }, + Object { + "name": "unnamed_2", + "query": "{ + student { + id + name + address + } +}", + }, + Object { + "name": "getArticles", + "query": "query getArticles { + article { + id + title + } +}", + }, + Object { + "name": "getArticle", + "query": "query getArticle { + article { + id + title + ...frag + } +} + +fragment frag on Starship { + name +}", + }, + Object { + "name": "addArticles", + "query": "mutation addArticles { + insert_articles { + id + title + } +}", + }, +] +`; diff --git a/console/src/components/Services/Settings/__tests__/fixtures/allow-list.ts b/console/src/components/Services/Settings/__tests__/fixtures/allow-list.ts new file mode 100644 index 00000000000..bd1af87fe13 --- /dev/null +++ b/console/src/components/Services/Settings/__tests__/fixtures/allow-list.ts @@ -0,0 +1,94 @@ +export const uploadedFileData = `# will be ignored by the allow-list +type Starship { + id: ID! + name: String! + length(unit: LengthUnit = METER): Float +} + +# will be ignored by the allow-list +scalar parsec + +# will be ignored by the allow-list +enum Episode { + NEWHOPE + EMPIRE + JEDI +} + +# will be stored in the allow-list +query getAuthors{ + author { + id + name + } +} + +query getAuthors{ + author { + id + name + address + } +} + +query getAuthors{ + author { + id + name + age + } +} + +query { + student { + id + name + age + } +} + +query { + student { + id + name + roll + } +} + +query { + student { + id + name + address + } +} + +fragment frag on Starship { + name +} + +# will be stored in the allow-list +query getArticles { + article { + id + title + } +} + +# will be stored in the allow-list after patching in the fragment +query getArticle { + article { + id + title + ...frag + } +} + +# will be stored in the allow-list +mutation addArticles { + insert_articles { + id + title + } +} +`; diff --git a/console/src/components/Services/Settings/__tests__/settings.test.ts b/console/src/components/Services/Settings/__tests__/settings.test.ts new file mode 100644 index 00000000000..cee5757a140 --- /dev/null +++ b/console/src/components/Services/Settings/__tests__/settings.test.ts @@ -0,0 +1,20 @@ +import { allowedQueriesCollection } from '../../../../metadata/utils'; +import { renameDuplicates, parseQueryString } from '../AllowedQueries/utils'; +import { uploadedFileData } from './fixtures/allow-list'; + +describe('AllowedQueries_Utils.ts', () => { + describe('renameDuplicates', () => { + it('should rename duplicate queries', () => { + const allowedListQueries = [ + { + name: 'getAuthors', + query: 'query getAuthors { author {id name} }', + collection: allowedQueriesCollection, + }, + ]; + const fileQueries = parseQueryString(uploadedFileData); + const updatedQueries = renameDuplicates(fileQueries, allowedListQueries); + expect(updatedQueries).toMatchSnapshot(); + }); + }); +});