chore(content-releases): show Banner reached limit max releases (#19276)

* chore(content-releases): add alert reached max limit pending releases

* chore(content-releases): use max pending releases number from config

* chore(content-releases): change limits string content

* chore(content-releases): unit test to check the limit banner

* chore(content-releases): fix ts error

* chore(content-releases): fix review comments

* chore(content-releases): refactor the solution to use useLicenseLimits

* chore(content-releases): fix review comments

* chore(content-releases): fix type error

* chore(content-releases): fix HeaderLayout wrong height because of subtitle empty on loading

* chore(content-releases): remove ReleaseLayout component

* chore(content-releases): remove useless translation

---------

Co-authored-by: Fernando Chavez <fernando.chavez@strapi.io>
This commit is contained in:
Simone 2024-01-30 12:03:33 +01:00 committed by GitHub
parent 2d371a2d60
commit cae3a5a17d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 111 additions and 60 deletions

View File

@ -5,3 +5,5 @@ export type { Store } from './core/store/configure';
export type { SanitizedAdminUser } from '../../shared/contracts/shared';
export { useDocument as unstable_useDocument } from './hooks/useDocument';
// TODO: Replace this export with the same hook exported from the @strapi/admin/strapi-admin/ee in another iteration of this solution
export { useLicenseLimits } from '../../ee/admin/src/hooks/useLicenseLimits';

View File

@ -172,6 +172,13 @@ export interface ReviewWorkflowsFeature {
options?: { numberOfWorkflows: number | null; stagesPerWorkflow: number | null };
}
export interface ContentReleasesFeature {
name: 'cms-content-releases';
options?: {
maximumReleases: number;
};
}
/**
* TODO: this response needs refactoring because we're mixing the admin seat limit info with
* regular EE feature info.
@ -185,7 +192,7 @@ export declare namespace GetLicenseLimitInformation {
data: {
currentActiveUserCount: number;
enforcementUserCount: number;
features: (SSOFeature | AuditLogsFeature | ReviewWorkflowsFeature)[];
features: (SSOFeature | AuditLogsFeature | ReviewWorkflowsFeature | ContentReleasesFeature)[];
isHostedOnStrapiCloud: boolean;
licenseLimitStatus: unknown;
permittedSeats: number;

View File

@ -1,6 +1,9 @@
import * as React from 'react';
// TODO: Replace this import with the same hook exported from the @strapi/admin/strapi-admin/ee in another iteration of this solution
import { useLicenseLimits } from '@strapi/admin/strapi-admin';
import {
Alert,
Box,
Button,
ContentLayout,
@ -44,57 +47,6 @@ import {
useCreateReleaseMutation,
} from '../services/release';
/* -------------------------------------------------------------------------------------------------
* ReleasesLayout
* -----------------------------------------------------------------------------------------------*/
interface ReleasesLayoutProps {
isLoading?: boolean;
totalReleases?: number;
onClickAddRelease: () => void;
children: React.ReactNode;
}
export const ReleasesLayout = ({
isLoading,
totalReleases,
onClickAddRelease,
children,
}: ReleasesLayoutProps) => {
const { formatMessage } = useIntl();
return (
<Main aria-busy={isLoading}>
<HeaderLayout
title={formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases',
})}
subtitle={
!isLoading &&
formatMessage(
{
id: 'content-releases.pages.Releases.header-subtitle',
defaultMessage:
'{number, plural, =0 {No releases} one {# release} other {# releases}}',
},
{ number: totalReleases }
)
}
primaryAction={
<CheckPermissions permissions={PERMISSIONS.create}>
<Button startIcon={<Plus />} onClick={onClickAddRelease}>
{formatMessage({
id: 'content-releases.header.actions.add-release',
defaultMessage: 'New release',
})}
</Button>
</CheckPermissions>
}
/>
{children}
</Main>
);
};
/* -------------------------------------------------------------------------------------------------
* ReleasesGrid
* -----------------------------------------------------------------------------------------------*/
@ -177,6 +129,15 @@ interface CustomLocationState {
errors?: Record<'code', string>[];
}
const StyledAlert = styled(Alert)`
button {
display: none;
}
p + div {
margin-left: auto;
}
`;
const INITIAL_FORM_VALUES = {
name: '',
} satisfies FormValues;
@ -192,7 +153,10 @@ const ReleasesPage = () => {
const [{ query }, setQuery] = useQueryParams<GetReleasesQueryParams>();
const response = useGetReleasesQuery(query);
const [createRelease, { isLoading: isSubmittingForm }] = useCreateReleaseMutation();
const { getFeature } = useLicenseLimits();
const { maximumNumberOfPendingReleases = 3 } = getFeature('cms-content-releases') as {
maximumNumberOfPendingReleases: number;
};
const { isLoading, isSuccess, isError } = response;
const activeTab = response?.currentData?.meta?.activeTab || 'pending';
const activeTabIndex = ['pending', 'done'].indexOf(activeTab);
@ -229,15 +193,14 @@ const ReleasesPage = () => {
if (isLoading) {
return (
<ReleasesLayout onClickAddRelease={toggleAddReleaseModal} isLoading>
<ContentLayout>
<LoadingIndicatorPage />
</ContentLayout>
</ReleasesLayout>
<Main aria-busy={isLoading}>
<LoadingIndicatorPage />
</Main>
);
}
const totalReleases = (isSuccess && response.currentData?.meta?.pagination?.total) || 0;
const hasReachedMaximumPendingReleases = totalReleases >= maximumNumberOfPendingReleases;
const handleTabChange = (index: number) => {
setQuery({
@ -283,9 +246,64 @@ const ReleasesPage = () => {
};
return (
<ReleasesLayout onClickAddRelease={toggleAddReleaseModal} totalReleases={totalReleases}>
<Main aria-busy={isLoading}>
<HeaderLayout
title={formatMessage({
id: 'content-releases.pages.Releases.title',
defaultMessage: 'Releases',
})}
subtitle={formatMessage(
{
id: 'content-releases.pages.Releases.header-subtitle',
defaultMessage: '{number, plural, =0 {No releases} one {# release} other {# releases}}',
},
{ number: totalReleases }
)}
primaryAction={
<CheckPermissions permissions={PERMISSIONS.create}>
<Button
startIcon={<Plus />}
onClick={toggleAddReleaseModal}
disabled={hasReachedMaximumPendingReleases}
>
{formatMessage({
id: 'content-releases.header.actions.add-release',
defaultMessage: 'New release',
})}
</Button>
</CheckPermissions>
}
/>
<ContentLayout>
<>
{activeTab === 'pending' && hasReachedMaximumPendingReleases && (
<StyledAlert
marginBottom={6}
action={
<Link href="https://strapi.io/pricing-cloud" isExternal>
{formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.action',
defaultMessage: 'Explore plans',
})}
</Link>
}
title={formatMessage(
{
id: 'content-releases.pages.Releases.max-limit-reached.title',
defaultMessage:
'You have reached the {number} pending {number, plural, one {release} other {releases}} limit.',
},
{ number: maximumNumberOfPendingReleases }
)}
onClose={() => {}}
closeLabel=""
>
{formatMessage({
id: 'content-releases.pages.Releases.max-limit-reached.message',
defaultMessage: 'Upgrade to manage an unlimited number of releases.',
})}
</StyledAlert>
)}
<TabGroup
label={formatMessage({
id: 'content-releases.pages.Releases.tab-group.label',
@ -355,7 +373,7 @@ const ReleasesPage = () => {
initialValues={INITIAL_FORM_VALUES}
/>
)}
</ReleasesLayout>
</Main>
);
};

View File

@ -12,6 +12,21 @@ jest.mock('@strapi/helper-plugin', () => ({
CheckPermissions: ({ children }: { children: JSX.Element }) => <div>{children}</div>,
}));
jest.mock('@strapi/admin/strapi-admin', () => ({
...jest.requireActual('@strapi/admin/strapi-admin'),
useLicenseLimits: jest.fn().mockReturnValue({
isLoading: false,
isError: false,
license: {
enforcementUserCount: 10,
licenseLimitStatus: '',
permittedSeats: 3,
isHostedOnStrapiCloud: false,
},
getFeature: jest.fn().mockReturnValue({ maximumReleases: 3 }),
}),
}));
describe('Releases home page', () => {
it('renders the tab content correctly when there are no releases', async () => {
server.use(
@ -54,5 +69,11 @@ describe('Releases home page', () => {
const lastEntry = screen.getByRole('heading', { level: 3, name: 'entry 17' });
expect(lastEntry).toBeInTheDocument();
// Check if you reached the maximum number of releases for license
const newReleaseButton = screen.queryByRole('button', { name: /new release/i });
expect(newReleaseButton).toBeDisabled();
const limitReachedMessage = screen.getByText(/you have reached the 3 pending releases limit/i);
expect(limitReachedMessage).toBeInTheDocument();
});
});

View File

@ -17,6 +17,9 @@
"plugin.name": "Releases",
"pages.Releases.title": "Releases",
"pages.Releases.header-subtitle": "{number, plural, =0 {No releases} one {# release} other {# releases}}",
"pages.Releases.max-limit-reached.title": "You have reached the {number} pending {number, plural, one {release} other {releases}} limit.",
"pages.Releases.max-limit-reached.message": "Upgrade to manage an unlimited number of releases.",
"pages.Releases.max-limit-reached.action": "Explore plans",
"header.actions.add-release": "New Release",
"header.actions.refresh": "Refresh",
"header.actions.publish": "Publish",