Handle plugin model in Content Manager

This commit is contained in:
Aurelsicoko 2017-11-27 17:27:16 +01:00
parent 1c2f625a2b
commit eceede450b
19 changed files with 240 additions and 111 deletions

View File

@ -20,7 +20,13 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
return ( return (
<li className={styles.item}> <li className={styles.item}>
<Link className={`${styles.link} ${isLinkActive ? styles.linkActive : ''}`} to={this.props.destination}> <Link
className={`${styles.link} ${isLinkActive ? styles.linkActive : ''}`}
to={{
pathname: this.props.destination,
search: `?source=${this.props.source}`,
}}
>
<i className={`${styles.linkIcon} fa-${this.props.icon} fa`}></i> <i className={`${styles.linkIcon} fa-${this.props.icon} fa`}></i>
<FormattedMessage <FormattedMessage
id={this.props.label} id={this.props.label}
@ -40,6 +46,11 @@ LeftMenuLink.propTypes = {
destination: PropTypes.string.isRequired, destination: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired, icon: PropTypes.string.isRequired,
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
source: PropTypes.string,
};
LeftMenuLink.defaultProps = {
source: '',
}; };
export default LeftMenuLink; export default LeftMenuLink;

View File

@ -23,7 +23,8 @@ function LeftMenuLinkContainer({ plugins }) {
acc[snakeCase(section.name)] = { acc[snakeCase(section.name)] = {
name: section.name, name: section.name,
links: (get(acc[snakeCase(section.name)], 'links') || []).concat(section.links.map(link => { links: (get(acc[snakeCase(section.name)], 'links') || []).concat(section.links.map(link => {
link.plugin = !isEmpty(pluginsObject[link.plugin] ? link.plugin : pluginsObject[current].id); link.source = current;
link.plugin = !isEmpty(pluginsObject[link.plugin]) ? link.plugin : pluginsObject[current].id;
return link; return link;
})), })),
@ -38,7 +39,7 @@ function LeftMenuLinkContainer({ plugins }) {
<p className={styles.title}>{pluginsSections[current].name}</p> <p className={styles.title}>{pluginsSections[current].name}</p>
<ul className={styles.list}> <ul className={styles.list}>
{sortBy(pluginsSections[current].links, 'label').map(link => {sortBy(pluginsSections[current].links, 'label').map(link =>
<LeftMenuLink key={link.label} icon={link.icon || 'link'} label={link.label} destination={`/plugins/${link.plugin}/${link.destination}`} /> <LeftMenuLink key={link.label} icon={link.icon || 'link'} label={link.label} destination={`/plugins/${link.plugin}/${link.destination}`} source={link.source} />
)} )}
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,4 @@
export default (location, n) => {
const half = location.split(n + '=')[1];
return half !== undefined ? decodeURIComponent(half.split('&')[0]) : null;
};

View File

@ -12,6 +12,9 @@ import { findIndex, get, omit, isFunction, merge } from 'lodash';
// Components. // Components.
import Input from 'components/Input'; import Input from 'components/Input';
// Utils.
import getQueryParameters from 'utils/getQueryParameters';
// Styles. // Styles.
import styles from './styles.scss'; import styles from './styles.scss';
@ -42,8 +45,12 @@ class EditForm extends React.Component {
} }
render() { render() {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
const currentLayout = source === undefined || source === 'content-manager' ? get(this.props.layout, [this.props.currentModelName]) : get(this.props.layout, ['plugins', source, this.props.currentModelName]);
// Remove `id` field // Remove `id` field
const displayedFields = merge(this.props.layout[this.props.currentModelName], omit(this.props.schema[this.props.currentModelName].fields, 'id')); const displayedFields = merge(currentLayout, omit(currentSchema.fields, 'id'));
// List fields inputs // List fields inputs
const fields = Object.keys(displayedFields).map(attr => { const fields = Object.keys(displayedFields).map(attr => {
@ -53,14 +60,14 @@ class EditForm extends React.Component {
const validationsIndex = findIndex(this.props.formValidations, ['name', attr]); const validationsIndex = findIndex(this.props.formValidations, ['name', attr]);
const validations = get(this.props.formValidations[validationsIndex], 'validations') || {}; const validations = get(this.props.formValidations[validationsIndex], 'validations') || {};
const layout = Object.keys(get(this.props.layout[this.props.currentModelName], attr, {})).reduce((acc, current) => { const layout = Object.keys(get(currentLayout, attr, {})).reduce((acc, current) => {
acc[current] = isFunction(this.props.layout[this.props.currentModelName][attr][current]) ? acc[current] = isFunction(currentLayout[attr][current]) ?
this.props.layout[this.props.currentModelName][attr][current](this) : currentLayout[attr][current](this) :
this.props.layout[this.props.currentModelName][attr][current]; currentLayout[attr][current];
return acc; return acc;
}, {}); }, {});
return ( return (
<Input <Input
key={attr} key={attr}
@ -95,6 +102,9 @@ EditForm.propTypes = {
formErrors: PropTypes.array.isRequired, formErrors: PropTypes.array.isRequired,
formValidations: PropTypes.array.isRequired, formValidations: PropTypes.array.isRequired,
layout: PropTypes.object.isRequired, layout: PropTypes.object.isRequired,
location: PropTypes.shape({
search: PropTypes.string,
}).isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
record: PropTypes.oneOfType([ record: PropTypes.oneOfType([

View File

@ -8,8 +8,14 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { get, map, size } from 'lodash'; import { get, map, size } from 'lodash';
// Components.
import SelectOne from 'components/SelectOne'; import SelectOne from 'components/SelectOne';
import SelectMany from 'components/SelectMany'; import SelectMany from 'components/SelectMany';
// Utils.
import getQueryParameters from 'utils/getQueryParameters';
// Style.
import styles from './styles.scss'; import styles from './styles.scss';
class EditFormRelations extends React.Component { // eslint-disable-line react/prefer-stateless-function class EditFormRelations extends React.Component { // eslint-disable-line react/prefer-stateless-function
@ -20,7 +26,10 @@ class EditFormRelations extends React.Component { // eslint-disable-line react/p
} }
render() { render() {
const relations = map(this.props.schema[this.props.currentModelName].relations, (relation, i) => { const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
const relations = map(currentSchema.relations, (relation, i) => {
switch (relation.nature) { switch (relation.nature) {
case 'oneToOne': case 'oneToOne':
@ -75,6 +84,9 @@ EditFormRelations.propTypes = {
PropTypes.string, PropTypes.string,
]).isRequired, ]).isRequired,
isNull: PropTypes.bool.isRequired, isNull: PropTypes.bool.isRequired,
location: PropTypes.shape({
search: PropTypes.string,
}).isRequired,
record: PropTypes.oneOfType([ record: PropTypes.oneOfType([
PropTypes.object, PropTypes.object,
PropTypes.bool, PropTypes.bool,

View File

@ -19,10 +19,11 @@ export function emptyStore() {
}; };
} }
export function getModelEntries(modelName) { export function getModelEntries(modelName, source) {
return { return {
type: GET_MODEL_ENTRIES, type: GET_MODEL_ENTRIES,
modelName, modelName,
source,
}; };
} }

View File

@ -14,39 +14,26 @@ import { isEmpty, get } from 'lodash';
import { Switch, Route } from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
import injectSaga from 'utils/injectSaga'; import injectSaga from 'utils/injectSaga';
import getQueryParameters from 'utils/getQueryParameters';
import Home from 'containers/Home'; import Home from 'containers/Home';
import Edit from 'containers/Edit'; import Edit from 'containers/Edit';
import List from 'containers/List'; import List from 'containers/List';
import EmptyAttributesView from 'components/EmptyAttributesView'; import EmptyAttributesView from 'components/EmptyAttributesView';
import { emptyStore, getModelEntries, loadModels, updateSchema } from './actions'; import { emptyStore, getModelEntries, loadModels } from './actions';
import { makeSelectLoading, makeSelectModels, makeSelectModelEntries } from './selectors'; import { makeSelectLoading, makeSelectModels, makeSelectModelEntries } from './selectors';
import saga from './sagas'; import saga from './sagas';
const tryRequire = (path) => {
try {
return require(`containers/${path}.js`); // eslint-disable-line global-require
} catch (err) {
return null;
}
};
class App extends React.Component { class App extends React.Component {
componentDidMount() { componentDidMount() {
const config = tryRequire('../../../../config/admin.json'); this.props.loadModels();
if (!isEmpty(get(config, 'admin.schema'))) {
this.props.updateSchema(config.admin.schema);
} else {
this.props.loadModels();
}
const modelName = this.props.location.pathname.split('/')[3]; const modelName = this.props.location.pathname.split('/')[3];
if (modelName) { if (modelName) {
this.props.getModelEntries(modelName); this.props.getModelEntries(modelName, getQueryParameters(this.props.location.search, 'source'));
} }
} }
@ -54,7 +41,7 @@ class App extends React.Component {
const currentModelName = this.props.location.pathname.split('/')[3]; const currentModelName = this.props.location.pathname.split('/')[3];
if (prevProps.location.pathname !== this.props.location.pathname && currentModelName) { if (prevProps.location.pathname !== this.props.location.pathname && currentModelName) {
this.props.getModelEntries(currentModelName); this.props.getModelEntries(currentModelName, getQueryParameters(this.props.location.search, 'source'));
} }
} }
@ -68,9 +55,12 @@ class App extends React.Component {
} }
const currentModelName = this.props.location.pathname.split('/')[3]; const currentModelName = this.props.location.pathname.split('/')[3];
const source = getQueryParameters(this.props.location.search, 'source');
if (currentModelName && isEmpty(get(this.props.models, [currentModelName, 'attributes']))) { if (currentModelName && source && isEmpty(get(this.props.models.plugins, [source, 'models', currentModelName, 'attributes']))) {
return <EmptyAttributesView currentModelName={currentModelName} history={this.props.history} modelEntries={this.props.modelEntries} />; if (currentModelName && isEmpty(get(this.props.models.models, [currentModelName, 'attributes']))) {
return <EmptyAttributesView currentModelName={currentModelName} history={this.props.history} modelEntries={this.props.modelEntries} />;
}
} }
return ( return (
@ -101,7 +91,6 @@ App.propTypes = {
PropTypes.bool, PropTypes.bool,
PropTypes.object, PropTypes.object,
]).isRequired, ]).isRequired,
updateSchema: PropTypes.func.isRequired,
}; };
export function mapDispatchToProps(dispatch) { export function mapDispatchToProps(dispatch) {
@ -110,7 +99,6 @@ export function mapDispatchToProps(dispatch) {
emptyStore, emptyStore,
getModelEntries, getModelEntries,
loadModels, loadModels,
updateSchema,
}, },
dispatch, dispatch,
); );

View File

@ -1,4 +1,4 @@
import { map } from 'lodash'; import { map, omit } from 'lodash';
import { fork, put, select, call, takeLatest } from 'redux-saga/effects'; import { fork, put, select, call, takeLatest } from 'redux-saga/effects';
import request from 'utils/request'; import request from 'utils/request';
@ -10,7 +10,8 @@ import { makeSelectModels } from './selectors';
export function* modelEntriesGet(action) { export function* modelEntriesGet(action) {
try { try {
const requestUrl = `${strapi.backendURL}/content-manager/explorer/${action.modelName}/count`; const requestUrl = `${strapi.backendURL}/content-manager/explorer/${action.modelName}/count${action.source !== undefined ? `?source=${action.source}`: ''}`;
const response = yield call(request, requestUrl, { method: 'GET' }); const response = yield call(request, requestUrl, { method: 'GET' });
yield put(getModelEntriesSucceeded(response.count)); yield put(getModelEntriesSucceeded(response.count));
@ -27,7 +28,7 @@ export const generateMenu = function () {
.then(displayedModels => { .then(displayedModels => {
return [{ return [{
name: 'Content Types', name: 'Content Types',
links: map(displayedModels, (model, key) => ({ links: map(omit(displayedModels, 'plugins'), (model, key) => ({
label: model.labelPlural || model.label || key, label: model.labelPlural || model.label || key,
destination: key, destination: key,
})), })),

View File

@ -33,24 +33,27 @@ export function cancelChanges() {
}; };
} }
export function deleteRecord(id, modelName) { export function deleteRecord(id, modelName, source) {
return { return {
type: DELETE_RECORD, type: DELETE_RECORD,
id, id,
modelName, modelName,
source,
}; };
} }
export function editRecord() { export function editRecord(source) {
return { return {
type: EDIT_RECORD, type: EDIT_RECORD,
source,
}; };
} }
export function loadRecord(id) { export function loadRecord(id, source) {
return { return {
type: LOAD_RECORD, type: LOAD_RECORD,
id, id,
source,
}; };
} }

View File

@ -24,6 +24,7 @@ import PluginHeader from 'components/PluginHeader';
import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors'; import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors';
// Utils. // Utils.
import getQueryParameters from 'utils/getQueryParameters';
import injectReducer from 'utils/injectReducer'; import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga'; import injectSaga from 'utils/injectSaga';
import templateObject from 'utils/templateObject'; import templateObject from 'utils/templateObject';
@ -97,15 +98,20 @@ export class Edit extends React.Component {
} }
componentDidMount() { componentDidMount() {
const source = getQueryParameters(this.props.location.search, 'source');
const attributes =
get(this.props.models, ['models', this.props.match.params.slug.toLowerCase(), 'attributes']) ||
get(this.props.models, ['plugins', source, 'models', this.props.match.params.slug.toLowerCase(), 'attributes']);
this.props.setInitialState(); this.props.setInitialState();
this.props.setCurrentModelName(this.props.match.params.slug.toLowerCase()); this.props.setCurrentModelName(this.props.match.params.slug.toLowerCase());
this.props.setFormValidations(this.props.models[this.props.match.params.slug.toLowerCase()].attributes); this.props.setFormValidations(attributes);
this.props.setForm(this.props.models[this.props.match.params.slug.toLowerCase()].attributes); this.props.setForm(attributes);
// Detect that the current route is the `create` route or not // Detect that the current route is the `create` route or not
if (this.props.match.params.id === 'create') { if (this.props.match.params.id === 'create') {
this.props.setIsCreating(); this.props.setIsCreating();
} else { } else {
this.props.loadRecord(this.props.match.params.id); this.props.loadRecord(this.props.match.params.id, source);
} }
} }
@ -127,11 +133,14 @@ export class Edit extends React.Component {
} }
handleChange = (e) => { handleChange = (e) => {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
let formattedValue = e.target.value; let formattedValue = e.target.value;
if (isObject(e.target.value) && e.target.value._isAMomentObject === true) { if (isObject(e.target.value) && e.target.value._isAMomentObject === true) {
formattedValue = moment(e.target.value, 'YYYY-MM-DD HH:mm:ss').format(); formattedValue = moment(e.target.value, 'YYYY-MM-DD HH:mm:ss').format();
} else if (['float', 'integer', 'bigint'].indexOf(this.props.schema[this.props.currentModelName].fields[e.target.name].type) !== -1) { } else if (['float', 'integer', 'bigint'].indexOf(currentSchema.fields[e.target.name].type) !== -1) {
formattedValue = toNumber(e.target.value); formattedValue = toNumber(e.target.value);
} }
@ -140,12 +149,15 @@ export class Edit extends React.Component {
handleSubmit = (e) => { handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
const form = this.props.form.toJS(); const form = this.props.form.toJS();
map(this.props.record.toJS(), (value, key) => form[key] = value); map(this.props.record.toJS(), (value, key) => form[key] = value);
const formErrors = checkFormValidity(form, this.props.formValidations.toJS()); const formErrors = checkFormValidity(form, this.props.formValidations.toJS());
const source = getQueryParameters(this.props.location.search, 'source');
if (isEmpty(formErrors)) { if (isEmpty(formErrors)) {
this.props.editRecord(); this.props.editRecord(source);
} else { } else {
this.props.setFormErrors(formErrors); this.props.setFormErrors(formErrors);
} }
@ -156,9 +168,12 @@ export class Edit extends React.Component {
return <p>Loading...</p>; return <p>Loading...</p>;
} }
const source = getQueryParameters(this.props.location.search, 'source');
const currentModel = get(this.props.models, ['models', this.props.currentModelName]) || get(this.props.models, ['plugins', source, 'models', this.props.currentModelName]);
// Plugin header config // Plugin header config
const primaryKey = this.props.models[this.props.currentModelName].primaryKey; const primaryKey = currentModel.primaryKey;
const mainField = get(this.props.models, `${this.props.currentModelName}.info.mainField`) || primaryKey; const mainField = get(currentModel, 'info.mainField') || primaryKey;
const pluginHeaderTitle = this.props.isCreating ? 'New entry' : templateObject({ mainField }, this.props.record.toJS()).mainField; const pluginHeaderTitle = this.props.isCreating ? 'New entry' : templateObject({ mainField }, this.props.record.toJS()).mainField;
const pluginHeaderDescription = this.props.isCreating ? 'New entry' : `#${this.props.record && this.props.record.get(primaryKey)}`; const pluginHeaderDescription = this.props.isCreating ? 'New entry' : `#${this.props.record && this.props.record.get(primaryKey)}`;
@ -192,6 +207,7 @@ export class Edit extends React.Component {
didCheckErrors={this.props.didCheckErrors} didCheckErrors={this.props.didCheckErrors}
formValidations={this.props.formValidations.toJS()} formValidations={this.props.formValidations.toJS()}
layout={this.layout} layout={this.layout}
location={this.props.location}
/> />
</div> </div>
</div> </div>
@ -204,6 +220,7 @@ export class Edit extends React.Component {
setRecordAttribute={this.props.setRecordAttribute} setRecordAttribute={this.props.setRecordAttribute}
isNull={this.props.isRelationComponentNull} isNull={this.props.isRelationComponentNull}
toggleNull={this.props.toggleNull} toggleNull={this.props.toggleNull}
location={this.props.location}
/> />
</div> </div>
</div> </div>

View File

@ -19,15 +19,21 @@ import {
makeSelectIsCreating, makeSelectIsCreating,
} from './selectors'; } from './selectors';
export function* getRecord(params) { export function* getRecord(action) {
const currentModelName = yield select(makeSelectCurrentModelName()); const currentModelName = yield select(makeSelectCurrentModelName());
const params = {};
if (action.source !== undefined) {
params.source = action.source;
}
try { try {
const requestUrl = `${strapi.backendURL}/content-manager/explorer/${currentModelName}/${params.id}`; const requestUrl = `${strapi.backendURL}/content-manager/explorer/${currentModelName}/${action.id}`;
// Call our request helper (see 'utils/request') // Call our request helper (see 'utils/request')
const response = yield request(requestUrl, { const response = yield request(requestUrl, {
method: 'GET', method: 'GET',
params,
}); });
yield put(recordLoaded(response)); yield put(recordLoaded(response));
@ -36,7 +42,7 @@ export function* getRecord(params) {
} }
} }
export function* editRecord() { export function* editRecord(action) {
const currentModelName = yield select(makeSelectCurrentModelName()); const currentModelName = yield select(makeSelectCurrentModelName());
const record = yield select(makeSelectRecord()); const record = yield select(makeSelectRecord());
const recordJSON = record.toJSON(); const recordJSON = record.toJSON();
@ -49,6 +55,11 @@ export function* editRecord() {
const isCreating = yield select(makeSelectIsCreating()); const isCreating = yield select(makeSelectIsCreating());
const id = isCreating ? '' : recordCleaned.id; const id = isCreating ? '' : recordCleaned.id;
const params = {};
if (action.source !== undefined) {
params.source = action.source;
}
try { try {
const requestUrl = `${strapi.backendURL}/content-manager/explorer/${currentModelName}/${id}`; const requestUrl = `${strapi.backendURL}/content-manager/explorer/${currentModelName}/${id}`;
@ -57,6 +68,7 @@ export function* editRecord() {
yield call(request, requestUrl, { yield call(request, requestUrl, {
method: isCreating ? 'POST' : 'PUT', method: isCreating ? 'POST' : 'PUT',
body: recordCleaned, body: recordCleaned,
params,
}); });
yield put(recordEdited()); yield put(recordEdited());
@ -67,21 +79,31 @@ export function* editRecord() {
} }
} }
export function* deleteRecord({ id, modelName }) { export function* deleteRecord({ id, modelName, source }) {
function* httpCall(id, modelName) { function* httpCall(id, modelName) {
try { try {
const requestUrl = `${strapi.backendURL}/content-manager/explorer/${modelName}/${id}`; const requestUrl = `${strapi.backendURL}/content-manager/explorer/${modelName}/${id}`;
const params = {};
if (action.source !== undefined) {
params.source = action.source;
}
// Call our request helper (see 'utils/request') // Call our request helper (see 'utils/request')
yield call(request, requestUrl, { yield call(request, requestUrl, {
method: 'DELETE', method: 'DELETE',
params,
}); });
yield put(recordDeleted(id)); yield put(recordDeleted(id));
strapi.notification.success('content-manager.success.record.delete'); strapi.notification.success('content-manager.success.record.delete');
// Redirect to the list page. // Redirect to the list page.
router.push(`/plugins/content-manager/${modelName}`); router.push({
pathname: `/plugins/content-manager/${modelName}`,
state: {
source,
},
});
} catch (err) { } catch (err) {
yield put(recordDeleteError()); yield put(recordDeleteError());
strapi.notification.error('content-manager.error.record.delete'); strapi.notification.error('content-manager.error.record.delete');

View File

@ -42,15 +42,17 @@ export function changeSort(sort) {
}; };
} }
export function loadCount() { export function loadCount(source) {
return { return {
type: LOAD_COUNT, type: LOAD_COUNT,
source,
}; };
} }
export function loadRecords() { export function loadRecords(source) {
return { return {
type: LOAD_RECORDS, type: LOAD_RECORDS,
source,
}; };
} }

View File

@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux'; import { bindActionCreators, compose } from 'redux';
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from 'reselect';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { isEmpty, isUndefined, map, replace, split } from 'lodash'; import { isEmpty, isUndefined, map, get, toInteger } from 'lodash';
import { router } from 'app'; import { router } from 'app';
// Selectors. // Selectors.
@ -24,6 +24,9 @@ import PopUpWarning from 'components/PopUpWarning';
import injectReducer from 'utils/injectReducer'; import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga'; import injectSaga from 'utils/injectSaga';
// Utils
import getQueryParameters from 'utils/getQueryParameters';
// Actions. // Actions.
import { import {
deleteRecord, deleteRecord,
@ -88,24 +91,24 @@ export class List extends React.Component {
// Set current model name // Set current model name
this.props.setCurrentModelName(slug.toLowerCase()); this.props.setCurrentModelName(slug.toLowerCase());
const searchParams = split(replace(props.location.search, '?', ''), '&'); const source = getQueryParameters(props.location.search, 'source');
const sort = isEmpty(props.location.search) ? const sort = (isEmpty(props.location.search) ?
this.props.models[slug.toLowerCase()].primaryKey || 'id' : get(this.props.models, ['models', slug.toLowerCase(), 'primaryKey']) || get(this.props.models.plugins, [source, 'models', slug.toLowerCase(), 'primaryKey']) :
replace(searchParams[2], 'sort=', ''); getQueryParameters('sort')) || 'id';
if (!isEmpty(props.location.search)) { if (!isEmpty(props.location.search)) {
this.props.changePage(parseInt(replace(searchParams[0], 'page=', ''), 10)); this.props.changePage(toInteger(getQueryParameters('page')));
this.props.changeLimit(parseInt(replace(searchParams[1], 'limit=', ''), 10)); this.props.changeLimit(toInteger(getQueryParameters('limit')));
} }
this.props.changeSort(sort); this.props.changeSort(sort);
// Load records // Load records
this.props.loadRecords(); this.props.loadRecords(source);
// Get the records count // Get the records count
this.props.loadCount(); this.props.loadCount(source);
// Define the `create` route url // Define the `create` route url
this.addRoute = `${this.props.match.path.replace(':slug', slug)}/create`; this.addRoute = `${this.props.match.path.replace(':slug', slug)}/create`;
@ -139,7 +142,9 @@ export class List extends React.Component {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.props.deleteRecord(this.state.target, this.props.currentModelName); const source = getQueryParameters(this.props.location.search, 'source');
this.props.deleteRecord(this.state.target, this.props.currentModelName, source);
this.setState({ showWarning: false }); this.setState({ showWarning: false });
} }
@ -156,18 +161,20 @@ export class List extends React.Component {
} }
render() { render() {
if (!this.props.currentModelName || !this.props.schema) { // Detect current model structure from models list
const source = getQueryParameters(this.props.location.search, 'source');
const currentModel = get(this.props.models, ['models', this.props.currentModelName]) || get(this.props.models, ['plugins', source, 'models', this.props.currentModelName]);
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
if (!this.props.currentModelName || !currentSchema) {
return <div />; return <div />;
} }
// Detect current model structure from models list
const currentModel = this.props.models[this.props.currentModelName];
// Define table headers // Define table headers
const tableHeaders = map(this.props.schema[this.props.currentModelName].list, (value) => ({ const tableHeaders = map(currentSchema.list, (value) => ({
name: value, name: value,
label: this.props.schema[this.props.currentModelName].fields[value].label, label: currentSchema.fields[value].label,
type: this.props.schema[this.props.currentModelName].fields[value].type, type: currentSchema.fields[value].type,
})); }));
tableHeaders.splice(0, 0, { name: currentModel.primaryKey || 'id', label: 'Id', type: 'string' }); tableHeaders.splice(0, 0, { name: currentModel.primaryKey || 'id', label: 'Id', type: 'string' });
@ -183,12 +190,12 @@ export class List extends React.Component {
history={this.props.history} history={this.props.history}
primaryKey={currentModel.primaryKey || 'id'} primaryKey={currentModel.primaryKey || 'id'}
handleDelete={this.toggleModalWarning} handleDelete={this.toggleModalWarning}
redirectUrl={`?redirectUrl=/plugins/content-manager/${this.props.currentModelName.toLowerCase()}/?page=${this.props.currentPage}&limit=${this.props.limit}&sort=${this.props.sort}`} redirectUrl={`?redirectUrl=/plugins/content-manager/${this.props.currentModelName.toLowerCase()}/?page=${this.props.currentPage}&limit=${this.props.limit}&sort=${this.props.sort}&source=${source}`}
/> />
); );
// Plugin header config // Plugin header config
const pluginHeaderTitle = this.props.schema[this.props.currentModelName].label || 'Content Manager'; const pluginHeaderTitle = currentSchema.label || 'Content Manager';
// Define plugin header actions // Define plugin header actions
const pluginHeaderActions = [ const pluginHeaderActions = [

View File

@ -25,7 +25,7 @@ import {
makeSelectSort, makeSelectSort,
} from './selectors'; } from './selectors';
export function* getRecords() { export function* getRecords(action) {
const currentModel = yield select(makeSelectCurrentModelName()); const currentModel = yield select(makeSelectCurrentModelName());
const limit = yield select(makeSelectLimit()); const limit = yield select(makeSelectLimit());
const currentPage = yield select(makeSelectCurrentPage()); const currentPage = yield select(makeSelectCurrentPage());
@ -40,6 +40,10 @@ export function* getRecords() {
sort, sort,
}; };
if (action.source !== undefined) {
params.source = action.source;
}
try { try {
const requestUrl = `${strapi.backendURL}/content-manager/explorer/${currentModel}`; const requestUrl = `${strapi.backendURL}/content-manager/explorer/${currentModel}`;
// Call our request helper (see 'utils/request') // Call our request helper (see 'utils/request')
@ -54,14 +58,19 @@ export function* getRecords() {
} }
} }
export function* getCount() { export function* getCount(action) {
const currentModel = yield select(makeSelectCurrentModelName()); const currentModel = yield select(makeSelectCurrentModelName());
const params = {};
if (action.source !== undefined) {
params.source = action.source;
}
try { try {
const response = yield call( const response = yield call(request,`${strapi.backendURL}/content-manager/explorer/${currentModel}/count`, {
request, method: 'GET',
`${strapi.backendURL}/content-manager/explorer/${currentModel}/count`, params,
); });
yield put(loadedCount(response.count)); yield put(loadedCount(response.count));
} catch (err) { } catch (err) {

View File

@ -1,4 +1,4 @@
import { forEach, upperFirst, mapValues, pickBy, slice, findKey, keys, get } from 'lodash'; import { forEach, upperFirst, mapValues, pickBy, slice, findKey, keys, get, set } from 'lodash';
import pluralize from 'pluralize'; import pluralize from 'pluralize';
/** /**
@ -7,11 +7,13 @@ import pluralize from 'pluralize';
* *
* @param models * @param models
*/ */
const generateSchema = (models) => { const generateSchema = (responses) => {
// Init `schema` object // Init `schema` object
const schema = {}; const schema = {
plugins: {},
};
forEach(models, (model, name) => { const buildSchema = (model, name, plugin = false) => {
// Model data // Model data
const schemaModel = { const schemaModel = {
label: upperFirst(name), label: upperFirst(name),
@ -46,8 +48,24 @@ const generateSchema = (models) => {
}, {}); }, {});
} }
if (plugin) {
return set(schema.plugins, `${plugin}.${name}`, schemaModel);
}
// Set the formatted model to the schema // Set the formatted model to the schema
schema[name] = schemaModel; schema[name] = schemaModel;
};
// Generate schema for plugins.
forEach(responses.plugins, (plugin, pluginName) => {
forEach(plugin.models, (model, name) => {
buildSchema(model, name, pluginName);
});
});
// Generate schema for models.
forEach(responses.models, (model, name) => {
buildSchema(model, name);
}); });
return schema; return schema;

View File

@ -8,28 +8,37 @@ const _ = require('lodash');
module.exports = { module.exports = {
models: async ctx => { models: async ctx => {
ctx.body = _.mapValues(strapi.models, model => const pickData = (model) => _.pick(model, [
_.pick(model, [ 'info',
'info', 'connection',
'connection', 'collectionName',
'collectionName', 'attributes',
'attributes', 'identity',
'identity', 'globalId',
'globalId', 'globalName',
'globalName', 'orm',
'orm', 'loadedModel',
'loadedModel', 'primaryKey',
'primaryKey', 'associations'
'associations' ]);
])
); ctx.body = {
models: _.mapValues(strapi.models, pickData),
plugins: Object.keys(strapi.plugins).reduce((acc, current) => {
acc[current] = {
models: _.mapValues(strapi.plugins[current].models, pickData)
};
return acc;
}, {})
};
}, },
find: async ctx => { find: async ctx => {
const { limit, skip = 0, sort, query, queryAttribute } = ctx.request.query; const { limit, skip = 0, sort, query, queryAttribute, source } = ctx.request.query;
// Find entries using `queries` system // Find entries using `queries` system
const entries = await strapi.query(ctx.params.model).find({ const entries = await strapi.query(ctx.params.model, source).find({
limit, limit,
skip, skip,
sort, sort,
@ -41,8 +50,10 @@ module.exports = {
}, },
count: async ctx => { count: async ctx => {
const { source } = ctx.request.query;
// Count using `queries` system // Count using `queries` system
const count = await strapi.query(ctx.params.model).count(); const count = await strapi.query(ctx.params.model, source).count();
ctx.body = { ctx.body = {
count: _.isNumber(count) ? count : _.toNumber(count) count: _.isNumber(count) ? count : _.toNumber(count)
@ -50,8 +61,10 @@ module.exports = {
}, },
findOne: async ctx => { findOne: async ctx => {
const { source } = ctx.request.query;
// Find an entry using `queries` system // Find an entry using `queries` system
const entry = await strapi.query(ctx.params.model).findOne({ const entry = await strapi.query(ctx.params.model, source).findOne({
id: ctx.params.id id: ctx.params.id
}); });
@ -64,8 +77,10 @@ module.exports = {
}, },
create: async ctx => { create: async ctx => {
const { source } = ctx.request.query;
// Create an entry using `queries` system // Create an entry using `queries` system
const entryCreated = await strapi.query(ctx.params.model).create({ const entryCreated = await strapi.query(ctx.params.model, source).create({
values: ctx.request.body values: ctx.request.body
}); });
@ -73,8 +88,10 @@ module.exports = {
}, },
update: async ctx => { update: async ctx => {
const { source } = ctx.request.query;
// Add current model to the flow of updates. // Add current model to the flow of updates.
const entry = strapi.query(ctx.params.model).update({ const entry = strapi.query(ctx.params.model, source).update({
id: ctx.params.id, id: ctx.params.id,
values: ctx.request.body values: ctx.request.body
}); });
@ -84,7 +101,9 @@ module.exports = {
}, },
delete: async ctx => { delete: async ctx => {
const { source } = ctx.request.query;
const params = ctx.params; const params = ctx.params;
const response = await strapi.query(params.model).findOne({ const response = await strapi.query(params.model).findOne({
id: params.id id: params.id
}); });
@ -102,11 +121,11 @@ module.exports = {
if (!_.isEmpty(params.values)) { if (!_.isEmpty(params.values)) {
// Run update to remove all relationships. // Run update to remove all relationships.
await strapi.query(params.model).update(params); await strapi.query(params.model, source).update(params);
} }
// Delete an entry using `queries` system // Delete an entry using `queries` system
const entryDeleted = await strapi.query(params.model).delete({ const entryDeleted = await strapi.query(params.model, source).delete({
id: params.id id: params.id
}); });

View File

@ -11,9 +11,9 @@ const bootstrap = (plugin) => new Promise((resolve, reject) => {
links: [{ links: [{
label: 'Users', label: 'Users',
destination: 'user', destination: 'user',
plugin: 'content-manager' plugin: 'content-manager',
}], }],
name: 'Content Types' name: 'Content Types',
}); });
return resolve(plugin); return resolve(plugin);

View File

@ -6,6 +6,10 @@
"content-manager": { "content-manager": {
"controllers": { "controllers": {
"contentmanager": { "contentmanager": {
"identity": {
"enabled": false,
"policy": ""
},
"models": { "models": {
"enabled": false, "enabled": false,
"policy": "" "policy": ""
@ -33,10 +37,6 @@
"delete": { "delete": {
"enabled": false, "enabled": false,
"policy": "" "policy": ""
},
"identity": {
"enabled": false,
"policy": ""
} }
} }
} }

View File

@ -22,3 +22,7 @@ npm run test
# Test `strapi-plugin-content-type-builder` # Test `strapi-plugin-content-type-builder`
cd ../strapi-plugin-content-type-builder cd ../strapi-plugin-content-type-builder
npm run test npm run test
# Test `strapi-plugin-content-type-builder`
cd ../strapi-plugin-users-permissions
npm run test