Add Delete an asset call to the ML (#11186)

This commit is contained in:
Marvin Frachet 2021-10-07 09:24:04 +02:00 committed by mfrachet
parent e11729184c
commit 4b311e777c
12 changed files with 1732 additions and 871 deletions

View File

@ -70,12 +70,12 @@
"prettier:code": "prettier \"**/*.js\"",
"prettier:other": "prettier \"**/*.{md,css,scss,yaml,yml}\"",
"test:clean": "rimraf ./coverage",
"test:front": "npm run test:clean && cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js",
"test:front:watch": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --watchAll",
"test:front:update": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --u",
"test:front:ce": "npm run test:clean && cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --coverage",
"test:front:watch:ce": "cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --watchAll",
"test:front:update:ce": "cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --u",
"test:front": "npm run test:clean && cross-env TZ=UTC NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js",
"test:front:watch": "cross-env TZ=UTC NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --watchAll",
"test:front:update": "cross-env TZ=UTC NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --u",
"test:front:ce": "npm run test:clean && cross-env TZ=UTC NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --coverage",
"test:front:watch:ce": "cross-env TZ=UTC NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --watchAll",
"test:front:update:ce": "cross-env TZ=UTC NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --u",
"test:snyk": "snyk test",
"test:unit": "jest --verbose",
"test:e2e": "FORCE_COLOR=true jest --config jest.config.e2e.js --verbose --runInBand --testRunner=jest-circus/runner --forceExit --detectOpenHandles",

View File

@ -1,7 +1,8 @@
import React from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import styled from 'styled-components';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Box } from '@strapi/parts/Box';
import { Row } from '@strapi/parts/Row';
import { Stack } from '@strapi/parts/Stack';
@ -10,7 +11,10 @@ import DeleteIcon from '@strapi/icons/DeleteIcon';
import DownloadIcon from '@strapi/icons/DownloadIcon';
import Resize from '@strapi/icons/Resize';
import LinkIcon from '@strapi/icons/LinkIcon';
import { prefixFileUrlWithBackendUrl, useNotification } from '@strapi/helper-plugin';
import getTrad from '../../utils/getTrad';
import { downloadFile } from '../../utils/downloadFile';
import { RemoveAssetDialog } from './RemoveAssetDialog';
const Wrapper = styled.div`
img {
@ -25,43 +29,84 @@ const ActionRow = styled(Row)`
height: ${52 / 16}rem;
`;
export const PreviewBox = ({ children }) => {
export const PreviewBox = ({ children, asset, onDelete }) => {
const toggleNotification = useNotification();
const { formatMessage } = useIntl();
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const handleCopyClipBoard = () => {
toggleNotification({
type: 'success',
message: { id: 'notification.link-copied', defaultMessage: 'Link copied into the clipboard' },
});
};
const assetUrl = prefixFileUrlWithBackendUrl(asset.url);
return (
<Box hasRadius background="neutral150" borderColor="neutral200">
<ActionRow paddingLeft={3} paddingRight={3} justifyContent="flex-end">
<Stack size={1} horizontal>
<IconButton
label={formatMessage({ id: getTrad('control-card.delete'), defaultMessage: 'Delete' })}
icon={<DeleteIcon />}
/>
<IconButton
label={formatMessage({
id: getTrad('control-card.download'),
defaultMessage: 'Download',
})}
icon={<DownloadIcon />}
/>
<IconButton
label={formatMessage({
id: getTrad('control-card.copy-link'),
defaultMessage: 'Copy link',
})}
icon={<LinkIcon />}
/>
<IconButton
label={formatMessage({ id: getTrad('control-card.crop'), defaultMessage: 'Crop' })}
icon={<Resize />}
/>
</Stack>
</ActionRow>
<Wrapper>{children}</Wrapper>
<ActionRow paddingLeft={3} paddingRight={3} />
</Box>
<>
<Box hasRadius background="neutral150" borderColor="neutral200">
<ActionRow paddingLeft={3} paddingRight={3} justifyContent="flex-end">
<Stack size={1} horizontal>
<IconButton
label={formatMessage({
id: getTrad('app.utils.delete'),
defaultMessage: 'Delete',
})}
icon={<DeleteIcon />}
onClick={() => setShowConfirmDialog(true)}
/>
<IconButton
label={formatMessage({
id: getTrad('control-card.download'),
defaultMessage: 'Download',
})}
icon={<DownloadIcon />}
onClick={() => downloadFile(assetUrl, asset.name)}
/>
<CopyToClipboard text={assetUrl} onCopy={handleCopyClipBoard}>
<IconButton
label={formatMessage({
id: getTrad('control-card.copy-link'),
defaultMessage: 'Copy link',
})}
icon={<LinkIcon />}
/>
</CopyToClipboard>
<IconButton
label={formatMessage({ id: getTrad('control-card.crop'), defaultMessage: 'Crop' })}
icon={<Resize />}
/>
</Stack>
</ActionRow>
<Wrapper>{children}</Wrapper>
<ActionRow paddingLeft={3} paddingRight={3} />
</Box>
{showConfirmDialog && (
<RemoveAssetDialog
onClose={() => {
setShowConfirmDialog(false);
onDelete();
}}
asset={asset}
/>
)}
</>
);
};
PreviewBox.propTypes = {
asset: PropTypes.shape({
id: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
size: PropTypes.number,
createdAt: PropTypes.string,
ext: PropTypes.string,
name: PropTypes.string,
url: PropTypes.string,
}).isRequired,
children: PropTypes.node.isRequired,
onDelete: PropTypes.func.isRequired,
};

View File

@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ConfirmDialog } from '@strapi/helper-plugin';
import { useRemoveAsset } from '../../hooks/useRemoveAsset';
export const RemoveAssetDialog = ({ onClose, asset }) => {
const { isLoading, removeAsset } = useRemoveAsset(onClose);
const handleConfirm = () => {
removeAsset(asset.id);
};
return (
<ConfirmDialog
isConfirmButtonLoading={isLoading}
isOpen
onToggleDialog={onClose}
onConfirm={handleConfirm}
/>
);
};
RemoveAssetDialog.propTypes = {
onClose: PropTypes.func.isRequired,
asset: PropTypes.shape({
id: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
size: PropTypes.number,
createdAt: PropTypes.string,
ext: PropTypes.string,
name: PropTypes.string,
url: PropTypes.string,
}).isRequired,
};

View File

@ -20,93 +20,94 @@ import { getTrad } from '../../utils';
import formatBytes from '../../utils/formatBytes';
export const EditAssetDialog = ({ onClose, asset }) => {
const { formatMessage } = useIntl();
const formatter = new Intl.DateTimeFormat();
const { formatMessage, formatDate } = useIntl();
// TODO implement when the code is ready
const handleSubmit = () => {};
return (
<ModalLayout onClose={onClose} labelledBy="title">
<ModalHeader>
<ButtonText textColor="neutral800" as="h2" id="title">
{formatMessage({ id: getTrad('modal.edit.title'), defaultMessage: 'Details' })}
</ButtonText>
</ModalHeader>
<ModalBody>
<Grid gap={4}>
<GridItem xs={12} col={6}>
<PreviewBox>
<img src={prefixFileUrlWithBackendUrl(asset.url)} alt={asset.name} />
</PreviewBox>
</GridItem>
<GridItem xs={12} col={6}>
<Stack size={3}>
<AssetMeta
size={formatBytes(asset.size)}
dimension={asset.height && asset.width ? `${asset.height}${asset.width}` : ''}
date={formatter.format(new Date(asset.createdAt))}
extension={getFileExtension(asset.ext)}
/>
<TextInput
size="S"
label={formatMessage({
id: getTrad('form.input.label.file-name'),
defaultMessage: 'File name',
})}
name="filename"
/>
<TextInput
size="S"
label={formatMessage({
id: getTrad('form.input.label.file-alt'),
defaultMessage: 'Alternative text',
})}
name="altText"
hint={formatMessage({
id: getTrad({ id: getTrad('form.input.decription.file-alt') }),
defaultMessage: 'This text will be displayed if the asset cant be shown.',
})}
/>
<TextInput
size="S"
label={formatMessage({
id: getTrad('form.input.label.file-caption'),
defaultMessage: 'Caption',
})}
name="caption"
/>
</Stack>
</GridItem>
</Grid>
</ModalBody>
<ModalFooter
startActions={
<Button onClick={onClose} variant="tertiary">
{formatMessage({ id: 'cancel', defaultMessage: 'Cancel' })}
</Button>
}
endActions={
<>
<Button variant="secondary">
{formatMessage({
id: getTrad('control-card.replace-media'),
defaultMessage: 'Replace media',
})}
<>
<ModalLayout onClose={onClose} labelledBy="title">
<ModalHeader>
<ButtonText textColor="neutral800" as="h2" id="title">
{formatMessage({ id: getTrad('modal.edit.title'), defaultMessage: 'Details' })}
</ButtonText>
</ModalHeader>
<ModalBody>
<Grid gap={4}>
<GridItem xs={12} col={6}>
<PreviewBox asset={asset} onDelete={onClose}>
<img src={prefixFileUrlWithBackendUrl(asset.url)} alt={asset.name} />
</PreviewBox>
</GridItem>
<GridItem xs={12} col={6}>
<Stack size={3}>
<AssetMeta
size={formatBytes(asset.size)}
dimension={asset.height && asset.width ? `${asset.height}${asset.width}` : ''}
date={formatDate(new Date(asset.createdAt))}
extension={getFileExtension(asset.ext)}
/>
<TextInput
size="S"
label={formatMessage({
id: getTrad('form.input.label.file-name'),
defaultMessage: 'File name',
})}
name="filename"
/>
<TextInput
size="S"
label={formatMessage({
id: getTrad('form.input.label.file-alt'),
defaultMessage: 'Alternative text',
})}
name="altText"
hint={formatMessage({
id: getTrad({ id: getTrad('form.input.decription.file-alt') }),
defaultMessage: 'This text will be displayed if the asset cant be shown.',
})}
/>
<TextInput
size="S"
label={formatMessage({
id: getTrad('form.input.label.file-caption'),
defaultMessage: 'Caption',
})}
name="caption"
/>
</Stack>
</GridItem>
</Grid>
</ModalBody>
<ModalFooter
startActions={
<Button onClick={onClose} variant="tertiary">
{formatMessage({ id: 'cancel', defaultMessage: 'Cancel' })}
</Button>
<Button onClick={handleSubmit}>
{formatMessage({ id: 'form.button.finish', defaultMessage: 'Finish' })}
</Button>
</>
}
/>
</ModalLayout>
}
endActions={
<>
<Button variant="secondary">
{formatMessage({
id: getTrad('control-card.replace-media'),
defaultMessage: 'Replace media',
})}
</Button>
<Button onClick={handleSubmit}>
{formatMessage({ id: 'form.button.finish', defaultMessage: 'Finish' })}
</Button>
</>
}
/>
</ModalLayout>
</>
);
};
EditAssetDialog.propTypes = {
asset: PropTypes.shape({
id: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
size: PropTypes.number,

View File

@ -0,0 +1,653 @@
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { ThemeProvider, lightTheme } from '@strapi/parts';
import { IntlProvider } from 'react-intl';
import { QueryClient, QueryClientProvider } from 'react-query';
import { NotificationsProvider } from '@strapi/helper-plugin';
import { RemoveAssetDialog } from '../RemoveAssetDialog';
import en from '../../../translations/en.json';
import server from './server';
const messageForPlugin = Object.keys(en).reduce((acc, curr) => {
acc[curr] = `upload.${en[curr]}`;
return acc;
}, {});
const asset = {
id: 8,
name: 'Screenshot 2.png',
alternativeText: null,
caption: null,
width: 1476,
height: 780,
formats: {
thumbnail: {
name: 'thumbnail_Screenshot 2.png',
hash: 'thumbnail_Screenshot_2_5d4a574d61',
ext: '.png',
mime: 'image/png',
width: 245,
height: 129,
size: 10.7,
path: null,
url: '/uploads/thumbnail_Screenshot_2_5d4a574d61.png',
},
large: {
name: 'large_Screenshot 2.png',
hash: 'large_Screenshot_2_5d4a574d61',
ext: '.png',
mime: 'image/png',
width: 1000,
height: 528,
size: 97.1,
path: null,
url: '/uploads/large_Screenshot_2_5d4a574d61.png',
},
medium: {
name: 'medium_Screenshot 2.png',
hash: 'medium_Screenshot_2_5d4a574d61',
ext: '.png',
mime: 'image/png',
width: 750,
height: 396,
size: 58.7,
path: null,
url: '/uploads/medium_Screenshot_2_5d4a574d61.png',
},
small: {
name: 'small_Screenshot 2.png',
hash: 'small_Screenshot_2_5d4a574d61',
ext: '.png',
mime: 'image/png',
width: 500,
height: 264,
size: 31.06,
path: null,
url: '/uploads/small_Screenshot_2_5d4a574d61.png',
},
},
hash: 'Screenshot_2_5d4a574d61',
ext: '.png',
mime: 'image/png',
size: 102.01,
url: '/uploads/Screenshot_2_5d4a574d61.png',
previewUrl: null,
provider: 'local',
provider_metadata: null,
createdAt: '2021-10-04T09:42:31.670Z',
updatedAt: '2021-10-04T09:42:31.670Z',
};
const renderCompo = (handleCloseSpy = jest.fn(), toggleNotificationSpy = jest.fn()) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});
return render(
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={lightTheme}>
<NotificationsProvider toggleNotification={toggleNotificationSpy}>
<IntlProvider locale="en" messages={messageForPlugin} defaultLocale="en">
<RemoveAssetDialog onClose={handleCloseSpy} asset={asset} />
</IntlProvider>
</NotificationsProvider>
</ThemeProvider>
</QueryClientProvider>,
{ container: document.body }
);
};
describe('RemoveAssetDialog', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('snapshots the component', () => {
const { container } = renderCompo();
expect(container).toMatchInlineSnapshot(`
.c0 {
border: 0;
-webkit-clip: rect(0 0 0 0);
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.c20 {
font-weight: 500;
font-size: 0.75rem;
line-height: 1.33;
color: #32324d;
}
.c23 {
padding-right: 8px;
}
.c17 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
cursor: pointer;
padding: 8px;
border-radius: 4px;
background: #ffffff;
border: 1px solid #dcdce4;
}
.c17 svg {
height: 12px;
width: 12px;
}
.c17 svg > g,
.c17 svg path {
fill: #ffffff;
}
.c17[aria-disabled='true'] {
pointer-events: none;
}
.c24 {
height: 100%;
}
.c18 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 8px 16px;
background: #4945ff;
border: none;
border: 1px solid #dcdce4;
background: #ffffff;
}
.c18 .c22 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c18 .c19 {
color: #ffffff;
}
.c18[aria-disabled='true'] {
border: 1px solid #dcdce4;
background: #eaeaef;
}
.c18[aria-disabled='true'] .c19 {
color: #666687;
}
.c18[aria-disabled='true'] svg > g,
.c18[aria-disabled='true'] svg path {
fill: #666687;
}
.c18[aria-disabled='true']:active {
border: 1px solid #dcdce4;
background: #eaeaef;
}
.c18[aria-disabled='true']:active .c19 {
color: #666687;
}
.c18[aria-disabled='true']:active svg > g,
.c18[aria-disabled='true']:active svg path {
fill: #666687;
}
.c18:hover {
background-color: #f6f6f9;
}
.c18:active {
background-color: #eaeaef;
}
.c18 .c19 {
color: #32324d;
}
.c18 svg > g,
.c18 svg path {
fill: #32324d;
}
.c21 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 8px 16px;
background: #4945ff;
border: none;
border: 1px solid #f5c0b8;
background: #fcecea;
}
.c21 .c22 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c21 .c19 {
color: #ffffff;
}
.c21[aria-disabled='true'] {
border: 1px solid #dcdce4;
background: #eaeaef;
}
.c21[aria-disabled='true'] .c19 {
color: #666687;
}
.c21[aria-disabled='true'] svg > g,
.c21[aria-disabled='true'] svg path {
fill: #666687;
}
.c21[aria-disabled='true']:active {
border: 1px solid #dcdce4;
background: #eaeaef;
}
.c21[aria-disabled='true']:active .c19 {
color: #666687;
}
.c21[aria-disabled='true']:active svg > g,
.c21[aria-disabled='true']:active svg path {
fill: #666687;
}
.c21:hover {
background-color: #ffffff;
}
.c21:active {
background-color: #ffffff;
border: 1px solid #d02b20;
}
.c21:active .c19 {
color: #d02b20;
}
.c21:active svg > g,
.c21:active svg path {
fill: #d02b20;
}
.c21 .c19 {
color: #b72b1a;
}
.c21 svg > g,
.c21 svg path {
fill: #b72b1a;
}
.c2 {
background: #ffffff;
border-radius: 4px;
box-shadow: 0px 2px 15px rgba(33,33,52,0.1);
}
.c4 {
padding: 24px;
}
.c8 {
padding-top: 40px;
padding-right: 24px;
padding-bottom: 40px;
padding-left: 24px;
}
.c9 {
padding-bottom: 8px;
}
.c14 {
padding: 16px;
}
.c5 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c7 {
font-weight: 600;
font-size: 1.125rem;
line-height: 1.22;
color: #32324d;
}
.c1 {
position: fixed;
z-index: 4;
inset: 0;
background: #32324d33;
padding: 0 40px;
}
.c3 {
max-width: 25.75rem;
margin: 0 auto;
overflow: hidden;
margin-top: 10%;
}
.c6 {
border-bottom: 1px solid #eaeaef;
}
.c10 svg {
width: 24px;
height: 24px;
}
.c10 svg path {
fill: #d02b20;
}
.c16 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
}
.c16 > * {
margin-left: 0;
margin-right: 0;
}
.c16 > * + * {
margin-left: 8px;
}
.c15 {
border-top: 1px solid #eaeaef;
}
.c15 button {
width: 100%;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c12 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c11 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.c11 > * {
margin-top: 0;
margin-bottom: 0;
}
.c11 > * + * {
margin-top: 8px;
}
.c13 {
font-weight: 400;
font-size: 0.875rem;
line-height: 1.43;
color: #32324d;
}
<body
class="lock-body-scroll"
>
<div
class="c0"
>
<p
aria-live="polite"
aria-relevant="all"
id="live-region-log"
role="log"
/>
<p
aria-live="polite"
aria-relevant="all"
id="live-region-status"
role="status"
/>
<p
aria-live="assertive"
aria-relevant="all"
id="live-region-alert"
role="alert"
/>
</div>
<div
data-react-portal="true"
>
<div
class="c1"
>
<div>
<div
aria-labelledby="dialog-1-label"
aria-modal="true"
class="c2 c3"
role="dialog"
>
<div
class="c4 c5 c6"
>
<h2
class="c7"
id="dialog-1-label"
>
Confirmation
</h2>
</div>
<div
class=""
>
<div
class="c8"
>
<div
class="c9 c10"
>
<div
class="c5"
>
<svg
fill="none"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12 12-5.383 12-12S18.617 0 12 0zm1.154 18.456h-2.308V16.15h2.308v2.307zm-.23-3.687h-1.847l-.346-9.23h2.538l-.346 9.23z"
fill="#212134"
/>
</svg>
</div>
</div>
<div
class="c11"
>
<div
class="c12"
>
<span
class="c13"
id="confirm-description"
>
Are you sure you want to delete this?
</span>
</div>
</div>
</div>
<div
class="c14 c15"
>
<div
class="c16"
>
<button
aria-disabled="false"
class="c17 c18"
type="button"
>
<span
class="c19 c20"
>
Cancel
</span>
</button>
<button
aria-disabled="false"
class="c17 c21"
id="confirm-delete"
type="button"
>
<div
aria-hidden="true"
class="c22 c23 c24"
>
<svg
fill="none"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.236 6.149a.2.2 0 00-.197.233L6 24h12l2.96-17.618a.2.2 0 00-.196-.233H3.236zM21.8 1.983c.11 0 .2.09.2.2v1.584a.2.2 0 01-.2.2H2.2a.2.2 0 01-.2-.2V2.183c0-.11.09-.2.2-.2h5.511c.9 0 1.631-1.09 1.631-1.983h5.316c0 .894.73 1.983 1.631 1.983H21.8z"
fill="#32324D"
/>
</svg>
</div>
<span
class="c19 c20"
>
Confirm
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
`);
});
it('closes the dialog when pressing cancel', () => {
const handleCloseSpy = jest.fn();
renderCompo(handleCloseSpy);
fireEvent.click(screen.getByText('Cancel'));
expect(handleCloseSpy).toHaveBeenCalled();
});
describe('remove asset', () => {
it('closes the dialog when everything is going okay when removing', async () => {
const handleCloseSpy = jest.fn();
const toggleNotificationSpy = jest.fn();
renderCompo(handleCloseSpy, toggleNotificationSpy);
fireEvent.click(screen.getByText('Confirm'));
await waitFor(() => expect(handleCloseSpy).toHaveBeenCalled());
await waitFor(() =>
expect(toggleNotificationSpy).toHaveBeenCalledWith({
message: {
defaultMessage: 'The asset has been successfully removed.',
id: 'modal.remove.success-label',
},
type: 'success',
})
);
});
});
});

View File

@ -0,0 +1,12 @@
import { setupServer } from 'msw/node';
import { rest } from 'msw';
const handlers = [
rest.delete('*/upload/files/8', (req, res, ctx) => {
return res(ctx.status(200));
}),
];
const server = setupServer(...handlers);
export default server;

View File

@ -0,0 +1,44 @@
import { useMutation, useQueryClient } from 'react-query';
import { useNotification } from '@strapi/helper-plugin';
import { axiosInstance } from '../utils';
const removeAssetRequest = assetId => {
const endpoint = `/upload/files/${assetId}`;
return axiosInstance({
method: 'delete',
url: endpoint,
headers: {},
});
};
export const useRemoveAsset = onSuccess => {
const toggleNotification = useNotification();
const queryClient = useQueryClient();
const mutation = useMutation(assetId => removeAssetRequest(assetId), {
onSuccess: ({ data }) => {
// Coupled with the cache of useAssets
queryClient.setQueryData('assets', (cachedAssets = []) =>
cachedAssets.filter(asset => asset.id !== data.id)
);
toggleNotification({
type: 'success',
message: {
id: 'modal.remove.success-label',
defaultMessage: 'The asset has been successfully removed.',
},
});
onSuccess();
},
onError: error => {
toggleNotification({ type: 'warning', message: error.message });
},
});
const removeAsset = assetId => mutation.mutate(assetId);
return { ...mutation, removeAsset };
};

View File

@ -92,5 +92,6 @@
"sort.updated_at_desc": "Most recent updates",
"tabs.title": "How do you want to upload your assets?",
"window.confirm.close-modal.file": "Are you sure? Your changes will be lost.",
"window.confirm.close-modal.files": "Are you sure? You have some files that have not been uploaded yet."
"window.confirm.close-modal.files": "Are you sure? You have some files that have not been uploaded yet.",
"modal.remove.success-label": "The asset has been successfully removed."
}

View File

@ -0,0 +1,16 @@
import axios from 'axios';
export const downloadFile = (url, fileName) => {
axios({
url,
method: 'GET',
responseType: 'blob',
}).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
link.click();
});
};

View File

@ -78,10 +78,8 @@ module.exports = {
},
async destroy(ctx) {
const {
state: { userAbility },
params: { id },
} = ctx;
const { id } = ctx.params;
const { userAbility } = ctx.state;
const { pm, file } = await findEntityAndCheckPermissions(
userAbility,
@ -218,9 +216,7 @@ const findEntityAndCheckPermissions = async (ability, action, model, id) => {
const pm = strapi.admin.services.permission.createPermissionsManager({ ability, action, model });
const author = await strapi.admin.services.user.findOne({ id: file[CREATED_BY_ATTRIBUTE].id }, [
'roles',
]);
const author = await strapi.admin.services.user.findOne(file[CREATED_BY_ATTRIBUTE].id, ['roles']);
const fileWithRoles = _.set(_.cloneDeep(file), 'createdBy', author);

View File

@ -22,3 +22,5 @@ global.strapi = {
},
projectType: 'Community',
};
global.prompt = jest.fn();