mirror of
https://github.com/strapi/strapi.git
synced 2025-12-26 06:35:47 +00:00
feat(content-releases): removing scheduling future flag for stable release
This commit is contained in:
parent
298c6fc616
commit
5cd7dce98d
@ -1,5 +1 @@
|
||||
module.exports = ({ env }) => ({
|
||||
future: {
|
||||
contentReleasesScheduling: env.bool('STRAPI_FEATURES_FUTURE_RELEASES_SCHEDULING', false),
|
||||
},
|
||||
});
|
||||
module.exports = ({ env }) => ({});
|
||||
|
||||
@ -1,5 +1 @@
|
||||
module.exports = ({ env }) => ({
|
||||
future: {
|
||||
contentReleasesScheduling: env.bool('STRAPI_FUTURE_CONTENT_RELEASES_SCHEDULING', false),
|
||||
},
|
||||
});
|
||||
module.exports = ({ env }) => ({});
|
||||
|
||||
@ -259,7 +259,6 @@ export const CMReleasesContainer = () => {
|
||||
} = useCMEditViewDataManager();
|
||||
|
||||
const contentTypeUid = slug as Common.UID.ContentType;
|
||||
const IsSchedulingEnabled = window.strapi.future.isEnabled('contentReleasesScheduling');
|
||||
const canFetch = entryId != null && contentTypeUid != null;
|
||||
const fetchParams = canFetch
|
||||
? {
|
||||
@ -361,7 +360,7 @@ export const CMReleasesContainer = () => {
|
||||
<Typography fontSize={2} fontWeight="bold" variant="omega" textColor="neutral700">
|
||||
{release.name}
|
||||
</Typography>
|
||||
{IsSchedulingEnabled && release.scheduledAt && release.timezone && (
|
||||
{release.scheduledAt && release.timezone && (
|
||||
<Typography variant="pi" textColor="neutral600">
|
||||
{formatMessage(
|
||||
{
|
||||
|
||||
@ -51,7 +51,6 @@ export const ReleaseModal = ({
|
||||
const { formatMessage } = useIntl();
|
||||
const { pathname } = useLocation();
|
||||
const isCreatingRelease = pathname === `/plugins/${pluginId}`;
|
||||
const IsSchedulingEnabled = window.strapi.future.isEnabled('contentReleasesScheduling');
|
||||
// Set default first timezone from the list if no system timezone detected
|
||||
const { timezoneList, systemTimezone = { value: 'UTC+00:00-Africa/Abidjan ' } } = getTimezones(
|
||||
initialValues.scheduledAt ? new Date(initialValues.scheduledAt) : new Date()
|
||||
@ -122,97 +121,89 @@ export const ReleaseModal = ({
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
{/* Remove future flag check after Scheduling Beta release */}
|
||||
{IsSchedulingEnabled && (
|
||||
<Box width="max-content">
|
||||
<Checkbox
|
||||
name="isScheduled"
|
||||
value={values.isScheduled}
|
||||
onChange={(event) => {
|
||||
setFieldValue('isScheduled', event.target.checked);
|
||||
if (!event.target.checked) {
|
||||
// Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB
|
||||
setFieldValue('date', null);
|
||||
setFieldValue('time', '');
|
||||
setFieldValue('timezone', null);
|
||||
} else {
|
||||
// On ticking back schedule release date, time and timezone should be restored to the initial state
|
||||
setFieldValue('date', initialValues.date);
|
||||
setFieldValue('time', initialValues.time);
|
||||
setFieldValue('timezone', initialValues.timezone ?? systemTimezone?.value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
textColor={values.isScheduled ? 'primary600' : 'neutral800'}
|
||||
fontWeight={values.isScheduled ? 'semiBold' : 'regular'}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'modal.form.input.label.schedule-release',
|
||||
defaultMessage: 'Schedule release',
|
||||
})}
|
||||
</Typography>
|
||||
</Checkbox>
|
||||
</Box>
|
||||
{values.isScheduled && (
|
||||
<>
|
||||
<Box width="max-content">
|
||||
<Checkbox
|
||||
name="isScheduled"
|
||||
value={values.isScheduled}
|
||||
onChange={(event) => {
|
||||
setFieldValue('isScheduled', event.target.checked);
|
||||
if (!event.target.checked) {
|
||||
// Clear scheduling info from a release on unchecking schedule release, which reset scheduling info in DB
|
||||
setFieldValue('date', null);
|
||||
setFieldValue('time', '');
|
||||
setFieldValue('timezone', null);
|
||||
} else {
|
||||
// On ticking back schedule release date, time and timezone should be restored to the initial state
|
||||
setFieldValue('date', initialValues.date);
|
||||
setFieldValue('time', initialValues.time);
|
||||
setFieldValue(
|
||||
'timezone',
|
||||
initialValues.timezone ?? systemTimezone?.value
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
textColor={values.isScheduled ? 'primary600' : 'neutral800'}
|
||||
fontWeight={values.isScheduled ? 'semiBold' : 'regular'}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'modal.form.input.label.schedule-release',
|
||||
defaultMessage: 'Schedule release',
|
||||
<Flex gap={4} alignItems="start">
|
||||
<Box width="100%">
|
||||
<DatePicker
|
||||
label={formatMessage({
|
||||
id: 'content-releases.modal.form.input.label.date',
|
||||
defaultMessage: 'Date',
|
||||
})}
|
||||
</Typography>
|
||||
</Checkbox>
|
||||
</Box>
|
||||
{values.isScheduled && (
|
||||
<>
|
||||
<Flex gap={4} alignItems="start">
|
||||
<Box width="100%">
|
||||
<DatePicker
|
||||
label={formatMessage({
|
||||
id: 'content-releases.modal.form.input.label.date',
|
||||
defaultMessage: 'Date',
|
||||
})}
|
||||
name="date"
|
||||
error={errors.date}
|
||||
onChange={(date) => {
|
||||
const isoFormatDate = date
|
||||
? formatISO(date, { representation: 'date' })
|
||||
: null;
|
||||
setFieldValue('date', isoFormatDate);
|
||||
}}
|
||||
clearLabel={formatMessage({
|
||||
id: 'content-releases.modal.form.input.clearLabel',
|
||||
defaultMessage: 'Clear',
|
||||
})}
|
||||
onClear={() => {
|
||||
setFieldValue('date', null);
|
||||
}}
|
||||
selectedDate={values.date || undefined}
|
||||
required
|
||||
minDate={utcToZonedTime(new Date(), values.timezone.split('&')[1])}
|
||||
/>
|
||||
</Box>
|
||||
<Box width="100%">
|
||||
<TimePicker
|
||||
label={formatMessage({
|
||||
id: 'content-releases.modal.form.input.label.time',
|
||||
defaultMessage: 'Time',
|
||||
})}
|
||||
name="time"
|
||||
error={errors.time}
|
||||
onChange={(time) => {
|
||||
setFieldValue('time', time);
|
||||
}}
|
||||
clearLabel={formatMessage({
|
||||
id: 'content-releases.modal.form.input.clearLabel',
|
||||
defaultMessage: 'Clear',
|
||||
})}
|
||||
onClear={() => {
|
||||
setFieldValue('time', '');
|
||||
}}
|
||||
value={values.time || undefined}
|
||||
required
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<TimezoneComponent timezoneOptions={timezoneList} />
|
||||
</>
|
||||
)}
|
||||
name="date"
|
||||
error={errors.date}
|
||||
onChange={(date) => {
|
||||
const isoFormatDate = date
|
||||
? formatISO(date, { representation: 'date' })
|
||||
: null;
|
||||
setFieldValue('date', isoFormatDate);
|
||||
}}
|
||||
clearLabel={formatMessage({
|
||||
id: 'content-releases.modal.form.input.clearLabel',
|
||||
defaultMessage: 'Clear',
|
||||
})}
|
||||
onClear={() => {
|
||||
setFieldValue('date', null);
|
||||
}}
|
||||
selectedDate={values.date || undefined}
|
||||
required
|
||||
minDate={utcToZonedTime(new Date(), values.timezone.split('&')[1])}
|
||||
/>
|
||||
</Box>
|
||||
<Box width="100%">
|
||||
<TimePicker
|
||||
label={formatMessage({
|
||||
id: 'content-releases.modal.form.input.label.time',
|
||||
defaultMessage: 'Time',
|
||||
})}
|
||||
name="time"
|
||||
error={errors.time}
|
||||
onChange={(time) => {
|
||||
setFieldValue('time', time);
|
||||
}}
|
||||
clearLabel={formatMessage({
|
||||
id: 'content-releases.modal.form.input.clearLabel',
|
||||
defaultMessage: 'Clear',
|
||||
})}
|
||||
onClear={() => {
|
||||
setFieldValue('time', '');
|
||||
}}
|
||||
value={values.time || undefined}
|
||||
required
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<TimezoneComponent timezoneOptions={timezoneList} />
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@ -315,7 +315,6 @@ export const ReleaseDetailsLayout = ({
|
||||
const totalEntries = release.actions.meta.count || 0;
|
||||
const hasCreatedByUser = Boolean(getCreatedByUser());
|
||||
|
||||
const IsSchedulingEnabled = window.strapi.future.isEnabled('contentReleasesScheduling');
|
||||
const isScheduled = release.scheduledAt && release.timezone;
|
||||
const numberOfEntriesText = formatMessage(
|
||||
{
|
||||
@ -354,8 +353,7 @@ export const ReleaseDetailsLayout = ({
|
||||
subtitle={
|
||||
<Flex gap={2} lineHeight={6}>
|
||||
<Typography textColor="neutral600" variant="epsilon">
|
||||
{numberOfEntriesText +
|
||||
(IsSchedulingEnabled && isScheduled ? ` - ${scheduledText}` : '')}
|
||||
{numberOfEntriesText + (isScheduled ? ` - ${scheduledText}` : '')}
|
||||
</Typography>
|
||||
<Badge {...getBadgeProps(release.status)}>{release.status}</Badge>
|
||||
</Flex>
|
||||
|
||||
@ -96,7 +96,6 @@ const getBadgeProps = (status: Release['status']) => {
|
||||
|
||||
const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }: ReleasesGridProps) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const IsSchedulingEnabled = window.strapi.future.isEnabled('contentReleasesScheduling');
|
||||
|
||||
if (isError) {
|
||||
return <AnErrorOccurred />;
|
||||
@ -121,7 +120,7 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }: Releases
|
||||
|
||||
return (
|
||||
<Grid gap={4}>
|
||||
{releases.map(({ id, name, actions, scheduledAt, status }) => (
|
||||
{releases.map(({ id, name, scheduledAt, status }) => (
|
||||
<GridItem col={3} s={6} xs={12} key={id}>
|
||||
<LinkCard href={`content-releases/${id}`} isExternal={false}>
|
||||
<Flex
|
||||
@ -141,24 +140,13 @@ const ReleasesGrid = ({ sectionTitle, releases = [], isError = false }: Releases
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography variant="pi" textColor="neutral600">
|
||||
{IsSchedulingEnabled ? (
|
||||
scheduledAt ? (
|
||||
<CapitalizeRelativeTime timestamp={new Date(scheduledAt)} />
|
||||
) : (
|
||||
formatMessage({
|
||||
id: 'content-releases.pages.Releases.not-scheduled',
|
||||
defaultMessage: 'Not scheduled',
|
||||
})
|
||||
)
|
||||
{scheduledAt ? (
|
||||
<CapitalizeRelativeTime timestamp={new Date(scheduledAt)} />
|
||||
) : (
|
||||
formatMessage(
|
||||
{
|
||||
id: 'content-releases.page.Releases.release-item.entries',
|
||||
defaultMessage:
|
||||
'{number, plural, =0 {No entries} one {# entry} other {# entries}}',
|
||||
},
|
||||
{ number: actions.meta.count }
|
||||
)
|
||||
formatMessage({
|
||||
id: 'content-releases.pages.Releases.not-scheduled',
|
||||
defaultMessage: 'Not scheduled',
|
||||
})
|
||||
)}
|
||||
</Typography>
|
||||
</Flex>
|
||||
@ -191,8 +179,7 @@ const INITIAL_FORM_VALUES = {
|
||||
name: '',
|
||||
date: null,
|
||||
time: '',
|
||||
// Remove future flag check after Scheduling Beta release and replace with true as creating new release should include scheduling by default
|
||||
isScheduled: window.strapi.future.isEnabled('contentReleasesScheduling'),
|
||||
isScheduled: true,
|
||||
scheduledAt: null,
|
||||
timezone: null,
|
||||
} satisfies FormValues;
|
||||
|
||||
@ -5,8 +5,6 @@ import { ACTIONS, RELEASE_ACTION_MODEL_UID, RELEASE_MODEL_UID } from '../constan
|
||||
|
||||
const { features } = require('@strapi/strapi/dist/utils/ee');
|
||||
const { register } = require('../register');
|
||||
const { bootstrap } = require('../bootstrap');
|
||||
const { getService } = require('../utils');
|
||||
|
||||
jest.mock('@strapi/strapi/dist/utils/ee', () => ({
|
||||
features: {
|
||||
@ -99,57 +97,3 @@ describe('register', () => {
|
||||
expect(mockGraphQlDisable).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bootstrap', () => {
|
||||
const mockSyncFromDatabase = jest.fn();
|
||||
|
||||
getService.mockReturnValue({
|
||||
syncFromDatabase: mockSyncFromDatabase,
|
||||
});
|
||||
|
||||
const strapi = {
|
||||
db: {
|
||||
lifecycles: {
|
||||
subscribe: jest.fn(),
|
||||
},
|
||||
},
|
||||
features: {
|
||||
future: {
|
||||
isEnabled: jest.fn(),
|
||||
},
|
||||
},
|
||||
log: {
|
||||
error: jest.fn(),
|
||||
},
|
||||
contentTypes: {
|
||||
contentTypeA: {
|
||||
uid: 'contentTypeA',
|
||||
},
|
||||
contentTypeB: {
|
||||
uid: 'contentTypeB',
|
||||
},
|
||||
},
|
||||
webhookStore: {
|
||||
addAllowedEvent: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should sync scheduled jobs from the database if contentReleasesScheduling flag is enabled', async () => {
|
||||
strapi.features.future.isEnabled.mockReturnValue(true);
|
||||
features.isEnabled.mockReturnValue(true);
|
||||
mockSyncFromDatabase.mockResolvedValue(new Map());
|
||||
await bootstrap({ strapi });
|
||||
expect(mockSyncFromDatabase).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not sync scheduled jobs from the database if contentReleasesScheduling flag is disabled', async () => {
|
||||
strapi.features.future.isEnabled.mockReturnValue(false);
|
||||
features.isEnabled.mockReturnValue(true);
|
||||
await bootstrap({ strapi });
|
||||
expect(mockSyncFromDatabase).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -153,20 +153,18 @@ export const bootstrap = async ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (strapi.features.future.isEnabled('contentReleasesScheduling')) {
|
||||
getService('scheduling', { strapi })
|
||||
.syncFromDatabase()
|
||||
.catch((err: Error) => {
|
||||
strapi.log.error(
|
||||
'Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling.'
|
||||
);
|
||||
getService('scheduling', { strapi })
|
||||
.syncFromDatabase()
|
||||
.catch((err: Error) => {
|
||||
strapi.log.error(
|
||||
'Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling.'
|
||||
);
|
||||
|
||||
throw err;
|
||||
});
|
||||
|
||||
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||
strapi.webhookStore.addAllowedEvent(key, value);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||
strapi.webhookStore.addAllowedEvent(key, value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -5,13 +5,11 @@ import { Release } from '../../shared/contracts/releases';
|
||||
import { getService } from './utils';
|
||||
|
||||
export const destroy = async ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
if (strapi.features.future.isEnabled('contentReleasesScheduling')) {
|
||||
const scheduledJobs: Map<Release['id'], Job> = getService('scheduling', {
|
||||
strapi,
|
||||
}).getAll();
|
||||
const scheduledJobs: Map<Release['id'], Job> = getService('scheduling', {
|
||||
strapi,
|
||||
}).getAll();
|
||||
|
||||
for (const [, job] of scheduledJobs) {
|
||||
job.cancel();
|
||||
}
|
||||
for (const [, job] of scheduledJobs) {
|
||||
job.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
@ -5,5 +5,5 @@ import scheduling from './scheduling';
|
||||
export const services = {
|
||||
release,
|
||||
'release-validation': releaseValidation,
|
||||
...(strapi.features.future.isEnabled('contentReleasesScheduling') ? { scheduling } : {}),
|
||||
scheduling,
|
||||
};
|
||||
|
||||
@ -224,10 +224,7 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
strapi.features.future.isEnabled('contentReleasesScheduling') &&
|
||||
releaseWithCreatorFields.scheduledAt
|
||||
) {
|
||||
if (releaseWithCreatorFields.scheduledAt) {
|
||||
const schedulingService = getService('scheduling', { strapi });
|
||||
|
||||
await schedulingService.set(release.id, release.scheduledAt);
|
||||
@ -390,16 +387,14 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
data: releaseWithCreatorFields,
|
||||
});
|
||||
|
||||
if (strapi.features.future.isEnabled('contentReleasesScheduling')) {
|
||||
const schedulingService = getService('scheduling', { strapi });
|
||||
const schedulingService = getService('scheduling', { strapi });
|
||||
|
||||
if (releaseData.scheduledAt) {
|
||||
// set function always cancel the previous job if it exists, so we can call it directly
|
||||
await schedulingService.set(id, releaseData.scheduledAt);
|
||||
} else if (release.scheduledAt) {
|
||||
// When user don't send a scheduledAt and we have one on the release, means that user want to unschedule it
|
||||
schedulingService.cancel(id);
|
||||
}
|
||||
if (releaseData.scheduledAt) {
|
||||
// set function always cancel the previous job if it exists, so we can call it directly
|
||||
await schedulingService.set(id, releaseData.scheduledAt);
|
||||
} else if (release.scheduledAt) {
|
||||
// When user don't send a scheduledAt and we have one on the release, means that user want to unschedule it
|
||||
schedulingService.cancel(id);
|
||||
}
|
||||
|
||||
this.updateReleaseStatus(id);
|
||||
@ -630,7 +625,7 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
await strapi.entityService.delete(RELEASE_MODEL_UID, releaseId);
|
||||
});
|
||||
|
||||
if (strapi.features.future.isEnabled('contentReleasesScheduling') && release.scheduledAt) {
|
||||
if (release.scheduledAt) {
|
||||
const schedulingService = getService('scheduling', { strapi });
|
||||
await schedulingService.cancel(release.id);
|
||||
}
|
||||
@ -706,23 +701,19 @@ const createReleaseService = ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (strapi.features.future.isEnabled('contentReleasesScheduling')) {
|
||||
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
||||
isPublished: true,
|
||||
release,
|
||||
});
|
||||
}
|
||||
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
||||
isPublished: true,
|
||||
release,
|
||||
});
|
||||
|
||||
strapi.telemetry.send('didPublishContentRelease');
|
||||
|
||||
return { release, error: null };
|
||||
} catch (error) {
|
||||
if (strapi.features.future.isEnabled('contentReleasesScheduling')) {
|
||||
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
||||
isPublished: false,
|
||||
error,
|
||||
});
|
||||
}
|
||||
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
||||
isPublished: false,
|
||||
error,
|
||||
});
|
||||
|
||||
// We need to run the update in the same transaction because the release is locked
|
||||
await strapi.db
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user