Skip to content

Commit

Permalink
Merge pull request #18586 from strapi/feature/content-releases
Browse files Browse the repository at this point in the history
feat(content-releases): add content releases
  • Loading branch information
Feranchz committed Dec 15, 2023
2 parents 0ad5d09 + 62eeed5 commit c469b9f
Show file tree
Hide file tree
Showing 99 changed files with 6,250 additions and 24 deletions.
24 changes: 24 additions & 0 deletions docs/docs/docs/01-core/content-releases/00-intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: Introduction
tags:
- content-releases
---

# Content Releases

A release contains various content entries, each capable of being assigned a specific action such as publish or unpublish. Within a release, entries may be in different locales or come from different content types. With a simple click of a button, a release can execute the designated action for each entry. Content Releases is an enterprise edition feature.

### Architecture

As opposed to other EE features built in the [EE folder](docs/docs/01-core/admin/01-ee/00-intro.md), Releases is built as a plugin. The plugin can be found in:

```
packages/core/content-releases
```

```mdx-code-block
import DocCardList from '@theme/DocCardList';
import { useCurrentSidebarCategory } from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items} />
```
155 changes: 155 additions & 0 deletions docs/docs/docs/01-core/content-releases/01-backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
title: Backend Design
description: Content Releases backend
tags:
- content-releases
- tech design
---

All backend code can be found in:

```
packages/core/content-releases/server
```

## Content-types

The content-releases plugin creates two hidden content-types.

### Release

The `Release` content type stores all the information about a release and its associated Release Actions. It is saved in the database as `strapi_releases`. The schema can be found in:

```
packages/core/content-releases/server/src/content-types/release/schema.ts
```

### Release Action

Th `Release Action` content type is associated with any entry from any content-type that has draft and publish enabled. It is responsible for storing the action to perform for an associated entry. It is saved in the database as `strapi_release_actions`. The schema can be found in:

```
packages/core/content-releases/server/src/content-types/release-action/schema.ts
```

## Routes

Release and Release Action routes are only accessible on the Admin API.

### Release

Release routes can be found in:

```
packages/core/content-releases/server/src/routes/release.ts
```

**Get all releases**:

- method: `GET`
- endpoint: `/content-releases/`
- params:
```ts
{
page: number;
pageSize: number;
}
```

**Get a single release**

- method: `GET`
- endpoint: `/content-releases/:id`

**Create a release**:

- method: `POST`
- endpoint: `/content-releases/`
- body:
```ts
{
name: string;
}
```

**Update a release**:

- method: `PUT`
- endpoint: `/content-releases/:id`
- body:
```ts
{
name: string;
}
```

**Publish a release**:

- method: `POST`
- endpoint: `/content-releases/:id/publish`

### Release Action

**Create a release action**

- method: `POST`
- endpoint: `/content-releases/:releaseId/actions`
- body:

```ts
{
entry: {
id: number,
contentType: string
}
type: 'publish' | 'unpublish'
}
```

**Update a release action**

- method: `PUT`
- endpoint: `/content-releases/:releaseId/actions/:actionId`
- body:
```ts
{
type: 'publish' | 'unpublish';
}
```

**Delete a release action**

- method: `DELETE`
- endpoint: `/content-releases/:releaseId/actions/:actionId`

## Controllers

### Release

Handles requests to interact with the Release content type

```
packages/core/content-releases/server/src/controllers/release.ts
```

### Release Action

Handles requests to interact with the Release Action content type

## Services

### Release

Interacts with the database for Release and Release Action CRUD operations

```
packages/core/content-releases/server/src/services/release.ts
```

### Release Validation

Exposes validation functions to run before performing operations on a Release

```
packages/core/content-releases/server/src/services/validation.ts
```
5 changes: 5 additions & 0 deletions docs/docs/docs/01-core/content-releases/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"label": "Content Releases",
"collapsible": true,
"collapsed": true
}
32 changes: 32 additions & 0 deletions docs/docs/docs/06-future-flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: Future Flags
---

In Strapi, we have incoming features that are not yet ready to be shipped to all users, but we aim to keep them updated with our codebase. Additionally, we want to offer community users the opportunity to provide early feedback on these new features or changes.

To achieve this, we utilize future flags, which provide a way to enable unstable features **at your own risk**. Please considered that these flags may be subject to change, removal and it's possible that they contain breaking changes.

Future flags can be used for unstable features that have not yet been shipped. So, if you decide to enable an unstable feature (prefixed with `unstable`), please be aware that this feature is likely to be modified or even removed. It's also highly probable that this unstable feature is not fully ready for use; some parts may still be under development or using mock data at the moment.

Additionally, future flags can be utilized for enabling coming breaking changes in upcoming versions (when prefixed by `vX`, with 'X' being the target version). In this scenario, if you decide to enable a future flag for a breaking change, please consider that you will need to migrate your application to adapt to this breaking change.

## How to enable a future flag.

To enable a future flag, you should add it to your config/features.(js|ts) file in your Strapi application. If you don't have this file, create one.

```ts
// config/features.ts

export default {
future: {
unstableFeatureName: true,
v5breakingChange: env('STRAPI_FEATURES_FUTURE_V5BREAKINGCHANGE', false),
},
};
```

## How to add and start using a future flag.

Developers are responsible for adding new future flags if they intend to introduce a new unstable feature into the Strapi codebase. Features config is part of the config object and can be easily accessed with `strapi.config.get('features')`.

We also provide an API in the strapi object that allows you to check if a future flag is enabled. You can do this using the following method: `strapi.future.isEnabled('featureName')`.
1 change: 1 addition & 0 deletions examples/getstarted/config/features.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = ({ env }) => ({});
9 changes: 8 additions & 1 deletion packages/core/admin/_internal/node/createBuildContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getStrapiAdminEnvVars, loadEnv } from './core/env';
import type { BuildOptions } from './build';
import { DevelopOptions } from './develop';
import { PluginMeta, getEnabledPlugins, getMapOfPluginsWithAdmin } from './core/plugins';
import { Strapi } from '@strapi/types';
import { Strapi, FeaturesService } from '@strapi/types';
import { AppFile, loadUserAppFile } from './core/admin-customisations';

interface BuildContext {
Expand Down Expand Up @@ -47,6 +47,10 @@ interface BuildContext {
* The environment variables to be included in the JS bundle
*/
env: Record<string, string>;
/**
* Features object with future flags
*/
features?: FeaturesService['config'];
logger: CLIContext['logger'];
/**
* The build options
Expand Down Expand Up @@ -160,6 +164,8 @@ const createBuildContext = async ({

const customisations = await loadUserAppFile({ appDir, runtimeDir });

const features = strapiInstance.config.get('features', undefined);

const buildContext = {
appDir,
basePath: `${adminPath}/`,
Expand All @@ -176,6 +182,7 @@ const createBuildContext = async ({
strapi: strapiInstance,
target,
tsconfig,
features,
} satisfies BuildContext;

return buildContext;
Expand Down
1 change: 1 addition & 0 deletions packages/core/admin/_internal/node/staticFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const getEntryModule = (ctx: BuildContext): string => {
document.getElementById("strapi"),
{
${ctx.customisations?.modulePath ? 'customisations,' : ''}
${ctx.features ? `features: ${JSON.stringify(ctx.features)},` : ''}
plugins: {
${pluginsObject}
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/admin/_internal/node/webpack/aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const getAdminDependencyAliases = (monorepo?: StrapiMonorepo) =>
*/
const devAliases: Record<string, string> = {
'@strapi/admin/strapi-admin': './packages/core/admin/admin/src',
'@strapi/content-releases/strapi-admin': './packages/core/content-releases/admin/src',
'@strapi/content-type-builder/strapi-admin': './packages/core/content-type-builder/admin/src',
'@strapi/email/strapi-admin': './packages/core/email/admin/src',
'@strapi/upload/strapi-admin': './packages/core/upload/admin/src',
Expand Down
40 changes: 22 additions & 18 deletions packages/core/admin/admin/custom.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type StrapiTheme } from '@strapi/design-system';
import { type Attribute } from '@strapi/types';
import { type Attribute, type FeaturesService } from '@strapi/types';
import { type BaseEditor } from 'slate';
import { type HistoryEditor } from 'slate-history';
import { type ReactEditor } from 'slate-react';
Expand All @@ -22,24 +22,28 @@ declare module 'slate' {
}
}

interface BrowserStrapi {
backendURL: string;
isEE: boolean;
future: {
isEnabled: (name: keyof FeaturesService['config']) => boolean;
};
features: {
SSO: 'sso';
AUDIT_LOGS: 'audit-logs';
REVIEW_WORKFLOWS: 'review-workflows';
isEnabled: (featureName?: string) => boolean;
};
flags: {
promoteEE?: boolean;
nps?: boolean;
};
projectType: 'Community' | 'Enterprise';
telemetryDisabled: boolean;
}

declare global {
interface Window {
strapi: {
backendURL: string;
isEE: boolean;
features: {
SSO: 'sso';
AUDIT_LOGS: 'audit-logs';
REVIEW_WORKFLOWS: 'review-workflows';
isEnabled: (featureName?: string) => boolean;
};
flags: {
promoteEE?: boolean;
nps?: boolean;
promoteEE: boolean;
};
projectType: 'Community' | 'Enterprise';
telemetryDisabled: boolean;
};
strapi: BrowserStrapi;
}
}
2 changes: 2 additions & 0 deletions packages/core/admin/admin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './components/DefaultDocument';
export * from './render';

export type { SanitizedAdminUser } from '../../shared/contracts/shared';
12 changes: 10 additions & 2 deletions packages/core/admin/admin/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import { createRoot } from 'react-dom/client';

import { StrapiApp, StrapiAppConstructorArgs } from './StrapiApp';

import type { FeaturesService } from '@strapi/types';

interface RenderAdminArgs {
customisations: StrapiAppConstructorArgs['adminConfig'];
plugins: StrapiAppConstructorArgs['appPlugins'];
features?: FeaturesService['config'];
}

const renderAdmin = async (
mountNode: HTMLElement | null,
{ plugins, customisations }: RenderAdminArgs
{ plugins, customisations, features }: RenderAdminArgs
) => {
if (!mountNode) {
throw new Error('[@strapi/admin]: Could not find the root element to mount the admin app');
}

// @ts-expect-error – there's pollution from the global scope of Node.
window.strapi = {
/**
* This ENV variable is passed from the strapi instance, by default no url is set
Expand All @@ -28,6 +30,12 @@ const renderAdmin = async (
backendURL: process.env.STRAPI_ADMIN_BACKEND_URL || window.location.origin,
isEE: false,
telemetryDisabled: process.env.STRAPI_TELEMETRY_DISABLED === 'true' ? true : false,
future: {
isEnabled: (name: keyof FeaturesService['config']) => {
return features?.future?.[name] === true;
},
},
// @ts-expect-error – there's pollution from the global scope of Node.
features: {
SSO: 'sso',
AUDIT_LOGS: 'audit-logs',
Expand Down
2 changes: 1 addition & 1 deletion packages/core/admin/ee/admin/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ declare global {
};
flags: {
nps?: boolean;
promoteEE: boolean;
promoteEE?: boolean;
};
projectType: 'Community' | 'Enterprise';
telemetryDisabled: boolean;
Expand Down
16 changes: 16 additions & 0 deletions packages/core/content-releases/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[{package.json,*.yml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false

0 comments on commit c469b9f

Please sign in to comment.