Skip to content

Commit

Permalink
Publish multiple locales (#20258)
Browse files Browse the repository at this point in the history
* fix: date comparison

* feat(core): document service publish multiple locales (#20046)

* feat(core): document service (un)publish multiple locales

* fix(core): cleanup locale param types

* feat(content-manager): wip - multiple locale support

* Count draft relations across locales (#20116)

* feat(content-manager): publish multiple locales from CM route

* feat(core): multiple locales in document service discard draft

* feat(content-manager): use bulkpublish for locale publish

* feat(content-manager): use query from bulkpublish locales

* feat(content-manager): publishMany

* feat(content-manager): api test for bulk locale + document publish

* chore(content-manager): fix for build

* fix(core): avoid * in locale data

* chore(content-manager): pr amends and validation improvement

* feat(content-manager): use transaction in document manager publish many

* feat(core): throw when a non string locale is provided and not supported

* fix(core): doc service find many

* chore: clean up

* feat(content-manager): Select fields that require validation in availablelocales (#20156)

* feat(content-manager): wip - select more fields in availablelocales

* feat(content-manager): wip available locales using entity traversal

* feat(content-manager): wip test available locales returns fields with validation

* feat(content-manager): test available locales returns fields with validation

* fix(content-manager): ensure sensitive info not exposed in available statuses

* fix(content-manager): sanitize document metadata available status

* fix(content-manager): sanitize document metadata available status at controller level

* fix(content-manager): populate only for validatable fields

* chore: clean up

* chore: clean up

* chore: clean up

* fix(content-manager): build issues

* fix(content-manager): allow null locales

* fix(content-manager): history service (#20185)

* fix(content-manager): history service

* chore(content-manager): clean up

* fix: pr feedback

* chore: update actions deps

* chore: update utility deps

* chore: upgrade sentry

* chore: upgrade graphql-tools

* fix: http-errors ugprade

* chore: add fs-extra types where needed

* docs(typescript): type system cheat sheet

* chore!: remove deprecated verbose option from ts:generate-types

* chore: clean up

fix(content-manager) correctly count bulk publish results

* fix(content-manager): pr feedback and test improvements

* feat(i18n): bulk locale publish modal in CM edit view (#20069)

* feat(i18n): wip bulk locale publish modal

* fix(i18n): wip - fe bulk locale publish

* feat(content-manager): multi locale publish, integrate with backend and add basic e2e test

* feat(i18n): wip - display validation errors in bulk locale modal

* chore: clean up

* chore(i18n): design system changes

* feat(i18n): display correct status after publish and clean up error messaging

* feat(i18n): access onclose from modal body

* fix(i18n): selected locale change

* fix(i18n): locale table state

* fix(i18n): edit view e2e test

* chore(content-manager): validation tweak wip

* feat(i18n): cover validation cases in i18n e2e tests

* chore: clean up

* fix(i18n): edit view more document actions disabled state

* chore: feedback

* fix(i18n): send all params to publish many

* fix(i18n): place bulk locale publish 3rd in array

* fix(content-manager): validation error extraction

* fix(content-manager): pr feedback

* fix: build

* chore(content-manager): simplify exports

* chore(i18n): revert package

---------

Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
Co-authored-by: Jean-S茅bastien Herbaux <jean-sebastien.herbaux@epitech.eu>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>

* fix(i18n): disable publish button after bulk locale publish

* fix(content-manager): ce e2e fix

* chore: typography

* fix(i18n): e2e edit view test

* fix: wip date comparison

* fix: wip date comparison

* fix(content-manager): simplify date comparison

* fix: clean up metadata api test

* chore: update api tests

* fix: draft versions must be ahead of published to be considered modified

* fix: published modified calculation

* fix: clean up

* fix: simplify draft and publish comparison

* chore: clean up

* fix: flaky fe tests

* fix: pr feedback

* fix(i18n): error message extraction in bulk locale modal

* chore: remove old publish action

* chore: pr feedback

* chore: refactor error types & validation messages

* chore: use anchor link for locale changes

* chore: fix releases

* fix: clean up

* chore: snapshot

---------

Co-authored-by: Marc-Roig <marc12info@gmail.com>
Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
Co-authored-by: Jean-S茅bastien Herbaux <jean-sebastien.herbaux@epitech.eu>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com>
  • Loading branch information
6 people committed May 10, 2024
1 parent 521cd3f commit 8c5105d
Show file tree
Hide file tree
Showing 52 changed files with 2,034 additions and 1,008 deletions.
41 changes: 28 additions & 13 deletions packages/core/admin/admin/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,18 +228,7 @@ const Form = React.forwardRef<HTMLFormElement, FormProps>(
return { data };
} catch (err) {
if (isErrorYupValidationError(err)) {
let errors: FormErrors = {};

if (err.inner) {
if (err.inner.length === 0) {
return setIn(errors, err.path!, err.message);
}
for (const error of err.inner) {
if (!getIn(errors, error.path!)) {
errors = setIn(errors, error.path!, error.message);
}
}
}
const errors = getYupValidationErrors(err);

if (shouldSetErrors) {
setErrors(errors);
Expand Down Expand Up @@ -470,6 +459,31 @@ const isErrorYupValidationError = (err: any): err is Yup.ValidationError =>
typeof err.name === 'string' &&
err.name === 'ValidationError';

/* -------------------------------------------------------------------------------------------------
* getYupValidationErrors
* -----------------------------------------------------------------------------------------------*/

/**
* @description handy utility to convert a yup validation error into a form
* error object. To be used elsewhere.
*/
const getYupValidationErrors = (err: Yup.ValidationError): FormErrors => {
let errors: FormErrors = {};

if (err.inner) {
if (err.inner.length === 0) {
return setIn(errors, err.path!, err.message);
}
for (const error of err.inner) {
if (!getIn(errors, error.path!)) {
errors = setIn(errors, error.path!, error.message);
}
}
}

return errors;
};

/* -------------------------------------------------------------------------------------------------
* reducer
* -----------------------------------------------------------------------------------------------*/
Expand Down Expand Up @@ -777,8 +791,9 @@ const Blocker = ({ onProceed = () => {}, onCancel = () => {} }: BlockerProps) =>
return null;
};

export { Form, Blocker, useField, useForm };
export { Form, Blocker, useField, useForm, getYupValidationErrors };
export type {
FormErrors,
FormHelpers,
FormProps,
FormValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ const GridLayout = ({ size, children }: GridLayoutProps) => {
};

export { GridLayout };
export type { GridColSize };
export type { GridLayoutProps, GridColSize };
4 changes: 2 additions & 2 deletions packages/core/admin/admin/src/components/Layouts/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { styled } from 'styled-components';

import { ActionLayout } from './ActionLayout';
import { ContentLayout } from './ContentLayout';
import { GridLayout } from './GridLayout';
import { GridLayout, GridLayoutProps } from './GridLayout';
import { HeaderLayout, BaseHeaderLayout } from './HeaderLayout';

interface LayoutProps {
Expand Down Expand Up @@ -40,4 +40,4 @@ const Layouts = {
Content: ContentLayout,
};

export { Layouts, type LayoutProps };
export { Layouts, type LayoutProps, type GridLayoutProps };
1 change: 1 addition & 0 deletions packages/core/admin/admin/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@
"app.utils.unpublish": "Unpublish",
"app.utils.published": "Published",
"app.utils.ready-to-publish": "Ready to publish",
"app.utils.ready-to-publish-changes": "Ready to publish changes",
"app.confirm.body": "Are you sure?",
"clearLabel": "Clear",
"coming.soon": "This content is currently under construction and will be back in a few weeks!",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/content-manager/admin/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* but should still export the same things.
*/

export { buildValidParams } from './utils/api';

export { useDocument as unstable_useDocument } from './hooks/useDocument';
export { useDocumentActions as unstable_useDocumentActions } from './hooks/useDocumentActions';
export { useDocumentLayout as unstable_useDocumentLayout } from './hooks/useDocumentLayout';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,7 @@ describe('useDocument', () => {
})
).toMatchInlineSnapshot(`
{
"postal_code": {
"defaultMessage": "postal_code must be a \`string\` type, but the final value was: \`12\`.",
"id": "postal_code must be a \`string\` type, but the final value was: \`12\`.",
"values": {
"typeError": undefined,
},
},
"postal_code": "postal_code must be a \`string\` type, but the final value was: \`12\`.",
}
`);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('useDocumentActions', () => {

expect(result.current).toEqual({
autoClone: expect.any(Function),
publishMany: expect.any(Function),
clone: expect.any(Function),
create: expect.any(Function),
discard: expect.any(Function),
Expand Down
21 changes: 12 additions & 9 deletions packages/core/content-manager/admin/src/hooks/useDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@

import * as React from 'react';

import { useNotification, useAPIErrorHandler, useQueryParams } from '@strapi/admin/strapi-admin';
import {
useNotification,
useAPIErrorHandler,
useQueryParams,
FormErrors,
getYupValidationErrors,
} from '@strapi/admin/strapi-admin';
import { Modules } from '@strapi/types';
import { useParams } from 'react-router-dom';
import { ValidationError } from 'yup';

import { SINGLE_TYPES } from '../constants/collections';
import { useGetDocumentQuery } from '../services/documents';
import { buildValidParams } from '../utils/api';
import { createYupSchema, getInnerErrors } from '../utils/validation';
import { createYupSchema } from '../utils/validation';

import { useContentTypeSchema, ComponentsDictionary } from './useContentTypeSchema';

import type { FindOne } from '../../../shared/contracts/collection-types';
import type { ContentType } from '../../../shared/contracts/content-types';
import type { MessageDescriptor, PrimitiveType } from 'react-intl';

interface UseDocumentArgs {
collectionType: string;
Expand Down Expand Up @@ -50,9 +56,7 @@ type UseDocument = (
* This is the schema of the content type, it is not the same as the layout.
*/
schema?: Schema;
validate: (
document: Document
) => null | Record<string, MessageDescriptor & { values?: Record<string, PrimitiveType> }>;
validate: (document: Document) => null | FormErrors;
};

/* -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -117,7 +121,7 @@ const useDocument: UseDocument = (args, opts) => {
}, [schema, components]);

const validate = React.useCallback(
(document: Document) => {
(document: Modules.Documents.AnyDocument): FormErrors | null => {
if (!validationSchema) {
throw new Error(
'There is no validation schema generated, this is likely due to the schema not being loaded yet.'
Expand All @@ -126,11 +130,10 @@ const useDocument: UseDocument = (args, opts) => {

try {
validationSchema.validateSync(document, { abortEarly: false, strict: true });

return null;
} catch (error) {
if (error instanceof ValidationError) {
return getInnerErrors(error);
return getYupValidationErrors(error);
}

throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
useDiscardDocumentMutation,
useLazyGetDocumentQuery,
usePublishDocumentMutation,
usePublishManyDocumentsMutation,
useUnpublishDocumentMutation,
useUnpublishManyDocumentsMutation,
useUpdateDocumentMutation,
Expand All @@ -37,6 +38,7 @@ import type {
Publish,
Update,
Unpublish,
BulkPublish,
BulkUnpublish,
} from '../../../shared/contracts/collection-types';

Expand All @@ -45,7 +47,8 @@ const DEFAULT_UNEXPECTED_ERROR_MSG = {
defaultMessage: 'An error occurred, please try again',
} satisfies MessageDescriptor;

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

Expand Down Expand Up @@ -124,6 +127,11 @@ type UseDocumentActions = () => {
},
document: Partial<Document>
) => Promise<OperationResponse<Publish.Response>>;
publishMany: (args: {
model: string;
documentIds: string[];
params?: object;
}) => Promise<BulkOperationResponse<BulkPublish.Response>>;
update: (
args: {
collectionType: string;
Expand Down Expand Up @@ -361,6 +369,48 @@ const useDocumentActions: UseDocumentActions = () => {
[trackUsage, publishDocument, toggleNotification, formatMessage, formatAPIError]
);

const [publishManyDocuments] = usePublishManyDocumentsMutation();
const publishMany: IUseDocumentActs['publishMany'] = React.useCallback(
async ({ model, documentIds, params }) => {
try {
// TODO Confirm tracking events for bulk publish?

const res = await publishManyDocuments({
model,
documentIds,
params,
});
if ('error' in res) {
toggleNotification({ type: 'danger', message: formatAPIError(res.error) });
return { error: res.error };
}

toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.publish'),
defaultMessage: 'Published document',
}),
});

return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG),
});
throw err;
}
},
[
// trackUsage,
publishManyDocuments,
toggleNotification,
formatMessage,
formatAPIError,
]
);

const [updateDocument] = useUpdateDocumentMutation();
const update: IUseDocumentActs['update'] = React.useCallback(
async ({ collectionType, model, documentId, params }, data, trackerProperty) => {
Expand Down Expand Up @@ -644,6 +694,7 @@ const useDocumentActions: UseDocumentActions = () => {
discard,
getDocument,
publish,
publishMany,
unpublish,
unpublishMany,
update,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ interface NotificationOptions {
interface ModalOptions {
type: 'modal';
title: string;
content: React.ReactNode;
content: React.ComponentType<{ onClose: () => void }> | React.ReactNode;
footer: React.ComponentType<{ onClose: () => void }> | React.ReactNode;
onClose?: () => void;
}
Expand Down Expand Up @@ -438,7 +438,7 @@ const DocumentActionModal = ({
title,
onClose,
footer: Footer,
content,
content: Content,
onModalClose,
}: DocumentActionModalProps) => {
const id = React.useId();
Expand All @@ -462,7 +462,9 @@ const DocumentActionModal = ({
{title}
</Typography>
</ModalHeader>
<ModalBody>{content}</ModalBody>
<ModalBody>
{typeof Content === 'function' ? <Content onClose={handleClose} /> : Content}
</ModalBody>
<Box
paddingTop={4}
paddingBottom={4}
Expand Down

0 comments on commit 8c5105d

Please sign in to comment.