enhancement: promote preview configuration docs (#23961)

* enhancement: promote preview configuration docs

* chore: add tests

* fix: api test

* fix: don't show panel in create mode
This commit is contained in:
Rémi de Juvigny 2025-07-22 05:34:48 -04:00 committed by GitHub
parent 5aa166b6d1
commit 39b256a9a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 162 additions and 13 deletions

View File

@ -31,22 +31,59 @@ const PreviewSidePanel: PanelComponent = ({ model, documentId, document }) => {
const { pathname } = useLocation();
const [{ query }] = useQueryParams();
const isModified = useForm('PreviewSidePanel', (state) => state.modified);
const isUnsaved = Boolean(!document || !document.id);
const title = formatMessage({
id: 'content-manager.preview.panel.title',
defaultMessage: 'Preview',
});
/**
* The preview URL isn't used in this component, we just fetch it to know if preview is enabled
* for the content type. If it's not, the panel is not displayed. If it is, we display a link to
* /preview, and the URL will already be loaded in the RTK query cache.
*/
const { data, error } = useGetPreviewUrlQuery({
params: {
contentType: model as UID.ContentType,
const { data, error } = useGetPreviewUrlQuery(
{
params: {
contentType: model as UID.ContentType,
},
query: {
documentId,
locale: document?.locale,
status: document?.status,
},
},
query: {
documentId,
locale: document?.locale,
status: document?.status,
},
});
// Don't bother making the request since we won't show any UI
{ skip: isUnsaved }
);
if (isUnsaved) {
return null;
}
// Preview was not configured but not disabled either (otherwise it would be a success 204).
// So we encourage the user to set it up.
if (error && error.name === 'NotFoundError') {
return {
title,
content: (
<Button
variant="tertiary"
tag={Link}
to="https://docs.strapi.io/cms/features/preview"
target="_blank"
rel="noopener noreferrer"
width="100%"
>
{formatMessage({
id: 'content-manager.preview.panel.button-configuration',
defaultMessage: 'Set up preview',
})}
</Button>
),
};
}
if (!data?.data?.url || error) {
return null;
@ -59,7 +96,7 @@ const PreviewSidePanel: PanelComponent = ({ model, documentId, document }) => {
};
return {
title: formatMessage({ id: 'content-manager.preview.panel.title', defaultMessage: 'Preview' }),
title,
content: (
<ConditionalTooltip
label={formatMessage({

View File

@ -241,6 +241,7 @@
"popover.display-relations.label": "Display relations",
"preview.panel.title": "Preview",
"preview.panel.button": "Open preview",
"preview.panel.button-configuration": "Set up preview",
"preview.panel.button-disabled-tooltip": "Please save to open the preview",
"preview.page-title": "{contentType} preview",
"preview.header.close": "Close preview",

View File

@ -30,7 +30,49 @@ describe('Preview Config', () => {
expect(createPreviewConfigService({ strapi }).isEnabled()).toBe(true);
});
describe('Validation', () => {
describe('isConfigured', () => {
test('Is configured when preview is explicitly disabled', () => {
const strapi = {
config: {
get: () => ({ enabled: false }),
},
} as any;
expect(createPreviewConfigService({ strapi }).isConfigured()).toBe(true);
});
test('Is configured when handler is configured', () => {
const strapi = {
config: {
get: () => getConfig(true, () => {}),
},
} as any;
expect(createPreviewConfigService({ strapi }).isConfigured()).toBe(true);
});
test('Is not configured when preview is neither disabled nor configured', () => {
const strapi = {
config: {
get: () => ({ enabled: true }),
},
} as any;
expect(createPreviewConfigService({ strapi }).isConfigured()).toBe(false);
});
test('Is not configured when no config is provided', () => {
const strapi = {
config: {
get: () => undefined,
},
} as any;
expect(createPreviewConfigService({ strapi }).isConfigured()).toBe(false);
});
});
describe('validate', () => {
test('Passes on valid configuration', () => {
const strapi = {
config: {

View File

@ -0,0 +1,58 @@
import { errors } from '@strapi/utils';
import { createPreviewService } from '../preview';
const mockConfig = {
isConfigured: jest.fn(),
getPreviewHandler: jest.fn(),
};
const mockStrapi = {
log: {
error: jest.fn(),
},
} as any;
const mockGetService = jest.fn().mockReturnValue(mockConfig);
jest.mock('../../utils', () => ({
getService: jest.fn().mockImplementation(() => mockGetService()),
}));
describe('Preview Service', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('Throws 404 when preview is not configured', async () => {
mockConfig.isConfigured.mockReturnValue(false);
const previewService = createPreviewService({ strapi: mockStrapi });
await expect(
previewService.getPreviewUrl('api::article.article', {
documentId: '',
locale: '',
status: 'published',
})
).rejects.toThrow(new errors.NotFoundError('Preview config not found'));
expect(mockConfig.isConfigured).toHaveBeenCalled();
expect(mockConfig.getPreviewHandler).not.toHaveBeenCalled();
});
test('Calls handler when preview is configured', async () => {
const mockHandler = jest.fn().mockResolvedValue('http://preview.example.com');
mockConfig.isConfigured.mockReturnValue(true);
mockConfig.getPreviewHandler.mockReturnValue(mockHandler);
const previewService = createPreviewService({ strapi: mockStrapi });
const params = { documentId: '1', locale: 'en', status: 'published' as const };
const result = await previewService.getPreviewUrl('api::article.article', params);
expect(result).toBe('http://preview.example.com');
expect(mockConfig.isConfigured).toHaveBeenCalled();
expect(mockConfig.getPreviewHandler).toHaveBeenCalled();
expect(mockHandler).toHaveBeenCalledWith('api::article.article', params);
});
});

View File

@ -81,6 +81,11 @@ const createPreviewConfigService = ({ strapi }: { strapi: Core.Strapi }) => {
}
},
isConfigured() {
const config = strapi.config.get('admin.preview') as PreviewConfig;
return config?.enabled === false || config?.config?.handler != null;
},
isEnabled() {
const config = strapi.config.get('admin.preview') as PreviewConfig;
@ -113,8 +118,6 @@ const createPreviewConfigService = ({ strapi }: { strapi: Core.Strapi }) => {
* Utility to get the preview handler from the configuration
*/
getPreviewHandler(): PreviewConfig['config']['handler'] {
const config = strapi.config.get('admin.preview') as PreviewConfig;
const emptyHandler = () => {
return undefined;
};
@ -123,6 +126,8 @@ const createPreviewConfigService = ({ strapi }: { strapi: Core.Strapi }) => {
return emptyHandler;
}
const config = strapi.config.get('admin.preview') as PreviewConfig;
return config?.config?.handler || emptyHandler;
},
};

View File

@ -12,6 +12,12 @@ const createPreviewService = ({ strapi }: { strapi: Core.Strapi }) => {
return {
async getPreviewUrl(uid: UID.ContentType, params: HandlerParams) {
const isConfigured = config.isConfigured();
if (!isConfigured) {
throw new errors.NotFoundError('Preview config not found');
}
const handler = config.getPreviewHandler();
try {