Merge branch 'features/i18n' of github.com:strapi/strapi into features/i18n

This commit is contained in:
Alexandre Bodin 2021-03-10 10:15:13 +01:00
commit 48c3817541
20 changed files with 288 additions and 175 deletions

View File

@ -4,13 +4,7 @@
*
*/
import {
GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
GET_USER_PERMISSIONS,
GET_USER_PERMISSIONS_ERROR,
GET_USER_PERMISSIONS_SUCCEEDED,
SET_APP_ERROR,
} from './constants';
import { GET_STRAPI_LATEST_RELEASE_SUCCEEDED, SET_APP_ERROR } from './constants';
export function getStrapiLatestReleaseSucceeded(latestStrapiReleaseTag, shouldUpdateStrapi) {
return {
@ -20,26 +14,6 @@ export function getStrapiLatestReleaseSucceeded(latestStrapiReleaseTag, shouldUp
};
}
export function getUserPermissions() {
return {
type: GET_USER_PERMISSIONS,
};
}
export function getUserPermissionsError(error) {
return {
type: GET_USER_PERMISSIONS_ERROR,
error,
};
}
export function getUserPermissionsSucceeded(data) {
return {
type: GET_USER_PERMISSIONS_SUCCEEDED,
data,
};
}
export function setAppError() {
return {
type: SET_APP_ERROR,

View File

@ -7,6 +7,3 @@
export const SET_APP_ERROR = 'StrapiAdmin/Admin/SET_APP_ERROR';
export const GET_STRAPI_LATEST_RELEASE_SUCCEEDED =
'StrapiAdmin/Admin/GET_STRAPI_LATEST_RELEASE_SUCCEEDED';
export const GET_USER_PERMISSIONS = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS';
export const GET_USER_PERMISSIONS_ERROR = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_ERROR';
export const GET_USER_PERMISSIONS_SUCCEEDED = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_SUCCEEDED';

View File

@ -19,7 +19,6 @@ import {
GlobalContextProvider,
LoadingIndicatorPage,
OverlayBlocker,
UserProvider,
CheckPagePermissions,
request,
} from 'strapi-helper-plugin';
@ -31,14 +30,14 @@ import Header from '../../components/Header/index';
import NavTopRightWrapper from '../../components/NavTopRightWrapper';
import LeftMenu from '../LeftMenu';
import InstalledPluginsPage from '../InstalledPluginsPage';
import HomePage from '../HomePage';
import MarketplacePage from '../MarketplacePage';
import NotFoundPage from '../NotFoundPage';
import OnboardingVideos from '../Onboarding';
import SettingsPage from '../SettingsPage';
import PermissionsManager from '../PermissionsManager';
import PluginDispatcher from '../PluginDispatcher';
import ProfilePage from '../ProfilePage';
import SettingsPage from '../SettingsPage';
import Logout from './Logout';
import {
disableGlobalOverlayBlocker,
@ -47,13 +46,7 @@ import {
updatePlugin,
} from '../App/actions';
import makeSelecApp from '../App/selectors';
import {
getStrapiLatestReleaseSucceeded,
getUserPermissions,
getUserPermissionsError,
getUserPermissionsSucceeded,
setAppError,
} from './actions';
import { getStrapiLatestReleaseSucceeded, setAppError } from './actions';
import makeSelectAdmin from './selectors';
import Wrapper from './Wrapper';
import Content from './Content';
@ -165,24 +158,6 @@ export class Admin extends React.Component {
}
};
fetchUserPermissions = async (resetState = false) => {
const { getUserPermissions, getUserPermissionsError, getUserPermissionsSucceeded } = this.props;
if (resetState) {
// Show a loader
getUserPermissions();
}
try {
const { data } = await request('/admin/users/me/permissions', { method: 'GET' });
getUserPermissionsSucceeded(data);
} catch (err) {
console.error(err);
getUserPermissionsError(err);
}
};
hasApluginNotReady = props => {
const {
global: { plugins },
@ -194,7 +169,6 @@ export class Admin extends React.Component {
initApp = async () => {
await this.fetchAppInfo();
await this.fetchStrapiLatestRelease();
await this.fetchUserPermissions(true);
};
/**
@ -233,7 +207,7 @@ export class Admin extends React.Component {
render() {
const {
admin: { isLoading, shouldUpdateStrapi, userPermissions },
admin: { shouldUpdateStrapi },
global: {
autoReload,
blockApp,
@ -259,29 +233,23 @@ export class Admin extends React.Component {
);
}
// Show a loader while permissions are being fetched
if (isLoading) {
return <LoadingIndicatorPage />;
}
return (
<GlobalContextProvider
autoReload={autoReload}
emitEvent={this.emitEvent}
currentEnvironment={currentEnvironment}
currentLocale={locale}
disableGlobalOverlayBlocker={disableGlobalOverlayBlocker}
enableGlobalOverlayBlocker={enableGlobalOverlayBlocker}
fetchUserPermissions={this.fetchUserPermissions}
formatMessage={formatMessage}
shouldUpdateStrapi={shouldUpdateStrapi}
menu={this.menuRef.current}
plugins={plugins}
settingsBaseURL={SETTINGS_BASE_URL || '/settings'}
strapiVersion={strapiVersion}
updatePlugin={updatePlugin}
>
<UserProvider value={userPermissions}>
<PermissionsManager>
<GlobalContextProvider
autoReload={autoReload}
emitEvent={this.emitEvent}
currentEnvironment={currentEnvironment}
currentLocale={locale}
disableGlobalOverlayBlocker={disableGlobalOverlayBlocker}
enableGlobalOverlayBlocker={enableGlobalOverlayBlocker}
formatMessage={formatMessage}
shouldUpdateStrapi={shouldUpdateStrapi}
menu={this.menuRef.current}
plugins={plugins}
settingsBaseURL={SETTINGS_BASE_URL || '/settings'}
strapiVersion={strapiVersion}
updatePlugin={updatePlugin}
>
<Wrapper>
<LeftMenu
shouldUpdateStrapi={shouldUpdateStrapi}
@ -327,8 +295,8 @@ export class Admin extends React.Component {
/>
{SHOW_TUTORIALS && <OnboardingVideos />}
</Wrapper>
</UserProvider>
</GlobalContextProvider>
</GlobalContextProvider>
</PermissionsManager>
);
}
}
@ -343,17 +311,12 @@ Admin.defaultProps = {
Admin.propTypes = {
admin: PropTypes.shape({
appError: PropTypes.bool,
isLoading: PropTypes.bool,
shouldUpdateStrapi: PropTypes.bool.isRequired,
userPermissions: PropTypes.array,
}).isRequired,
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
getInfosDataSucceeded: PropTypes.func.isRequired,
getStrapiLatestReleaseSucceeded: PropTypes.func.isRequired,
getUserPermissions: PropTypes.func.isRequired,
getUserPermissionsError: PropTypes.func.isRequired,
getUserPermissionsSucceeded: PropTypes.func.isRequired,
global: PropTypes.shape({
autoReload: PropTypes.bool,
blockApp: PropTypes.bool,
@ -385,9 +348,6 @@ export function mapDispatchToProps(dispatch) {
enableGlobalOverlayBlocker,
getInfosDataSucceeded,
getStrapiLatestReleaseSucceeded,
getUserPermissions,
getUserPermissionsError,
getUserPermissionsSucceeded,
setAppError,
updatePlugin,
},

View File

@ -7,21 +7,13 @@
import produce from 'immer';
import packageJSON from '../../../../package.json';
import {
GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
GET_USER_PERMISSIONS,
GET_USER_PERMISSIONS_ERROR,
GET_USER_PERMISSIONS_SUCCEEDED,
SET_APP_ERROR,
} from './constants';
import { GET_STRAPI_LATEST_RELEASE_SUCCEEDED, SET_APP_ERROR } from './constants';
const packageVersion = packageJSON.version;
const initialState = {
appError: false,
isLoading: true,
latestStrapiReleaseTag: `v${packageVersion}`,
shouldUpdateStrapi: false,
userPermissions: [],
};
const reducer = (state = initialState, action) =>
@ -33,21 +25,7 @@ const reducer = (state = initialState, action) =>
draftState.shouldUpdateStrapi = action.shouldUpdateStrapi;
break;
}
case GET_USER_PERMISSIONS: {
draftState.isLoading = true;
break;
}
case GET_USER_PERMISSIONS_ERROR: {
draftState.error = action.error;
draftState.isLoading = false;
break;
}
case GET_USER_PERMISSIONS_SUCCEEDED: {
draftState.isLoading = false;
draftState.userPermissions = action.data;
break;
}
case SET_APP_ERROR: {
draftState.appError = true;
break;

View File

@ -1,12 +1,6 @@
import produce from 'immer';
import packageJSON from '../../../../../package.json';
import {
setAppError,
getUserPermissions,
getUserPermissionsError,
getUserPermissionsSucceeded,
getStrapiLatestReleaseSucceeded,
} from '../actions';
import { setAppError, getStrapiLatestReleaseSucceeded } from '../actions';
import adminReducer from '../reducer';
describe('adminReducer', () => {
@ -15,9 +9,7 @@ describe('adminReducer', () => {
beforeEach(() => {
state = {
appError: false,
isLoading: true,
latestStrapiReleaseTag: `v${packageJSON.version}`,
userPermissions: [],
shouldUpdateStrapi: false,
};
});
@ -44,32 +36,4 @@ describe('adminReducer', () => {
expect(adminReducer(state, setAppError())).toEqual(expected);
});
it('should handle the getUserPermissions action correctly', () => {
const expected = produce(state, draft => {
draft.isLoading = true;
});
expect(adminReducer(state, getUserPermissions())).toEqual(expected);
});
it('should handle the getUserPermissionsError action correctly', () => {
const error = 'Error';
const expected = produce(state, draft => {
draft.isLoading = false;
draft.error = error;
});
expect(adminReducer(state, getUserPermissionsError(error))).toEqual(expected);
});
it('should handle the getUserPermissionsSucceeded action correctly', () => {
const data = ['permission 1', 'permission 2'];
const expected = produce(state, draft => {
draft.isLoading = false;
draft.userPermissions = data;
});
expect(adminReducer(state, getUserPermissionsSucceeded(data))).toEqual(expected);
});
});

View File

@ -32,7 +32,7 @@ import Wrapper from './Wrapper';
const LeftMenu = forwardRef(({ shouldUpdateStrapi, version, plugins }, ref) => {
const location = useLocation();
const permissions = useContext(UserContext);
const { userPermissions: permissions } = useContext(UserContext);
const { menu: settingsMenu } = useSettingsMenu(true);
// TODO: this needs to be added to the settings API in the v4

View File

@ -0,0 +1,25 @@
import {
GET_USER_PERMISSIONS,
GET_USER_PERMISSIONS_SUCCEEDED,
GET_USER_PERMISSIONS_ERROR,
} from './constants';
export function getUserPermissions() {
return {
type: GET_USER_PERMISSIONS,
};
}
export function getUserPermissionsError(error) {
return {
type: GET_USER_PERMISSIONS_ERROR,
error,
};
}
export function getUserPermissionsSucceeded(data) {
return {
type: GET_USER_PERMISSIONS_SUCCEEDED,
data,
};
}

View File

@ -0,0 +1,5 @@
export const GET_USER_PERMISSIONS = 'StrapiAdmin/PermissionsManager/GET_USER_PERMISSIONS';
export const GET_USER_PERMISSIONS_ERROR =
'StrapiAdmin/PermissionsManager/GET_USER_PERMISSIONS_ERROR';
export const GET_USER_PERMISSIONS_SUCCEEDED =
'StrapiAdmin/PermissionsManager/GET_USER_PERMISSIONS_SUCCEEDED';

View File

@ -0,0 +1,49 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useSelector, useDispatch } from 'react-redux';
import { LoadingIndicatorPage, UserProvider, request } from 'strapi-helper-plugin';
import {
getUserPermissions,
getUserPermissionsError,
getUserPermissionsSucceeded,
} from './actions';
const PermissionsManager = ({ children }) => {
const { isLoading, userPermissions } = useSelector(state => state.get('permissionsManager'));
const dispatch = useDispatch();
const fetchUserPermissions = async (resetState = false) => {
if (resetState) {
// Show a loader
dispatch(getUserPermissions());
}
try {
const { data } = await request('/admin/users/me/permissions', { method: 'GET' });
dispatch(getUserPermissionsSucceeded(data));
} catch (err) {
console.error(err);
dispatch(getUserPermissionsError(err));
}
};
useEffect(() => {
fetchUserPermissions(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (isLoading) {
return <LoadingIndicatorPage />;
}
return <UserProvider value={{ userPermissions, fetchUserPermissions }}>{children}</UserProvider>;
};
PermissionsManager.defaultProps = {};
PermissionsManager.propTypes = {
children: PropTypes.node.isRequired,
};
export default PermissionsManager;

View File

@ -0,0 +1,66 @@
/*
*
* PermissionsManager reducer
* The goal of this reducer is to provide
* the plugins with an access to the user's permissions
* in our middleware system
*
*/
import produce from 'immer';
import {
GET_USER_PERMISSIONS,
GET_USER_PERMISSIONS_ERROR,
GET_USER_PERMISSIONS_SUCCEEDED,
} from './constants';
const initialState = {
isLoading: true,
userPermissions: [],
collectionTypesRelatedPermissions: {},
};
const reducer = (state = initialState, action) =>
// eslint-disable-next-line consistent-return
produce(state, draftState => {
switch (action.type) {
case GET_USER_PERMISSIONS: {
draftState.isLoading = true;
draftState.userPermissions = [];
draftState.collectionTypesRelatedPermissions = {};
break;
}
case GET_USER_PERMISSIONS_ERROR: {
draftState.error = action.error;
draftState.isLoading = false;
break;
}
case GET_USER_PERMISSIONS_SUCCEEDED: {
draftState.isLoading = false;
draftState.userPermissions = action.data;
draftState.collectionTypesRelatedPermissions = action.data
.filter(perm => perm.subject)
.reduce((acc, current) => {
const { subject, action } = current;
if (!acc[subject]) {
acc[subject] = {};
}
acc[subject] = acc[subject][action]
? { ...acc[subject], [action]: [...acc[subject][action], current] }
: { ...acc[subject], [action]: [current] };
return acc;
}, {});
break;
}
default:
return state;
}
});
export default reducer;
export { initialState };

View File

@ -0,0 +1,103 @@
import produce from 'immer';
import {
getUserPermissions,
getUserPermissionsError,
getUserPermissionsSucceeded,
} from '../actions';
import permissionsManagerReducer from '../reducer';
describe('permissionsManagerReducer', () => {
let state;
beforeEach(() => {
state = {
isLoading: true,
userPermissions: [],
collectionTypesRelatedPermissions: {},
};
});
it('returns the initial state', () => {
const expected = state;
expect(permissionsManagerReducer(undefined, {})).toEqual(expected);
});
it('should handle the getUserPermissions action correctly', () => {
state.userPermissions = ['test'];
state.collectionTypesRelatedPermissions = null;
const expected = produce(state, draft => {
draft.isLoading = true;
draft.userPermissions = [];
draft.collectionTypesRelatedPermissions = {};
});
expect(permissionsManagerReducer(state, getUserPermissions())).toEqual(expected);
});
it('should handle the getUserPermissionsError action correctly', () => {
const error = 'Error';
const expected = produce(state, draft => {
draft.isLoading = false;
draft.error = error;
});
expect(permissionsManagerReducer(state, getUserPermissionsError(error))).toEqual(expected);
});
it('should handle the getUserPermissionsSucceeded action correctly', () => {
const data = [
{
action: 'create',
subject: 'address',
properties: {
fields: ['f1'],
},
conditions: [],
},
{
action: 'create',
subject: 'address',
properties: {
fields: ['f2'],
},
conditions: [],
},
{
action: 'tes',
subject: null,
properties: {},
conditions: [],
},
];
const expected = produce(state, draft => {
draft.isLoading = false;
draft.userPermissions = data;
draft.collectionTypesRelatedPermissions = {
address: {
create: [
{
action: 'create',
subject: 'address',
properties: {
fields: ['f1'],
},
conditions: [],
},
{
action: 'create',
subject: 'address',
properties: {
fields: ['f2'],
},
conditions: [],
},
],
},
};
});
expect(permissionsManagerReducer(state, getUserPermissionsSucceeded(data))).toEqual(expected);
});
});

View File

@ -5,7 +5,7 @@ import reducer, { initialState } from './reducer';
import init from './init';
const useSettingsMenu = (noCheck = false) => {
const permissions = useContext(UserContext);
const { userPermissions: permissions } = useContext(UserContext);
const { plugins } = useGlobalContext();
const [{ isLoading, menu }, dispatch] = useReducer(reducer, initialState, () =>

View File

@ -9,7 +9,7 @@ import adminReducer from './containers/Admin/reducer';
import languageProviderReducer from './containers/LanguageProvider/reducer';
import notificationProviderReducer from './containers/NotificationProvider/reducer';
import newNotificationReducer from './containers/NewNotification/reducer';
import permissionsManagerReducer from './containers/PermissionsManager/reducer';
/**
* Creates the main reducer with the dynamically injected ones
*/
@ -20,6 +20,7 @@ export default function createReducer(injectedReducers) {
language: languageProviderReducer,
notification: notificationProviderReducer,
newNotification: newNotificationReducer,
permissionsManager: permissionsManagerReducer,
...injectedReducers,
});
}

View File

@ -8,7 +8,8 @@ import LoadingIndicatorPage from '../LoadingIndicatorPage';
const CheckPagePermissions = ({ permissions, children }) => {
const abortController = new AbortController();
const { signal } = abortController;
const userPermissions = useUser();
const { userPermissions } = useUser();
const [state, setState] = useState({ isLoading: true, canAccess: false });
const isMounted = useRef(true);

View File

@ -8,7 +8,7 @@ import hasPermissions from '../../utils/hasPermissions';
// except that it does not handle redirections nor loading state
const CheckPermissions = ({ permissions, children }) => {
const userPermissions = useUser();
const { userPermissions } = useUser();
const [state, setState] = useState({ isLoading: true, canAccess: false });
const isMounted = useRef(true);
const abortController = new AbortController();

View File

@ -14,7 +14,7 @@ const useUserPermissions = pluginPermissions => {
const permissionNames = useMemo(() => {
return Object.keys(pluginPermissions);
}, [pluginPermissions]);
const currentUserPermissions = useUser();
const { userPermissions: currentUserPermissions } = useUser();
const [state, dispatch] = useReducer(reducer, {}, () => init(permissionNames));
const checkPermissionsRef = useRef();
const generateArrayOfPromisesRef = useRef();

View File

@ -11,7 +11,7 @@ const DynamicComponent = ({ componentUid, friendlyName, icon, setIsOverDynamicZo
const [isOver, setIsOver] = useState(false);
const [{ isLoading, canAccess }, setState] = useState({ isLoading: true, canAccess: false });
const { push } = useHistory();
const userPermissions = useUser();
const { userPermissions } = useUser();
useEffect(() => {
const checkPermission = async () => {

View File

@ -34,7 +34,7 @@ function FilterPicker({
}) {
const { emitEvent } = useGlobalContext();
const emitEventRef = useRef(emitEvent);
const userPermissions = useUser();
const { userPermissions } = useUser();
const readActionAllowedFields = useMemo(() => {
const matchingPermissions = findMatchingPermissions(userPermissions, [
{

View File

@ -39,7 +39,7 @@ const EditView = ({ isSingleType, goBack, layout, slug, state, id, origin }) =>
const { allowedActions, isLoading: isLoadingForPermissions } = useUserPermissions(
viewPermissions
);
const userPermissions = useUser();
const { userPermissions } = useUser();
// Here in case of a 403 response when fetching data we will either redirect to the previous page
// Or to the homepage if there's no state in the history stack

View File

@ -7,6 +7,7 @@ import {
useGlobalContext,
PopUpWarning,
useStrapi,
useUser,
} from 'strapi-helper-plugin';
import { useHistory, useLocation, useRouteMatch, Redirect } from 'react-router-dom';
import { connect, useDispatch } from 'react-redux';
@ -67,7 +68,9 @@ const DataManagerProvider = ({
} = useStrapi();
const { apis } = getPlugin(pluginId);
const [infoModals, toggleInfoModal] = useState({ cancel: false });
const { autoReload, emitEvent, fetchUserPermissions, formatMessage, menu } = useGlobalContext();
const { autoReload, emitEvent, formatMessage } = useGlobalContext();
const { fetchUserPermissions } = useUser();
const { pathname } = useLocation();
const { push } = useHistory();
const contentTypeMatch = useRouteMatch(`/plugins/${pluginId}/content-types/:uid`);
@ -301,8 +304,6 @@ const DataManagerProvider = ({
// Refetch the permissions
await updatePermissions();
// Update the app menu
await updateAppMenu();
// Refetch all the data
getDataRef.current();
}
@ -399,7 +400,6 @@ const DataManagerProvider = ({
const dataShape = orderAllDataAttributesWithImmutable(newSchemaToSet, isInContentTypeView);
// This prevents from losing the created content type or component when clicking on the link from the left menu
const hasJustCreatedSchema =
get(schemaToSet, 'isTemporary', false) &&
size(get(schemaToSet, 'schema.attributes', {})) === 0;
@ -471,9 +471,6 @@ const DataManagerProvider = ({
await updatePermissions();
// Update the app menu
await updateAppMenu();
// Submit ct tracking success
if (isInContentTypeView) {
emitEvent('didSaveContentType');
@ -512,13 +509,6 @@ const DataManagerProvider = ({
toggleInfoModal(prev => ({ ...prev, cancel: !prev.cancel }));
};
// Update the menu using the internal API
const updateAppMenu = async () => {
if (menu.getModels) {
await menu.getModels();
}
};
const updatePermissions = async () => {
await fetchUserPermissions();
};