mirror of
https://github.com/strapi/strapi.git
synced 2025-07-26 02:20:32 +00:00
feat(content-releases): Edit Release (#18956)
* first draft implementation edit release * add dialog unit test * add permission to the edit button * add permissions type and remove old unit test * add createAction to the PermissionMap * fix type errors * fix unit test * fix lint error * fix review comments * change state naming * change dialog to modal
This commit is contained in:
parent
9b4c03b10b
commit
b4936e04a9
@ -7,65 +7,29 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import { useAPIErrorHandler, useNotification } from '@strapi/helper-plugin';
|
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { RELEASE_SCHEMA } from '../../../shared/validation-schemas';
|
import { RELEASE_SCHEMA } from '../../../shared/validation-schemas';
|
||||||
import { isAxiosError } from '../services/axios';
|
|
||||||
import { useCreateReleaseMutation } from '../services/release';
|
|
||||||
|
|
||||||
interface FormValues {
|
export interface FormValues {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_VALUES = {
|
interface ReleaseModalProps {
|
||||||
name: '',
|
|
||||||
} satisfies FormValues;
|
|
||||||
|
|
||||||
interface AddReleaseDialogProps {
|
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
|
handleSubmit: (values: FormValues) => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
initialValues: FormValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddReleaseDialog = ({ handleClose }: AddReleaseDialogProps) => {
|
export const ReleaseModal = ({
|
||||||
|
handleClose,
|
||||||
|
handleSubmit,
|
||||||
|
initialValues,
|
||||||
|
isLoading = false,
|
||||||
|
}: ReleaseModalProps) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const toggleNotification = useNotification();
|
|
||||||
const { formatAPIError } = useAPIErrorHandler();
|
|
||||||
const { push } = useHistory();
|
|
||||||
|
|
||||||
const [createRelease, { isLoading }] = useCreateReleaseMutation();
|
|
||||||
|
|
||||||
const handleSubmit = async (values: FormValues) => {
|
|
||||||
const response = await createRelease({
|
|
||||||
name: values.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
if ('data' in response) {
|
|
||||||
// When the response returns an object with 'data', handle success
|
|
||||||
toggleNotification({
|
|
||||||
type: 'success',
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'content-releases.modal.release-created-notification-success',
|
|
||||||
defaultMessage: 'Release created.',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
push(`/plugins/content-releases/${response.data.data.id}`);
|
|
||||||
} else if (isAxiosError(response.error)) {
|
|
||||||
// When the response returns an object with 'error', handle axios error
|
|
||||||
toggleNotification({
|
|
||||||
type: 'warning',
|
|
||||||
message: formatAPIError(response.error),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Otherwise, the response returns an object with 'error', handle a generic error
|
|
||||||
toggleNotification({
|
|
||||||
type: 'warning',
|
|
||||||
message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalLayout onClose={handleClose} labelledBy="title">
|
<ModalLayout onClose={handleClose} labelledBy="title">
|
||||||
@ -80,7 +44,7 @@ export const AddReleaseDialog = ({ handleClose }: AddReleaseDialogProps) => {
|
|||||||
<Formik
|
<Formik
|
||||||
validateOnChange={false}
|
validateOnChange={false}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
initialValues={INITIAL_VALUES}
|
initialValues={initialValues}
|
||||||
validationSchema={RELEASE_SCHEMA}
|
validationSchema={RELEASE_SCHEMA}
|
||||||
>
|
>
|
||||||
{({ values, errors, handleChange }) => (
|
{({ values, errors, handleChange }) => (
|
@ -1,26 +0,0 @@
|
|||||||
import { within } from '@testing-library/react';
|
|
||||||
import { render, screen } from '@tests/utils';
|
|
||||||
|
|
||||||
import { AddReleaseDialog } from '../AddReleaseDialog';
|
|
||||||
|
|
||||||
describe('AddReleaseDialog', () => {
|
|
||||||
it('renders correctly the dialog content', async () => {
|
|
||||||
const handleCloseMocked = jest.fn();
|
|
||||||
const { user } = render(<AddReleaseDialog handleClose={handleCloseMocked} />);
|
|
||||||
const dialogContainer = screen.getByRole('dialog');
|
|
||||||
const dialogCancelButton = within(dialogContainer).getByRole('button', {
|
|
||||||
name: /cancel/i,
|
|
||||||
});
|
|
||||||
expect(dialogCancelButton).toBeInTheDocument();
|
|
||||||
await user.click(dialogCancelButton);
|
|
||||||
expect(handleCloseMocked).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
// enable the submit button when there is content inside the input
|
|
||||||
const dialogContinueButton = within(dialogContainer).getByRole('button', {
|
|
||||||
name: /continue/i,
|
|
||||||
});
|
|
||||||
const inputElement = within(dialogContainer).getByRole('textbox', { name: /name/i });
|
|
||||||
await user.type(inputElement, 'new release');
|
|
||||||
expect(dialogContinueButton).toBeEnabled();
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,58 @@
|
|||||||
|
import { within } from '@testing-library/react';
|
||||||
|
import { render, screen } from '@tests/utils';
|
||||||
|
|
||||||
|
import { ReleaseModal } from '../ReleaseModal';
|
||||||
|
|
||||||
|
describe('ReleaseModal', () => {
|
||||||
|
it('renders correctly the dialog content on create', async () => {
|
||||||
|
const handleCloseMocked = jest.fn();
|
||||||
|
const { user } = render(
|
||||||
|
<ReleaseModal
|
||||||
|
handleClose={handleCloseMocked}
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
initialValues={{ name: '' }}
|
||||||
|
isLoading={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const dialogContainer = screen.getByRole('dialog');
|
||||||
|
const dialogCancelButton = within(dialogContainer).getByRole('button', {
|
||||||
|
name: /cancel/i,
|
||||||
|
});
|
||||||
|
expect(dialogCancelButton).toBeInTheDocument();
|
||||||
|
await user.click(dialogCancelButton);
|
||||||
|
expect(handleCloseMocked).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// the initial field value is empty
|
||||||
|
const inputElement = within(dialogContainer).getByRole('textbox', { name: /name/i });
|
||||||
|
expect(inputElement).toHaveValue('');
|
||||||
|
|
||||||
|
// enable the submit button when there is content inside the input
|
||||||
|
const dialogContinueButton = within(dialogContainer).getByRole('button', {
|
||||||
|
name: /continue/i,
|
||||||
|
});
|
||||||
|
await user.type(inputElement, 'new release');
|
||||||
|
expect(dialogContinueButton).toBeEnabled();
|
||||||
|
});
|
||||||
|
it('renders correctly the dialog content on update', async () => {
|
||||||
|
const handleCloseMocked = jest.fn();
|
||||||
|
render(
|
||||||
|
<ReleaseModal
|
||||||
|
handleClose={handleCloseMocked}
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
initialValues={{ name: 'title' }}
|
||||||
|
isLoading={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const dialogContainer = screen.getByRole('dialog');
|
||||||
|
|
||||||
|
// the initial field value is the title
|
||||||
|
const inputElement = within(dialogContainer).getByRole('textbox', { name: /name/i });
|
||||||
|
expect(inputElement).toHaveValue('title');
|
||||||
|
|
||||||
|
// enable the submit button when there is content inside the input
|
||||||
|
const dialogContinueButton = within(dialogContainer).getByRole('button', {
|
||||||
|
name: /continue/i,
|
||||||
|
});
|
||||||
|
expect(dialogContinueButton).toBeEnabled();
|
||||||
|
});
|
||||||
|
});
|
@ -1,20 +1,62 @@
|
|||||||
export const PERMISSIONS = {
|
import { Permission } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
|
interface PermissionMap {
|
||||||
|
main: Permission[];
|
||||||
|
create: Permission[];
|
||||||
|
update: Permission[];
|
||||||
|
delete: Permission[];
|
||||||
|
createAction: Permission[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PERMISSIONS: PermissionMap = {
|
||||||
main: [
|
main: [
|
||||||
{
|
{
|
||||||
|
id: 293,
|
||||||
action: 'plugin::content-releases.read',
|
action: 'plugin::content-releases.read',
|
||||||
subject: null,
|
subject: null,
|
||||||
|
conditions: [],
|
||||||
|
actionParameters: [],
|
||||||
|
properties: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
create: [
|
create: [
|
||||||
{
|
{
|
||||||
|
id: 294,
|
||||||
action: 'plugin::content-releases.create',
|
action: 'plugin::content-releases.create',
|
||||||
subject: null,
|
subject: null,
|
||||||
|
conditions: [],
|
||||||
|
actionParameters: [],
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
update: [
|
||||||
|
{
|
||||||
|
id: 295,
|
||||||
|
action: 'plugin::content-releases.update',
|
||||||
|
subject: null,
|
||||||
|
conditions: [],
|
||||||
|
actionParameters: [],
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
delete: [
|
||||||
|
{
|
||||||
|
id: 296,
|
||||||
|
action: 'plugin::content-releases.delete',
|
||||||
|
subject: null,
|
||||||
|
conditions: [],
|
||||||
|
actionParameters: [],
|
||||||
|
properties: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
createAction: [
|
createAction: [
|
||||||
{
|
{
|
||||||
|
id: 297,
|
||||||
action: 'plugin::content-releases.create-action',
|
action: 'plugin::content-releases.create-action',
|
||||||
subject: null,
|
subject: null,
|
||||||
|
conditions: [],
|
||||||
|
actionParameters: [],
|
||||||
|
properties: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -12,12 +12,16 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import { CheckPermissions } from '@strapi/helper-plugin';
|
import { CheckPermissions, useAPIErrorHandler, useNotification } from '@strapi/helper-plugin';
|
||||||
import { ArrowLeft, EmptyDocuments, More, Pencil, Trash } from '@strapi/icons';
|
import { ArrowLeft, EmptyDocuments, More, Pencil, Trash } from '@strapi/icons';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { ReleaseModal, FormValues } from '../components/ReleaseModal';
|
||||||
import { PERMISSIONS } from '../constants';
|
import { PERMISSIONS } from '../constants';
|
||||||
|
import { isAxiosError } from '../services/axios';
|
||||||
|
import { useUpdateReleaseMutation } from '../services/release';
|
||||||
|
|
||||||
const PopoverButton = styled(Flex)`
|
const PopoverButton = styled(Flex)`
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
@ -46,10 +50,14 @@ const ReleaseInfoWrapper = styled(Flex)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const ReleaseDetailsPage = () => {
|
const ReleaseDetailsPage = () => {
|
||||||
|
const { releaseId } = useParams<{ releaseId: string }>();
|
||||||
|
const [releaseModalShown, setReleaseModalShown] = React.useState(false);
|
||||||
const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
|
const [isPopoverVisible, setIsPopoverVisible] = React.useState(false);
|
||||||
const moreButtonRef = React.useRef<HTMLButtonElement>(null!);
|
const moreButtonRef = React.useRef<HTMLButtonElement>(null!);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
// TODO: get the title from the API
|
const toggleNotification = useNotification();
|
||||||
|
const { formatAPIError } = useAPIErrorHandler();
|
||||||
|
// TODO: get title from the API
|
||||||
const title = 'Release title';
|
const title = 'Release title';
|
||||||
|
|
||||||
const totalEntries = 0; // TODO: replace it with the total number of entries
|
const totalEntries = 0; // TODO: replace it with the total number of entries
|
||||||
@ -60,6 +68,47 @@ const ReleaseDetailsPage = () => {
|
|||||||
setIsPopoverVisible((prev) => !prev);
|
setIsPopoverVisible((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleEditReleaseModal = () => {
|
||||||
|
setReleaseModalShown((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openReleaseModal = () => {
|
||||||
|
toggleEditReleaseModal();
|
||||||
|
handleTogglePopover();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [updateRelease, { isLoading }] = useUpdateReleaseMutation();
|
||||||
|
|
||||||
|
const handleEditRelease = async (values: FormValues) => {
|
||||||
|
const response = await updateRelease({
|
||||||
|
id: releaseId,
|
||||||
|
name: values.name,
|
||||||
|
});
|
||||||
|
if ('data' in response) {
|
||||||
|
// When the response returns an object with 'data', handle success
|
||||||
|
toggleNotification({
|
||||||
|
type: 'success',
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'content-releases.modal.release-updated-notification-success',
|
||||||
|
defaultMessage: 'Release updated.',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else if (isAxiosError(response.error)) {
|
||||||
|
// When the response returns an object with 'error', handle axios error
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: formatAPIError(response.error),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Otherwise, the response returns an object with 'error', handle a generic error
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toggleEditReleaseModal();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Main>
|
<Main>
|
||||||
<HeaderLayout
|
<HeaderLayout
|
||||||
@ -100,6 +149,28 @@ const ReleaseDetailsPage = () => {
|
|||||||
minWidth="242px"
|
minWidth="242px"
|
||||||
>
|
>
|
||||||
<Flex alignItems="center" justifyContent="center" direction="column" padding={1}>
|
<Flex alignItems="center" justifyContent="center" direction="column" padding={1}>
|
||||||
|
<CheckPermissions permissions={PERMISSIONS.update}>
|
||||||
|
<PopoverButton
|
||||||
|
paddingTop={2}
|
||||||
|
paddingBottom={2}
|
||||||
|
paddingLeft={4}
|
||||||
|
paddingRight={4}
|
||||||
|
alignItems="center"
|
||||||
|
gap={2}
|
||||||
|
as="button"
|
||||||
|
hasRadius
|
||||||
|
onClick={openReleaseModal}
|
||||||
|
>
|
||||||
|
<PencilIcon />
|
||||||
|
<Typography ellipsis>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'content-releases.header.actions.edit',
|
||||||
|
defaultMessage: 'Edit',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
</PopoverButton>
|
||||||
|
</CheckPermissions>
|
||||||
|
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
paddingTop={2}
|
paddingTop={2}
|
||||||
paddingBottom={2}
|
paddingBottom={2}
|
||||||
@ -108,25 +179,7 @@ const ReleaseDetailsPage = () => {
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={2}
|
gap={2}
|
||||||
as="button"
|
as="button"
|
||||||
borderRadius="4px"
|
hasRadius
|
||||||
>
|
|
||||||
<PencilIcon />
|
|
||||||
<Typography ellipsis>
|
|
||||||
{formatMessage({
|
|
||||||
id: 'content-releases.header.actions.edit',
|
|
||||||
defaultMessage: 'Edit',
|
|
||||||
})}
|
|
||||||
</Typography>
|
|
||||||
</PopoverButton>
|
|
||||||
<PopoverButton
|
|
||||||
paddingTop={2}
|
|
||||||
paddingBottom={2}
|
|
||||||
paddingLeft={4}
|
|
||||||
paddingRight={4}
|
|
||||||
alignItems="center"
|
|
||||||
gap={2}
|
|
||||||
as="button"
|
|
||||||
borderRadius="4px"
|
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
<Typography ellipsis textColor="danger600">
|
<Typography ellipsis textColor="danger600">
|
||||||
@ -187,6 +240,14 @@ const ReleaseDetailsPage = () => {
|
|||||||
icon={<EmptyDocuments width="10rem" />}
|
icon={<EmptyDocuments width="10rem" />}
|
||||||
/>
|
/>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
|
{releaseModalShown && (
|
||||||
|
<ReleaseModal
|
||||||
|
handleClose={toggleEditReleaseModal}
|
||||||
|
handleSubmit={handleEditRelease}
|
||||||
|
isLoading={isLoading}
|
||||||
|
initialValues={{ name: title }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Main>
|
</Main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -25,15 +25,23 @@ import {
|
|||||||
PageSizeURLQuery,
|
PageSizeURLQuery,
|
||||||
PaginationURLQuery,
|
PaginationURLQuery,
|
||||||
useQueryParams,
|
useQueryParams,
|
||||||
|
useAPIErrorHandler,
|
||||||
|
useNotification,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import { EmptyDocuments, Plus } from '@strapi/icons';
|
import { EmptyDocuments, Plus } from '@strapi/icons';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { GetReleases } from '../../../shared/contracts/releases';
|
import { GetReleases } from '../../../shared/contracts/releases';
|
||||||
import { AddReleaseDialog } from '../components/AddReleaseDialog';
|
import { ReleaseModal, FormValues } from '../components/ReleaseModal';
|
||||||
import { PERMISSIONS } from '../constants';
|
import { PERMISSIONS } from '../constants';
|
||||||
import { useGetReleasesQuery, GetReleasesQueryParams } from '../services/release';
|
import { isAxiosError } from '../services/axios';
|
||||||
|
import {
|
||||||
|
useGetReleasesQuery,
|
||||||
|
GetReleasesQueryParams,
|
||||||
|
useCreateReleaseMutation,
|
||||||
|
} from '../services/release';
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------------------------------
|
||||||
* ReleasesLayout
|
* ReleasesLayout
|
||||||
@ -164,21 +172,29 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }: Releases
|
|||||||
/* -------------------------------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------------------------------
|
||||||
* ReleasesPage
|
* ReleasesPage
|
||||||
* -----------------------------------------------------------------------------------------------*/
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
const INITIAL_FORM_VALUES = {
|
||||||
|
name: '',
|
||||||
|
} satisfies FormValues;
|
||||||
|
|
||||||
const ReleasesPage = () => {
|
const ReleasesPage = () => {
|
||||||
const [addReleaseDialogIsShown, setAddReleaseDialogIsShown] = React.useState(false);
|
const [releaseModalShown, setReleaseModalShown] = React.useState(false);
|
||||||
|
const toggleNotification = useNotification();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const { push } = useHistory();
|
||||||
|
const { formatAPIError } = useAPIErrorHandler();
|
||||||
const [{ query }, setQuery] = useQueryParams<GetReleasesQueryParams>();
|
const [{ query }, setQuery] = useQueryParams<GetReleasesQueryParams>();
|
||||||
const response = useGetReleasesQuery(query);
|
const response = useGetReleasesQuery(query);
|
||||||
|
const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
|
||||||
|
|
||||||
const { isLoading, isSuccess, isError } = response;
|
const { isLoading, isSuccess, isError } = response;
|
||||||
|
|
||||||
const toggleAddReleaseDialog = () => {
|
const toggleAddReleaseModal = () => {
|
||||||
setAddReleaseDialogIsShown((prev) => !prev);
|
setReleaseModalShown((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<ReleasesLayout onClickAddRelease={toggleAddReleaseDialog} isLoading>
|
<ReleasesLayout onClickAddRelease={toggleAddReleaseModal} isLoading>
|
||||||
<ContentLayout>
|
<ContentLayout>
|
||||||
<LoadingIndicatorPage />
|
<LoadingIndicatorPage />
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
@ -203,8 +219,38 @@ const ReleasesPage = () => {
|
|||||||
|
|
||||||
const activeTab = response?.currentData?.meta?.activeTab || 'pending';
|
const activeTab = response?.currentData?.meta?.activeTab || 'pending';
|
||||||
|
|
||||||
|
const handleAddRelease = async (values: FormValues) => {
|
||||||
|
const response = await createRelease({
|
||||||
|
name: values.name,
|
||||||
|
});
|
||||||
|
if ('data' in response) {
|
||||||
|
// When the response returns an object with 'data', handle success
|
||||||
|
toggleNotification({
|
||||||
|
type: 'success',
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'content-releases.modal.release-created-notification-success',
|
||||||
|
defaultMessage: 'Release created.',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
push(`/plugins/content-releases/${response.data.data.id}`);
|
||||||
|
} else if (isAxiosError(response.error)) {
|
||||||
|
// When the response returns an object with 'error', handle axios error
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: formatAPIError(response.error),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Otherwise, the response returns an object with 'error', handle a generic error
|
||||||
|
toggleNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: formatMessage({ id: 'notification.error', defaultMessage: 'An error occurred' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReleasesLayout onClickAddRelease={toggleAddReleaseDialog} totalReleases={totalReleases}>
|
<ReleasesLayout onClickAddRelease={toggleAddReleaseModal} totalReleases={totalReleases}>
|
||||||
<ContentLayout>
|
<ContentLayout>
|
||||||
<>
|
<>
|
||||||
<TabGroup
|
<TabGroup
|
||||||
@ -266,7 +312,14 @@ const ReleasesPage = () => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
{addReleaseDialogIsShown && <AddReleaseDialog handleClose={toggleAddReleaseDialog} />}
|
{releaseModalShown && (
|
||||||
|
<ReleaseModal
|
||||||
|
handleClose={toggleAddReleaseModal}
|
||||||
|
handleSubmit={handleAddRelease}
|
||||||
|
isLoading={isSubmittingForm}
|
||||||
|
initialValues={INITIAL_FORM_VALUES}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ReleasesLayout>
|
</ReleasesLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,36 @@
|
|||||||
import { render, screen } from '@tests/utils';
|
import { render, screen, server } from '@tests/utils';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
|
||||||
import { ReleaseDetailsPage } from '../ReleaseDetailsPage';
|
import { ReleaseDetailsPage } from '../ReleaseDetailsPage';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useParams: jest.fn().mockImplementation(() => ({ id: '1' })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
|
// eslint-disable-next-line
|
||||||
|
CheckPermissions: ({ children }: { children: JSX.Element}) => <div>{children}</div>
|
||||||
|
}));
|
||||||
|
|
||||||
describe('Release details page', () => {
|
describe('Release details page', () => {
|
||||||
it('renders correctly the heading content', async () => {
|
it('renders correctly the heading content', async () => {
|
||||||
|
server.use(
|
||||||
|
rest.put('/content-releases/1', (req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.json({
|
||||||
|
data: {
|
||||||
|
id: 2,
|
||||||
|
name: 'Release title focus',
|
||||||
|
releasedAt: null,
|
||||||
|
createdAt: '2023-11-30T16:02:40.908Z',
|
||||||
|
updatedAt: '2023-12-01T11:12:04.441Z',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
const { user } = render(<ReleaseDetailsPage />);
|
const { user } = render(<ReleaseDetailsPage />);
|
||||||
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Release title');
|
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Release title');
|
||||||
// if there are 0 entries
|
// if there are 0 entries
|
||||||
|
@ -5,9 +5,9 @@ import { pluginId } from '../pluginId';
|
|||||||
|
|
||||||
import { axiosBaseQuery } from './axios';
|
import { axiosBaseQuery } from './axios';
|
||||||
|
|
||||||
import type { CreateRelease, GetReleases } from '../../../shared/contracts/releases';
|
import type { CreateRelease, GetReleases, UpdateRelease } from '../../../shared/contracts/releases';
|
||||||
|
|
||||||
interface GetReleasesQueryParams {
|
export interface GetReleasesQueryParams {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
filters?: {
|
filters?: {
|
||||||
@ -104,6 +104,19 @@ const releaseApi = createApi({
|
|||||||
},
|
},
|
||||||
invalidatesTags: ['Releases'],
|
invalidatesTags: ['Releases'],
|
||||||
}),
|
}),
|
||||||
|
updateRelease: build.mutation<
|
||||||
|
void,
|
||||||
|
UpdateRelease.Request['params'] & UpdateRelease.Request['body']
|
||||||
|
>({
|
||||||
|
query({ id, ...data }) {
|
||||||
|
return {
|
||||||
|
url: `/content-releases/${id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
invalidatesTags: ['Releases'],
|
||||||
|
}),
|
||||||
createReleaseAction: build.mutation<
|
createReleaseAction: build.mutation<
|
||||||
CreateReleaseAction.Response,
|
CreateReleaseAction.Response,
|
||||||
CreateReleaseAction.Request
|
CreateReleaseAction.Request
|
||||||
@ -126,6 +139,7 @@ const {
|
|||||||
useGetReleasesForEntryQuery,
|
useGetReleasesForEntryQuery,
|
||||||
useCreateReleaseMutation,
|
useCreateReleaseMutation,
|
||||||
useCreateReleaseActionMutation,
|
useCreateReleaseActionMutation,
|
||||||
|
useUpdateReleaseMutation,
|
||||||
} = releaseApi;
|
} = releaseApi;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -133,7 +147,6 @@ export {
|
|||||||
useGetReleasesForEntryQuery,
|
useGetReleasesForEntryQuery,
|
||||||
useCreateReleaseMutation,
|
useCreateReleaseMutation,
|
||||||
useCreateReleaseActionMutation,
|
useCreateReleaseActionMutation,
|
||||||
|
useUpdateReleaseMutation,
|
||||||
releaseApi,
|
releaseApi,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type { GetReleasesQueryParams };
|
|
||||||
|
@ -17,8 +17,9 @@
|
|||||||
"header.actions.edit": "Edit",
|
"header.actions.edit": "Edit",
|
||||||
"header.actions.delete": "Delete",
|
"header.actions.delete": "Delete",
|
||||||
"header.actions.created": "Created",
|
"header.actions.created": "Created",
|
||||||
"header.actions.created.description": "{number, plural, =0 {# days} one {# day} other {# days}} ago by {user}",
|
"header.actions.created.description": "{number, plural, =0 {# days} one {# day} other {# days}} ago by {createdBy}",
|
||||||
"modal.release-created-notification-success": "Release created",
|
"modal.release-created-notification-success": "Release created",
|
||||||
|
"modal.release-updated-notification-success": "Release updated",
|
||||||
"modal.add-release-title": "New Release",
|
"modal.add-release-title": "New Release",
|
||||||
"modal.form.input.label.release-name": "Name",
|
"modal.form.input.label.release-name": "Name",
|
||||||
"modal.form.button.submit": "Continue",
|
"modal.form.button.submit": "Continue",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user