console: remove allow list feature flag

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5889
GitOrigin-RevId: b9a3bab2bd83edb94137603850b405df1766e43b
This commit is contained in:
Daniele Cammareri 2022-09-18 17:38:42 +02:00 committed by hasura-bot
parent 30bc2185e6
commit f34bd93e9c
18 changed files with 12 additions and 711 deletions

View File

@ -1,20 +1,12 @@
import React from 'react';
import { Link, RouteComponentProps } from 'react-router';
import { canAccessSecuritySettings } from '@/utils/permissions';
import {
availableFeatureFlagIds,
useIsFeatureFlagEnabled,
} from '@/features/FeatureFlags';
type TopNavProps = {
location: RouteComponentProps<unknown, unknown>['location'];
};
const TopNav: React.FC<TopNavProps> = ({ location }) => {
const { enabled: allowListEnabled } = useIsFeatureFlagEnabled(
availableFeatureFlagIds.allowListId
);
const sectionsData = [
[
{
@ -31,16 +23,12 @@ const TopNav: React.FC<TopNavProps> = ({ location }) => {
},
],
[
...(allowListEnabled
? [
{
key: 'allow-list',
link: '/api/allow-list',
dataTestVal: 'allow-list',
title: 'Allow List',
},
]
: []),
{
key: 'allow-list',
link: '/api/allow-list',
dataTestVal: 'allow-list',
title: 'Allow List',
},
],
];

View File

@ -1,169 +0,0 @@
import React, { useState } from 'react';
import AceEditor from 'react-ace';
import { isConsoleError } from '@/components/Common/utils/jsUtils';
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
import Tooltip from '../../../Common/Tooltip/Tooltip';
import { readFile, parseQueryString, renameDuplicates } 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';
import { inputStyles } from '../constants';
const defaultManualQuery: AllowedQueriesCollection = {
name: '',
query: '',
collection: allowedQueriesCollection,
};
type AddAllowedQueryProps = {
dispatch: Dispatch;
allowedQueries: AllowedQueriesCollection[];
};
const AddAllowedQuery: React.FC<AddAllowedQueryProps> = props => {
const { dispatch, allowedQueries } = 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);
const updatedQueries = renameDuplicates(fileQueries, allowedQueries);
dispatch(addAllowedQueries(updatedQueries, toggle));
} catch (error) {
if (isConsoleError(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="mb-sm">
<b>Query name:</b>
<Tooltip
message="This is an identifier for the query in the collection.
This should be unique in the collection and can be different from the operation name of the query."
/>
</div>
<input
type="text"
className={`${inputStyles} inline-block`}
placeholder="operation_name"
value={manualQuery.name}
onChange={handleNameChange}
/>
</div>
<div className="mt-md">
<div>
<div className="mb-sm">
<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="mb-sm">
<b>Graphql File:</b>
<Tooltip message=".graphql file with operations" />
</div>
<input
type="file"
className={`${inputStyles} inline-block`}
onChange={handleFileUpload}
/>
</div>
);
return (
<div>
<h4 className="text-lg font-bold pb-sm">
Add new operations to allow-list
</h4>
<div className="px-sm">
<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="mt-md">OR</div>
<div className="mt-md">
<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;

View File

@ -1,67 +0,0 @@
import React from 'react';
import { browserHistory } from 'react-router';
import {
availableFeatureFlagIds,
FeatureFlagToast,
useIsFeatureFlagEnabled,
} from '@/features/FeatureFlags';
import AllowedQueriesNotes from './AllowedQueriesNotes';
import AddAllowedQuery from './AddAllowedQuery';
import AllowedQueriesList from './AllowedQueriesList';
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;
const { enabled: featureFlagEnabled } = useIsFeatureFlagEnabled(
availableFeatureFlagIds.allowListId
);
if (featureFlagEnabled) {
browserHistory.push('/api/allow-list');
}
return (
<div className="clear-both pl-md pt-md mb-md">
<div>
<h2 className="text-xl font-bold">Allow List</h2>
<div className="mt-md w-1/2">
<AllowedQueriesNotes />
<hr className="my-md" />
<AddAllowedQuery
dispatch={dispatch}
allowedQueries={allowedQueries}
/>
<hr className="my-md" />
<AllowedQueriesList
dispatch={dispatch}
allowedQueries={allowedQueries}
/>
</div>
</div>
<FeatureFlagToast flagId={availableFeatureFlagIds.allowListId} />
</div>
);
};
const mapStateToProps = (state: ReduxState) => {
return {
allowedQueries: getAllowedQueries(state),
};
};
const allowedQueriesConnector = (connect: any) =>
connect(mapStateToProps, mapDispatchToPropsEmpty)(AllowedQueries);
export default allowedQueriesConnector;

View File

@ -1,184 +0,0 @@
import React, { useState } from 'react';
import AceEditor from 'react-ace';
import { Button } from '@/new-components/Button';
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
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';
import Tooltip from '../../../Common/Tooltip/Tooltip';
import { inputStyles } from '../constants';
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 queryId = `${queryName}_${collectionName}_${i}`;
const collapsedLabel = () => (
<div>
<b>{queryName} </b>
<i>- {collectionName}</i>
</div>
);
const expandedLabel = collapsedLabel;
const queryEditorExpanded = () => {
const modifiedQuery = modifiedQueries[queryId] || { ...query };
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newModifiedQueries = { ...modifiedQueries };
newModifiedQueries[queryId].name = e.target.value;
setModifiedQueries(newModifiedQueries);
};
const handleQueryChange = (val: string) => {
const newModifiedQueries = { ...modifiedQueries };
newModifiedQueries[queryId].query = val;
setModifiedQueries(newModifiedQueries);
};
return (
<div>
<div>
<div className="mb-md">
<b>Query name:</b>
<Tooltip
message="This is an identifier for the query in the collection.
This should be unique in the collection and can be different from the operation name of the query."
/>
</div>
<input
type="text"
className={`${inputStyles} inline-block`}
value={modifiedQuery.name}
placeholder="operation_name"
onChange={handleNameChange}
/>
</div>
<div className="mt-mb">
<div className="mb-md">
<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[queryId] = { ...query };
setModifiedQueries(newModifiedQueries);
};
const editorCollapseCallback = () => {
const newModifiedQueries = { ...modifiedQueries };
delete newModifiedQueries[queryId];
setModifiedQueries(newModifiedQueries);
};
const onSubmit = () => {
dispatch(
updateAllowedQuery(
queryName,
modifiedQueries[queryId],
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={queryId}>
<ExpandableEditor
editorExpanded={queryEditorExpanded}
property={`query-${i}`}
service="modify-allowed-operation"
saveFunc={onSubmit}
removeFunc={onDelete}
collapsedClass="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="text-lg font-bold pb-md">
Allow List
<span className="pl-md">
<Button
size="sm"
mode="destructive"
onClick={handleDeleteAll}
disabled={allowedQueries.length === 0}
>
Delete all
</Button>
</span>
</h4>
<div className="pl-md">{getQueryList()}</div>
</div>
);
};
export default AllowedQueriesList;

View File

@ -1,29 +0,0 @@
import React from 'react';
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.&nbsp;
<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="mt-md">
<b>Notes</b>
<div className="pl-lg pt-sm">
All allowed operations need to have a unique name for reference
</div>
</div>
</div>
);
};
export default AllowedQueriesNotes;

View File

@ -1,9 +1,5 @@
/* eslint-disable no-underscore-dangle */
import React from 'react';
import {
availableFeatureFlagIds,
useIsFeatureFlagEnabled,
} from '@/features/FeatureFlags';
import { Link, RouteComponentProps } from 'react-router';
import LeftContainer from '../../Common/Layout/LeftContainer/LeftContainer';
import CheckIcon from '../../Common/Icons/Check';
@ -48,10 +44,6 @@ interface SectionData {
const Sidebar: React.FC<SidebarProps> = ({ location, metadata }) => {
const sectionsData: SectionData[] = [];
const { enabled: newAllowListEnabled } = useIsFeatureFlagEnabled(
availableFeatureFlagIds.allowListId
);
sectionsData.push({
key: 'actions',
link: '/settings/metadata-actions',
@ -81,7 +73,7 @@ const Sidebar: React.FC<SidebarProps> = ({ location, metadata }) => {
sectionsData.push({
key: 'allow-list',
link: newAllowListEnabled ? '/api/allow-list' : '/settings/allow-list',
link: '/api/allow-list',
dataTestVal: 'allow-list-link',
title: 'Allow List',
});

View File

@ -1,7 +1,6 @@
export { default as metadataContainer } from './Container';
export { default as metadataOptionsContainer } from './MetadataOptions/MetadataOptions';
export { default as metadataStatusContainer } from './MetadataStatus/MetadataStatus';
export { default as allowedQueriesContainer } from './AllowedQueries/AllowedQueries';
export { default as logoutContainer } from './Logout/Logout';
export { default as aboutContainer } from './About/About';
export { default as InheritedRolesContainer } from './InheritedRoles/InheritedRoles';

View File

@ -156,7 +156,7 @@ export const AllowListPermissions: React.FC<AllowListPermissionsTabProps> = ({
</div>
</td>
<td className="group relative text-center p-sm whitespace-nowrap cursor-pointer">
{updatingRoles.includes(roleName) ? (
{updatingRoles.length > 0 ? (
<CgSpinner className={`animate-spin ${'w-5 h-5'}`} />
) : (
<Switch
@ -180,7 +180,7 @@ export const AllowListPermissions: React.FC<AllowListPermissionsTabProps> = ({
/>
</td>
<td className="group relative text-center p-sm whitespace-nowrap cursor-pointer flex items-center justify-center">
{updatingRoles.includes(newRole) ? (
{updatingRoles.length > 0 ? (
<CgSpinner className={`animate-spin ${'w-5 h-5'}`} />
) : (
<Switch

View File

@ -21,16 +21,6 @@ export const availableFeatureFlags: FeatureFlagDefinition[] = [
defaultValue: false,
discussionUrl: '',
},
{
id: allowListId,
title: 'New Allow List Manager',
description:
'Try out the new UI for the Allow List management in the API section.',
section: 'api',
status: 'alpha',
defaultValue: false,
discussionUrl: '',
},
{
id: gdcId,
title: 'Experimental features for GDC',

View File

@ -6,10 +6,7 @@ import {
FieldWrapper,
FieldWrapperPassThroughProps,
} from '@/new-components/Form';
import {
parseQueryString,
readFile,
} from '@/components/Services/Settings/AllowedQueries/utils';
import { parseQueryString, readFile } from './utils';
export type GraphQLFileUploadProps = FieldWrapperPassThroughProps & {
/**

View File

@ -1,6 +1,6 @@
import { allowedQueriesCollection } from '../../../../metadata/utils';
import { renameDuplicates, parseQueryString } from '../AllowedQueries/utils';
import { uploadedFileData } from './fixtures/allow-list';
import { renameDuplicates, parseQueryString } from './utils';
import { uploadedFileData } from './fixtures';
describe('AllowedQueries_Utils.ts', () => {
describe('renameDuplicates', () => {

View File

@ -19,7 +19,6 @@ import settingsContainer from './components/Services/Settings/Container';
import ApiContainer from './components/Services/ApiExplorer/Container';
import metadataOptionsConnector from './components/Services/Settings/MetadataOptions/MetadataOptions';
import metadataStatusConnector from './components/Services/Settings/MetadataStatus/MetadataStatus';
import allowedQueriesConnector from './components/Services/Settings/AllowedQueries/AllowedQueries';
import inheritedRolesConnector from './components/Services/Settings/InheritedRoles/InheritedRoles';
import logoutConnector from './components/Services/Settings/Logout/Logout';
import aboutConnector from './components/Services/Settings/About/About';
@ -150,10 +149,6 @@ const routes = store => {
path="metadata-status"
component={metadataStatusConnector(connect)}
/>
<Route
path="allow-list"
component={allowedQueriesConnector(connect)}
/>
<Route path="logout" component={logoutConnector(connect)} />
<Route path="about" component={aboutConnector(connect)} />
<Route path="inherited-roles" component={inheritedRolesConnector} />

View File

@ -1,97 +0,0 @@
// 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
}
}",
},
]
`;

View File

@ -1,94 +0,0 @@
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
}
}
`;

View File

@ -1,20 +0,0 @@
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();
});
});
});