Merge pull request #10522 from strapi/core/remove-middleware-cm-table

Remove addColumnToLV middleware in i18n
This commit is contained in:
cyril lopez 2021-06-22 12:38:11 +02:00 committed by GitHub
commit 31c75b2bd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 116 additions and 134 deletions

View File

@ -153,21 +153,6 @@ class StrapiApp {
}); });
}; };
async initialize() {
Object.keys(this.appPlugins).forEach(plugin => {
this.appPlugins[plugin].register({
addComponents: this.addComponents,
addCorePluginMenuLink: this.addCorePluginMenuLink,
addFields: this.addFields,
addMenuLink: this.addMenuLink,
addMiddlewares: this.addMiddlewares,
addReducers: this.addReducers,
createSettingSection: this.createSettingSection,
registerPlugin: this.registerPlugin,
});
});
}
async boot() { async boot() {
Object.keys(this.appPlugins).forEach(plugin => { Object.keys(this.appPlugins).forEach(plugin => {
const boot = this.appPlugins[plugin].boot; const boot = this.appPlugins[plugin].boot;
@ -177,11 +162,16 @@ class StrapiApp {
addSettingsLink: this.addSettingsLink, addSettingsLink: this.addSettingsLink,
addSettingsLinks: this.addSettingsLinks, addSettingsLinks: this.addSettingsLinks,
getPlugin: this.getPlugin, getPlugin: this.getPlugin,
registerHook: this.registerHook,
}); });
} }
}); });
} }
createHook = name => {
this.hooksDict[name] = createHook();
};
createSettingSection = (section, links) => { createSettingSection = (section, links) => {
invariant(section.id, 'section.id should be defined'); invariant(section.id, 'section.id should be defined');
invariant( invariant(
@ -209,6 +199,22 @@ class StrapiApp {
return this.plugins[pluginId]; return this.plugins[pluginId];
}; };
async initialize() {
Object.keys(this.appPlugins).forEach(plugin => {
this.appPlugins[plugin].register({
addComponents: this.addComponents,
addCorePluginMenuLink: this.addCorePluginMenuLink,
addFields: this.addFields,
addMenuLink: this.addMenuLink,
addMiddlewares: this.addMiddlewares,
addReducers: this.addReducers,
createHook: this.createHook,
createSettingSection: this.createSettingSection,
registerPlugin: this.registerPlugin,
});
});
}
async loadAdminTrads() { async loadAdminTrads() {
const arrayOfPromises = this.appLocales.map(locale => { const arrayOfPromises = this.appLocales.map(locale => {
return import(/* webpackChunkName: "[request]" */ `./translations/${locale}.json`) return import(/* webpackChunkName: "[request]" */ `./translations/${locale}.json`)
@ -270,20 +276,16 @@ class StrapiApp {
return Promise.resolve(); return Promise.resolve();
} }
registerHook = (name, fn) => {
this.hooksDict[name].register(fn);
};
registerPlugin = pluginConf => { registerPlugin = pluginConf => {
const plugin = Plugin(pluginConf); const plugin = Plugin(pluginConf);
this.plugins[plugin.pluginId] = plugin; this.plugins[plugin.pluginId] = plugin;
}; };
createHook = name => {
this.hooksDict[name] = createHook();
};
registerHook = (name, fn) => {
this.hooksDict[name].register(fn);
};
runHookSeries = (name, asynchronous = false) => runHookSeries = (name, asynchronous = false) =>
asynchronous ? this.hooksDict[name].runSeriesAsync() : this.hooksDict[name].runSeries(); asynchronous ? this.hooksDict[name].runSeriesAsync() : this.hooksDict[name].runSeries();

View File

@ -17,7 +17,6 @@ const name = pluginPkg.strapi.name;
export default { export default {
register(app) { register(app) {
app.addReducers(reducers);
app.addCorePluginMenuLink({ app.addCorePluginMenuLink({
to: `/plugins/${pluginId}`, to: `/plugins/${pluginId}`,
icon: 'book-open', icon: 'book-open',
@ -28,6 +27,11 @@ export default {
permissions: pluginPermissions.main, permissions: pluginPermissions.main,
}); });
app.addReducers(reducers);
// Hook that allows to mutate the displayed headers of the list view table
app.createHook('cm/inject-column-in-table');
app.registerPlugin({ app.registerPlugin({
description: pluginDescription, description: pluginDescription,
icon, icon,

View File

@ -18,6 +18,7 @@ import {
useNotification, useNotification,
useQueryParams, useQueryParams,
useRBACProvider, useRBACProvider,
useStrapiApp,
request, request,
} from '@strapi/helper-plugin'; } from '@strapi/helper-plugin';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
@ -93,6 +94,14 @@ function ListView({
const trackUsageRef = useRef(trackUsage); const trackUsageRef = useRef(trackUsage);
const fetchPermissionsRef = useRef(refetchPermissions); const fetchPermissionsRef = useRef(refetchPermissions);
const { runHookWaterfall } = useStrapiApp();
const tableHeaders = useMemo(() => {
const headers = runHookWaterfall('cm/inject-column-in-table', { displayedHeaders, layout });
return headers;
}, [runHookWaterfall, displayedHeaders, layout]);
const [{ query }, setQuery] = useQueryParams(); const [{ query }, setQuery] = useQueryParams();
const params = buildQueryString(query); const params = buildQueryString(query);
@ -466,7 +475,7 @@ function ListView({
canCreate={canCreate} canCreate={canCreate}
canDelete={canDelete} canDelete={canDelete}
canUpdate={canUpdate} canUpdate={canUpdate}
displayedHeaders={displayedHeaders} displayedHeaders={tableHeaders}
hasDraftAndPublish={hasDraftAndPublish} hasDraftAndPublish={hasDraftAndPublish}
isBulkable={isBulkable} isBulkable={isBulkable}
setQuery={setQuery} setQuery={setQuery}

View File

@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Padded, Text } from '@buffetjs/core'; import { Padded, Text } from '@buffetjs/core';
import { Tooltip } from '@buffetjs/styles'; import { Tooltip } from '@buffetjs/styles';
import get from 'lodash/get'; import get from 'lodash/get';
import styled from 'styled-components'; import styled from 'styled-components';
import selectI18NLocales from '../../selectors/selectI18nLocales';
const mapToLocaleName = (locales, localeCode) => const mapToLocaleName = (locales, localeCode) =>
get( get(
@ -18,7 +20,8 @@ const LocaleName = styled.div`
white-space: nowrap; white-space: nowrap;
`; `;
const LocaleListCell = ({ locales, localizations, locale: currentLocaleCode, id }) => { const LocaleListCell = ({ localizations, locale: currentLocaleCode, id }) => {
const locales = useSelector(selectI18NLocales);
const allLocalizations = [{ locale: currentLocaleCode }, ...localizations]; const allLocalizations = [{ locale: currentLocaleCode }, ...localizations];
const localizationNames = allLocalizations.map(locale => locale.locale); const localizationNames = allLocalizations.map(locale => locale.locale);
const defaultLocale = locales.find(locale => locale.isDefault); const defaultLocale = locales.find(locale => locale.isDefault);
@ -77,13 +80,6 @@ LocaleListCell.propTypes = {
locale: PropTypes.string.isRequired, locale: PropTypes.string.isRequired,
}) })
).isRequired, ).isRequired,
locales: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
code: PropTypes.string.isRequired,
isDefault: PropTypes.bool,
})
).isRequired,
locale: PropTypes.string.isRequired, locale: PropTypes.string.isRequired,
}; };

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { useSelector } from 'react-redux';
import LocaleListCell from '../LocaleListCell'; import LocaleListCell from '../LocaleListCell';
jest.mock('@buffetjs/styles', () => ({ jest.mock('@buffetjs/styles', () => ({
@ -11,6 +12,10 @@ jest.mock('@buffetjs/core', () => ({
Text: props => <p {...props} />, Text: props => <p {...props} />,
})); }));
jest.mock('react-redux', () => ({
useSelector: jest.fn(() => []),
}));
describe('LocaleListCell', () => { describe('LocaleListCell', () => {
it('returns the default locale first, then the others sorted alphabetically', () => { it('returns the default locale first, then the others sorted alphabetically', () => {
const locales = [ const locales = [
@ -40,6 +45,8 @@ describe('LocaleListCell', () => {
}, },
]; ];
useSelector.mockImplementation(() => locales);
const locale = 'en'; const locale = 'en';
const localizations = [{ locale: 'fr-FR' }, { locale: 'ar' }]; const localizations = [{ locale: 'fr-FR' }, { locale: 'ar' }];
@ -78,6 +85,8 @@ describe('LocaleListCell', () => {
}, },
]; ];
useSelector.mockImplementation(() => locales);
const locale = 'en'; const locale = 'en';
const localizations = [{ locale: 'ar' }]; const localizations = [{ locale: 'ar' }];
@ -115,6 +124,7 @@ describe('LocaleListCell', () => {
isDefault: false, isDefault: false,
}, },
]; ];
useSelector.mockImplementation(() => locales);
const locale = 'fr-FR'; const locale = 'fr-FR';
const localizations = [{ locale: 'en' }, { locale: 'ar' }]; const localizations = [{ locale: 'en' }, { locale: 'ar' }];

View File

@ -0,0 +1,24 @@
import React from 'react';
import get from 'lodash/get';
import LocaleListCell from '../components/LocaleListCell/LocaleListCell';
const addColumnToTableHook = ({ displayedHeaders, layout }) => {
const isFieldLocalized = get(layout, 'contentType.pluginOptions.i18n.localized', false);
if (!isFieldLocalized) {
return displayedHeaders;
}
return [
...displayedHeaders,
{
key: '__locale_key__',
fieldSchema: { type: 'string' },
metadatas: { label: 'Content available in', searchable: false, sortable: false },
name: 'locales',
cellFormatter: props => <LocaleListCell {...props} />,
},
];
};
export default addColumnToTableHook;

View File

@ -0,0 +1,36 @@
import addColumnToTable from '../addColumnToTable';
describe('i18n | contentManagerHooks | addColumnToTable', () => {
it('does nothing when there s no i18n.localized key in the action', () => {
const displayedHeaders = ['one'];
const layout = {
contentType: { pluginOptions: {} },
};
const result = addColumnToTable({ displayedHeaders, layout });
expect(result).toHaveLength(1);
expect(result).toEqual(['one']);
});
it('adds a header to the displayedHeaders array when the content type is localized', () => {
const displayedHeaders = [];
const layout = {
contentType: {
pluginOptions: {
i18n: { localized: true },
},
},
};
const result = addColumnToTable({ displayedHeaders, layout });
// The anonymous function of cellFormatter creates problem, because it's anonymous
// In our scenario, it's even more tricky because we use a closure in order to pass
// the locales.
// Stringifying the action allows us to have a name inside the expectation for the "cellFormatter" key
expect(JSON.stringify(result)).toBe(
'[{"key":"__locale_key__","fieldSchema":{"type":"string"},"metadatas":{"label":"Content available in","searchable":false,"sortable":false},"name":"locales"}]'
);
});
});

View File

@ -16,6 +16,7 @@ import mutateCTBContentTypeSchema from './utils/mutateCTBContentTypeSchema';
import LOCALIZED_FIELDS from './utils/localizedFields'; import LOCALIZED_FIELDS from './utils/localizedFields';
import i18nReducers from './hooks/reducers'; import i18nReducers from './hooks/reducers';
import DeleteModalAdditionalInfos from './components/DeleteModalAdditionalInfos'; import DeleteModalAdditionalInfos from './components/DeleteModalAdditionalInfos';
import addColumnToTableHook from './contentManagerHooks/addColumnToTable';
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description; const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon; const icon = pluginPkg.strapi.icon;
@ -39,6 +40,7 @@ export default {
}); });
}, },
boot(app) { boot(app) {
app.registerHook('cm/inject-column-in-table', addColumnToTableHook);
// Add the settings link // Add the settings link
app.addSettingsLink('global', { app.addSettingsLink('global', {
intlLabel: { intlLabel: {

View File

@ -1,32 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import LocaleListCell from '../components/LocaleListCell/LocaleListCell';
const addLocaleColumnToListViewMiddleware = () => ({ getState }) => next => action => {
if (action.type !== 'ContentManager/ListView/SET_LIST_LAYOUT ') {
return next(action);
}
const isFieldLocalized = get(action, 'contentType.pluginOptions.i18n.localized', false);
if (!isFieldLocalized) {
return next(action);
}
const store = getState();
const { locales } = store.i18n_locales;
const locale = {
key: '__locale_key__',
fieldSchema: { type: 'string' },
metadatas: { label: 'Content available in', searchable: false, sortable: false },
name: 'locales',
cellFormatter: props => <LocaleListCell {...props} locales={locales} />,
};
action.displayedHeaders = [...action.displayedHeaders, locale];
return next(action);
};
export default addLocaleColumnToListViewMiddleware;

View File

@ -4,7 +4,6 @@ import addLocaleToSingleTypesMiddleware from './addLocaleToSingleTypesMiddleware
import extendCMEditViewLayoutMiddleware from './extendCMEditViewLayoutMiddleware'; import extendCMEditViewLayoutMiddleware from './extendCMEditViewLayoutMiddleware';
import extendCTBInitialDataMiddleware from './extendCTBInitialDataMiddleware'; import extendCTBInitialDataMiddleware from './extendCTBInitialDataMiddleware';
import extendCTBAttributeInitialDataMiddleware from './extendCTBAttributeInitialDataMiddleware'; import extendCTBAttributeInitialDataMiddleware from './extendCTBAttributeInitialDataMiddleware';
import addLocaleColumnToListViewMiddleware from './addLocaleColumnToListViewMiddleware';
import localePermissionMiddleware from './localePermissionMiddleware'; import localePermissionMiddleware from './localePermissionMiddleware';
const middlewares = [ const middlewares = [
@ -14,7 +13,6 @@ const middlewares = [
extendCMEditViewLayoutMiddleware, extendCMEditViewLayoutMiddleware,
extendCTBInitialDataMiddleware, extendCTBInitialDataMiddleware,
extendCTBAttributeInitialDataMiddleware, extendCTBAttributeInitialDataMiddleware,
addLocaleColumnToListViewMiddleware,
localePermissionMiddleware, localePermissionMiddleware,
]; ];

View File

@ -1,67 +0,0 @@
import { fixtures } from '../../../../../../admin-test-utils';
import addLocaleColumnToListViewMiddleware from '../addLocaleColumnToListViewMiddleware';
describe('addLocaleColumnToListViewMiddleware', () => {
let getState;
beforeEach(() => {
const store = { ...fixtures.store.state, i18n_locales: { locales: [] } };
getState = () => store;
});
it('does nothing on unknown actions', () => {
const middleware = addLocaleColumnToListViewMiddleware()({ getState });
const nextFn = jest.fn();
const action = { type: 'UNKNOWN' };
middleware(nextFn)(action);
expect(nextFn).toBeCalledWith(action);
expect(action).toEqual({
type: 'UNKNOWN',
});
});
it('does nothing when there s no i18n.localized key in the action', () => {
const middleware = addLocaleColumnToListViewMiddleware()({ getState });
const nextFn = jest.fn();
const action = {
type: 'ContentManager/ListView/SET_LIST_LAYOUT ',
contentType: { pluginOptions: {} },
};
middleware(nextFn)(action);
expect(nextFn).toBeCalledWith(action);
expect(action).toEqual({
contentType: { pluginOptions: {} },
type: 'ContentManager/ListView/SET_LIST_LAYOUT ',
});
});
it('adds a header to the displayedHeaders array when the content type is localized', () => {
const middleware = addLocaleColumnToListViewMiddleware()({ getState });
const nextFn = jest.fn();
const action = {
type: 'ContentManager/ListView/SET_LIST_LAYOUT ',
displayedHeaders: [],
contentType: {
pluginOptions: {
i18n: { localized: true },
},
},
};
middleware(nextFn)(action);
expect(nextFn).toBeCalledWith(action);
// The anonymous function of cellFormatter creates problem, because it's anonymous
// In our scenario, it's even more tricky because we use a closure in order to pass
// the locales.
// Stringifying the action allows us to have a name inside the expectation for the "cellFormatter" key
expect(JSON.stringify(action)).toBe(
'{"type":"ContentManager/ListView/SET_LIST_LAYOUT ","displayedHeaders":[{"key":"__locale_key__","fieldSchema":{"type":"string"},"metadatas":{"label":"Content available in","searchable":false,"sortable":false},"name":"locales"}],"contentType":{"pluginOptions":{"i18n":{"localized":true}}}}'
);
});
});