feat: support sort m2m & o2m fields (#768)

Co-authored-by: sealday <sealday@gmail.com>
Reviewed-on: daoyoucloud/tachybase#768
This commit is contained in:
sealday 2024-04-24 21:46:05 +08:00
parent c82fe54fe4
commit b3301e19de
3 changed files with 103 additions and 4 deletions

View File

@ -3,6 +3,7 @@ import { Context } from '..';
import { getRepositoryFromParams, pageArgsToLimitArgs } from '../utils'; import { getRepositoryFromParams, pageArgsToLimitArgs } from '../utils';
import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '../constants'; import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '../constants';
import { Op, QueryTypes } from 'sequelize'; import { Op, QueryTypes } from 'sequelize';
import qs from 'querystring';
function totalPage(total, pageSize): number { function totalPage(total, pageSize): number {
return Math.ceil(total / pageSize); return Math.ceil(total / pageSize);
@ -11,6 +12,32 @@ function totalPage(total, pageSize): number {
function findArgs(ctx: Context) { function findArgs(ctx: Context) {
const resourceName = ctx.action.resourceName; const resourceName = ctx.action.resourceName;
const params = ctx.action.params; const params = ctx.action.params;
// 处理 sort 字段
const includeSort = params.sort?.filter((item) => item.split('.').length > 1) ?? [];
const sortItems = [];
includeSort.forEach((sort) => {
const parts = sort[0] === '-' ? sort.slice(1, sort.length).split('.') : sort.split('.');
const prefix = parts.slice(0, -1).join('.');
const sign = sort[0] === '-' ? '-' : '';
const sortItem = sign + parts.slice(-1).join('.');
const findIndex = sortItems.findIndex((item) => item.prefix === prefix);
if (findIndex !== -1) {
sortItems[findIndex].items.push(sortItem);
} else {
sortItems.push({
prefix,
sortItems: [sortItem],
});
}
});
sortItems.forEach((item) => {
const i = params.appends?.findIndex((append) => append === item.prefix) ?? -1;
if (i !== -1) {
params.appends[i] = `${item.prefix}(${qs.stringify({ sort: item.sortItems })})`;
}
});
params.sort = params.sort?.filter((item) => item.split('.').length == 1) ?? [];
if (params.tree) { if (params.tree) {
const [collectionName, associationName] = resourceName.split('.'); const [collectionName, associationName] = resourceName.split('.');
const collection = ctx.db.getCollection(resourceName); const collection = ctx.db.getCollection(resourceName);
@ -81,10 +108,10 @@ async function listWithPagination(ctx: Context) {
) )
SELECT DISTINCT * SELECT DISTINCT *
FROM ( FROM (
SELECT * SELECT *
FROM tree1 FROM tree1
UNION ALL UNION ALL
SELECT * SELECT *
FROM tree2 FROM tree2
);`; );`;
const filterTreeDatas = await ctx.db.sequelize.query(query, { const filterTreeDatas = await ctx.db.sequelize.query(query, {

View File

@ -1,6 +1,7 @@
import { mockDatabase } from '../index'; import { mockDatabase } from '../index';
import Database from '@nocobase/database'; import Database from '@nocobase/database';
import { Collection } from '../../collection'; import { Collection } from '../../collection';
import qs from 'qs';
describe('find with associations', () => { describe('find with associations', () => {
let db: Database; let db: Database;
@ -412,6 +413,63 @@ describe('find with associations', () => {
expect(findResult[0].get('name')).toEqual('u1'); expect(findResult[0].get('name')).toEqual('u1');
}); });
it('should find with associations with sort params', async () => {
const User = db.collection({
name: 'users',
fields: [
{ type: 'string', name: 'name' },
{
type: 'hasMany',
name: 'posts',
},
],
});
const Post = db.collection({
name: 'posts',
fields: [
{ type: 'string', name: 'title' },
{ type: 'belongsTo', name: 'user' },
],
});
await db.sync();
await User.repository.create({
values: [
{
name: 'u1',
posts: [
{
title: 'u1p1',
},
{
title: 'u1p2',
},
],
},
{
name: 'u2',
posts: [
{
title: 'u2p1',
},
{
title: 'u2p2',
},
],
},
],
});
const appendArgs = [`posts(${qs.stringify({ sort: ['-id'] })})`];
const users = await User.repository.find({
appends: appendArgs,
});
expect(users[0].get('name')).toEqual('u1');
expect(users[0].get('posts')[0].get('title')).toEqual('u1p2');
});
}); });
describe('repository find', () => { describe('repository find', () => {

View File

@ -257,6 +257,20 @@ export class EagerLoadingTree {
const association = node.association; const association = node.association;
const associationType = association.associationType; const associationType = association.associationType;
let params: any = {};
const otherFindOptions = lodash.pick(node.includeOption, ['sort']) || {};
const collection = this.db.modelCollection.get(node.model);
if (collection && !lodash.isEmpty(otherFindOptions)) {
const parser = new OptionsParser(otherFindOptions, {
collection,
});
params = parser.toSequelizeParams();
}
if (associationType == 'HasOne' || associationType == 'HasMany') { if (associationType == 'HasOne' || associationType == 'HasMany') {
const foreignKey = association.foreignKey; const foreignKey = association.foreignKey;
const foreignKeyValues = node.parent.instances.map((instance) => instance.get(association.sourceKey)); const foreignKeyValues = node.parent.instances.map((instance) => instance.get(association.sourceKey));
@ -271,7 +285,7 @@ export class EagerLoadingTree {
const findOptions = { const findOptions = {
where, where,
attributes: node.attributes, attributes: node.attributes,
order: orderOption(association), order: params.order || orderOption(association),
transaction, transaction,
}; };
@ -358,7 +372,7 @@ export class EagerLoadingTree {
}, },
}, },
], ],
order: orderOption(association), order: params.order || orderOption(association),
}); });
} }
} }