fix(FilterFormBlock): fix association field can not to filter (#1699)
* feat: add getValuesByPath * fix(FilterFormBlock): fix association field can not to filter * test: hasMany filter test * fix: remove null in filter * fix: fix not responding for filter button * fix: fix oho and o2m and obo * fix: fix isInFilterFormBlock * fix: fix errors * fix: should filter out when params is empty --------- Co-authored-by: chareice <chareice@live.com>
This commit is contained in:
parent
d4165babf7
commit
c02544c68b
@ -3,6 +3,7 @@ import { Schema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
import { useCollectionManager } from '../collection-manager';
|
import { useCollectionManager } from '../collection-manager';
|
||||||
|
import { isInFilterFormBlock } from '../filter-provider';
|
||||||
import { RecordProvider, useRecord } from '../record-provider';
|
import { RecordProvider, useRecord } from '../record-provider';
|
||||||
import { SchemaComponentOptions } from '../schema-component';
|
import { SchemaComponentOptions } from '../schema-component';
|
||||||
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
||||||
@ -164,7 +165,7 @@ export const TableSelectorProvider = (props: TableSelectorProviderProps) => {
|
|||||||
}
|
}
|
||||||
let extraFilter;
|
let extraFilter;
|
||||||
if (collectionField) {
|
if (collectionField) {
|
||||||
if (['oho', 'o2m'].includes(collectionField.interface)) {
|
if (['oho', 'o2m'].includes(collectionField.interface) && !isInFilterFormBlock(fieldSchema)) {
|
||||||
if (record?.[collectionField.sourceKey]) {
|
if (record?.[collectionField.sourceKey]) {
|
||||||
extraFilter = {
|
extraFilter = {
|
||||||
$or: [
|
$or: [
|
||||||
@ -188,7 +189,7 @@ export const TableSelectorProvider = (props: TableSelectorProviderProps) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['obo'].includes(collectionField.interface)) {
|
if (['obo'].includes(collectionField.interface) && !isInFilterFormBlock(fieldSchema)) {
|
||||||
const fields = getCollectionFields(collectionField.target);
|
const fields = getCollectionFields(collectionField.target);
|
||||||
const targetField = fields.find((f) => f.foreignKey && f.foreignKey === collectionField.foreignKey);
|
const targetField = fields.find((f) => f.foreignKey && f.foreignKey === collectionField.foreignKey);
|
||||||
if (targetField) {
|
if (targetField) {
|
||||||
|
@ -10,7 +10,7 @@ import { useHistory } from 'react-router-dom';
|
|||||||
import { useReactToPrint } from 'react-to-print';
|
import { useReactToPrint } from 'react-to-print';
|
||||||
import { AssociationFilter, useFormBlockContext, useTableBlockContext } from '../..';
|
import { AssociationFilter, useFormBlockContext, useTableBlockContext } from '../..';
|
||||||
import { useAPIClient, useRequest } from '../../api-client';
|
import { useAPIClient, useRequest } from '../../api-client';
|
||||||
import { useCollection } from '../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../collection-manager';
|
||||||
import { useFilterBlock } from '../../filter-provider/FilterProvider';
|
import { useFilterBlock } from '../../filter-provider/FilterProvider';
|
||||||
import { transformToFilter } from '../../filter-provider/utils';
|
import { transformToFilter } from '../../filter-provider/utils';
|
||||||
import { useRecord } from '../../record-provider';
|
import { useRecord } from '../../record-provider';
|
||||||
@ -241,6 +241,8 @@ export const useFilterBlockActionProps = () => {
|
|||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { getDataBlocks } = useFilterBlock();
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
const { name } = useCollection();
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
|
||||||
actionField.data = actionField.data || {};
|
actionField.data = actionField.data || {};
|
||||||
|
|
||||||
@ -260,7 +262,9 @@ export const useFilterBlockActionProps = () => {
|
|||||||
// 保留原有的 filter
|
// 保留原有的 filter
|
||||||
const storedFilter = block.service.params?.[1]?.filters || {};
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
storedFilter[uid] = removeNullCondition(transformToFilter(form.values, fieldSchema));
|
storedFilter[uid] = removeNullCondition(
|
||||||
|
transformToFilter(form.values, fieldSchema, getCollectionJoinField, name),
|
||||||
|
);
|
||||||
|
|
||||||
const mergedFilter = mergeFilter([
|
const mergedFilter = mergeFilter([
|
||||||
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
||||||
@ -279,6 +283,7 @@ export const useFilterBlockActionProps = () => {
|
|||||||
);
|
);
|
||||||
actionField.data.loading = false;
|
actionField.data.loading = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
actionField.data.loading = false;
|
actionField.data.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -785,8 +790,6 @@ export const useRefreshActionProps = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useDetailsPaginationProps = () => {
|
export const useDetailsPaginationProps = () => {
|
||||||
const ctx = useDetailsBlockContext();
|
const ctx = useDetailsBlockContext();
|
||||||
const count = ctx.service?.data?.meta?.count || 0;
|
const count = ctx.service?.data?.meta?.count || 0;
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import { transformToFilter } from '../utils';
|
||||||
|
|
||||||
|
// TODO: 前端测试报错解决之后,把该文件重命名为 transformToFilter.test.ts
|
||||||
|
describe('transformToFilter', () => {
|
||||||
|
it('should transform to filter', () => {
|
||||||
|
const values = {
|
||||||
|
name: 'name',
|
||||||
|
email: 'email',
|
||||||
|
user: {
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
list: [{ name: 'name1' }, { name: 'name2' }, { name: 'name3' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldSchema = {
|
||||||
|
'x-filter-operators': {
|
||||||
|
name: '$eq',
|
||||||
|
email: '$eq',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getField = (name: string) => {
|
||||||
|
if (name === 'user' || name === 'list') {
|
||||||
|
return {
|
||||||
|
targetKey: 'name',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
targetKey: undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(transformToFilter(values, fieldSchema as any, getField)).toEqual({
|
||||||
|
$and: [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
$eq: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: {
|
||||||
|
$eq: 'email',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'user.name': {
|
||||||
|
$eq: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'list.name': {
|
||||||
|
$eq: ['name1', 'name2', 'name3'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,10 +1,10 @@
|
|||||||
import { Schema, useFieldSchema } from '@formily/react';
|
import { Schema, useFieldSchema } from '@formily/react';
|
||||||
import { isEmpty, isPlainObject } from '@nocobase/utils/client';
|
import { flatten, getValuesByPath } from '@nocobase/utils/client';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { mergeFilter } from '../block-provider';
|
import { mergeFilter } from '../block-provider';
|
||||||
import { FilterTarget, findFilterTargets } from '../block-provider/hooks';
|
import { FilterTarget, findFilterTargets } from '../block-provider/hooks';
|
||||||
import { Collection, FieldOptions, useCollection } from '../collection-manager';
|
import { Collection, CollectionFieldOptions, FieldOptions, useCollection } from '../collection-manager';
|
||||||
import { removeNullCondition } from '../schema-component';
|
import { removeNullCondition } from '../schema-component';
|
||||||
import { findFilterOperators } from '../schema-component/antd/form-item/SchemaSettingOptions';
|
import { findFilterOperators } from '../schema-component/antd/form-item/SchemaSettingOptions';
|
||||||
import { useFilterBlock } from './FilterProvider';
|
import { useFilterBlock } from './FilterProvider';
|
||||||
@ -45,38 +45,55 @@ export const useSupportedBlocks = (filterBlockType: FilterBlockType) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const transformToFilter = (
|
||||||
* Recursively flatten an object and generate a query object.
|
values: Record<string, any>,
|
||||||
* @param result - The resulting query object.
|
fieldSchema: Schema,
|
||||||
* @param obj - The object to be flattened and queried.
|
getCollectionJoinField: (name: string) => CollectionFieldOptions,
|
||||||
* @param key - The current key in the object tree.
|
collectionName: string,
|
||||||
* @param operators - The operators to use for each field.
|
) => {
|
||||||
* @returns The resulting query object.
|
|
||||||
*/
|
|
||||||
const flattenAndQueryObject = (result: Record<string, any>, obj: any, key: string, operators: Record<string, any>) => {
|
|
||||||
if (!obj) return result;
|
|
||||||
|
|
||||||
if (!isPlainObject(obj)) {
|
|
||||||
result[key] = {
|
|
||||||
[operators[key] || '$eq']: obj,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
Object.keys(obj).forEach((k) => {
|
|
||||||
flattenAndQueryObject(result, obj[k], `${key}.${k}`, operators);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const transformToFilter = (values: Record<string, any>, fieldSchema: Schema) => {
|
|
||||||
const { operators } = findFilterOperators(fieldSchema);
|
const { operators } = findFilterOperators(fieldSchema);
|
||||||
|
|
||||||
return {
|
values = flatten(values, {
|
||||||
|
breakOn({ value, path }) {
|
||||||
|
const collectionField = getCollectionJoinField(`${collectionName}.${path}`);
|
||||||
|
if (collectionField?.target) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const targetKey = collectionField.targetKey || 'id';
|
||||||
|
if (value && value[targetKey] != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = {
|
||||||
$and: Object.keys(values)
|
$and: Object.keys(values)
|
||||||
.map((key) => flattenAndQueryObject({}, values[key], key, operators))
|
.map((key) => {
|
||||||
.filter((item) => !isEmpty(item)),
|
let value = _.get(values, key);
|
||||||
|
const collectionField = getCollectionJoinField(`${collectionName}.${key}`);
|
||||||
|
|
||||||
|
if (collectionField?.target) {
|
||||||
|
value = getValuesByPath(value, collectionField.targetKey || 'id');
|
||||||
|
key = `${key}.${collectionField.targetKey || 'id'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value || value.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
[key]: {
|
||||||
|
[operators[key] || '$eq']: value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAssociatedFields = () => {
|
export const useAssociatedFields = () => {
|
||||||
@ -86,7 +103,9 @@ export const useAssociatedFields = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isAssocField = (field?: FieldOptions) => {
|
export const isAssocField = (field?: FieldOptions) => {
|
||||||
return ['o2o', 'oho', 'obo', 'm2o', 'createdBy', 'updatedBy', 'o2m', 'm2m', 'linkTo'].includes(field?.interface);
|
return ['o2o', 'oho', 'obo', 'm2o', 'createdBy', 'updatedBy', 'o2m', 'm2m', 'linkTo', 'chinaRegion'].includes(
|
||||||
|
field?.interface,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isSameCollection = (c1: Collection, c2: Collection) => {
|
export const isSameCollection = (c1: Collection, c2: Collection) => {
|
||||||
@ -167,3 +186,13 @@ export const useFilterAPI = () => {
|
|||||||
doFilter,
|
doFilter,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isInFilterFormBlock = (fieldSchema: Schema) => {
|
||||||
|
while (fieldSchema) {
|
||||||
|
if (fieldSchema['x-filter-targets']) {
|
||||||
|
return fieldSchema['x-decorator'] === 'FilterFormBlockProvider';
|
||||||
|
}
|
||||||
|
fieldSchema = fieldSchema.parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { useFieldSchema } from '@formily/react';
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { isInFilterFormBlock } from '../../../filter-provider';
|
||||||
import { useRecord } from '../../../record-provider';
|
import { useRecord } from '../../../record-provider';
|
||||||
|
|
||||||
export default function useServiceOptions(props) {
|
export default function useServiceOptions(props) {
|
||||||
@ -43,7 +44,7 @@ export default function useServiceOptions(props) {
|
|||||||
return mergeFilter(
|
return mergeFilter(
|
||||||
[
|
[
|
||||||
mergeFilter([
|
mergeFilter([
|
||||||
isOToAny
|
isOToAny && !isInFilterFormBlock(fieldSchema)
|
||||||
? {
|
? {
|
||||||
[collectionField.foreignKey]: {
|
[collectionField.foreignKey]: {
|
||||||
$is: null,
|
$is: null,
|
||||||
@ -52,7 +53,7 @@ export default function useServiceOptions(props) {
|
|||||||
: null,
|
: null,
|
||||||
params?.filter,
|
params?.filter,
|
||||||
]),
|
]),
|
||||||
isOToAny && sourceValue !== undefined && sourceValue !== null
|
isOToAny && sourceValue !== undefined && sourceValue !== null && !isInFilterFormBlock(fieldSchema)
|
||||||
? {
|
? {
|
||||||
[collectionField.foreignKey]: {
|
[collectionField.foreignKey]: {
|
||||||
$eq: sourceValue,
|
$eq: sourceValue,
|
||||||
|
@ -239,7 +239,7 @@ const Drawer: React.FunctionComponent<{
|
|||||||
console.log('collectionField', options);
|
console.log('collectionField', options);
|
||||||
const getFilter = () => {
|
const getFilter = () => {
|
||||||
const targetKey = collectionField.targetKey || 'id';
|
const targetKey = collectionField.targetKey || 'id';
|
||||||
const list = options.map((option) => option[targetKey]);
|
const list = options.map((option) => option[targetKey]).filter(Boolean);
|
||||||
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
||||||
return filter;
|
return filter;
|
||||||
};
|
};
|
||||||
|
@ -294,10 +294,10 @@ export const useFilterFormItemInitializerFields = (options?: any) => {
|
|||||||
if (isAssocField(field)) {
|
if (isAssocField(field)) {
|
||||||
schema = {
|
schema = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: field.name,
|
name: `${field.name}`,
|
||||||
required: false,
|
required: false,
|
||||||
'x-designer': 'AssociationSelect.FilterDesigner',
|
'x-designer': 'FormItem.FilterFormDesigner',
|
||||||
'x-component': 'AssociationSelect',
|
'x-component': 'CollectionField',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-collection-field': `${name}.${field.name}`,
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
'x-component-props': field.uiSchema?.['x-component-props'],
|
'x-component-props': field.uiSchema?.['x-component-props'],
|
||||||
@ -397,6 +397,9 @@ const getItem = (
|
|||||||
title: field.uiSchema?.title,
|
title: field.uiSchema?.title,
|
||||||
children: subFields
|
children: subFields
|
||||||
.map((subField) =>
|
.map((subField) =>
|
||||||
|
// 使用 | 分隔,是为了防止 form.values 中出现 { a: { b: 1 } } 的情况
|
||||||
|
// 使用 | 分隔后,form.values 中会出现 { 'a|b': 1 } 的情况,这种情况下
|
||||||
|
// 就可以知道该字段是一个关系字段中的输入框,进而特殊处理
|
||||||
getItem(subField, `${schemaName}.${subField.name}`, collectionName, getCollectionFields, [
|
getItem(subField, `${schemaName}.${subField.name}`, collectionName, getCollectionFields, [
|
||||||
...processedCollections,
|
...processedCollections,
|
||||||
field.target,
|
field.target,
|
||||||
|
@ -14,6 +14,47 @@ describe('filter', () => {
|
|||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should filter by hasMany association field', async () => {
|
||||||
|
const DeptCollection = db.collection({
|
||||||
|
name: 'depts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'belongsTo', name: 'org', target: 'orgs' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const OrgCollection = db.collection({
|
||||||
|
name: 'orgs',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'depts', target: 'depts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.sync();
|
||||||
|
|
||||||
|
await OrgCollection.repository.create({
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
name: 'org1',
|
||||||
|
depts: [{ name: 'dept1' }, { name: 'dept2' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'org2',
|
||||||
|
depts: [{ name: 'dept3' }, { name: 'dept4' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dept1 = await DeptCollection.repository.findOne({});
|
||||||
|
|
||||||
|
const orgs = await OrgCollection.repository.find({
|
||||||
|
filter: { $and: [{ depts: { id: { $eq: dept1.get('id') } } }] },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(orgs.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('should filter by association field', async () => {
|
it('should filter by association field', async () => {
|
||||||
const UserCollection = db.collection({
|
const UserCollection = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
|
89
packages/core/utils/src/__tests__/getValuesByPath.test.ts
Normal file
89
packages/core/utils/src/__tests__/getValuesByPath.test.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { getValuesByPath } from '../getValuesByPath';
|
||||||
|
|
||||||
|
describe('getValuesByPath', () => {
|
||||||
|
it('should return correct value', () => {
|
||||||
|
const obj = {
|
||||||
|
a: {
|
||||||
|
b: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.b');
|
||||||
|
expect(result).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an array of values', () => {
|
||||||
|
const obj = {
|
||||||
|
a: [{ b: 1 }, { b: 2 }],
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.b');
|
||||||
|
expect(result).toEqual([1, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested array', () => {
|
||||||
|
const obj = {
|
||||||
|
a: [{ b: [{ c: 1 }, { c: 2 }] }, { b: [{ c: 3 }, { c: 4 }] }],
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.b.c');
|
||||||
|
expect(result).toEqual([1, 2, 3, 4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when path is empty', () => {
|
||||||
|
const obj = {
|
||||||
|
a: { b: 1 },
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, '');
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when path is not found', () => {
|
||||||
|
const obj = {
|
||||||
|
a: { b: 1 },
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.c');
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when path is not found in nested array', () => {
|
||||||
|
const obj = {
|
||||||
|
a: [{ b: 1 }, { b: 2 }],
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.c');
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when path is not found in nested array with empty string', () => {
|
||||||
|
const obj = {
|
||||||
|
a: [{ b: 1 }, { b: 2 }],
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.');
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when obj is null', () => {
|
||||||
|
const obj = null;
|
||||||
|
const result = getValuesByPath(obj, 'a.b');
|
||||||
|
expect(result).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('default value', () => {
|
||||||
|
const obj = {
|
||||||
|
a: { b: 1 },
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.c', null);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array when obj key value is undefined', () => {
|
||||||
|
const obj = {
|
||||||
|
a: undefined,
|
||||||
|
};
|
||||||
|
const result = getValuesByPath(obj, 'a.b');
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('the initial value is an array', () => {
|
||||||
|
const arr = [{ b: 1 }, { b: 2 }];
|
||||||
|
const result = getValuesByPath(arr, 'b', []);
|
||||||
|
expect(result).toEqual([1, 2]);
|
||||||
|
});
|
||||||
|
});
|
@ -2,6 +2,7 @@ export * from './collections-graph';
|
|||||||
export * from './common';
|
export * from './common';
|
||||||
export * from './date';
|
export * from './date';
|
||||||
export * from './forEach';
|
export * from './forEach';
|
||||||
|
export * from './getValuesByPath';
|
||||||
export * from './merge';
|
export * from './merge';
|
||||||
export * from './number';
|
export * from './number';
|
||||||
export * from './parse-filter';
|
export * from './parse-filter';
|
||||||
|
32
packages/core/utils/src/getValuesByPath.ts
Normal file
32
packages/core/utils/src/getValuesByPath.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export const getValuesByPath = (obj: object, path: string, defaultValue?: any) => {
|
||||||
|
if (!obj) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
const keys = path.split('.');
|
||||||
|
let result: any[] = [];
|
||||||
|
let currentValue = obj;
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i];
|
||||||
|
|
||||||
|
if (Array.isArray(currentValue)) {
|
||||||
|
for (let j = 0; j < currentValue.length; j++) {
|
||||||
|
const value = getValuesByPath(currentValue[j], keys.slice(i).join('.'), defaultValue);
|
||||||
|
result = result.concat(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentValue = currentValue[key] === undefined ? defaultValue : currentValue[key];
|
||||||
|
|
||||||
|
if (currentValue == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === keys.length - 1) {
|
||||||
|
result.push(currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.length === 1 ? result[0] : result;
|
||||||
|
};
|
@ -12,7 +12,7 @@ function keyIdentity(key) {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatten(target, opts?: any) {
|
export function flatten(target, opts?: any) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
const delimiter = opts.delimiter || '.';
|
const delimiter = opts.delimiter || '.';
|
||||||
@ -32,7 +32,7 @@ function flatten(target, opts?: any) {
|
|||||||
|
|
||||||
const newKey = prev ? prev + delimiter + transformKey(key) : transformKey(key);
|
const newKey = prev ? prev + delimiter + transformKey(key) : transformKey(key);
|
||||||
|
|
||||||
if (opts.breakOn({ key })) {
|
if (opts.breakOn?.({ key, value, path: newKey })) {
|
||||||
output[newKey] = transformValue(value, newKey);
|
output[newKey] = transformValue(value, newKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user