From 63c5e67d079f0df8bbae300d2a33b92edc42f88e Mon Sep 17 00:00:00 2001 From: Joshua Jones Date: Wed, 12 Oct 2022 13:38:16 -0700 Subject: [PATCH] Feature/sprint 56 UI fixes (#596) * Add fixes for deleting application optimistically and preventing saves in controls dialog * Tighten up applications mocks, errors --- ...alhost4180devportalapplications-7fc130.gql | 2 +- ...estsnew610c1e2329ee4c2072b16fa8-312522.gql | 2 +- src/mocks/handlers.js | 9 ++ src/mocks/resolvers/applications.js | 98 +++++++++++++++++++ .../access-request-dialog.tsx | 10 +- .../application-select.tsx | 2 +- .../components/access-request/edit-dialog.tsx | 22 ++++- .../new-application-dialog.tsx | 5 +- .../pages/devportal/applications/index.tsx | 74 ++++++++------ 9 files changed, 183 insertions(+), 41 deletions(-) create mode 100644 src/mocks/resolvers/applications.js diff --git a/src/authz/graphql-whitelist/httplocalhost4180devportalapplications-7fc130.gql b/src/authz/graphql-whitelist/httplocalhost4180devportalapplications-7fc130.gql index b8ab34974..9a1989902 100644 --- a/src/authz/graphql-whitelist/httplocalhost4180devportalapplications-7fc130.gql +++ b/src/authz/graphql-whitelist/httplocalhost4180devportalapplications-7fc130.gql @@ -1,5 +1,5 @@ - mutation Remove($id: ID!) { + mutation RemoveApplication($id: ID!) { deleteApplication(id: $id) { name id diff --git a/src/authz/graphql-whitelist/httplocalhost4180devportalrequestsnew610c1e2329ee4c2072b16fa8-312522.gql b/src/authz/graphql-whitelist/httplocalhost4180devportalrequestsnew610c1e2329ee4c2072b16fa8-312522.gql index 1d2a0294c..e6ece6fe4 100644 --- a/src/authz/graphql-whitelist/httplocalhost4180devportalrequestsnew610c1e2329ee4c2072b16fa8-312522.gql +++ b/src/authz/graphql-whitelist/httplocalhost4180devportalrequestsnew610c1e2329ee4c2072b16fa8-312522.gql @@ -1,5 +1,5 @@ - mutation Add($name: String!, $description: String) { + mutation AddApplication($name: String!, $description: String) { createApplication(data: { name: $name, description: $description }) { id appId diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index d82348bf2..be8f66966 100644 --- a/src/mocks/handlers.js +++ b/src/mocks/handlers.js @@ -1,6 +1,11 @@ import { graphql, rest } from 'msw'; import { harley, mark } from './resolvers/personas'; +import { + allApplicationsHandler, + createApplicationHandler, + removeApplicationHandler, +} from './resolvers/applications'; import { allProductsByNamespaceHandler, accessRequestAuthHandler, @@ -116,6 +121,10 @@ export const handlers = [ 'GetConsumerProductsAndEnvironments', allProductsByNamespaceHandler ), + // Applications + keystone.query('MyApplications', allApplicationsHandler), + keystone.mutation('AddApplication', createApplicationHandler), + keystone.mutation('RemoveApplication', removeApplicationHandler), // Service accounts keystone.query('GetAllServiceAccounts', getAllServiceAccountsHandler), keystone.mutation('CreateServiceAccount', createServiceAccountHandler), diff --git a/src/mocks/resolvers/applications.js b/src/mocks/resolvers/applications.js new file mode 100644 index 000000000..07f048226 --- /dev/null +++ b/src/mocks/resolvers/applications.js @@ -0,0 +1,98 @@ +import casual from 'casual-browserify'; + +const applications = new Map([ + [ + 'app1', + { + id: 'app1', + appId: 'ABC123', + description: '', + name: 'Demo App', + owner: { + name: 'Mark Smith', + }, + }, + ], + [ + 'app2', + { + id: 'app2', + appId: 'ABC123', + description: '', + name: 'Shoppers Drug Mart 112', + owner: { + name: 'Mark Smith', + }, + }, + ], + [ + 'app3error', + { + id: 'app3error', + appId: 'ABC123', + description: '', + name: 'Easy Mart Store 123', + owner: { + name: 'Carter Schleifer', + }, + }, + ], +]); + +export const allApplicationsHandler = (req, res, ctx) => { + return res( + ctx.data({ + myApplications: Array.from(applications.values()), + allTemporaryIdentities: [ + { + id: 'u1', + userId: '1', + }, + ], + }) + ); +}; + +export const createApplicationHandler = (req, res, ctx) => { + if (req.variables.name === 'error') { + return res( + ctx.data({ + errors: [{ message: 'You do not have permission' }], + }) + ); + } + + const record = { + id: `app1${applications.size + 1}`, + appId: casual.uuid, + ...req.variables, + }; + applications.set(record.id, record); + return res( + ctx.data({ + createApplication: record, + }) + ); +}; + +export const removeApplicationHandler = (req, res, ctx) => { + const { id } = req.variables; + + if (id === 'app3error') { + return res( + ctx.data({ + errors: [ + { message: 'You do not have permission to delete this application' }, + ], + }) + ); + } + + const application = applications.get(id); + applications.delete(id); + return res( + ctx.data({ + deleteApplication: application, + }) + ); +}; diff --git a/src/nextapp/components/access-request-form/access-request-dialog.tsx b/src/nextapp/components/access-request-form/access-request-dialog.tsx index 362441f83..6fc8430da 100644 --- a/src/nextapp/components/access-request-form/access-request-dialog.tsx +++ b/src/nextapp/components/access-request-form/access-request-dialog.tsx @@ -71,11 +71,11 @@ const AccessRequestDialog: React.FC = ({ const buttonProps = !isInline ? {} : { - fontWeight: 'normal', - fontSize: 'inherit', - color: 'bc-link', - textDecor: 'underline', - }; + fontWeight: 'normal', + fontSize: 'inherit', + color: 'bc-link', + textDecor: 'underline', + }; // Events const handleAccessSubmit = React.useCallback( diff --git a/src/nextapp/components/access-request-form/application-select.tsx b/src/nextapp/components/access-request-form/application-select.tsx index eba1db464..fe761f070 100644 --- a/src/nextapp/components/access-request-form/application-select.tsx +++ b/src/nextapp/components/access-request-form/application-select.tsx @@ -213,7 +213,7 @@ const query = gql` `; const mutation = gql` - mutation Add($name: String!, $description: String) { + mutation AddApplication($name: String!, $description: String) { createApplication(data: { name: $name, description: $description }) { id appId diff --git a/src/nextapp/components/access-request/edit-dialog.tsx b/src/nextapp/components/access-request/edit-dialog.tsx index fe2774b03..d4fbd9a1b 100644 --- a/src/nextapp/components/access-request/edit-dialog.tsx +++ b/src/nextapp/components/access-request/edit-dialog.tsx @@ -19,11 +19,12 @@ import { Grid, useToast, Flex, + Alert, + AlertDescription, + AlertIcon, } from '@chakra-ui/react'; import { ConsumerPlugin, - GatewayPlugin, - GatewayPluginCreateInput, } from '@/shared/types/query.types'; import EnvironmentTag from '@/components/environment-tag'; import format from 'date-fns/format'; @@ -56,6 +57,7 @@ const ConsumerEditDialog: React.FC = ({ { suspense: false, enabled: isOpen } ); const [tabIndex, setTabIndex] = React.useState(0); + const [hasUnsavedChanges, setHasUnsavedChanges] = React.useState(false); const restrictions = React.useState(() => { return ( data?.getConsumerProdEnvAccess.plugins @@ -93,6 +95,7 @@ const ConsumerEditDialog: React.FC = ({ const handleClose = () => { const [, setRestrictions] = restrictions; const [, setRateLimits] = rateLimits; + setHasUnsavedChanges(false); setRestrictions( data?.getConsumerProdEnvAccess.plugins .filter((p) => p.name === 'ip-restriction') @@ -124,10 +127,16 @@ const ConsumerEditDialog: React.FC = ({ const data = { plugins: [...restrictsionsData, ...rateLimitsData], }; - const authorizationForm = ref?.current.querySelector( 'form[name="authorizationForm"]' ); + const ipRestrictionForm = new FormData(ref?.current.querySelector('form[name="ipRestrictionsForm"]')); + + if (ipRestrictionForm.get('allow') !== '[]') { + setHasUnsavedChanges(true); + return false; + } + if (authorizationForm) { const authorizationFormData = new FormData(authorizationForm); const defaultClientScopes = authorizationFormData.getAll( @@ -153,6 +162,7 @@ const ConsumerEditDialog: React.FC = ({ client.invalidateQueries(['consumerEdit', prodEnvId, consumerId]); onClose(); setTabIndex(0); + setHasUnsavedChanges(false); } catch (err) { toast({ title: 'Request save failed', @@ -249,6 +259,12 @@ const ConsumerEditDialog: React.FC = ({ {isSuccess && ( + {hasUnsavedChanges && ( + + + You have an unapplied IP Restrictions control. + + )}