Merge branch 'migrations/admin-users-delete' of github.com:strapi/strapi into migrations/admin-users-search

This commit is contained in:
soupette 2021-09-02 13:22:07 +02:00
commit 5b34f91d82
9 changed files with 111 additions and 79 deletions

View File

@ -59,6 +59,7 @@ module.exports = {
'<rootDir>/test/config/front/test-bundler.js', '<rootDir>/test/config/front/test-bundler.js',
'<rootDir>/packages/admin-test-utils/lib/mocks/LocalStorageMock.js', '<rootDir>/packages/admin-test-utils/lib/mocks/LocalStorageMock.js',
'<rootDir>/packages/admin-test-utils/lib/mocks/IntersectionObserver.js', '<rootDir>/packages/admin-test-utils/lib/mocks/IntersectionObserver.js',
'<rootDir>/packages/admin-test-utils/lib/mocks/ResizeObserver.js',
], ],
testPathIgnorePatterns: [ testPathIgnorePatterns: [
'/node_modules/', '/node_modules/',

View File

@ -0,0 +1,11 @@
'use strict';
class ResizeObserverMock {
constructor() {
this.disconnect = () => null;
this.observe = () => null;
this.unobserve = () => null;
}
}
global.ResizeObserver = ResizeObserverMock;

View File

@ -1,10 +1,10 @@
import React from 'react'; import { Box, Row, TableLabel } from '@strapi/parts';
import { MultiSelectNested } from '@strapi/parts/Select';
import { upperFirst } from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Row, TableLabel } from '@strapi/parts'; import React from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import styled from 'styled-components'; import styled from 'styled-components';
// import ConditionsSelect from '../ConditionsSelect';
import { rowHeight } from '../../Permissions/utils/constants'; import { rowHeight } from '../../Permissions/utils/constants';
const RowWrapper = styled(Row)` const RowWrapper = styled(Row)`
@ -12,16 +12,46 @@ const RowWrapper = styled(Row)`
`; `;
const ActionRow = ({ const ActionRow = ({
// arrayOfOptionsGroupedByCategory, arrayOfOptionsGroupedByCategory,
// isFormDisabled, isFormDisabled,
isGrey, isGrey,
label, label,
// name, name,
// onCategoryChange, onChange,
// onChange, value,
// value,
}) => { }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const options = arrayOfOptionsGroupedByCategory.reduce((arr, curr) => {
const [label, children] = curr;
const obj = {
label: upperFirst(label),
children: children.map(child => ({
label: child.displayName,
value: child.id,
})),
};
return [...arr, obj];
}, []);
// Output: ['value1', 'value2']
const values = Object.values(value)
.map(x =>
Object.entries(x)
.filter(([, value]) => value)
.map(([key]) => key)
)
.flat();
// ! Only expects arrayOfOpt to be [['default', obj]] - might break in future changes
const handleChange = val => {
const [[, values]] = arrayOfOptionsGroupedByCategory;
const formattedValues = values.reduce(
(acc, curr) => ({ [curr.id]: val.includes(curr.id), ...acc }),
{}
);
onChange(name, formattedValues);
};
return ( return (
<RowWrapper as="li" background={isGrey ? 'neutral100' : 'neutral0'}> <RowWrapper as="li" background={isGrey ? 'neutral100' : 'neutral0'}>
@ -56,26 +86,27 @@ const ActionRow = ({
})} })}
</TableLabel> </TableLabel>
</Row> </Row>
{/* <ConditionsSelect <Box style={{ maxWidth: 430, width: '100%' }}>
arrayOfOptionsGroupedByCategory={arrayOfOptionsGroupedByCategory} <MultiSelectNested
name={name} id={name}
isFormDisabled={isFormDisabled} customizeContent={values => `${values.length} currently selected`}
onCategoryChange={onCategoryChange} onChange={handleChange}
onChange={onChange} value={values}
value={value} options={options}
/> */} disabled={isFormDisabled}
/>
</Box>
</RowWrapper> </RowWrapper>
); );
}; };
ActionRow.propTypes = { ActionRow.propTypes = {
// arrayOfOptionsGroupedByCategory: PropTypes.array.isRequired, arrayOfOptionsGroupedByCategory: PropTypes.array.isRequired,
// isFormDisabled: PropTypes.bool.isRequired, isFormDisabled: PropTypes.bool.isRequired,
isGrey: PropTypes.bool.isRequired, isGrey: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
// name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
// value: PropTypes.object.isRequired, value: PropTypes.object.isRequired,
// onCategoryChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
// onChange: PropTypes.func.isRequired,
}; };
export default ActionRow; export default ActionRow;

View File

@ -3,31 +3,24 @@ import {
Breadcrumbs, Breadcrumbs,
Button, Button,
Crumb, Crumb,
Divider,
H2, H2,
ModalFooter, ModalFooter,
ModalHeader, ModalHeader,
ModalLayout, ModalLayout,
Stack, Stack,
Text, Text,
Divider,
} from '@strapi/parts'; } from '@strapi/parts';
import { cloneDeep, get, groupBy, set, upperFirst } from 'lodash'; import produce from 'immer';
import { get, groupBy, upperFirst } from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { usePermissionsDataManager } from '../../../hooks'; import { usePermissionsDataManager } from '../../../hooks';
import updateValues from '../Permissions/utils/updateValues';
import ActionRow from './ActionRow'; import ActionRow from './ActionRow';
import createDefaultConditionsForm from './utils/createDefaultConditionsForm'; import createDefaultConditionsForm from './utils/createDefaultConditionsForm';
const ConditionsModal = ({ const ConditionsModal = ({ actions, headerBreadCrumbs, isFormDisabled, onClosed, onToggle }) => {
actions,
headerBreadCrumbs,
isOpen,
isFormDisabled,
onClosed,
onToggle,
}) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { availableConditions, modifiedData, onChangeConditions } = usePermissionsDataManager(); const { availableConditions, modifiedData, onChangeConditions } = usePermissionsDataManager();
@ -50,26 +43,20 @@ const ConditionsModal = ({
const [state, setState] = useState(initState); const [state, setState] = useState(initState);
const handleCategoryChange = ({ keys, value }) => { const handleChange = (name, values) => {
setState(prevState => { setState(
const updatedState = cloneDeep(prevState); produce(draft => {
const objToUpdate = get(prevState, keys, {}); if (!draft[name]) {
const updatedValues = updateValues(objToUpdate, value); draft[name] = {};
}
set(updatedState, keys, updatedValues); if (!draft[name].default) {
draft[name].default = {};
}
return updatedState; draft[name].default = values;
}); })
}; );
const handleChange = ({ keys, value }) => {
setState(prevState => {
const updatedState = cloneDeep(prevState);
set(updatedState, keys, value);
return updatedState;
});
}; };
const handleSubmit = () => { const handleSubmit = () => {
@ -89,8 +76,6 @@ const ConditionsModal = ({
onToggle(); onToggle();
}; };
if (!isOpen) return null;
return ( return (
<ModalLayout onClose={onClosed}> <ModalLayout onClose={onClosed}>
<ModalHeader> <ModalHeader>
@ -140,7 +125,6 @@ const ConditionsModal = ({
isFormDisabled={isFormDisabled} isFormDisabled={isFormDisabled}
isGrey={index % 2 === 0} isGrey={index % 2 === 0}
name={name} name={name}
onCategoryChange={handleCategoryChange}
onChange={handleChange} onChange={handleChange}
value={get(state, name, {})} value={get(state, name, {})}
/> />
@ -181,7 +165,6 @@ ConditionsModal.propTypes = {
}) })
).isRequired, ).isRequired,
headerBreadCrumbs: PropTypes.arrayOf(PropTypes.string).isRequired, headerBreadCrumbs: PropTypes.arrayOf(PropTypes.string).isRequired,
isOpen: PropTypes.bool.isRequired,
isFormDisabled: PropTypes.bool.isRequired, isFormDisabled: PropTypes.bool.isRequired,
onClosed: PropTypes.func.isRequired, onClosed: PropTypes.func.isRequired,
onToggle: PropTypes.func.isRequired, onToggle: PropTypes.func.isRequired,

View File

@ -228,14 +228,15 @@ const Collapse = ({
)} )}
</Row> </Row>
<Box style={{ width: 120 }} /> <Box style={{ width: 120 }} />
{isModalOpen && (
<ConditionsModal <ConditionsModal
headerBreadCrumbs={[label, 'app.components.LeftMenuLinkContainer.settings']} headerBreadCrumbs={[label, 'app.components.LeftMenuLinkContainer.settings']}
actions={checkboxesActions} actions={checkboxesActions}
isOpen={isModalOpen}
isFormDisabled={isFormDisabled} isFormDisabled={isFormDisabled}
onClosed={handleModalClose} onClosed={handleModalClose}
onToggle={handleToggleModalIsOpen} onToggle={handleToggleModalIsOpen}
/> />
)}
</Wrapper> </Wrapper>
<AbsoluteBox> <AbsoluteBox>
<ConditionsButton <ConditionsButton

View File

@ -130,14 +130,15 @@ const SubCategory = ({ categoryName, isFormDisabled, subCategoryName, actions, p
/> />
</Row> </Row>
</Box> </Box>
{isModalOpen && (
<ConditionsModal <ConditionsModal
headerBreadCrumbs={[categoryName, subCategoryName]} headerBreadCrumbs={[categoryName, subCategoryName]}
actions={formattedActions} actions={formattedActions}
isOpen={isModalOpen}
isFormDisabled={isFormDisabled} isFormDisabled={isFormDisabled}
onClosed={handleModalClose} onClosed={handleModalClose}
onToggle={handleToggleModalIsOpen} onToggle={handleToggleModalIsOpen}
/> />
)}
</> </>
); );
}; };

View File

@ -1,16 +1,16 @@
import { useEffect, useReducer } from 'react'; import { useEffect, useReducer } from 'react';
import { hasPermissions, useRBACProvider, useStrapiApp } from '@strapi/helper-plugin'; import { hasPermissions, useRBACProvider, useStrapiApp, useAppInfos } from '@strapi/helper-plugin';
import reducer, { initialState } from './reducer'; import reducer, { initialState } from './reducer';
import init from './init'; import init from './init';
const useSettingsMenu = (noCheck = false) => { const useSettingsMenu = (noCheck = false) => {
const { allPermissions: permissions } = useRBACProvider(); const { allPermissions: permissions } = useRBACProvider();
const { shouldUpdateStrapi } = useAppInfos();
const { settings } = useStrapiApp(); const { settings } = useStrapiApp();
const [{ isLoading, menu }, dispatch] = useReducer(reducer, initialState, () => const [{ isLoading, menu }, dispatch] = useReducer(reducer, initialState, () =>
init(initialState, settings) init(initialState, { settings, shouldUpdateStrapi })
); );
useEffect(() => { useEffect(() => {

View File

@ -4,11 +4,15 @@ import adminPermissions from '../../permissions';
import formatLinks from './utils/formatLinks'; import formatLinks from './utils/formatLinks';
import globalLinks from './utils/globalLinks'; import globalLinks from './utils/globalLinks';
const init = (initialState, settings) => { const init = (initialState, { settings, shouldUpdateStrapi }) => {
// Retrieve the links that will be injected into the global section // Retrieve the links that will be injected into the global section
const pluginsGlobalLinks = settings.global.links; const pluginsGlobalLinks = settings.global.links;
// Sort the links by name // Sort the links by name
const sortedGlobalLinks = sortLinks([...pluginsGlobalLinks, ...globalLinks]); const sortedGlobalLinks = sortLinks([...pluginsGlobalLinks, ...globalLinks]).map(link => ({
...link,
hasNotification: link.id === 'application-infos' && shouldUpdateStrapi,
}));
const otherSections = Object.values(omit(settings, 'global')); const otherSections = Object.values(omit(settings, 'global'));
const menu = [ const menu = [

View File

@ -38,7 +38,7 @@ const SettingsNav = ({ menu }) => {
{sections.map(section => ( {sections.map(section => (
<SubNavSection key={section.id} label={formatMessage(section.intlLabel)}> <SubNavSection key={section.id} label={formatMessage(section.intlLabel)}>
{section.links.map(link => ( {section.links.map(link => (
<SubNavLink to={link.to} key={link.id}> <SubNavLink withBullet={link.hasNotification} to={link.to} key={link.id}>
{formatMessage(link.intlLabel)} {formatMessage(link.intlLabel)}
</SubNavLink> </SubNavLink>
))} ))}