Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancement(CM): bulk delete and unpublish migration to v5 #20235

Merged
merged 33 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7c70880
fix: added bulk actions
madhurisandbhor Apr 4, 2024
a2e14b4
chore: migrate bulk publish & unpublish to v5
Feranchz Apr 23, 2024
5693666
chore: change findLocales type to accept arrays
Feranchz Apr 24, 2024
6357df1
Merge branch 'v5/main' of github.com:strapi/strapi into v5/bulk-publi…
Feranchz Apr 24, 2024
df2d2f0
chore: fix lint error
Feranchz Apr 24, 2024
a63f691
Merge branch 'v5/main' of github.com:strapi/strapi into v5/bulk-publi…
Feranchz Apr 25, 2024
b1574bc
chore: migrate bulkDelete to v5 (#20161)
Feranchz Apr 25, 2024
80dc8b8
Merge branch 'v5/main' into fix/bulk-actions-migration
madhurisandbhor Apr 26, 2024
d17b241
Merge branch 'v5/bulk-publish-unpublish' into fix/bulk-actions-migration
madhurisandbhor Apr 26, 2024
a50e1df
enhancement: migrate countManyDraftRelations to v5
Feranchz Apr 26, 2024
a241141
Merge branch 'v5/bulk-publish-unpublish' of github.com:strapi/strapi …
Feranchz Apr 26, 2024
49a3dbb
Merge branch 'v5/main' of github.com:strapi/strapi into v5/bulk-publi…
Feranchz Apr 26, 2024
6dd70ca
feat: bulk delete with locale selection option
madhurisandbhor Apr 29, 2024
f231f25
fix: bulk unpublish working without locale support
madhurisandbhor Apr 29, 2024
6ba5cee
Merge branch 'v5/main' into fix/bulk-actions-migration
madhurisandbhor Apr 29, 2024
98e818d
test: e2e tests updated
madhurisandbhor Apr 29, 2024
f74b03b
fix: documents api change, deleteMany tags updated, test skipped
madhurisandbhor Apr 30, 2024
ca69188
chore: renaming
madhurisandbhor Apr 30, 2024
6749b4d
test: reverting e2e tests for delete action changes
madhurisandbhor Apr 30, 2024
fee12d7
Merge branch 'v5/bulk-publish-unpublish' into fix/bulk-actions-migration
madhurisandbhor Apr 30, 2024
6b3dd96
fix: reverting publish action changes
madhurisandbhor Apr 30, 2024
9a65933
fix: hide add to release as its not ready to be migrated
madhurisandbhor Apr 30, 2024
504bede
fix: error message updated
madhurisandbhor Apr 30, 2024
5abb934
fix: tests updated for useDocumentAction
madhurisandbhor Apr 30, 2024
258b953
fix: updated request params
madhurisandbhor May 2, 2024
52e02eb
fix: reverting wrong commit message
madhurisandbhor May 2, 2024
453c50b
fix: comments updated
madhurisandbhor May 2, 2024
11614ff
chore: renaming
madhurisandbhor May 3, 2024
a4e2e61
chore: add type to bulk action actions (#20256)
joshuaellis May 3, 2024
3739cf0
chore: actionType renamed to just type
madhurisandbhor May 3, 2024
57cc559
Merge branch 'v5/main' of github.com:strapi/strapi into fix/bulk-acti…
Feranchz May 7, 2024
9a1e8dd
fix: dont show unpublish button on contentTypes without draft and pub…
Feranchz May 8, 2024
57d794e
fix: set a fixed width for FieldActionWrapper on UID input
Feranchz May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/admin/admin/src/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ const CheckboxCell = ({ id, ...props }: Table.CheckboxCellProps) => {
<Cell {...props} onClick={(e) => e.stopPropagation()}>
<BaseCheckbox
aria-label={formatMessage({
id: 'global.select-all-entries',
defaultMessage: 'Select all entries',
id: 'global.select',
defaultMessage: 'Select',
})}
disabled={rows.length === 0}
checked={isChecked}
Expand Down
3 changes: 3 additions & 0 deletions packages/core/admin/admin/src/features/Tracking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ interface EventWithoutProperties {
| 'didAccessAuthenticatedAdministration'
| 'didAddComponentToDynamicZone'
| 'didBulkDeleteEntries'
| 'didNotBulkDeleteEntries'
| 'didChangeDisplayedFields'
| 'didCheckDraftRelations'
| 'didClickGuidedTourHomepageApiTokens'
Expand Down Expand Up @@ -132,6 +133,8 @@ interface EventWithoutProperties {
| 'didPluginLearnMore'
| 'didPublishEntry'
| 'didUnpublishEntry'
| 'didBulkUnpublishEntries'
| 'didNotBulkUnpublishEntries'
| 'didSaveComponent'
| 'didSaveContentType'
| 'didSearch'
Expand Down
6 changes: 3 additions & 3 deletions packages/core/content-manager/admin/src/content-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ interface ListViewContext {
*/
collectionType: string;
/**
* TODO: add comment
* The current selected documents in the table
*/
documentIds: string[];
documents: Document[];
/**
* The current content-type's model.
*/
Expand Down Expand Up @@ -106,7 +106,7 @@ interface BulkActionComponentProps extends ListViewContext {}

interface BulkActionComponent
extends DescriptionComponent<BulkActionComponentProps, BulkActionDescription> {
actionType?: 'delete' | 'publish' | 'unpublish';
type?: 'delete' | 'publish' | 'unpublish';
}

/* -------------------------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ describe('useDocumentActions', () => {
create: expect.any(Function),
discard: expect.any(Function),
delete: expect.any(Function),
deleteMany: expect.any(Function),
getDocument: expect.any(Function),
publish: expect.any(Function),
update: expect.any(Function),
unpublish: expect.any(Function),
unpublishMany: expect.any(Function),
});
});

Expand Down Expand Up @@ -243,6 +245,71 @@ describe('useDocumentActions', () => {
});
});

describe('bulk delete', () => {
it('should return the deleted documents when successful', async () => {
const { result } = renderHook(() => useDocumentActions());

let response;

await act(async () => {
const res = await result.current.deleteMany({
model: mockData.contentManager.contentType,
documentIds: ['12345', '6789'],
params: {
locale: 'en',
},
});

response = res;
});

expect(response).toMatchInlineSnapshot(`
{
"count": 2,
}
`);
});

it('should return the errors when unsuccessful', async () => {
server.use(
rest.post('/content-manager/collection-types/:uid/actions/bulkDelete', (_, res, ctx) => {
return res(
ctx.status(500),
ctx.json({
error: new errors.ApplicationError("Couldn't delete entries."),
})
);
})
);

const { result } = renderHook(() => useDocumentActions());

let response;

await act(async () => {
const res = await result.current.deleteMany({
model: mockData.contentManager.contentType,
documentIds: ['12345', '6789'],
params: { locale: 'en' },
});

response = res;
});

expect(response).toMatchInlineSnapshot(`
{
"error": {
"details": {},
"message": "Couldn't delete entries.",
"name": "ApplicationError",
},
}
`);

await screen.findByText("Couldn't delete entries.");
});
});

describe('discard', () => {
it('should return the discarded document when successful', async () => {
const { result } = renderHook(() => useDocumentActions());
Expand Down Expand Up @@ -597,4 +664,65 @@ describe('useDocumentActions', () => {
await screen.findByText("Couldn't unpublish entry.");
});
});

describe('bulk unpublish', () => {
it('should return the unpublished document when successful', async () => {
const { result } = renderHook(() => useDocumentActions());

let response;

await act(async () => {
const res = await result.current.unpublishMany({
model: mockData.contentManager.contentType,
documentIds: ['12345', '6789'],
});

response = res;
});

expect(response).toMatchInlineSnapshot(`
{
"count": 2,
}
`);
});

it('should return the errors when unsuccessful', async () => {
server.use(
rest.post('/content-manager/collection-types/:uid/actions/bulkUnpublish', (_, res, ctx) => {
return res(
ctx.status(500),
ctx.json({
error: new errors.ApplicationError("Couldn't unpublish entries."),
})
);
})
);

const { result } = renderHook(() => useDocumentActions());

let response;

await act(async () => {
const res = await result.current.unpublishMany({
model: mockData.contentManager.contentType,
documentIds: ['12345', '6789'],
});

response = res;
});

expect(response).toMatchInlineSnapshot(`
{
"error": {
"details": {},
"message": "Couldn't unpublish entries.",
"name": "ApplicationError",
},
}
`);

await screen.findByText("Couldn't unpublish entries.");
});
});
});
112 changes: 112 additions & 0 deletions packages/core/content-manager/admin/src/hooks/useDocumentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
useCloneDocumentMutation,
useCreateDocumentMutation,
useDeleteDocumentMutation,
useDeleteManyDocumentsMutation,
useDiscardDocumentMutation,
useLazyGetDocumentQuery,
usePublishDocumentMutation,
useUnpublishDocumentMutation,
useUnpublishManyDocumentsMutation,
useUpdateDocumentMutation,
} from '../services/documents';
import { BaseQueryError } from '../utils/api';
Expand All @@ -29,11 +31,13 @@ import type {
Clone,
Create,
Delete,
BulkDelete,
Discard,
FindOne,
Publish,
Update,
Unpublish,
BulkUnpublish,
} from '../../../shared/contracts/collection-types';

const DEFAULT_UNEXPECTED_ERROR_MSG = {
Expand All @@ -45,6 +49,10 @@ type OperationResponse<TResponse extends { data: any; meta: any; error?: any }>
| Pick<TResponse, 'data' | 'meta'>
| { error: BaseQueryError | SerializedError };

type BulkOperationResponse<TResponse extends { data: any; error?: any }> =
| Pick<TResponse, 'data'>
| { error: BaseQueryError | SerializedError };

type UseDocumentActions = () => {
/**
* @description Attempts to clone a document based on the provided sourceId.
Expand Down Expand Up @@ -90,6 +98,11 @@ type UseDocumentActions = () => {
{ name: 'willDeleteEntry' | 'didDeleteEntry' | 'didNotDeleteEntry' }
>['properties']
) => Promise<OperationResponse<Delete.Response>>;
deleteMany: (args: {
model: string;
documentIds: string[];
params?: object;
}) => Promise<BulkOperationResponse<BulkDelete.Response>>;
discard: (args: {
collectionType: string;
model: string;
Expand Down Expand Up @@ -133,6 +146,11 @@ type UseDocumentActions = () => {
},
discardDraft?: boolean
) => Promise<OperationResponse<Unpublish.Response>>;
unpublishMany: (args: {
model: string;
documentIds: string[];
params?: object;
}) => Promise<BulkOperationResponse<BulkUnpublish.Response>>;
};

type IUseDocumentActs = ReturnType<UseDocumentActions>;
Expand Down Expand Up @@ -213,6 +231,54 @@ const useDocumentActions: UseDocumentActions = () => {
[trackUsage, deleteDocument, toggleNotification, formatMessage, formatAPIError]
);

const [deleteManyDocuments] = useDeleteManyDocumentsMutation();

const deleteMany: IUseDocumentActs['deleteMany'] = React.useCallback(
async ({ model, documentIds, params }) => {
try {
trackUsage('willBulkDeleteEntries');

const res = await deleteManyDocuments({
model,
documentIds,
params,
});

if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error),
});

return { error: res.error };
}

toggleNotification({
type: 'success',
title: formatMessage({
id: getTranslation('success.records.delete'),
defaultMessage: 'Successfully deleted.',
}),
message: '',
});

trackUsage('didBulkDeleteEntries');

return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG),
});

trackUsage('didNotBulkDeleteEntries');

throw err;
}
},
[trackUsage, deleteManyDocuments, toggleNotification, formatMessage, formatAPIError]
);

const [discardDocument] = useDiscardDocumentMutation();
const discard: IUseDocumentActs['discard'] = React.useCallback(
async ({ collectionType, model, documentId }) => {
Expand Down Expand Up @@ -386,6 +452,50 @@ const useDocumentActions: UseDocumentActions = () => {
[trackUsage, unpublishDocument, toggleNotification, formatMessage, formatAPIError]
);

const [unpublishManyDocuments] = useUnpublishManyDocumentsMutation();
const unpublishMany: IUseDocumentActs['unpublishMany'] = React.useCallback(
async ({ model, documentIds, params }) => {
try {
trackUsage('willBulkUnpublishEntries');

const res = await unpublishManyDocuments({
model,
documentIds,
params,
});

if ('error' in res) {
toggleNotification({ type: 'danger', message: formatAPIError(res.error) });

return { error: res.error };
}

trackUsage('didBulkUnpublishEntries');

toggleNotification({
type: 'success',
title: formatMessage({
id: getTranslation('success.records.unpublish'),
defaultMessage: 'Successfully unpublished.',
}),
message: '',
});

return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG),
});

trackUsage('didNotBulkUnpublishEntries');

throw err;
}
},
[trackUsage, unpublishManyDocuments, toggleNotification, formatMessage, formatAPIError]
);

const [createDocument] = useCreateDocumentMutation();
const create: IUseDocumentActs['create'] = React.useCallback(
async ({ model, params }, data, trackerProperty) => {
Expand Down Expand Up @@ -530,10 +640,12 @@ const useDocumentActions: UseDocumentActions = () => {
clone,
create,
delete: _delete,
deleteMany,
discard,
getDocument,
publish,
unpublish,
unpublishMany,
update,
} satisfies IUseDocumentActs;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ const UIDInput = React.forwardRef<any, UIDInputProps>(
* -----------------------------------------------------------------------------------------------*/

const FieldActionWrapper = styled(Field.Action)`
width: 1.6rem;

svg {
height: 1.6rem;
width: 1.6rem;
Expand Down