Skip to content

Commit

Permalink
fix: [M3-6540] - Surface general errors in the Object Storage Bucket …
Browse files Browse the repository at this point in the history
…Create Drawer (#9067)

Surfaces any general errors as a notice in the Object Storage Create Bucket drawer
Previously, only errors specific to the label and region would show

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
  • Loading branch information
bnussman-akamai and bnussman authored May 2, 2023
1 parent 46e4754 commit 4a6b0b9
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { CreateBucketDrawer } from './CreateBucketDrawer';
import { waitFor } from '@testing-library/react';
import { renderWithTheme } from 'src/utilities/testHelpers';
import userEvent from '@testing-library/user-event';
import { rest, server } from 'src/mocks/testServer';
import { makeResourcePage } from 'src/mocks/serverHandlers';
import {
accountSettingsFactory,
objectStorageClusterFactory,
regionFactory,
} from 'src/factories';

const props = {
isOpen: true,
onClose: jest.fn(),
};

jest.mock('src/components/EnhancedSelect/Select');

describe('CreateBucketDrawer', () => {
it('Should show a general error notice if the API returns one', async () => {
server.use(
rest.post('*/object-storage/buckets', (req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({ errors: [{ reason: 'Object Storage is offline!' }] })
);
}),
rest.get('*/regions', async (req, res, ctx) => {
return res(
ctx.json(
makeResourcePage(
regionFactory.buildList(1, { id: 'us-east', label: 'Newark, NJ' })
)
)
);
}),
rest.get('*object-storage/clusters', (req, res, ctx) => {
return res(
ctx.json(
makeResourcePage(
objectStorageClusterFactory.buildList(1, {
region: 'us-east',
id: 'us-east-1',
})
)
)
);
}),
rest.get('*/account/settings', (req, res, ctx) => {
return res(
ctx.json(accountSettingsFactory.build({ object_storage: 'active' }))
);
})
);

const {
getByTestId,
getByLabelText,
getByPlaceholderText,
findByText,
} = renderWithTheme(<CreateBucketDrawer {...props} />);

userEvent.type(getByLabelText('Label'), 'my-test-bucket');

// We must waitFor because we need to load region and cluster data from the API
await waitFor(() =>
userEvent.selectOptions(
getByPlaceholderText('Select a Region'),
'Newark, NJ (us-east-1)'
)
);

const saveButton = getByTestId('create-bucket-button');

userEvent.click(saveButton);

await findByText('Object Storage is offline!');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const CreateBucketDrawer = (props: Props) => {
error,
reset,
} = useCreateBucketMutation();

const { data: agreements } = useAccountAgreements();
const { mutateAsync: updateAccountAgreements } = useMutateAccountAgreements();
const { data: accountSettings } = useAccountSettings();
Expand Down Expand Up @@ -121,6 +122,7 @@ export const CreateBucketDrawer = (props: Props) => {
data-qa-permissions-notice
/>
)}
{Boolean(errorMap.none) && <Notice error text={errorMap.none} />}
<TextField
data-qa-cluster-label
label="Label"
Expand All @@ -134,7 +136,7 @@ export const CreateBucketDrawer = (props: Props) => {
/>
<ClusterSelect
data-qa-cluster-select
error={formik.touched.cluster ? errorMap.cluster : undefined}
error={errorMap.cluster}
onBlur={formik.handleBlur}
onChange={(value) => formik.setFieldValue('cluster', value)}
selectedCluster={formik.values.cluster}
Expand All @@ -153,7 +155,12 @@ export const CreateBucketDrawer = (props: Props) => {
<Button buttonType="secondary" onClick={onClose}>
Cancel
</Button>
<Button buttonType="primary" type="submit" loading={isLoading}>
<Button
buttonType="primary"
type="submit"
loading={isLoading}
data-testid="create-bucket-button"
>
Create Bucket
</Button>
</ActionsPanel>
Expand Down
3 changes: 3 additions & 0 deletions packages/manager/src/mocks/serverHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,9 @@ export const handlers = [
const buckets = objectStorageBucketFactory.buildList(10);
return res(ctx.json(makeResourcePage(buckets)));
}),
rest.post('*/object-storage/buckets', (req, res, ctx) => {
return res(ctx.json(objectStorageBucketFactory.build()));
}),
rest.get('*object-storage/clusters', (req, res, ctx) => {
const clusters = objectStorageClusterFactory.buildList(3);
return res(ctx.json(makeResourcePage(clusters)));
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/queries/objectStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
getObjectURL,
ObjectStorageObjectURLOptions,
ObjectStorageObjectURL,
} from '@linode/api-v4/lib/object-storage';
} from '@linode/api-v4';
import { queryKey as accountSettingsQueryKey } from './accountSettings';

export interface BucketError {
Expand Down
8 changes: 8 additions & 0 deletions packages/manager/src/utilities/testHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import thunk from 'redux-thunk';
import { FlagSet } from 'src/featureFlags';
import LinodeThemeWrapper from 'src/LinodeThemeWrapper';
import { queryClientFactory } from 'src/queries/base';
import { setupInterceptors } from 'src/request';
import {
storeFactory,
ApplicationState,
Expand Down Expand Up @@ -55,6 +56,13 @@ export const wrapWithTheme = (ui: any, options: Options = {}) => {
const storeToPass = customStore
? baseStore(customStore)
: storeFactory(queryClient);

// we have to call setupInterceptors so that our API error normalization works as expected
// I'm sorry that it makes us pass it the "ApplicationStore"
setupInterceptors(
configureStore<ApplicationState>([thunk])(defaultState)
);

return (
<Provider store={storeToPass}>
<QueryClientProvider client={passedQueryClient || queryClient}>
Expand Down

0 comments on commit 4a6b0b9

Please sign in to comment.