mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 10:23:34 +00:00
Attributes list & delete attribute in GroupPage
This commit is contained in:
parent
d576b2129a
commit
c37f9e54df
@ -42,6 +42,18 @@ const List = styled.div`
|
||||
}
|
||||
}
|
||||
}
|
||||
td {
|
||||
padding: 0.75em;
|
||||
vertical-align: middle;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.8rem;
|
||||
&:first-of-type {
|
||||
padding-left: calc(3rem + 0.75em);
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-right: calc(3rem + 0.75em);
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
color: ${colors.blueTxt};
|
||||
tr {
|
||||
@ -65,18 +77,6 @@ const List = styled.div`
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
td {
|
||||
padding: 0.75em;
|
||||
vertical-align: middle;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.8rem;
|
||||
&:first-of-type {
|
||||
padding-left: calc(3rem + 0.75em);
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-right: calc(3rem + 0.75em);
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: ${sizes.tablet}) {
|
||||
width: 100%;
|
||||
|
||||
@ -12,6 +12,12 @@ const ListWrapper = styled.div`
|
||||
width: 100%;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.list-button {
|
||||
padding: 10px 30px 25px 30px;
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media (min-width: ${sizes.tablet}) {
|
||||
.table-wrapper {
|
||||
width: 100%;
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/**
|
||||
*
|
||||
* StyedListRow
|
||||
*
|
||||
*/
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyedListRow = styled.tr`
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
img {
|
||||
width: 35px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #f7f8f8;
|
||||
}
|
||||
td:first-of-type {
|
||||
padding-left: 3rem;
|
||||
}
|
||||
td:nth-child(2) {
|
||||
width: 25rem;
|
||||
p {
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyedListRow;
|
||||
@ -0,0 +1,27 @@
|
||||
import boolean from '../../assets/images/icon_boolean.png';
|
||||
import date from '../../assets/images/icon_date.png';
|
||||
import email from '../../assets/images/icon_email.png';
|
||||
import enumeration from '../../assets/images/icon_enumeration.png';
|
||||
import media from '../../assets/images/icon_media.png';
|
||||
import json from '../../assets/images/icon_json.png';
|
||||
import number from '../../assets/images/icon_number.png';
|
||||
import password from '../../assets/images/icon_password.png';
|
||||
import relation from '../../assets/images/icon_relation.png';
|
||||
import string from '../../assets/images/icon_string.png';
|
||||
import text from '../../assets/images/icon_text.png';
|
||||
|
||||
const assets = {
|
||||
boolean,
|
||||
date,
|
||||
email,
|
||||
enumeration,
|
||||
media,
|
||||
json,
|
||||
number,
|
||||
password,
|
||||
relation,
|
||||
string,
|
||||
text,
|
||||
};
|
||||
|
||||
export default assets;
|
||||
@ -0,0 +1,146 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { PopUpWarning } from 'strapi-helper-plugin';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
import StyledListRow from './StyledListRow';
|
||||
|
||||
import assets from './assets';
|
||||
|
||||
function ListRow({
|
||||
canOpenModal,
|
||||
deleteAttribute,
|
||||
isTemporary,
|
||||
name,
|
||||
onClickGoTo,
|
||||
source,
|
||||
uid,
|
||||
target,
|
||||
type,
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const ico = ['integer', 'biginteger', 'float', 'decimal'].includes(type)
|
||||
? 'number'
|
||||
: type;
|
||||
|
||||
const src = target ? assets.relation : assets[ico];
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledListRow
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
|
||||
const to = uid || name;
|
||||
onClickGoTo(to, source);
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<img src={src} alt={`icon-${ico}`} />
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
{name}
|
||||
{source && (
|
||||
<FormattedMessage id={`${pluginId}.from`}>
|
||||
{message => (
|
||||
<span
|
||||
style={{
|
||||
fontStyle: 'italic',
|
||||
color: '#787E8F',
|
||||
fontWeight: '500',
|
||||
}}
|
||||
>
|
||||
({message}: {source})
|
||||
</span>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
)}
|
||||
|
||||
{isTemporary && (
|
||||
<FormattedMessage
|
||||
id={`${pluginId}.contentType.temporaryDisplay`}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<FormattedMessage id={`${pluginId}.attribute.${type}`} />
|
||||
</td>
|
||||
<td>
|
||||
{!source && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
|
||||
const to = uid || name;
|
||||
onClickGoTo(to, source, canOpenModal || isTemporary);
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-pencil link-icon" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (canOpenModal || isTemporary) {
|
||||
setIsOpen(true);
|
||||
} else {
|
||||
strapi.notification.info(
|
||||
`${pluginId}.notification.info.work.notSaved`
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-trash link-icon" />
|
||||
</button>
|
||||
|
||||
<PopUpWarning
|
||||
isOpen={isOpen}
|
||||
toggleModal={() => setIsOpen(prevState => !prevState)}
|
||||
content={{
|
||||
message: `${pluginId}.popUpWarning.bodyMessage.${
|
||||
type === 'models' ? 'contentType' : 'groups'
|
||||
}.delete`,
|
||||
}}
|
||||
type="danger"
|
||||
onConfirm={() => {
|
||||
setIsOpen(false);
|
||||
deleteAttribute(name);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</StyledListRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ListRow.defaultProps = {
|
||||
target: null,
|
||||
source: null,
|
||||
uid: null,
|
||||
deleteAttribute: () => {},
|
||||
};
|
||||
|
||||
ListRow.propTypes = {
|
||||
canOpenModal: PropTypes.bool,
|
||||
context: PropTypes.object,
|
||||
deleteAttribute: PropTypes.func,
|
||||
isTemporary: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onClickGoTo: PropTypes.func.isRequired,
|
||||
source: PropTypes.string,
|
||||
uid: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ListRow;
|
||||
@ -15,6 +15,7 @@ import {
|
||||
CREATE_TEMP_CONTENT_TYPE,
|
||||
CREATE_TEMP_GROUP,
|
||||
DELETE_GROUP,
|
||||
DELETE_GROUP_ATTRIBUTE,
|
||||
DELETE_GROUP_SUCCEEDED,
|
||||
DELETE_MODEL,
|
||||
DELETE_MODEL_ATTRIBUTE,
|
||||
@ -113,6 +114,13 @@ export function deleteGroup(uid) {
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteGroupAttribute(keys) {
|
||||
return {
|
||||
type: DELETE_GROUP_ATTRIBUTE,
|
||||
keys,
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteGroupSucceeded(uid) {
|
||||
return {
|
||||
type: DELETE_GROUP_SUCCEEDED,
|
||||
|
||||
@ -20,6 +20,8 @@ export const CREATE_TEMP_CONTENT_TYPE =
|
||||
'ContentTypeBuilder/App/CREATE_TEMP_CONTENT_TYPE';
|
||||
export const CREATE_TEMP_GROUP = 'ContentTypeBuilder/App/CREATE_TEMP_GROUP';
|
||||
export const DELETE_GROUP = 'ContentTypeBuilder/App/DELETE_GROUP';
|
||||
export const DELETE_GROUP_ATTRIBUTE =
|
||||
'ContentTypeBuilder/App/DELETE_GROUP_ATTRIBUTE';
|
||||
export const DELETE_GROUP_SUCCEEDED =
|
||||
'ContentTypeBuilder/App/DELETE_GROUP_SUCCEEDED';
|
||||
export const DELETE_MODEL = 'ContentTypeBuilder/App/DELETE_MODEL';
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
CLEAR_TEMPORARY_ATTRIBUTE_RELATION,
|
||||
CREATE_TEMP_CONTENT_TYPE,
|
||||
CREATE_TEMP_GROUP,
|
||||
DELETE_GROUP_ATTRIBUTE,
|
||||
DELETE_GROUP_SUCCEEDED,
|
||||
DELETE_MODEL_ATTRIBUTE,
|
||||
DELETE_MODEL_SUCCEEDED,
|
||||
@ -258,6 +259,20 @@ function appReducer(state = initialState, action) {
|
||||
)
|
||||
)
|
||||
.update('newGroupClone', () => state.get('newGroup'));
|
||||
case DELETE_GROUP_ATTRIBUTE: {
|
||||
const pathToAttributes = action.keys
|
||||
.slice()
|
||||
.reverse()
|
||||
.splice(1)
|
||||
.reverse();
|
||||
const attributes = state.getIn(pathToAttributes);
|
||||
const attributeName = action.keys.pop();
|
||||
const attributeToDelete = attributes.findIndex(
|
||||
attribute => attribute.get('name') === attributeName
|
||||
);
|
||||
|
||||
return state.removeIn([...pathToAttributes, attributeToDelete]);
|
||||
}
|
||||
case DELETE_GROUP_SUCCEEDED:
|
||||
console.log({
|
||||
st: state
|
||||
|
||||
@ -1,22 +1,49 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get } from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { get, pickBy } from 'lodash';
|
||||
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
import ViewContainer from '../ViewContainer';
|
||||
import AttributesModalPicker from '../AttributesPickerModal';
|
||||
|
||||
import ListRow from '../../components/ListRow';
|
||||
|
||||
import {
|
||||
BackHeader,
|
||||
Button,
|
||||
EmptyAttributesBlock,
|
||||
getQueryParameters,
|
||||
ListWrapper,
|
||||
ListHeader,
|
||||
List,
|
||||
} from 'strapi-helper-plugin';
|
||||
|
||||
import { deleteGroupAttribute } from '../App/actions';
|
||||
|
||||
/* eslint-disable no-extra-boolean-cast */
|
||||
class GroupPage extends React.Component {
|
||||
export class GroupPage extends React.Component {
|
||||
featureType = 'group';
|
||||
|
||||
getFeature = () => {
|
||||
const { modifiedDataGroup, newGroup } = this.props;
|
||||
|
||||
if (this.isUpdatingTempFeature()) {
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
return get(modifiedDataGroup, this.getFeatureName(), {});
|
||||
};
|
||||
|
||||
getFeatureSchema = () => get(this.getFeature(), 'schema', {});
|
||||
|
||||
getFeatureAttributes = () => get(this.getFeatureSchema(), 'attributes', []);
|
||||
|
||||
getFeatureAttributesLength = () =>
|
||||
Object.keys(this.getFeatureAttributes()).length;
|
||||
|
||||
getFeatureName = () => {
|
||||
const {
|
||||
match: {
|
||||
@ -66,6 +93,21 @@ class GroupPage extends React.Component {
|
||||
return search;
|
||||
};
|
||||
|
||||
handleDeleteGroupAttribute = attrToDelete => {
|
||||
const { deleteGroupAttribute } = this.props;
|
||||
|
||||
const keys = this.isUpdatingTempFeature()
|
||||
? ['newGroup', 'schema', 'attributes', attrToDelete]
|
||||
: [
|
||||
'modifiedDataGroup',
|
||||
this.getFeatureName(),
|
||||
'schema',
|
||||
'attributes',
|
||||
attrToDelete,
|
||||
];
|
||||
deleteGroupAttribute(keys);
|
||||
};
|
||||
|
||||
handleGoBack = () => this.props.history.goBack();
|
||||
|
||||
isUpdatingTempFeature = () => {
|
||||
@ -77,9 +119,22 @@ class GroupPage extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
canOpenModal,
|
||||
history: { push },
|
||||
} = this.props;
|
||||
|
||||
const attributes = this.getFeatureAttributes();
|
||||
const attributesNumber = this.getFeatureAttributesLength();
|
||||
let listTitle = `${pluginId}.table.attributes.title.${
|
||||
attributesNumber > 1 ? 'plural' : 'singular'
|
||||
}`;
|
||||
|
||||
const buttonProps = {
|
||||
kind: 'secondaryHotlineAdd',
|
||||
label: `${pluginId}.button.attributes.add`,
|
||||
onClick: this.handleClick,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BackHeader onClick={this.handleGoBack} />
|
||||
@ -89,14 +144,47 @@ class GroupPage extends React.Component {
|
||||
headerTitle={this.getFeatureHeaderTitle()}
|
||||
headerDescription={this.getFeatureHeaderDescription()}
|
||||
>
|
||||
<EmptyAttributesBlock
|
||||
description={`${pluginId}.home.emptyAttributes.description.${
|
||||
this.featureType
|
||||
}`}
|
||||
id="openAddAttr"
|
||||
label="content-type-builder.button.attributes.add"
|
||||
title="content-type-builder.home.emptyAttributes.title"
|
||||
/>
|
||||
{attributesNumber === 0 ? (
|
||||
<EmptyAttributesBlock
|
||||
description={`${pluginId}.home.emptyAttributes.description.${
|
||||
this.featureType
|
||||
}`}
|
||||
id="openAddAttr"
|
||||
label="content-type-builder.button.attributes.add"
|
||||
title="content-type-builder.home.emptyAttributes.title"
|
||||
/>
|
||||
) : (
|
||||
<ListWrapper>
|
||||
<ListHeader
|
||||
title={listTitle}
|
||||
titleValues={{ number: attributesNumber }}
|
||||
relationTitle={listTitle}
|
||||
relationTitleValues={{ number: attributesNumber }}
|
||||
button={{ ...buttonProps }}
|
||||
/>
|
||||
<List>
|
||||
<table>
|
||||
<tbody>
|
||||
{attributes.map(attribute => (
|
||||
<ListRow
|
||||
key={attribute.name}
|
||||
canOpenModal={canOpenModal}
|
||||
context={this.context}
|
||||
deleteAttribute={this.handleDeleteGroupAttribute}
|
||||
{...attribute}
|
||||
type={attribute.type}
|
||||
isTemporary={false}
|
||||
onClickGoTo={() => {}}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</List>
|
||||
<div className="list-button">
|
||||
<Button {...buttonProps} />
|
||||
</div>
|
||||
</ListWrapper>
|
||||
)}
|
||||
</ViewContainer>
|
||||
<AttributesModalPicker
|
||||
isOpen={this.getModalType() === 'chooseAttributes'}
|
||||
@ -108,6 +196,7 @@ class GroupPage extends React.Component {
|
||||
}
|
||||
|
||||
GroupPage.propTypes = {
|
||||
deleteGroupAttribute: PropTypes.func.isRequired,
|
||||
groups: PropTypes.array.isRequired,
|
||||
history: PropTypes.shape({
|
||||
push: PropTypes.func.isRequired,
|
||||
@ -124,4 +213,18 @@ GroupPage.propTypes = {
|
||||
newGroup: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default GroupPage;
|
||||
export function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
deleteGroupAttribute,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
}
|
||||
|
||||
const withConnect = connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
);
|
||||
|
||||
export default compose(withConnect)(GroupPage);
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import GroupPage from '../index';
|
||||
import { EmptyAttributesBlock } from 'strapi-helper-plugin';
|
||||
import { GroupPage, mapDispatchToProps } from '../index';
|
||||
|
||||
import { deleteGroupAttribute } from '../../App/actions';
|
||||
|
||||
const basePath = '/plugins/content-type-builder/groups';
|
||||
const props = {
|
||||
deleteGroupAttribute: jest.fn(),
|
||||
groups: [
|
||||
{
|
||||
icon: 'fa-cube',
|
||||
@ -89,6 +93,17 @@ describe('CTB <GroupPage />', () => {
|
||||
shallow(<GroupPage {...props} />);
|
||||
});
|
||||
|
||||
describe('CTB <ModelPage /> render', () => {
|
||||
it("should display the EmptyAttributeBlock if the group's attributes are empty", () => {
|
||||
props.initialDataGroup.tests.schema.attributes = {};
|
||||
props.modifiedDataGroup.tests.schema.attributes = {};
|
||||
|
||||
const wrapper = shallow(<GroupPage {...props} />);
|
||||
|
||||
expect(wrapper.find(EmptyAttributesBlock)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetFeatureHeaderDescription', () => {
|
||||
it("should return the model's description field", () => {
|
||||
const { getFeatureHeaderDescription } = shallow(
|
||||
@ -99,7 +114,22 @@ describe('CTB <GroupPage />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFeatureName', () => {
|
||||
describe('GetFeature', () => {
|
||||
it('should return the correct model', () => {
|
||||
const { getFeature } = shallow(<GroupPage {...props} />).instance();
|
||||
|
||||
expect(getFeature()).toEqual(props.modifiedDataGroup.tests);
|
||||
});
|
||||
it('should return newGroup isTemporary is true', () => {
|
||||
props.groups.find(item => item.name == 'tests').isTemporary = true;
|
||||
|
||||
const { getFeature } = shallow(<GroupPage {...props} />).instance();
|
||||
|
||||
expect(getFeature()).toEqual(props.newGroup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetFeatureName', () => {
|
||||
it("should return the model's name field", () => {
|
||||
const { getFeatureName } = shallow(<GroupPage {...props} />).instance();
|
||||
|
||||
@ -107,3 +137,37 @@ describe('CTB <GroupPage />', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CTB <GroupPage />, mapDispatchToProps', () => {
|
||||
describe('DeleteGroupAttribute', () => {
|
||||
it('should be injected', () => {
|
||||
const dispatch = jest.fn();
|
||||
const result = mapDispatchToProps(dispatch);
|
||||
|
||||
expect(result.deleteGroupAttribute).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call deleteGroupAttribute with modifiedDataGroup path when isTemporary is false', () => {
|
||||
props.groups.find(item => item.name == 'tests').isTemporary = false;
|
||||
|
||||
const { handleDeleteGroupAttribute } = shallow(
|
||||
<GroupPage {...props} />
|
||||
).instance();
|
||||
handleDeleteGroupAttribute('name');
|
||||
|
||||
const keys = ['modifiedDataGroup', 'tests', 'schema', 'attributes', 'name'];
|
||||
expect(props.deleteGroupAttribute).toHaveBeenCalledWith(keys);
|
||||
});
|
||||
|
||||
it('should call deleteGroupAttribute with modifiedDataGroup path when isTemporary is true', () => {
|
||||
props.groups.find(item => item.name == 'tests').isTemporary = true;
|
||||
const { handleDeleteGroupAttribute } = shallow(
|
||||
<GroupPage {...props} />
|
||||
).instance();
|
||||
|
||||
handleDeleteGroupAttribute('name');
|
||||
const keys = ['newGroup', 'schema', 'attributes', 'name'];
|
||||
expect(props.deleteGroupAttribute).toHaveBeenCalledWith(keys);
|
||||
});
|
||||
});
|
||||
|
||||
@ -191,5 +191,7 @@
|
||||
"table.contentType.title.singular": "{number} Content Type is available",
|
||||
"table.groups.title.plural": "{number} Groups are available",
|
||||
"table.groups.title.singular": "{number} Group is available",
|
||||
"table.attributes.title.plural": "{number} fields",
|
||||
"table.attributes.title.singular": "{number} field",
|
||||
"prompt.content.unsaved": "Are you sure you want to leave this content type? All your modifications will be lost."
|
||||
}
|
||||
|
||||
@ -185,5 +185,7 @@
|
||||
"table.contentType.title.singular": "{number} Type de Contenu est disponible",
|
||||
"table.group.title.plural": "{number} Groupes sont disponibles",
|
||||
"table.group.title.singular": "{number} Groupe est disponible",
|
||||
"table.attributes.title.plural": "{number} chammps",
|
||||
"table.attributes.title.singular": "{number} champ",
|
||||
"prompt.content.unsaved": "Etes-vous sûr de vouloir quitter ce model? Toutes vos modifications seront perdues."
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ module.exports = {
|
||||
model: 'file',
|
||||
via: 'related',
|
||||
plugin: 'upload',
|
||||
type: 'media',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -50,6 +51,23 @@ module.exports = {
|
||||
model: 'file',
|
||||
via: 'related',
|
||||
plugin: 'upload',
|
||||
type: 'media',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
uid: 'cats',
|
||||
name: 'Cats',
|
||||
source: null,
|
||||
schema: {
|
||||
connection: 'default',
|
||||
collectionName: 'cats',
|
||||
description: 'Little description for cats group',
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -62,12 +80,7 @@ module.exports = {
|
||||
connection: 'default',
|
||||
collectionName: 'cars',
|
||||
description: 'Little description for cars group',
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
attributes: {},
|
||||
},
|
||||
},
|
||||
//...
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user