feat(acl): supports 'current role' variable and collections filtering (#3181)
* feat(acl): support current role variable * feat(acl): support to filter collections * fix: the save conditions button should not be shown * feat(formDataTemplates): supports to use variables for data scope * test: make testing more stable * fix: should only support filtering title and name * fix: fix parse * test: make testing more stable * fix: should not use async function
This commit is contained in:
		
							parent
							
								
									2f892f00d7
								
							
						
					
					
						commit
						aaee46e0c2
					
				@ -265,6 +265,7 @@ export class ACL extends EventEmitter {
 | 
				
			|||||||
            state,
 | 
					            state,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          $user: async () => state.currentUser,
 | 
					          $user: async () => state.currentUser,
 | 
				
			||||||
 | 
					          $nRole: () => state.currentRole,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      json.filter = filter;
 | 
					      json.filter = filter;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,88 +0,0 @@
 | 
				
			|||||||
import React from 'react';
 | 
					 | 
				
			||||||
import { useCompile } from '../../schema-component';
 | 
					 | 
				
			||||||
import { useGetFilterOptions } 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;
 | 
					 | 
				
			||||||
  getFilterOptions: (collectionName: string) => any[];
 | 
					 | 
				
			||||||
  count?: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getOptions = (
 | 
					 | 
				
			||||||
  collectionName: string,
 | 
					 | 
				
			||||||
  { schema, operator, maxDepth, getFilterOptions, count = 1 }: GetOptionsParams,
 | 
					 | 
				
			||||||
) => {
 | 
					 | 
				
			||||||
  if (count > maxDepth) {
 | 
					 | 
				
			||||||
    return [];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const result = getFilterOptions(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 =
 | 
					 | 
				
			||||||
      getOptions(option.target, {
 | 
					 | 
				
			||||||
        schema,
 | 
					 | 
				
			||||||
        operator,
 | 
					 | 
				
			||||||
        maxDepth,
 | 
					 | 
				
			||||||
        count: count + 1,
 | 
					 | 
				
			||||||
        getFilterOptions,
 | 
					 | 
				
			||||||
      }) || [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      key: option.name,
 | 
					 | 
				
			||||||
      value: option.name,
 | 
					 | 
				
			||||||
      label: option.title,
 | 
					 | 
				
			||||||
      children,
 | 
					 | 
				
			||||||
      disabled: children.every((child) => child.disabled),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return result;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const useUserVariable = ({ schema, operator }) => {
 | 
					 | 
				
			||||||
  const getFilterOptions = useGetFilterOptions();
 | 
					 | 
				
			||||||
  const options = getOptions('users', { schema, operator, maxDepth: 1, getFilterOptions }) || [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  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>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Spin } from 'antd';
 | 
					import { Spin } from 'antd';
 | 
				
			||||||
import React, { createContext, useContext } from 'react';
 | 
					import React, { createContext, useContext } from 'react';
 | 
				
			||||||
import { useRequest } from '../../api-client';
 | 
					import { useRequest } from '../../api-client';
 | 
				
			||||||
import { SchemaComponent } from '../../schema-component';
 | 
					import { SchemaComponent, SchemaComponentContext } from '../../schema-component';
 | 
				
			||||||
import { MenuItemsProvider } from '../Configuration/MenuItemsProvider';
 | 
					import { MenuItemsProvider } from '../Configuration/MenuItemsProvider';
 | 
				
			||||||
import { PermissionProvider, SettingCenterPermissionProvider } from '../Configuration/PermisionProvider';
 | 
					import { PermissionProvider, SettingCenterPermissionProvider } from '../Configuration/PermisionProvider';
 | 
				
			||||||
import { roleSchema } from './schemas/roles';
 | 
					import { roleSchema } from './schemas/roles';
 | 
				
			||||||
@ -28,12 +28,14 @@ export const useAvailableActions = () => {
 | 
				
			|||||||
export const RoleTable = () => {
 | 
					export const RoleTable = () => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
 | 
					      <SchemaComponentContext.Provider value={{ designable: false }}>
 | 
				
			||||||
        <AvailableActionsProver>
 | 
					        <AvailableActionsProver>
 | 
				
			||||||
          <SchemaComponent
 | 
					          <SchemaComponent
 | 
				
			||||||
            schema={roleSchema}
 | 
					            schema={roleSchema}
 | 
				
			||||||
            components={{ MenuItemsProvider, SettingCenterPermissionProvider, PermissionProvider }}
 | 
					            components={{ MenuItemsProvider, SettingCenterPermissionProvider, PermissionProvider }}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </AvailableActionsProver>
 | 
					        </AvailableActionsProver>
 | 
				
			||||||
 | 
					      </SchemaComponentContext.Provider>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import { ISchema } from '@formily/react';
 | 
					import { ISchema } from '@formily/react';
 | 
				
			||||||
 | 
					import { uid } from '@formily/shared';
 | 
				
			||||||
import { useRoleResourceValues } from './useRoleResourceValues';
 | 
					import { useRoleResourceValues } from './useRoleResourceValues';
 | 
				
			||||||
import { useSaveRoleResourceAction } from './useSaveRoleResourceAction';
 | 
					import { useSaveRoleResourceAction } from './useSaveRoleResourceAction';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -90,6 +91,31 @@ export const roleCollectionsSchema: ISchema = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  properties: {
 | 
					  properties: {
 | 
				
			||||||
 | 
					    [uid()]: {
 | 
				
			||||||
 | 
					      type: 'void',
 | 
				
			||||||
 | 
					      'x-component': 'ActionBar',
 | 
				
			||||||
 | 
					      'x-component-props': {
 | 
				
			||||||
 | 
					        style: {
 | 
				
			||||||
 | 
					          marginBottom: 16,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        filter: {
 | 
				
			||||||
 | 
					          type: 'void',
 | 
				
			||||||
 | 
					          title: '{{ t("Filter") }}',
 | 
				
			||||||
 | 
					          default: {
 | 
				
			||||||
 | 
					            $and: [{ title: { $includes: '' } }, { name: { $includes: '' } }],
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          'x-action': 'filter',
 | 
				
			||||||
 | 
					          'x-component': 'Filter.Action',
 | 
				
			||||||
 | 
					          'x-component-props': {
 | 
				
			||||||
 | 
					            icon: 'FilterOutlined',
 | 
				
			||||||
 | 
					            useProps: '{{ cm.useFilterActionProps }}',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          'x-align': 'left',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    table1: {
 | 
					    table1: {
 | 
				
			||||||
      type: 'void',
 | 
					      type: 'void',
 | 
				
			||||||
      'x-uid': 'input',
 | 
					      'x-uid': 'input',
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +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 { VariableInput } from '../../../schema-settings';
 | 
				
			||||||
import { RoleResourceCollectionContext } from '../RolesResourcesActions';
 | 
					import { RoleResourceCollectionContext } from '../RolesResourcesActions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const rolesResourcesScopesCollection = {
 | 
					export const rolesResourcesScopesCollection = {
 | 
				
			||||||
@ -140,11 +140,10 @@ export const scopesSchema: ISchema = {
 | 
				
			|||||||
                                      'x-decorator': 'FormItem',
 | 
					                                      'x-decorator': 'FormItem',
 | 
				
			||||||
                                      'x-component': 'Filter',
 | 
					                                      'x-component': 'Filter',
 | 
				
			||||||
                                      'x-component-props': {
 | 
					                                      'x-component-props': {
 | 
				
			||||||
                                        dynamicComponent: FilterDynamicComponent,
 | 
					                                        dynamicComponent: VariableInput,
 | 
				
			||||||
                                        useProps() {
 | 
					                                        useProps() {
 | 
				
			||||||
                                          const ctx = useContext(RoleResourceCollectionContext);
 | 
					                                          const ctx = useContext(RoleResourceCollectionContext);
 | 
				
			||||||
                                          const options = useFilterOptions(ctx.name);
 | 
					                                          const options = useFilterOptions(ctx.name);
 | 
				
			||||||
                                          console.log('ctx.name', ctx.name, options);
 | 
					 | 
				
			||||||
                                          return {
 | 
					                                          return {
 | 
				
			||||||
                                            options,
 | 
					                                            options,
 | 
				
			||||||
                                          };
 | 
					                                          };
 | 
				
			||||||
@ -269,7 +268,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,
 | 
					                                                dynamicComponent: VariableInput,
 | 
				
			||||||
                                                useProps() {
 | 
					                                                useProps() {
 | 
				
			||||||
                                                  const ctx = useContext(RoleResourceCollectionContext);
 | 
					                                                  const ctx = useContext(RoleResourceCollectionContext);
 | 
				
			||||||
                                                  const options = useFilterOptions(ctx.name);
 | 
					                                                  const options = useFilterOptions(ctx.name);
 | 
				
			||||||
 | 
				
			|||||||
@ -78,7 +78,7 @@ const AssociationResourceActionProvider = (props) => {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
  const resource = api.resource(request.resource, resourceOf);
 | 
					  const resource = api.resource(request.resource, resourceOf);
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <ResourceContext.Provider value={{ type: 'association', resource, association }}>
 | 
					    <ResourceContext.Provider value={{ type: 'association', resource, association, collection }}>
 | 
				
			||||||
      <ResourceActionContext.Provider value={{ ...service, defaultRequest: request, dragSort }}>
 | 
					      <ResourceActionContext.Provider value={{ ...service, defaultRequest: request, dragSort }}>
 | 
				
			||||||
        <CollectionProvider collection={collection}>{props.children}</CollectionProvider>
 | 
					        <CollectionProvider collection={collection}>{props.children}</CollectionProvider>
 | 
				
			||||||
      </ResourceActionContext.Provider>
 | 
					      </ResourceActionContext.Provider>
 | 
				
			||||||
 | 
				
			|||||||
@ -579,7 +579,8 @@ export const useFilterActionProps = () => {
 | 
				
			|||||||
  const options = useFilterFieldOptions(collection.fields);
 | 
					  const options = useFilterFieldOptions(collection.fields);
 | 
				
			||||||
  const service = useResourceActionContext();
 | 
					  const service = useResourceActionContext();
 | 
				
			||||||
  return useFilterFieldProps({
 | 
					  return useFilterFieldProps({
 | 
				
			||||||
    options,
 | 
					    // 目前仅需要支持筛选 title 和 name,其它字段可能会报错。详见:https://nocobase.height.app/T-2745
 | 
				
			||||||
 | 
					    options: options.filter((option) => ['title', 'name'].includes(option.name)),
 | 
				
			||||||
    params: service.state?.params?.[0] || service.params,
 | 
					    params: service.state?.params?.[0] || service.params,
 | 
				
			||||||
    service,
 | 
					    service,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import { mergeFilter } from '../../block-provider';
 | 
				
			|||||||
import { useCollectionManager } from '../../collection-manager';
 | 
					import { useCollectionManager } from '../../collection-manager';
 | 
				
			||||||
import { SchemaComponent, SchemaComponentContext, removeNullCondition } from '../../schema-component';
 | 
					import { SchemaComponent, SchemaComponentContext, removeNullCondition } from '../../schema-component';
 | 
				
			||||||
import { ITemplate } from '../../schema-component/antd/form-v2/Templates';
 | 
					import { ITemplate } from '../../schema-component/antd/form-v2/Templates';
 | 
				
			||||||
 | 
					import { VariableInput } from '../VariableInput';
 | 
				
			||||||
import { AsDefaultTemplate } from './components/AsDefaultTemplate';
 | 
					import { AsDefaultTemplate } from './components/AsDefaultTemplate';
 | 
				
			||||||
import { ArrayCollapse } from './components/DataTemplateTitle';
 | 
					import { ArrayCollapse } from './components/DataTemplateTitle';
 | 
				
			||||||
import { getSelectedIdFilter } from './components/Designer';
 | 
					import { getSelectedIdFilter } from './components/Designer';
 | 
				
			||||||
@ -143,6 +144,9 @@ export const FormDataTemplates = observer(
 | 
				
			|||||||
                      title: '{{ t("Assign  data scope for the template") }}',
 | 
					                      title: '{{ t("Assign  data scope for the template") }}',
 | 
				
			||||||
                      'x-decorator': 'FormItem',
 | 
					                      'x-decorator': 'FormItem',
 | 
				
			||||||
                      'x-component': 'Filter',
 | 
					                      'x-component': 'Filter',
 | 
				
			||||||
 | 
					                      'x-component-props': {
 | 
				
			||||||
 | 
					                        dynamicComponent: VariableInput,
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
                      'x-decorator-props': {
 | 
					                      'x-decorator-props': {
 | 
				
			||||||
                        style: {
 | 
					                        style: {
 | 
				
			||||||
                          marginBottom: '0px',
 | 
					                          marginBottom: '0px',
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user