feat: add $isCurrentUser filter operator (#299)

* feat: add $isCurrentUser filter operator

* fix: supports three levels
This commit is contained in:
chenos 2022-04-19 17:04:54 +08:00 committed by GitHub
parent a37609e71b
commit f426c8a3ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 91 additions and 31 deletions

View File

@ -12,6 +12,7 @@ export async function get(ctx: Context, next) {
appends,
except,
filter,
context: ctx,
});
ctx.body = instance;

View File

@ -33,6 +33,7 @@ async function listWithPagination(ctx: Context) {
const repository = getRepositoryFromParams(ctx);
const [rows, count] = await repository.findAndCount({
context: ctx,
...findArgs(ctx.action.params),
...pageArgsToLimitArgs(parseInt(String(page)), parseInt(String(pageSize))),
});

View File

@ -33,6 +33,16 @@ export const createdBy: IField = {
},
filterable: {
children: [
{
name: 'id',
title: '{{t("ID")}}',
operators: operators.id,
schema: {
title: '{{t("ID")}}',
type: 'number',
'x-component': 'InputNumber',
},
},
{
name: 'nickname',
title: '{{t("Nickname")}}',

View File

@ -70,8 +70,16 @@ export const number = [
export const id = [
{ label: '{{t("is")}}', value: '$eq', selected: true },
{ label: '{{t("is not")}}', value: '$ne' },
{ label: '{{t("Exists")}}', value: '$exists', noValue: true },
{ label: '{{t("Not exists")}}', value: '$notExists', noValue: true },
{
label: '{{t("is current logged-in user")}}',
value: '$isCurrentUser',
noValue: true,
visible(field) {
return field.collectionName === 'users';
},
},
{ label: '{{t("exists")}}', value: '$exists', noValue: true },
{ label: '{{t("not exists")}}', value: '$notExists', noValue: true },
];
export const enumType = [

View File

@ -32,6 +32,16 @@ export const updatedBy: IField = {
},
filterable: {
children: [
{
name: 'id',
title: '{{t("ID")}}',
operators: operators.id,
schema: {
title: '{{t("ID")}}',
type: 'number',
'x-component': 'InputNumber',
},
},
{
name: 'nickname',
title: '{{t("Nickname")}}',

View File

@ -10,8 +10,9 @@ export const useFilterOptions = (collectionName: string) => {
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
const { getCollectionFields, getInterface } = useCollectionManager();
const fields = getCollectionFields(collectionName);
const field2option = (field, nochildren) => {
if (nonfilterable.length && !nochildren && nonfilterable.includes(field.name)) {
let depth = 0;
const field2option = (field) => {
if (nonfilterable.length && depth !== 1 && nonfilterable.includes(field.name)) {
return;
}
if (!field.interface) {
@ -26,9 +27,15 @@ export const useFilterOptions = (collectionName: string) => {
name: field.name,
title: field?.uiSchema?.title || field.name,
schema: field?.uiSchema,
operators: operators || [],
operators:
operators?.filter?.((operator) => {
return !operator?.visible || operator.visible(field);
}) || [],
};
if (nochildren) {
if (field.target && depth > 2) {
return;
}
if (depth > 2) {
return option;
}
if (children?.length) {
@ -36,16 +43,17 @@ export const useFilterOptions = (collectionName: string) => {
}
if (nested) {
const targetFields = getCollectionFields(field.target);
const options = getOptions(targetFields, true);
const options = getOptions(targetFields).filter(Boolean);
option['children'] = option['children'] || [];
option['children'].push(...options);
}
return option;
};
const getOptions = (fields, nochildren = false) => {
const getOptions = (fields) => {
++depth;
const options = [];
fields.forEach((field) => {
const option = field2option(field, nochildren);
const option = field2option(field);
if (option) {
options.push(option);
}

View File

@ -70,7 +70,7 @@ export const useValues = () => {
const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
field.data.operator = operator;
field.data.schema = merge(field.data.schema, operator.schema);
field.data.value = operator.noValue ? true : null;
field.data.value = operator.noValue ? operator.default || true : null;
data2value();
console.log('setOperator', field.data);
},

View File

@ -1,14 +1,13 @@
import { compile } from '@formily/json-schema/lib/compiler';
import { SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react';
import { Schema, SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react';
import { useContext } from 'react';
export const useCompile = () => {
const options = useContext(SchemaOptionsContext);
const scope = useContext(SchemaExpressionScopeContext);
return (source: any) => {
return (source: any, ext?: any) => {
if (!source) {
return source;
}
return compile(source, { ...options.scope, ...scope });
return Schema.compile(source, { ...options.scope, ...scope, ...ext });
};
};

View File

@ -1,6 +1,7 @@
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
import merge from 'deepmerge';
import { EventEmitter } from 'events';
import lodash from 'lodash';
import { ModelCtor, Op, Options, QueryInterfaceDropAllTablesOptions, Sequelize, SyncOptions, Utils } from 'sequelize';
import { Collection, CollectionOptions, RepositoryType } from './collection';
import { ImporterReader, ImportFileExtension } from './collection-importer';
@ -11,7 +12,6 @@ import { ModelHook } from './model-hook';
import extendOperators from './operators';
import { RelationRepository } from './relation-repository/relation-repository';
import { Repository } from './repository';
import lodash from 'lodash';
export interface MergeOptions extends merge.Options {}
@ -34,6 +34,7 @@ interface RegisterOperatorsContext {
db?: Database;
path?: string;
field?: Field;
app?: any;
}
export interface CleanOptions extends QueryInterfaceDropAllTablesOptions {

View File

@ -11,6 +11,7 @@ type FilterType = any;
interface FilterParserContext {
collection: Collection;
app?: any;
}
export default class FilterParser {
@ -125,6 +126,7 @@ export default class FilterParser {
const queryValue = lodash.get(unflatten(originalFiler), skipPrefix);
value = opKey(queryValue, {
app: this.context.app,
db: this.database,
path: skipPrefix,
fieldName: this.getFieldNameFromQueryPath(skipPrefix),

View File

@ -1,4 +1,4 @@
export { ModelCtor, SyncOptions } from 'sequelize';
export { ModelCtor, Op, SyncOptions } from 'sequelize';
export * from './collection';
export * from './database';
export { Database as default } from './database';

View File

@ -26,7 +26,12 @@ export class OptionsParser {
this.model = collection.model;
this.options = options;
this.database = collection.context.database;
this.filterParser = new FilterParser(options?.filter, { collection });
this.filterParser = new FilterParser(options?.filter, {
collection,
app: {
ctx: options?.context,
},
});
this.context = context;
}

View File

@ -66,7 +66,7 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
];
if (options && options['filter']) {
const filterResult = this.parseFilter(options['filter']);
const filterResult = this.parseFilter(options['filter'], options);
if (filterResult.include && filterResult.include.length > 0) {
return await this.destroyByFilter(options['filter'], transaction);

View File

@ -1,8 +1,5 @@
import { RelationRepository, transaction } from './relation-repository';
import lodash, { omit } from 'lodash';
import { omit } from 'lodash';
import { MultiAssociationAccessors, Op, Sequelize, Transaction } from 'sequelize';
import { UpdateGuard } from '../update-guard';
import { updateModelByValues } from '../update-associations';
import {
CommonFindOptions,
CountOptions,
@ -10,11 +7,14 @@ import {
Filter,
FilterByTk,
FindOptions,
TK,
TargetKey,
TK,
TransactionAble,
UpdateOptions,
UpdateOptions
} from '../repository';
import { updateModelByValues } from '../update-associations';
import { UpdateGuard } from '../update-guard';
import { RelationRepository, transaction } from './relation-repository';
export interface FindAndCountOptions extends CommonFindOptions {}
@ -187,10 +187,11 @@ export abstract class MultipleRelationRepository extends RelationRepository {
});
}
protected filterHasInclude(filter: Filter) {
const filterResult = this.parseFilter(filter);
protected filterHasInclude(filter: Filter, options?: any) {
const filterResult = this.parseFilter(filter, options);
return filterResult.include && filterResult.include.length > 0;
}
protected accessors() {
return <MultiAssociationAccessors>super.accessors();
}

View File

@ -90,9 +90,12 @@ export abstract class RelationRepository {
return { ...options, ...params };
}
protected parseFilter(filter: Filter) {
protected parseFilter(filter: Filter, options?: any) {
const parser = new FilterParser(filter, {
collection: this.targetCollection,
app: {
ctx: options?.context,
},
});
return parser.toSequelizeParams();
}

View File

@ -9,7 +9,7 @@ import {
ModelCtor,
Op,
Transaction,
UpdateOptions as SequelizeUpdateOptions,
UpdateOptions as SequelizeUpdateOptions
} from 'sequelize';
import { Collection } from './collection';
import { Database } from './database';
@ -74,6 +74,7 @@ export interface CommonFindOptions {
appends?: Appends;
except?: Except;
sort?: Sort;
context?: any;
}
interface FindOneOptions extends FindOptions, CommonFindOptions {}
@ -187,7 +188,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
if (countOptions?.filter) {
options = {
...options,
...this.parseFilter(countOptions.filter),
...this.parseFilter(countOptions.filter, countOptions),
};
}
@ -453,9 +454,12 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
return { where: {}, ...options, ...params };
}
protected parseFilter(filter: Filter) {
protected parseFilter(filter: Filter, options?: any) {
const parser = new FilterParser(filter, {
collection: this.collection,
app: {
ctx: options?.context,
},
});
return parser.toSequelizeParams();
}

View File

@ -1,4 +1,4 @@
import { Collection } from '@nocobase/database';
import { Collection, Op } from '@nocobase/database';
import { Plugin } from '@nocobase/server';
import { resolve } from 'path';
import * as actions from './actions/users';
@ -25,6 +25,13 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
}
async beforeLoad() {
this.db.registerOperators({
$isCurrentUser(_, ctx) {
return {
[Op.eq]: ctx?.app?.ctx?.state?.currentUser?.id || -1,
};
},
});
this.db.registerModels({ UserModel });
this.db.on('users.afterCreateWithAssociations', async (model, options) => {
const { transaction } = options;