fix: acl scope support variables (#1660)

* fix: acl scope support variables

* fix: parse filter

* fix: dynamicComponent
This commit is contained in:
chenos 2023-04-21 11:13:43 +08:00 committed by GitHub
parent 9c4f1a8d7f
commit 5930218811
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 10 deletions

View File

@ -1,7 +1,6 @@
import { Action } from '@nocobase/resourcer'; import { Action } from '@nocobase/resourcer';
import { assign, Toposort, ToposortOptions } from '@nocobase/utils'; import { assign, parseFilter, Toposort, ToposortOptions } from '@nocobase/utils';
import EventEmitter from 'events'; import EventEmitter from 'events';
import parse from 'json-templates';
import compose from 'koa-compose'; import compose from 'koa-compose';
import lodash from 'lodash'; import lodash from 'lodash';
import { ACLAvailableAction, AvailableActionOptions } from './acl-available-action'; import { ACLAvailableAction, AvailableActionOptions } from './acl-available-action';
@ -135,7 +134,7 @@ export class ACL extends EventEmitter {
if (params && resourcerAction.mergeParams) { if (params && resourcerAction.mergeParams) {
const filteredParams = filterParams(ctx, resourceName, params); const filteredParams = filterParams(ctx, resourceName, params);
const parsedParams = acl.parseJsonTemplate(filteredParams, ctx); const parsedParams = await acl.parseJsonTemplate(filteredParams, ctx);
ctx.permission.parsedParams = parsedParams; ctx.permission.parsedParams = parsedParams;
ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams); ctx.log?.info && ctx.log.info('acl parsedParams', parsedParams);
@ -332,12 +331,25 @@ export class ACL extends EventEmitter {
} }
} }
parseJsonTemplate(json: any, ctx: any) { async parseJsonTemplate(json: any, ctx: any) {
return parse(json)({ if (json.filter) {
ctx: { ctx.logger?.info?.('parseJsonTemplate.raw', JSON.parse(JSON.stringify(json.filter)));
state: JSON.parse(JSON.stringify(ctx.state)), const timezone = ctx?.get?.('x-timezone');
}, const state = JSON.parse(JSON.stringify(ctx.state));
}); const filter = await parseFilter(json.filter, {
timezone,
now: new Date().toISOString(),
vars: {
ctx: {
state,
},
$user: async () => state.currentUser,
},
});
json.filter = filter;
ctx.logger?.info?.('parseJsonTemplate.parsed', filter);
}
return json;
} }
middleware() { middleware() {

View File

@ -0,0 +1,82 @@
import React from 'react';
import { useCompile } from '../../schema-component';
import { useFilterOptions } from '../../schema-component/antd/filter';
import { useValues } from '../../schema-component/antd/filter/useValues';
import { Variable } from '../../schema-component/antd/variable';
interface GetOptionsParams {
schema: any;
operator: string;
maxDepth: number;
count?: number;
}
const useOptions = (collectionName: string, { schema, operator, maxDepth, count = 1 }: GetOptionsParams) => {
if (count > maxDepth) {
return [];
}
const result = useFilterOptions(collectionName).map((option) => {
if ((option.type !== 'belongsTo' && option.type !== 'hasOne') || !option.target) {
return {
key: option.name,
value: option.name,
label: option.title,
// TODO: 现在是通过组件的名称来过滤能够被选择的选项,这样的坏处是不够精确,后续可以优化
disabled: schema?.['x-component'] !== option.schema['x-component'],
};
}
const children =
useOptions(option.target, {
schema,
operator,
maxDepth,
count: count + 1,
}) || [];
return {
key: option.name,
value: option.name,
label: option.title,
children,
disabled: children.every((child) => child.disabled),
};
});
return result;
};
const useUserVariable = ({ schema, operator }) => {
const options = useOptions('users', { schema, operator, maxDepth: 1 }) || [];
return {
label: `{{t("Current user")}}`,
value: '$user',
key: '$user',
disabled: options.every((option) => option.disabled),
children: options,
};
};
const useVariableOptions = () => {
const { operator, schema } = useValues();
const userVariable = useUserVariable({ schema, operator });
if (!operator || !schema) return [];
return [userVariable];
};
export function FilterDynamicComponent(props) {
const { value, onChange, renderSchemaComponent } = props;
const options = useVariableOptions();
const compile = useCompile();
const scope = compile(options);
return (
<Variable.Input value={value} onChange={onChange} scope={scope}>
{renderSchemaComponent()}
</Variable.Input>
);
}

View File

@ -2,6 +2,7 @@ import { ISchema } from '@formily/react';
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import { useFormBlockContext } from '../../../block-provider'; import { useFormBlockContext } from '../../../block-provider';
import { useFilterOptions } from '../../../schema-component'; import { useFilterOptions } from '../../../schema-component';
import { FilterDynamicComponent } from '../FilterDynamicComponent';
import { RoleResourceCollectionContext } from '../RolesResourcesActions'; import { RoleResourceCollectionContext } from '../RolesResourcesActions';
export const rolesResourcesScopesCollection = { export const rolesResourcesScopesCollection = {
@ -139,6 +140,7 @@ export const scopesSchema: ISchema = {
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Filter', 'x-component': 'Filter',
'x-component-props': { 'x-component-props': {
dynamicComponent: FilterDynamicComponent,
useProps() { useProps() {
const ctx = useContext(RoleResourceCollectionContext); const ctx = useContext(RoleResourceCollectionContext);
const options = useFilterOptions(ctx.name); const options = useFilterOptions(ctx.name);
@ -267,6 +269,7 @@ export const scopesSchema: ISchema = {
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Filter', 'x-component': 'Filter',
'x-component-props': { 'x-component-props': {
dynamicComponent: FilterDynamicComponent,
useProps() { useProps() {
const ctx = useContext(RoleResourceCollectionContext); const ctx = useContext(RoleResourceCollectionContext);
const options = useFilterOptions(ctx.name); const options = useFilterOptions(ctx.name);

View File

@ -537,7 +537,7 @@ export class PluginACL extends Plugin {
ctx.permission.can = false; ctx.permission.can = false;
} }
} else { } else {
const filter = parseJsonTemplate(action?.params?.filter || {}, ctx); const filter = await parseJsonTemplate(action?.params?.filter || {}, ctx);
const sourceInstance = await ctx.db.getRepository(collectionName).findOne({ const sourceInstance = await ctx.db.getRepository(collectionName).findOne({
filterByTk: resourceOf, filterByTk: resourceOf,
filter, filter,