Content type attributes permissions integration

Signed-off-by: HichamELBSI <elabbassih@gmail.com>
This commit is contained in:
HichamELBSI 2020-06-05 09:37:05 +02:00 committed by Alexandre Bodin
parent 3f3f36a576
commit 3d4a43545e
23 changed files with 346 additions and 50 deletions

View File

@ -22,8 +22,8 @@
"type": "string"
},
"categories": {
"via": "addresses",
"collection": "category",
"via": "addresses",
"dominant": true
},
"cover": {

View File

@ -15,8 +15,8 @@
"type": "text"
},
"addresses": {
"collection": "address",
"via": "categories"
"via": "categories",
"collection": "address"
}
}
}

View File

@ -124,7 +124,7 @@ const CreatePage = () => {
/>
</FormCard>
{!isLayoutLoading && (
<Padded top size="md">
<Padded top bottom size="md">
<Permissions />
</Padded>
)}

View File

@ -0,0 +1,11 @@
import styled from 'styled-components';
import { Flex } from '@buffetjs/core';
const CollapseLabel = styled(Flex)`
padding-right: 10px;
overflow: hidden;
cursor: pointer;
flex: 1;
`;
export default CollapseLabel;

View File

@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Flex, Text, Checkbox, Padded } from '@buffetjs/core';
import PermissionCheckbox from '../../PermissionCheckbox';
import PermissionName from '../PermissionName';
import CollapseLabel from '../../CollapseLabel';
import Chevron from '../Chevron';
import PermissionWrapper from '../PermissionWrapper';
import AttributeRowWrapper from './AttributeRowWrapper';
const AttributeRow = ({ attribute }) => {
const isCollapsable = attribute.type === 'component';
const handleToggleAttributes = () => {
console.log('openAttribute');
};
return (
<AttributeRowWrapper isCollapsable={isCollapsable} alignItems="center">
<Flex style={{ flex: 1 }}>
<Padded left size="sm" />
<PermissionName width="15rem">
<Checkbox someChecked />
<CollapseLabel
title={attribute.attributeName}
alignItems="center"
onClick={handleToggleAttributes}
>
<Text
color="grey"
ellipsis
fontSize="xs"
fontWeight="bold"
lineHeight="20px"
textTransform="uppercase"
>
{attribute.attributeName}
</Text>
<Chevron icon="chevron-down" />
</CollapseLabel>
</PermissionName>
<PermissionWrapper>
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
</PermissionWrapper>
</Flex>
</AttributeRowWrapper>
);
};
AttributeRow.propTypes = {
attribute: PropTypes.object.isRequired,
};
export default AttributeRow;

View File

@ -0,0 +1,22 @@
/* eslint-disable indent */
import styled from 'styled-components';
import { Flex } from '@buffetjs/core';
import Chevron from '../Chevron';
const AttributeRowWrapper = styled(Flex)`
padding: 1rem 0;
flex: 1;
height: 36px;
${({ isCollapsable }) =>
isCollapsable &&
`
&:hover {
${Chevron} {
display: block;
}
}
`}
`;
export default AttributeRowWrapper;

View File

@ -0,0 +1,8 @@
import styled from 'styled-components';
const Wrapper = styled.div`
border: 1px solid ${({ theme }) => theme.main.colors.darkBlue};
border-top: none;
`;
export default Wrapper;

View File

@ -0,0 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Padded, Flex, Text } from '@buffetjs/core';
import { useGlobalContext } from 'strapi-helper-plugin';
import { useIntl } from 'react-intl';
import { getAttributesToDisplay } from '../../../../../../utils';
import AttributeRow from './AttributeRow';
import Wrapper from './Wrapper';
// Those styles are very specific.
// so it is not a big problem to use custom paddings and widths.
const ActionTitle = styled.div`
width: 12rem;
padding-top: 1rem;
padding-bottom: 1rem;
`;
const FieldsTitleWrapper = styled.div`
width: 18rem;
padding-top: 1rem;
padding-bottom: 1rem;
padding-left: 3.5rem;
`;
const Attributes = ({ attributes }) => {
const { plugins } = useGlobalContext();
const { formatMessage } = useIntl();
const attributesToDisplay = getAttributesToDisplay(plugins, attributes);
return (
<Wrapper>
<Flex>
<FieldsTitleWrapper>
<Text fontWeight="bold">
{formatMessage({
id: 'Settings.roles.form.permissions.fieldsPermissions',
defaultMessage: 'Fields permissions',
})}
</Text>
</FieldsTitleWrapper>
<ActionTitle>
<Text fontWeight="bold">
{formatMessage({
id: 'Settings.roles.form.permissions.create',
defaultMessage: 'Create',
})}
</Text>
</ActionTitle>
<ActionTitle>
<Text fontWeight="bold">
{formatMessage({
id: 'Settings.roles.form.permissions.read',
defaultMessage: 'Read',
})}
</Text>
</ActionTitle>
<ActionTitle>
<Text fontWeight="bold">
{formatMessage({
id: 'Settings.roles.form.permissions.update',
defaultMessage: 'Update',
})}
</Text>
</ActionTitle>
</Flex>
<Padded left size="md">
{attributesToDisplay.map(attribute => (
<AttributeRow attribute={attribute} key={attribute.attributeName} />
))}
</Padded>
</Wrapper>
);
};
Attributes.propTypes = {
attributes: PropTypes.object.isRequired,
};
export default Attributes;

View File

@ -8,7 +8,7 @@ const PermissionName = styled.div`
`;
PermissionName.defaultProps = {
width: '20rem',
width: '18rem',
};
PermissionName.propTypes = {
width: PropTypes.string,

View File

@ -0,0 +1,8 @@
import styled from 'styled-components';
import { Flex } from '@buffetjs/core';
const PermissionWrapper = styled(Flex)`
flex: 1;
`;
export default PermissionWrapper;

View File

@ -1,23 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Checkbox, Flex, Text, Padded } from '@buffetjs/core';
import Chevron from './Chevron';
import PermissionCheckbox from '../PermissionCheckbox';
import PermissionName from './PermissionName';
import Chevron from './Chevron';
import StyledRow from './StyledRow';
// No need to create an other file for this style. It will be used only in this file.
const CollapseLabel = styled(Flex)`
cursor: pointer;
`;
import Attributes from './Attributes';
import PermissionWrapper from './PermissionWrapper';
import CollapseLabel from '../CollapseLabel';
const ContentTypeRow = ({
openContentTypeAttributes,
openedContentTypeAttributes,
contentType,
index,
openContentTypeAttributes,
openedContentTypeAttributes,
}) => {
const isActive = openedContentTypeAttributes === contentType.name;
@ -26,31 +22,40 @@ const ContentTypeRow = ({
};
return (
<StyledRow isActive={isActive} isGrey={index % 2 === 0}>
<Flex>
<Padded left size="sm" />
<PermissionName>
<Checkbox someChecked />
<CollapseLabel alignItems="center" onClick={handleToggleAttributes}>
<Text
color="grey"
fontWeight="bold"
fontSize="xs"
textTransform="uppercase"
lineHeight="20px"
<>
<StyledRow isActive={isActive} isGrey={index % 2 === 0}>
<Flex style={{ flex: 1 }}>
<Padded left size="sm" />
<PermissionName>
<Checkbox someChecked />
<CollapseLabel
title={contentType.name}
alignItems="center"
onClick={handleToggleAttributes}
>
{contentType.name}
</Text>
<Chevron icon={isActive ? 'chevron-up' : 'chevron-down'} />
</CollapseLabel>
</PermissionName>
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
</Flex>
</StyledRow>
<Text
color="grey"
ellipsis
fontSize="xs"
fontWeight="bold"
lineHeight="20px"
textTransform="uppercase"
>
{contentType.name}
</Text>
<Chevron icon={isActive ? 'chevron-up' : 'chevron-down'} />
</CollapseLabel>
</PermissionName>
<PermissionWrapper>
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
<PermissionCheckbox />
</PermissionWrapper>
</Flex>
</StyledRow>
{isActive && <Attributes attributes={contentType.schema.attributes} />}
</>
);
};

View File

@ -2,7 +2,9 @@ import styled from 'styled-components';
import { Checkbox } from '@buffetjs/core';
const PermissionCheckbox = styled(Checkbox)`
width: 10rem;
min-width: 10rem;
max-width: 12rem;
flex: 1;
`;
export default PermissionCheckbox;

View File

@ -1,7 +1,7 @@
import styled from 'styled-components';
const PermissionsHeaderWrapper = styled.div`
padding-left: 211px;
padding-left: 165px;
padding-bottom: 25px;
padding-top: 26px;
`;

View File

@ -1,18 +1,40 @@
import React from 'react';
import { Flex } from '@buffetjs/core';
import { useIntl } from 'react-intl';
import PermissionCheckbox from '../PermissionCheckbox';
import Wrapper from './Wrapper';
const PermissionsHeader = () => {
const { formatMessage } = useIntl();
return (
<Wrapper>
<Flex>
<PermissionCheckbox message="Create" />
<PermissionCheckbox message="Read" />
<PermissionCheckbox message="Update" />
<PermissionCheckbox message="Delete" />
<PermissionCheckbox message="Publish" />
<PermissionCheckbox
message={formatMessage({
id: 'Settings.roles.form.permissions.create',
defaultMessage: 'Create',
})}
/>
<PermissionCheckbox
message={formatMessage({
id: 'Settings.roles.form.permissions.read',
defaultMessage: 'Read',
})}
/>
<PermissionCheckbox
message={formatMessage({
id: 'Settings.roles.form.permissions.update',
defaultMessage: 'Update',
})}
/>
<PermissionCheckbox
message={formatMessage({
id: 'Settings.roles.form.permissions.delete',
defaultMessage: 'Delete',
})}
/>
</Flex>
</Wrapper>
);

View File

@ -2,6 +2,11 @@ import styled from 'styled-components';
const Wrapper = styled.div`
background-color: ${({ theme }) => theme.main.colors.white};
overflow: auto;
::-webkit-scrollbar {
height: 10px;
}
`;
export default Wrapper;

View File

@ -112,7 +112,7 @@ const EditPage = () => {
role={role}
/>
{!isLayoutLoading && !isRoleLoading && (
<Padded top size="md">
<Padded top bottom size="md">
<Permissions />
</Padded>
)}

View File

@ -3,7 +3,7 @@ import { request } from 'strapi-helper-plugin';
import reducer, { initialState } from './reducer';
const Permissions = () => {
const useContentTypes = () => {
const [{ collectionTypes, singleTypes }, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
@ -11,6 +11,10 @@ const Permissions = () => {
}, []);
const fetchContentTypes = async () => {
dispatch({
type: 'GET_CONTENT_TYPES',
});
try {
const { data } = await request('/content-manager/content-types', {
method: 'GET',
@ -31,7 +35,8 @@ const Permissions = () => {
return {
singleTypes,
collectionTypes,
getData: fetchContentTypes,
};
};
export default Permissions;
export default useContentTypes;

View File

@ -10,6 +10,12 @@ export const initialState = {
const reducer = (state, action) =>
produce(state, draftState => {
switch (action.type) {
case 'GET_CONTENT_TYPES': {
draftState.collectionTypes = initialState.collectionTypes;
draftState.singleTypes = initialState.singleTypes;
draftState.isLoading = true;
break;
}
case 'GET_CONTENT_TYPES_SUCCEDED': {
const getContentTypeByKind = kind =>
action.data.filter(

View File

@ -39,6 +39,26 @@ describe('ADMIN | HOOKS | useContentTypes | reducer', () => {
});
});
describe('GET_DATA', () => {
it('should set isLoading to true to start getting the data', () => {
const action = {
type: 'GET_CONTENT_TYPES',
};
const initialState = {
collectionTypes: [],
singleTypes: [],
isLoading: true,
};
const expected = {
collectionTypes: [],
singleTypes: [],
isLoading: true,
};
expect(reducer(initialState, action)).toEqual(expected);
});
});
describe('GET_CONTENT_TYPES_SUCCEDED', () => {
it('should return the state with the collectionTypes and singleTypes', () => {
const action = {

View File

@ -274,8 +274,13 @@
"Settings.roles.form.input.description": "Description",
"Settings.roles.form.input.name": "Name",
"Settings.roles.form.title": "Details",
"Settings.roles.form.description": "Select the granted permissions for the token.",
"Settings.roles.form.description": "Name and description of the role",
"Settings.roles.form.button.users-with-role": "Users with this role",
"Settings.roles.form.permissions.create": "Create",
"Settings.roles.form.permissions.fieldsPermissions": "Fields permissions",
"Settings.roles.form.permissions.read": "Read",
"Settings.roles.form.permissions.update": "Update",
"Settings.roles.form.permissions.delete": "Delete",
"Settings.roles.list.button.add": "Add new role",
"Settings.roles.title.singular": "role",
"Settings.roles.list.title.singular": "{number} role",

View File

@ -0,0 +1,20 @@
import { get } from 'lodash';
const getAttributesToDisplay = (plugins, attributes) => {
const timestamps = get(
plugins,
['upload', 'fileModel', 'schema', 'options', 'timestamps'],
['created_at', 'updated_at']
);
const matchingAttributes = Object.keys(attributes).filter(
attribute => !['id', ...timestamps].includes(attribute)
);
const attributesToDisplay = matchingAttributes.map(attributeName => ({
...attributes[attributeName],
attributeName,
}));
return attributesToDisplay;
};
export default getAttributesToDisplay;

View File

@ -4,3 +4,4 @@ export { default as retrieveGlobalLinks } from './retrieveGlobalLinks';
export { default as retrievePluginsMenu } from './retrievePluginsMenu';
export { default as roleTabsLabel } from './roleTabsLabel';
export { default as fakePermissionsData } from './fakePermissionsData';
export { default as getAttributesToDisplay } from './getAttributesToDisplay';

View File

@ -0,0 +1,19 @@
import { getAttributesToDisplay } from '../index';
describe('ADMIN | utils | getAttributesToDisplay', () => {
it('should return attributes without id and timestamps', () => {
const attributes = {
id: { type: 'number' },
title: { type: 'string' },
description: { type: 'string' },
created_at: { type: 'timestamp' },
updated_at: { type: 'timestamp' },
};
const actual = getAttributesToDisplay(attributes);
const expectedAttributes = [
{ type: 'string', attributeName: 'title' },
{ type: 'string', attributeName: 'description' },
];
expect(actual).toEqual(expectedAttributes);
});
});