Merge branch 'v4/backend' into pluginAPI/loadPlugin

This commit is contained in:
Pierre Noël 2021-08-09 11:06:34 +02:00
commit 18da6e2188
50 changed files with 213 additions and 192 deletions

View File

@ -163,7 +163,7 @@ Before submitting an issue you need to make sure:
- You have already searched for related [issues](https://github.com/strapi/strapi/issues), and found none open (if you found a related _closed_ issue, please link to it from your post).
- You are not asking a question about how to use Strapi or about whether or not Strapi has a certain feature. For general help using Strapi, you may:
- Refer to [the official Strapi documentation](https://strapi.io).
- Ask a member of the community in the [Strapi Slack Community](https://slack.strapi.io/).
- Ask a member of the community in the [Strapi Discord Community](https://discord.strapi.io/).
- Ask a question on [our community forum](https://forum.strapi.io).
- Your issue title is concise, on-topic and polite.
- You can and do provide steps to reproduce your issue.

View File

@ -5,7 +5,7 @@ services:
image: postgres
restart: always
volumes:
- pgdata:/var/lib/postgresql/data
- pgdata_test:/var/lib/postgresql/data
environment:
POSTGRES_USER: strapi
POSTGRES_PASSWORD: strapi
@ -24,10 +24,10 @@ services:
MYSQL_ROOT_HOST: '%'
MYSQL_ROOT_PASSWORD: strapi
volumes:
- mysqldata:/var/lib/mysql
- mysqldata_test:/var/lib/mysql
ports:
- '3306:3306'
volumes:
pgdata:
mysqldata:
pgdata_test:
mysqldata_test:

View File

@ -10,7 +10,7 @@ const postgres = {
client: 'postgres',
connection: {
database: 'strapi',
username: 'strapi',
user: 'strapi',
password: 'strapi',
port: 5432,
host: 'localhost',
@ -22,7 +22,7 @@ const mysql = {
client: 'mysql',
connection: {
database: 'strapi',
username: 'strapi',
user: 'strapi',
password: 'strapi',
port: 3306,
host: 'localhost',

View File

@ -78,7 +78,7 @@
"test:front:update:ce": "cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --u",
"test:snyk": "snyk test",
"test:unit": "jest --verbose",
"test:e2e": "FORCE_COLOR=true jest --config jest.config.e2e.js --verbose --runInBand --testRunner=jest-circus/runner",
"test:e2e": "FORCE_COLOR=true jest --config jest.config.e2e.js --verbose --runInBand --testRunner=jest-circus/runner --forceExit --detectOpenHandles",
"test:generate-app": "node test/create-test-app.js",
"doc:api": "node scripts/open-api/serve.js"
},

View File

@ -27,7 +27,7 @@
"node-fetch": "^2.6.1",
"ora": "5.4.0",
"@strapi/generate-new": "3.6.6",
"tar": "6.1.2"
"tar": "6.1.4"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -33,10 +33,10 @@ const HeaderSearch = ({ label, queryParameter }) => {
// Create a new search in order to remove the filters
currentSearch = new URLSearchParams('');
// Keep the previous params _sort, pageSize, page
// Keep the previous params sort, pageSize, page
const pageSize = query.get('pageSize');
const page = query.get('page');
const _sort = query.get('_sort');
const sort = query.get('sort');
if (page) {
currentSearch.set('page', page);
@ -46,8 +46,8 @@ const HeaderSearch = ({ label, queryParameter }) => {
currentSearch.set('pageSize', pageSize);
}
if (_sort) {
currentSearch.set('_sort', _sort);
if (sort) {
currentSearch.set('sort', sort);
}
currentSearch.set(queryParameter, encodeURIComponent(value));

View File

@ -8,7 +8,7 @@ const ListItem = ({ onClick, selectedItem, label, value }) => {
const { formatMessage } = useIntl();
const handleClick = () => {
onClick({ target: { name: '_sort', value } });
onClick({ target: { name: 'sort', value } });
};
return (

View File

@ -4,9 +4,9 @@ import { Carret, useTracking } from '@strapi/helper-plugin';
import { useListView } from '../../../hooks';
const Header = ({ fieldSchema: { type }, metadatas: { label, sortable, mainField }, name }) => {
const { _sort, firstSortableHeader, setQuery } = useListView();
const { sort, firstSortableHeader, setQuery } = useListView();
const { trackUsage } = useTracking();
const [sortBy, sortOrder] = _sort.split(':');
const [sortBy, sortOrder] = sort.split(':');
let sortField = name;
let useRelation = false;
@ -29,7 +29,7 @@ const Header = ({ fieldSchema: { type }, metadatas: { label, sortable, mainField
}
setQuery({
_sort: value,
sort: value,
});
}
};

View File

@ -151,12 +151,12 @@ const SettingsViewWrapper = ({
kind,
uid,
} = modifiedData;
const _sort = `${defaultSortBy}:${defaultSortOrder}`;
const sort = `${defaultSortBy}:${defaultSortOrder}`;
const goBackSearch = `${stringify(
{
page: 1,
pageSize,
_sort,
sort,
},
{ encode: false }
)}${pluginsQueryParams ? `&${pluginsQueryParams}` : ''}`;

View File

@ -15,7 +15,7 @@ describe('CONTENT MANAGER | Containers | CollectionTypeFormWrapper | selectors',
{
kind: 'collectionType',
name: 'api::address.address',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
search: 'page=1&pageSize=50&sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Addresses',
to: '/content-manager/collectionType/api::address.address',
uid: 'api::address.address',
@ -23,7 +23,7 @@ describe('CONTENT MANAGER | Containers | CollectionTypeFormWrapper | selectors',
{
kind: 'collectionType',
name: 'api::category.category',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
search: 'page=1&pageSize=50&sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Categories',
to: '/content-manager/collectionType/api::category.category',
uid: 'api::category.category',
@ -36,7 +36,7 @@ describe('CONTENT MANAGER | Containers | CollectionTypeFormWrapper | selectors',
{
kind: 'collectionType',
name: 'api::address.address',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
search: 'page=1&pageSize=50&sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Addresses',
to: '/content-manager/collectionType/api::address.address',
uid: 'api::address.address',
@ -44,7 +44,7 @@ describe('CONTENT MANAGER | Containers | CollectionTypeFormWrapper | selectors',
{
kind: 'collectionType',
name: 'api::category.category',
search: 'page=1&pageSize=50&_sort=city:ASC&plugins[i18n][locale]=fr',
search: 'page=1&pageSize=50&sort=city:ASC&plugins[i18n][locale]=fr',
title: 'Categories',
to: '/content-manager/collectionType/api::category.category',
uid: 'api::category.category',

View File

@ -23,7 +23,7 @@ const generateLinks = (links, type, configurations = []) => {
const searchParams = {
page: 1,
pageSize: currentContentTypeConfig.settings.pageSize,
_sort: `${currentContentTypeConfig.settings.defaultSortBy}:${currentContentTypeConfig.settings.defaultSortOrder}`,
sort: `${currentContentTypeConfig.settings.defaultSortBy}:${currentContentTypeConfig.settings.defaultSortOrder}`,
};
search = stringify(searchParams, { encode: false });

View File

@ -48,7 +48,7 @@ describe('ADMIN | LeftMenu | utils', () => {
{
to: '/content-manager/collectionType/api::address.address',
isDisplayed: true,
search: `page=1&pageSize=2&_sort=name:ASC`,
search: `page=1&pageSize=2&sort=name:ASC`,
permissions: [
{
action: 'plugin::content-manager.explorer.create',

View File

@ -106,7 +106,7 @@ describe('checkPermissions', () => {
subject: 'api::address.address',
},
],
search: 'page=1&pageSize=10&_sort=name:ASC',
search: 'page=1&pageSize=10&sort=name:ASC',
},
{
destination: '/content-manager/collectionType/api::article.article',

View File

@ -122,7 +122,7 @@ function ListView({
return formatFiltersFromQuery(query);
}, [query]);
const _sort = query._sort;
const sort = query.sort;
const _q = query._q || '';
const label = contentType.info.label;
@ -382,7 +382,7 @@ function ListView({
<>
<ListViewProvider
_q={_q}
_sort={_sort}
sort={sort}
data={data}
entriesToDelete={entriesToDelete}
filters={filters}

View File

@ -5,19 +5,19 @@ describe('buildQueryString', () => {
const queryParams = {
page: '1',
pageSize: '10',
_sort: 'name:ASC',
sort: 'name:ASC',
};
const queryString = buildQueryString(queryParams);
expect(queryString).toBe('?page=1&pageSize=10&_sort=name:ASC');
expect(queryString).toBe('?page=1&pageSize=10&sort=name:ASC');
});
it('creates a valid query string with default params & plugin options', () => {
const queryParams = {
page: '1',
pageSize: '10',
_sort: 'name:ASC',
sort: 'name:ASC',
plugins: {
i18n: { locale: 'en' },
},
@ -25,27 +25,27 @@ describe('buildQueryString', () => {
const queryString = buildQueryString(queryParams);
expect(queryString).toBe('?page=1&pageSize=10&_sort=name:ASC&_locale=en');
expect(queryString).toBe('?page=1&pageSize=10&sort=name:ASC&_locale=en');
});
it('creates a valid query string with a _where clause', () => {
const queryParams = {
page: '1',
pageSize: '10',
_sort: 'name:ASC',
sort: 'name:ASC',
_where: [{ name: 'hello world' }],
};
const queryString = buildQueryString(queryParams);
expect(queryString).toBe('?page=1&pageSize=10&_sort=name:ASC&_where[0][name]=hello world');
expect(queryString).toBe('?page=1&pageSize=10&sort=name:ASC&_where[0][name]=hello world');
});
it('creates a valid query string with a _where and plugin options', () => {
const queryParams = {
page: '1',
pageSize: '10',
_sort: 'name:ASC',
sort: 'name:ASC',
_where: [{ name: 'hello world' }],
plugins: {
i18n: { locale: 'en' },
@ -55,7 +55,7 @@ describe('buildQueryString', () => {
const queryString = buildQueryString(queryParams);
expect(queryString).toBe(
'?page=1&pageSize=10&_sort=name:ASC&_where[0][name]=hello world&_locale=en'
'?page=1&pageSize=10&sort=name:ASC&_where[0][name]=hello world&_locale=en'
);
});
});

View File

@ -30,7 +30,7 @@ const init = (initialState, settings) => {
{
intlLabel: { id: 'Settings.permissions.menu.link.users.label' },
// Init the search params directly
to: '/settings/users?pageSize=10&page=1&_sort=firstname%3AASC',
to: '/settings/users?pageSize=10&page=1&sort=firstname',
id: 'users',
isDisplayed: false,
permissions: adminPermissions.settings.users.main,

View File

@ -11,7 +11,6 @@ const Wrapper = styled.div`
padding: 8px 16px;
.bannerImage {
margin-right: 1rem;
width: 48px;
height: 48px;
margin-right: 16px;

View File

@ -50,7 +50,7 @@ const ListPage = () => {
] = useReducer(reducer, initialState, init);
const pageSize = parseInt(query.get('pageSize') || 10, 10);
const page = parseInt(query.get('page') || 0, 10);
const _sort = decodeURIComponent(query.get('_sort'));
const sort = decodeURIComponent(query.get('sort'));
const _q = decodeURIComponent(query.get('_q') || '');
const getDataRef = useRef();
const listRef = useRef();
@ -242,7 +242,7 @@ const ListPage = () => {
<>
<BaselineAlignment top size="1px">
<Flex flexWrap="wrap">
<SortPicker onChange={handleChangeSort} value={_sort} />
<SortPicker onChange={handleChangeSort} value={sort} />
<Padded right size="10px" />
<BaselineAlignment bottom size="6px">
<FilterPicker onChange={handleChangeFilter} />

View File

@ -3,24 +3,24 @@ const getFilters = search => {
const filters = [];
// eslint-disable-next-line no-restricted-syntax
for (let pair of query.entries()) {
if (!['_sort', 'pageSize', 'page', '_q'].includes(pair[0])) {
const splitted = pair[0].split('_');
for (let [key, queryValue] of query.entries()) {
if (!['sort', 'pageSize', 'page', '_q'].includes(key)) {
const splitted = key.split('_');
let filterName;
let filterType;
// Filter type === '=')
if (splitted.length === 1) {
filterType = '=';
filterName = pair[0];
filterName = key;
} else {
filterType = `_${splitted[1]}`;
filterName = splitted[0];
}
const value = decodeURIComponent(pair[1]);
const value = decodeURIComponent(queryValue);
filters.push({ displayName: filterName, name: pair[0], filter: filterType, value });
filters.push({ displayName: filterName, name: key, filter: filterType, value });
}
}

View File

@ -2,13 +2,13 @@ import getFilters from '../getFilters';
describe('ADMIN | CONTAINERS | USERS | ListPage | utils | getFilters', () => {
it('should return an empty array if there is not filter', () => {
const search = '_q=test&_sort=firstname&page=1&pageSize=1';
const search = '_q=test&sort=firstname&page=1&pageSize=1';
expect(getFilters(search)).toHaveLength(0);
});
it('should handle the = filter correctly ', () => {
const search = '_sort=firstname&page=1&pageSize=1&firstname=test&firstname_ne=something';
const search = 'sort=firstname&page=1&pageSize=1&firstname=test&firstname_ne=something';
const expected = [
{
displayName: 'firstname',

View File

@ -135,7 +135,7 @@ module.exports = {
roles: superAdminRole ? [superAdminRole.id] : [],
});
await strapi.telemetry.send('didCreateFirstAdmin');
strapi.telemetry.send('didCreateFirstAdmin');
ctx.body = {
data: {

View File

@ -5,11 +5,11 @@ const { getService } = require('../utils');
const sendDidInviteUser = async () => {
const numberOfUsers = await getService('user').count();
const numberOfRoles = await getService('role').count();
return strapi.telemetry.send('didInviteUser', { numberOfRoles, numberOfUsers });
strapi.telemetry.send('didInviteUser', { numberOfRoles, numberOfUsers });
};
const sendDidUpdateRolePermissions = async () => {
return strapi.telemetry.send('didUpdateRolePermissions');
strapi.telemetry.send('didUpdateRolePermissions');
};
module.exports = {

View File

@ -41,7 +41,7 @@ const create = async attributes => {
.query('strapi::user')
.create({ data: user, populate: ['roles'] });
await getService('metrics').sendDidInviteUser();
getService('metrics').sendDidInviteUser();
return createdUser;
};

View File

@ -62,6 +62,8 @@ module.exports = {
const { model } = ctx.params;
const { body } = ctx.request;
const totalEntries = await strapi.query(model).count();
const entityManager = getService('entity-manager');
const permissionChecker = getService('permission-checker').create({ userAbility, model });
@ -77,9 +79,12 @@ module.exports = {
await wrapBadRequest(async () => {
const entity = await entityManager.create(sanitizeFn(body), model);
ctx.body = permissionChecker.sanitizeOutput(entity);
await strapi.telemetry.send('didCreateFirstContentTypeEntry', { model });
if (totalEntries === 0) {
strapi.telemetry.send('didCreateFirstContentTypeEntry', { model });
}
})();
},

View File

@ -1,15 +1,15 @@
'use strict';
const createLifecyclesManager = db => {
let subscribers = [];
const lifecycleManager = {
_subscribers: [],
subscribe(subscriber) {
// TODO: verify subscriber
this._subscribers.push(subscriber);
subscribers.push(subscriber);
return () => {
this._subscribers.splice(this._subscribers.indexOf(subscriber), 1);
subscribers.splice(subscribers.indexOf(subscriber), 1);
};
},
@ -24,10 +24,11 @@ const createLifecyclesManager = db => {
},
async run(action, uid, properties) {
for (const subscriber of this._subscribers) {
for (const subscriber of subscribers) {
if (typeof subscriber === 'function') {
const event = this.createEvent(action, uid, properties);
return await subscriber(event);
await subscriber(event);
continue;
}
const hasAction = action in subscriber;
@ -42,7 +43,7 @@ const createLifecyclesManager = db => {
},
clear() {
this._subscribers = [];
subscribers = [];
},
};

View File

@ -516,6 +516,10 @@ const applyPopulate = async (results, populate, ctx) => {
const { db, uid, qb } = ctx;
const meta = db.metadata.get(uid);
if (_.isEmpty(results)) {
return results;
}
for (const key in populate) {
// NOTE: Omit limit & offset to avoid needing a query per result to avoid making too many queries
const populateValue = _.pick(
@ -579,34 +583,16 @@ const applyPopulate = async (results, populate, ctx) => {
} = joinTable.joinColumn;
const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
if (isCount) {
const rows = await qb
.init(populateValue)
.join({
alias: alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on,
})
.select([`${alias}.${joinColumnName}`, qb.raw('count(*) AS count')])
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.groupBy(`${alias}.${joinColumnName}`)
.execute({ mapResults: false });
const map = rows.reduce((map, row) => {
map[row[joinColumnName]] = { count: row.count };
return map;
}, {});
const referencedValues = _.uniq(
results.map(r => r[referencedColumnName]).filter(value => !_.isNil(value))
);
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = map[result[referencedColumnName]] || { count: 0 };
result[key] = null;
});
continue;
}
@ -620,10 +606,8 @@ const applyPopulate = async (results, populate, ctx) => {
rootTable: qb.alias,
on: joinTable.on,
})
.addSelect(`${alias}.${joinColumnName}`)
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });
const map = _.groupBy(joinColumnName, rows);
@ -681,8 +665,20 @@ const applyPopulate = async (results, populate, ctx) => {
} = joinTable.joinColumn;
const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const referencedValues = _.uniq(
results.map(r => r[referencedColumnName]).filter(value => !_.isNil(value))
);
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = { count: 0 };
});
continue;
}
const rows = await qb
.init(populateValue)
.join({
@ -693,11 +689,9 @@ const applyPopulate = async (results, populate, ctx) => {
rootTable: qb.alias,
on: joinTable.on,
})
.select([`${alias}.${joinColumnName}`, qb.raw('count(*) AS count')])
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.groupBy(`${alias}.${joinColumnName}`)
.select([joinColAlias, qb.raw('count(*) AS count')])
.where({ [joinColAlias]: referencedValues })
.groupBy(joinColAlias)
.execute({ mapResults: false });
const map = rows.reduce((map, row) => {
@ -712,6 +706,13 @@ const applyPopulate = async (results, populate, ctx) => {
continue;
}
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = [];
});
continue;
}
const rows = await qb
.init(populateValue)
.join({
@ -722,10 +723,8 @@ const applyPopulate = async (results, populate, ctx) => {
rootTable: qb.alias,
on: joinTable.on,
})
.addSelect(`${alias}.${joinColumnName}`)
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });
const map = _.groupBy(joinColumnName, rows);
@ -745,8 +744,19 @@ const applyPopulate = async (results, populate, ctx) => {
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const referencedValues = _.uniq(
results.map(r => r[referencedColumnName]).filter(value => !_.isNil(value))
);
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = { count: 0 };
});
continue;
}
const rows = await qb
.init(populateValue)
.join({
@ -757,11 +767,9 @@ const applyPopulate = async (results, populate, ctx) => {
rootTable: qb.alias,
on: joinTable.on,
})
.select([`${alias}.${joinColumnName}`, qb.raw('count(*) AS count')])
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.groupBy(`${alias}.${joinColumnName}`)
.select([joinColAlias, qb.raw('count(*) AS count')])
.where({ [joinColAlias]: referencedValues })
.groupBy(joinColAlias)
.execute({ mapResults: false });
const map = rows.reduce((map, row) => {
@ -776,6 +784,13 @@ const applyPopulate = async (results, populate, ctx) => {
continue;
}
if (_.isEmpty(referencedValues)) {
results.forEach(result => {
result[key] = [];
});
continue;
}
const rows = await qb
.init(populateValue)
.join({
@ -786,10 +801,8 @@ const applyPopulate = async (results, populate, ctx) => {
rootTable: qb.alias,
on: joinTable.on,
})
.addSelect(`${alias}.${joinColumnName}`)
.where({
[`${alias}.${joinColumnName}`]: results.map(r => r[referencedColumnName]),
})
.addSelect(joinColAlias)
.where({ [joinColAlias]: referencedValues })
.execute({ mapResults: false });
const map = _.groupBy(joinColumnName, rows);

View File

@ -30,6 +30,13 @@ const createQueryBuilder = (uid, db) => {
alias: getAlias(),
getAlias,
select(args) {
state.type = 'select';
state.select = _.castArray(args).map(col => this.aliasColumn(col));
return this;
},
insert(data) {
state.type = 'insert';
state.data = data;
@ -65,13 +72,6 @@ const createQueryBuilder = (uid, db) => {
return this;
},
select(args) {
state.type = 'select';
state.select = _.castArray(args).map(col => this.aliasColumn(col));
return this;
},
addSelect(args) {
state.select.push(..._.castArray(args).map(col => this.aliasColumn(col)));
return this;

View File

@ -72,13 +72,18 @@ module.exports = db => ({
await db.connection.transaction(async trx => {
await this.createTables(schemaDiff.tables.added, trx);
// TODO: drop foreign keys targeting delete tables
// drop all delete table foreign keys then delete the tables
for (const table of schemaDiff.tables.removed) {
debug(`Removing table foreign keys: ${table.name}`);
const schemaBuilder = this.getSchemaBuilder(table, trx);
await dropTableForeignKeys(schemaBuilder, table);
}
for (const table of schemaDiff.tables.removed) {
debug(`Removing table: ${table.name}`);
const schemaBuilder = this.getSchemaBuilder(table, trx);
// TODO: add cascading if possible
await dropTable(schemaBuilder, table);
}
@ -288,6 +293,8 @@ const dropColumn = (tableBuilder, column) => {
const createTable = async (schemaBuilder, table) => {
if (await schemaBuilder.hasTable(table.name)) {
throw new Error(`Table already exists ${table.name}`);
// TODO: alter the table instead
}
await schemaBuilder.createTable(table.name, tableBuilder => {
@ -299,6 +306,13 @@ const createTable = async (schemaBuilder, table) => {
});
};
/**
* Drops a table from a database
* @param {Knex.SchemaBuilder} schemaBuilder
* @param {Table} table
*/
const dropTable = (schemaBuilder, table) => schemaBuilder.dropTableIfExists(table.name);
/**
* Creates a table foreign keys constraints
* @param {SchemaBuilder} schemaBuilder
@ -312,8 +326,13 @@ const createTableForeignKeys = async (schemaBuilder, table) => {
};
/**
* Drops a table from a database
* @param {Knex.SchemaBuilder} schemaBuilder
* Drops a table foreign keys constraints
* @param {SchemaBuilder} schemaBuilder
* @param {Table} table
*/
const dropTable = (schemaBuilder, table) => schemaBuilder.dropTableIfExists(table.name);
const dropTableForeignKeys = async (schemaBuilder, table) => {
// foreign keys
await schemaBuilder.table(table.name, tableBuilder => {
(table.foreignKeys || []).forEach(foreignKey => dropForeignKey(tableBuilder, foreignKey));
});
};

View File

@ -10,7 +10,7 @@ const generateFiltersFromSearch = search => {
x =>
!x.includes('_limit') &&
!x.includes('_page') &&
!x.includes('_sort') &&
!x.includes('sort') &&
!x.includes('_start') &&
!x.includes('_q=') &&
x !== ''

View File

@ -3,7 +3,7 @@ import generateFiltersFromSearch from '../generateFiltersFromSearch';
describe('HELPER PLUGIN | utils | generateFiltersFromSearch', () => {
it('should generate an array of filters', () => {
const search =
'?_sort=id:ASC&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
'?sort=id:ASC&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
const expected = [
{
name: 'bool',

View File

@ -4,7 +4,7 @@ describe('HELPER PLUGIN | utils | generateSearchFromFilters', () => {
it('should return a string with all the applied filters', () => {
const data = {
_limit: 10,
_sort: 'id:ASC',
sort: 'id:ASC',
_page: 2,
filters: [
{
@ -46,7 +46,7 @@ describe('HELPER PLUGIN | utils | generateSearchFromFilters', () => {
};
const expected =
'_limit=10&_sort=id:ASC&_page=2&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
'_limit=10&sort=id:ASC&_page=2&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
const encoded = expected
.split('&')
.map(pair => {

View File

@ -2,15 +2,15 @@ import generateSearchFromObject from '../generateSearchFromObject';
describe('HELPER PLUGIN | utils | generateSearchFromObject', () => {
it('should return a string containing the _limit, _start and order', () => {
const search = { _page: 1, _limit: 10, _sort: 'city:ASC' };
const expected = '_limit=10&_sort=city:ASC&_start=0';
const search = { _page: 1, _limit: 10, sort: 'city:ASC' };
const expected = '_limit=10&sort=city:ASC&_start=0';
expect(generateSearchFromObject(search)).toEqual(expected);
});
it('should remove the _q param from the search if it is empty', () => {
const search = { _page: 1, _limit: 10, _sort: 'city:ASC', _q: '' };
const expected = '_limit=10&_sort=city:ASC&_start=0';
const search = { _page: 1, _limit: 10, sort: 'city:ASC', _q: '' };
const expected = '_limit=10&sort=city:ASC&_start=0';
expect(generateSearchFromObject(search)).toEqual(expected);
});
@ -19,11 +19,11 @@ describe('HELPER PLUGIN | utils | generateSearchFromObject', () => {
const search = {
_page: 1,
_limit: 10,
_sort: 'city:ASC',
sort: 'city:ASC',
_q: '',
filters: [],
};
const expected = '_limit=10&_sort=city:ASC&_start=0';
const expected = '_limit=10&sort=city:ASC&_start=0';
expect(generateSearchFromObject(search)).toEqual(expected);
});
@ -33,11 +33,11 @@ describe('HELPER PLUGIN | utils | generateSearchFromObject', () => {
_limit: 10,
_page: 1,
_q: '',
_sort: 'city:ASC',
sort: 'city:ASC',
filters: [{ name: 'city', filter: '=', value: 'test' }],
};
const expected = '_limit=10&_sort=city:ASC&city=test&_start=0';
const expected = '_limit=10&sort=city:ASC&city=test&_start=0';
expect(generateSearchFromObject(search)).toEqual(expected);
});

View File

@ -2,7 +2,7 @@
const path = require('path');
const createMiddleware = require('../index');
const configProvider = require('../../../core/base-providers/config-provider');
const configProvider = require('../../../core/registries/config');
describe('Session middleware', () => {
beforeEach(() => {

View File

@ -134,7 +134,7 @@ const BrowseAssets = () => {
/>
</Padded>
)}
<SortPicker onChange={handleChangeParams} value={params._sort} />
<SortPicker onChange={handleChangeParams} value={params.sort} />
<Padded left size="sm" />
<Filters
filters={params.filters}

View File

@ -6,41 +6,17 @@
import { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import get from 'lodash/get';
import { request, useNotification } from '@strapi/helper-plugin';
import pluginId from '../../pluginId';
import { setFileModelTimestamps } from './actions';
// TODO use the models API and remove this component
const Initializer = ({ setPlugin }) => {
const ref = useRef();
const dispatch = useDispatch();
ref.current = setPlugin;
const toggleNotification = useNotification();
// TODO: remove
useEffect(() => {
const getData = async () => {
const requestURL = '/content-manager/content-types';
try {
const { data } = await request(requestURL, { method: 'GET' });
const fileModel = data.find(model => model.uid === 'plugin::upload.file');
const timestamps = get(fileModel, ['options', 'timestamps']);
dispatch(setFileModelTimestamps(timestamps));
ref.current(pluginId);
} catch (err) {
toggleNotification({
type: 'warning',
message: { id: 'content-manager.error.model.fetch' },
});
}
};
getData();
}, [dispatch, toggleNotification]);
ref.current(pluginId);
}, []);
return null;
};

View File

@ -2,7 +2,8 @@ import produce from 'immer';
import { SET_FILE_MODE_TIMESTAMPS } from './constants';
const initialState = {
fileModelTimestamps: [],
// TODO: rename to camelCase
fileModelTimestamps: ['created_at', 'updated_at'],
};
const reducer = (state = initialState, action) =>
@ -10,7 +11,7 @@ const reducer = (state = initialState, action) =>
produce(state, draftState => {
switch (action.type) {
case SET_FILE_MODE_TIMESTAMPS: {
draftState.fileModelTimestamps = action.timestamps;
// draftState.fileModelTimestamps = action.timestamps;
break;
}
default:

View File

@ -64,7 +64,7 @@ const InputModalStepperProvider = ({
: [],
params: {
...state.params,
_sort: `${updated_at}:DESC`,
sort: `${updated_at}:DESC`,
},
})
);
@ -336,7 +336,7 @@ const InputModalStepperProvider = ({
const fetchMediaLibFilesCount = async () => {
const requestURL = getRequestUrl('files/count');
const paramsToSend = getFilters(['_limit', '_sort', '_start']);
const paramsToSend = getFilters(['_limit', '_start']);
try {
return await request(`${requestURL}?${paramsToSend}`, {

View File

@ -21,7 +21,7 @@ const initialState = {
_start: 0,
_q: '',
filters: [],
_sort: null,
sort: null,
},
currentStep: 'list',
isFormDisabled: false,
@ -180,7 +180,7 @@ const reducer = (state, action) =>
}
case 'RESET_PROPS': {
if (action.defaultSort) {
draftState.params._sort = action.defaultSort;
draftState.params.sort = action.defaultSort;
} else {
return initialState;
}

View File

@ -1725,7 +1725,7 @@ describe('UPLOAD | components | InputModalStepperProvider | reducer', () => {
_start: 0,
_q: '',
filters: [],
_sort: null,
sort: null,
},
currentStep: 'list',
isFormDisabled: false,

View File

@ -8,7 +8,7 @@ import IntlText from '../IntlText';
const SortListItem = ({ onClick, selectedItem, label, value }) => {
const handleClick = () => {
onClick({ target: { name: '_sort', value } });
onClick({ target: { name: 'sort', value } });
};
return (

View File

@ -72,7 +72,7 @@ const HomePageSettings = ({
<Padded right />
</>
)}
<SortPicker onChange={onChange} value={query.get('_sort') || `${updated_at}:DESC`} />
<SortPicker onChange={onChange} value={query.get('sort') || `${updated_at}:DESC`} />
<Padded right />
<Filters onChange={onChange} filters={filters} onClick={onFilterDelete} />
</ControlsWrapper>

View File

@ -42,7 +42,7 @@ const HomePage = () => {
const isMounted = useRef(true);
const pluginName = formatMessage({ id: getTrad('plugin.name') });
const paramsKeys = ['_limit', '_start', '_q', '_sort'];
const paramsKeys = ['_limit', '_start', '_q', 'sort'];
useEffect(() => {
return () => (isMounted.current = false);
@ -74,9 +74,9 @@ const HomePage = () => {
const dataRequestURL = getRequestUrl('files');
const params = generateStringFromParams(query);
const paramsToSend = params.includes('_sort')
const paramsToSend = params.includes('sort')
? params
: params.concat(`&_sort=${updated_at}:DESC`);
: params.concat(`&sort=${updated_at}:DESC`);
try {
const data = await request(`${dataRequestURL}?${paramsToSend}`, {
@ -98,7 +98,7 @@ const HomePage = () => {
};
const fetchDataCount = async () => {
const params = generateStringFromParams(query, ['_limit', '_sort', '_start']);
const params = generateStringFromParams(query, ['_limit', '_start']);
const requestURL = getRequestUrl('files/count');
try {

View File

@ -1,6 +1,6 @@
'use strict';
const MANY_RELATIONS = ['oneToMany', 'manyToMany', 'manyWay'];
const MANY_RELATIONS = ['oneToMany', 'manyToMany'];
const getRelationalFields = contentType => {
return Object.keys(contentType.attributes).filter(attributeName => {

View File

@ -23,7 +23,7 @@
"node-fetch": "^2.6.1",
"node-machine-id": "^1.1.10",
"ora": "^5.4.0",
"tar": "6.1.2",
"tar": "6.1.4",
"uuid": "^3.3.2"
},
"scripts": {

View File

@ -35,7 +35,7 @@ describe('dataloader', () => {
test('makeQuery calls find', async () => {
const uid = 'uid';
const find = jest.fn(() => [{ id: 1 }]);
const filters = { _limit: 5, _sort: 'field' };
const filters = { limit: 5, sort: 'field' };
global.strapi = {
query() {

View File

@ -5,7 +5,7 @@ describe('getInitialLocale', () => {
const query = {
page: '1',
pageSize: '10',
_sort: 'Name:ASC',
sort: 'Name:ASC',
plugins: {
i18n: { locale: 'fr-FR' },
},
@ -47,7 +47,7 @@ describe('getInitialLocale', () => {
const query = {
page: '1',
pageSize: '10',
_sort: 'Name:ASC',
sort: 'Name:ASC',
plugins: {
something: 'great',
},
@ -90,7 +90,7 @@ describe('getInitialLocale', () => {
const query = {
page: '1',
pageSize: '10',
_sort: 'Name:ASC',
sort: 'Name:ASC',
plugins: {
something: 'great',
},

View File

@ -81,6 +81,12 @@ describe('i18n - Relation-list route', () => {
beforeAll(async () => {
await builder
.addContentTypes([productModel, shopModel])
.addFixtures('plugin::i18n.locale', [
{
name: 'It',
code: 'it',
},
])
.addFixtures(shopModel.name, shops)
.addFixtures(productModel.name, products)
.build();

View File

@ -26,7 +26,7 @@ const createTestBuilder = (options = {}) => {
},
sanitizedFixturesFor(modelName, strapi) {
const model = strapi.getModel(`application::${modelName}.${modelName}`);
const model = strapi.getModel(modelsUtils.toUID(modelName));
const fixtures = this.fixturesFor(modelName);
return sanitizeEntity(fixtures, { model });

View File

@ -3,6 +3,10 @@
const { isFunction, isNil, prop } = require('lodash/fp');
const { createStrapiInstance } = require('./strapi');
const toUID = name => {
return name.includes('::') ? name : `application::${name}.${name}`;
};
const createHelpers = async ({ strapi: strapiInstance = null, ...options } = {}) => {
const strapi = strapiInstance || (await createStrapiInstance(options));
const contentTypeService = strapi.plugins['content-type-builder'].services['content-types'];
@ -141,7 +145,7 @@ async function createFixtures(dataMap, { strapi: strapiIst } = {}) {
const entries = [];
for (const data of dataMap[model]) {
entries.push(await strapi.query(`application::${model}.${model}`).create({ data }));
entries.push(await strapi.entityService.create(toUID(model), { data }));
}
resultMap[model] = entries;
@ -158,9 +162,7 @@ async function createFixturesFor(model, entries, { strapi: strapiIst } = {}) {
for (const entry of entries) {
const dataToCreate = isFunction(entry) ? entry(results) : entry;
results.push(
await strapi.query(`application::${model}.${model}`).create({ data: dataToCreate })
);
results.push(await strapi.entityService.create(toUID(model), { data: dataToCreate }));
}
await cleanup();
@ -171,9 +173,7 @@ async function createFixturesFor(model, entries, { strapi: strapiIst } = {}) {
async function deleteFixturesFor(model, entries, { strapi: strapiIst } = {}) {
const { strapi, cleanup } = await createHelpers({ strapi: strapiIst });
await strapi
.query(`application::${model}.${model}`)
.delete({ where: { id: entries.map(prop('id')) } });
await strapi.query(toUID(model)).deleteMany({ where: { id: entries.map(prop('id')) } });
await cleanup();
}
@ -185,7 +185,7 @@ async function modifyContentType(data, { strapi } = {}) {
delete sanitizedData.editable;
delete sanitizedData.restrictRelationsTo;
const uid = `application::${sanitizedData.name}.${sanitizedData.name}`;
const uid = toUID(sanitizedData.name);
const ct = await contentTypeService.editContentType(uid, {
contentType: {
@ -201,7 +201,7 @@ async function modifyContentType(data, { strapi } = {}) {
async function getContentTypeSchema(modelName, { strapi: strapiIst } = {}) {
const { strapi, contentTypeService, cleanup } = await createHelpers({ strapi: strapiIst });
const uid = `application::${modelName}.${modelName}`;
const uid = toUID(modelName);
const ct = contentTypeService.formatContentType(strapi.contentTypes[uid]);
await cleanup();
@ -210,6 +210,7 @@ async function getContentTypeSchema(modelName, { strapi: strapiIst } = {}) {
}
module.exports = {
toUID,
// Create Content-Types
createContentType,
createContentTypes,

View File

@ -20172,10 +20172,10 @@ tar-stream@^2.0.1, tar-stream@^2.1.0, tar-stream@^2.1.4, tar-stream@^2.2.0:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.2.tgz#1f045a90a6eb23557a603595f41a16c57d47adc6"
integrity sha512-EwKEgqJ7nJoS+s8QfLYVGMDmAsj+StbI2AM/RTHeUSsOw6Z8bwNBRv5z3CY0m7laC5qUAqruLX5AhMuc5deY3Q==
tar@6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.4.tgz#9f0722b772a5e00dba7d52e1923b37a7ec3799b3"
integrity sha512-kcPWrO8S5ABjuZ/v1xQHP8xCEvj1dQ1d9iAb6Qs4jLYzaAIYWwST2IQpz7Ud8VNYRI+fGhFjrnzRKmRggKWg3g==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"