Commit adccd409 authored by drigle's avatar drigle

feat: configure table field count and label display

parent 197a63dc
......@@ -195,6 +195,8 @@ export default function SubjectRecordWorkbench({
const [records, setRecords] = useState([]);
const [subject, setSubject] = useState(null);
const [views, setViews] = useState([]);
const [tableDisplayConfig, setTableDisplayConfig] = useState({});
const [tableDisplayConfigError, setTableDisplayConfigError] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
......@@ -221,10 +223,14 @@ export default function SubjectRecordWorkbench({
setRecords(resources.records || []);
setSubject(resources.subject || null);
setViews(resources.views || []);
setTableDisplayConfig(resources.displayConfig || {});
setTableDisplayConfigError(resources.displayConfigError || '');
} catch (requestError) {
setRecords([]);
setSubject(null);
setViews([]);
setTableDisplayConfig({});
setTableDisplayConfigError('');
setError(requestError?.message || '列表加载失败,请稍后重试。');
} finally {
setLoading(false);
......@@ -253,8 +259,8 @@ export default function SubjectRecordWorkbench({
);
const hasAccessibleGridView = Boolean(activeView);
const configuredView = useMemo(
() => buildConfiguredWorkbenchView(records, filters, activeView, subject),
[records, filters, activeView, subject],
() => buildConfiguredWorkbenchView(records, filters, activeView, subject, tableDisplayConfig),
[records, filters, activeView, subject, tableDisplayConfig],
);
const filteredRecords = configuredView.filteredRecords || [];
const visibleColumns = useMemo(() => {
......@@ -565,6 +571,7 @@ export default function SubjectRecordWorkbench({
states={{
loading,
error,
warning: tableDisplayConfigError,
success,
loadingTitle: `正在加载${title}`,
loadingDescription: '正在读取对象结构、视图配置和记录数据,请稍候。',
......
import { Button, Chip, Tooltip } from '@heroui/react';
import { DataTableText } from './DataTable';
import { getTableFieldDisplay } from '@/utils/tableFieldDisplay';
export default function TableFieldValue({ value, column, onPress }) {
const display = getTableFieldDisplay(value, column);
if (onPress) {
const action = (
<Button variant="ghost" className="h-auto min-w-0 justify-start px-0 text-left" onPress={onPress}>
{display.showCount
? <Chip size="sm" aria-label={`${column.label || '关联条目'}:${display.count} 项`}>{display.count}</Chip>
: <DataTableText text={display.text || '-'} maxLines={2} showTooltip={false} />}
</Button>
);
return display.labels.length > 0 ? (
<Tooltip>
{action}
<Tooltip.Content>
<div className="max-h-64 max-w-80 overflow-auto whitespace-pre-wrap break-words">
{display.labels.join('、')}
</div>
</Tooltip.Content>
</Tooltip>
) : action;
}
if (display.showCount) {
const count = (
<Chip size="sm" aria-label={`${column.label || '关联条目'}:${display.count} 项`}>
{display.count}
</Chip>
);
return display.labels.length > 0 ? (
<Tooltip>
<Tooltip.Trigger aria-label={`${column.label || '关联条目'}:${display.count} 项,查看标题`}>
{count}
</Tooltip.Trigger>
<Tooltip.Content>
<div className="max-h-64 max-w-80 overflow-auto whitespace-pre-wrap break-words">
{display.labels.join('、')}
</div>
</Tooltip.Content>
</Tooltip>
) : count;
}
return <DataTableText text={display.text || '-'} maxLines={2} showTooltip />;
}
......@@ -27,6 +27,8 @@ import FilterSelect from '@/components/FilterSelect';
import FormDatePicker from '@/components/FormDatePicker';
import ReferenceSearchSelect from '@/pages/RecordDetailDrawer/components/ReferenceSearchSelect';
import UserSearchSelect from '@/components/UserSearchSelect';
import TableFieldValue from '@/components/TableFieldValue';
import { getTableFieldDisplayConfig } from '@/utils/tableFieldDisplay';
import {
formatFieldValue,
getFieldValue,
......@@ -329,6 +331,9 @@ function getFilterPlaceholder(field) {
function defaultRenderCell(record, column) {
const sourceRecord = record?.raw || record;
const value = resolveFieldValue(sourceRecord, column.field, column.fieldDef);
if (getTableFieldDisplayConfig(column).isConfigured) {
return <TableFieldValue value={value} column={column} />;
}
const text = formatFieldValue(value, column.fieldDef) || '-';
if (column.fieldDef?.type === 'user') {
......@@ -939,6 +944,11 @@ export default function RecordWorkbench({
column.field,
column.fieldDef,
);
if (getTableFieldDisplayConfig(column).isConfigured) {
return <TableFieldValue value={value} column={column} onPress={() => row.rowContext.openDetail(
row.isExpandedRecord || row.isExpandedPanel ? row.parentRecord : row.record,
)} />;
}
const text = formatFieldValue(value, column.fieldDef) || '-';
return (
<DataTablePrimaryAction
......@@ -1250,6 +1260,12 @@ export default function RecordWorkbench({
</Alert>
) : null}
{states?.warning ? (
<Alert status="warning">
<Alert.Content><Alert.Description>{states.warning}</Alert.Description></Alert.Content>
</Alert>
) : null}
<div ref={tableScrollRef} className={tableContentClass}>
<DataTable
ariaLabel={title || '业务数据列表'}
......
......@@ -132,6 +132,67 @@
- 本应用 `/integrity_risk_prevention_new` 前缀只在站内路由前移除;其他应用路径和外部 HTTP(S)
链接在新窗口打开,不改变原通知接口、`notice_id` payload 或鉴权。
### 2.4 表格字段显示配置
入口为 streams-admin 的应用布局配置或 Subject 的 Grid 视图配置,作用于过程表、展示表、措施库、往期材料归档的共享工作台表格中的引用、选择、子对象及 form 字段。页面加载应用及视图配置后按列渲染,切换视图时使用该视图的配置。
- 应用默认值读取 `layouts.default.subjects.<subjectName>.gridRecordConfig.displayConfig`,兼容 streams-admin 的 `displayConfig.maxTagCount`。
- 视图默认值读取 `settings.displayConfig`,同名属性覆盖应用默认值;`settings.columns[]` 中的单列 `displayConfig` 再覆盖视图默认值,列上的 `displayMode`、`maxTagCount`、`titleField` 优先级最高。
- `displayMode: "labels"` 显示已选选项或关联标题;选项文案来自字段 `settings.options`,仅展示当前记录实际选中的值。
- `displayMode: "count"` 显示有效条目数量,空值显示 `0`,单值显示 `1`。数量可通过鼠标悬停或键盘聚焦查看完整标题;空值不产生提示。计数只基于该字段已有数据,不额外查询关联记录总数。
- `displayMode: "auto"` 在条目数达到 `maxTagCount` 时显示数量,否则显示标题或选项。只配置 `maxTagCount` 时自动启用此模式,判断使用 `>=`,与 streams-admin 一致。显式 `auto` 未配置合法阈值时使用 `3`;阈值为 `0` 时所有非空字段显示数量。
- 子对象标签优先使用 `titleField` 指定的子字段,例如 `business_unit`;也支持 `metadata` 下的子字段。未配置或值为空时使用已有 `title/name/_id`,仍无标题时展示“第 N 条”,不猜测业务字段。form 对象为 `{form,data}` 时按 `data` 中的条目计数。
- 未配置显示方式或阈值时保留现有单元格行为,包括现有子对象明细摘要;非法显示方式或阈值不修改记录。普通文本、数字、日期、布尔、附件和人员字段不受这项集合显示配置影响。
- 应用配置读取失败时显示警告并使用视图配置;列表数据和详情入口仍可使用。重新加载页面会重试读取配置。配置不会改变筛选条件、排序、分页、审批状态、详情编辑、关联 ID 或 Record 的 `metadata`。
应用配置示例(`integrity_risk_prevention_new` 应用):
```json
{
"layouts": {
"default": {
"subjects": {
"risk_prevention_control_measures_table": {
"gridRecordConfig": {
"displayConfig": { "maxTagCount": 3 }
}
},
"integrity_risk_control_archive": {
"gridRecordConfig": {
"displayConfig": { "displayMode": "count" }
}
}
}
}
}
}
```
Grid 视图配置示例(列举的是配置片段,修改时应保留完整原列列表):
```json
{
"settings": {
"displayConfig": { "displayMode": "auto", "maxTagCount": 3 },
"columns": [
{ "field": "metadata.file_name_and_document_number", "displayMode": "labels" },
{
"field": "metadata.department_control_items",
"displayMode": "count",
"titleField": "business_unit"
},
{
"field": "metadata.subsidiary_system",
"displayMode": "auto",
"maxTagCount": 3
}
]
}
}
```
过程表仍按现有规则隐藏 `department_control_items` 主表列,部门展开行中的引用与选择字段应用相同列配置。首个可见字段配置为数量或标签时仍可点击进入详情。详情抽屉及归档嵌套明细弹窗沿用已有展示逻辑。
## 3. 过程表操作
### 3.0 推送链接与登录回跳
......
......@@ -14,6 +14,8 @@ import {
toArray,
} from '@/utils/subjectView';
import { loadApplicationTableDisplayConfig } from '@/services/tableFieldDisplayService';
const QUERY_PAGE_SIZE = 1000;
const RECORD_LEVEL_FIELDS = new Set(['_id', 'name', 'title', 'status', 'subject', 'content', 'content_type', 'order', 'created_at', 'updated_at']);
......@@ -123,9 +125,10 @@ export async function queryAllSubjectRecords(subjectName, query = {}) {
}
export async function loadSubjectWorkbenchResources(subjectName, query = {}, options = {}) {
const [subject, viewSummaries] = await Promise.all([
const [subject, viewSummaries, tableDisplayConfig] = await Promise.all([
loadSubject(subjectName),
getSubjectView(subjectName),
loadApplicationTableDisplayConfig(subjectName),
]);
const views = await Promise.all(
(Array.isArray(viewSummaries) ? viewSummaries : []).map(async (view) => {
......@@ -156,6 +159,7 @@ export async function loadSubjectWorkbenchResources(subjectName, query = {}, opt
records: records.map(normalizeWorkbenchRecord),
subject,
views: views || [],
...tableDisplayConfig,
};
}
......@@ -165,8 +169,8 @@ export function buildGridViews(views = []) {
.filter((view) => isViewAccessible(view));
}
export function buildConfiguredWorkbenchView(records, filters, view, subject) {
const columns = buildViewColumns(view, subject);
export function buildConfiguredWorkbenchView(records, filters, view, subject, displayConfig = {}) {
const columns = buildViewColumns(view, subject, { displayConfig });
const filteredRecords = records.filter((record) => {
const rawRecord = record.raw || record;
......
......@@ -44,6 +44,14 @@
- `404` `{"code":404,"message":"record '<name>' not found"}`
- `400` 业务校验失败(缺少必填字段、主键冲突、类型不匹配等)
### 1.2 表格显示配置的关联读取接口
通用列表通过已有 `DataService.loadApplication(APPLICATION_NAME)` 调用 `GET /application/:name`,当前应用为 `integrity_risk_prevention_new`,读取 `layouts.default.subjects.<subjectName>.gridRecordConfig.displayConfig`。
视图继续使用 `GET /subject/:subjectName/view` 与 `GET /subject/:subjectName/view/:viewName`,分别对应 `getSubjectView` / `getSubjectViewDetail`。视图默认值在 `settings.displayConfig`,单列覆盖在 `settings.columns[]`。支持 `displayMode: labels | count | auto`、`maxTagCount`(非负整数)和 `titleField`(子字段路径)。
这里只读取配置,不更新应用、视图、Subject 或 Record。应用配置失败时提示警告并使用视图配置,记录正常加载;刷新重试。完整规则见 `src/pages/integrity_risk_prevention/integrity-risk-prevention.business-flow.md` 的“2.4 表格字段显示配置”。
## 2. Record 核心模型字段
不同 `subject` 通过 `metadata` 扩展业务字段。
......
import { APPLICATION_NAME } from '@/config/application';
import { loadApplication } from '@/services/DataService';
export async function loadApplicationTableDisplayConfig(subjectName) {
try {
const application = await loadApplication(APPLICATION_NAME);
return {
displayConfig: application?.layouts?.default?.subjects?.[subjectName]?.gridRecordConfig?.displayConfig || {},
displayConfigError: '',
};
} catch (error) {
return {
displayConfig: {},
displayConfigError: `应用表格显示配置读取失败,暂使用视图默认显示:${error?.message || '请稍后重试'}`,
};
}
}
......@@ -473,9 +473,10 @@ export function shouldHideViewTitleColumn(view, fallbackView = null) {
return (view?.settings?.shortTitle ?? fallbackView?.settings?.shortTitle) === true;
}
export function buildViewColumns(view, subject, { includeHidden = false } = {}) {
export function buildViewColumns(view, subject, { includeHidden = false, displayConfig = {} } = {}) {
const configuredColumns = view?.settings?.columns || [];
const hideTitleColumn = !includeHidden && shouldHideViewTitleColumn(view);
const viewDisplayConfig = { ...displayConfig, ...view?.settings?.displayConfig };
if (configuredColumns.length > 0) {
return configuredColumns
......@@ -488,6 +489,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
const fieldDef = getFieldDefinition(column.field, subject);
return {
...column,
displayConfig: { ...viewDisplayConfig, ...column.displayConfig },
width: column.width || 180,
label: getFieldDisplayLabel(fieldDef, column.field),
fieldDef: fieldDef || { name: column.field, label: column.field, type: 'text' },
......@@ -499,6 +501,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
if (!hideTitleColumn) {
defaultColumns.push({
field: 'title',
displayConfig: viewDisplayConfig,
width: 180,
label: '标题',
fieldDef: { name: 'title', label: '标题', type: 'text' },
......@@ -508,6 +511,7 @@ export function buildViewColumns(view, subject, { includeHidden = false } = {})
toArray(subject?.fields).forEach((field) => {
defaultColumns.push({
field: `metadata.${field.name}`,
displayConfig: viewDisplayConfig,
width: 180,
label: getFieldDisplayLabel(field, `metadata.${field.name}`),
fieldDef: field,
......
import {
formatFieldValue,
getFieldValue,
getSelectOptions,
isReferenceFieldType,
normalizeRecordLabel,
normalizeText,
toArray,
} from './subjectView';
export const TABLE_FIELD_DISPLAY_MODES = ['labels', 'count', 'auto'];
export const DEFAULT_TABLE_MAX_TAG_COUNT = 3;
export function isTableFieldDisplayConfigurable(column) {
const type = column?.fieldDef?.type;
return isReferenceFieldType(type) || ['select', 'subject', 'form'].includes(type);
}
export function getTableFieldDisplayConfig(column) {
const config = column?.displayConfig || {};
const configuredMode = column?.displayMode ?? config.displayMode;
const configuredThreshold = column?.maxTagCount ?? config.maxTagCount;
const threshold = Number(configuredThreshold);
const hasThreshold = ['number', 'string'].includes(typeof configuredThreshold) &&
normalizeText(configuredThreshold) !== '' && Number.isInteger(threshold) && threshold >= 0;
const hasMode = TABLE_FIELD_DISPLAY_MODES.includes(configuredMode);
return {
isConfigured: isTableFieldDisplayConfigurable(column) && (hasMode || hasThreshold),
displayMode: hasMode ? configuredMode : hasThreshold ? 'auto' : 'labels',
maxTagCount: hasThreshold ? threshold : DEFAULT_TABLE_MAX_TAG_COUNT,
titleField: normalizeText(column?.titleField ?? config.titleField),
};
}
function getDisplayItems(value, fieldDef) {
const items = fieldDef?.type === 'form' && value && !Array.isArray(value) && Array.isArray(value.data)
? value.data
: toArray(value);
return items.filter((item) =>
item !== null && item !== undefined && !(typeof item === 'string' && !item.trim()),
);
}
export function getTableFieldDisplay(value, column) {
const config = getTableFieldDisplayConfig(column);
const items = getDisplayItems(value, column?.fieldDef);
const type = column?.fieldDef?.type;
const options = type === 'select' ? getSelectOptions(column.fieldDef) : [];
const labels = items.map((item, index) => {
if (type === 'select') {
const optionValue = item && typeof item === 'object' ? item.value : item;
const option = options.find((candidate) => String(candidate.value) === String(optionValue));
return normalizeText(option?.label ?? optionValue);
}
if (['subject', 'form'].includes(type)) {
const titleValue = config.titleField
? getFieldValue(item, config.titleField) ?? getFieldValue(item?.metadata, config.titleField)
: null;
return formatFieldValue(titleValue) || normalizeRecordLabel(item) || `第 ${index + 1} 条`;
}
if (type === 'tag') return normalizeText(item?.label) || normalizeRecordLabel(item);
return normalizeRecordLabel(item);
}).filter(Boolean);
const showCount = config.displayMode === 'count' ||
(config.displayMode === 'auto' && items.length > 0 && items.length >= config.maxTagCount);
return {
...config,
count: items.length,
labels,
showCount,
text: showCount ? String(items.length) : labels.join('、'),
};
}
export function formatTableFieldValue(value, column) {
const display = getTableFieldDisplay(value, column);
return display.isConfigured ? display.text : formatFieldValue(value, column?.fieldDef);
}
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, resolve } from 'node:path';
import test from 'node:test';
import ts from 'typescript';
const require = createRequire(import.meta.url);
const sourceRoot = new URL('../src/', import.meta.url).pathname;
function loadModule(entry, dataService = {}) {
const modules = new Map();
const load = (path) => {
if (modules.has(path)) return modules.get(path);
const exports = {};
modules.set(path, exports);
const { outputText } = ts.transpileModule(readFileSync(path, 'utf8'), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true },
});
const localRequire = (specifier) => {
if (specifier === './DataService' || specifier === '@/services/DataService') return dataService;
if (!specifier.startsWith('.') && !specifier.startsWith('@/')) return require(specifier);
const dependencyPath = specifier.startsWith('@/')
? resolve(sourceRoot, specifier.slice(2))
: resolve(dirname(path), specifier);
return load(`${dependencyPath}.js`);
};
new Function('require', 'exports', outputText)(localRequire, exports);
return exports;
};
return load(resolve(sourceRoot, entry));
}
const { getTableFieldDisplay, getTableFieldDisplayConfig, formatTableFieldValue } = loadModule('utils/tableFieldDisplay.js');
const { buildViewColumns, formatFieldValue } = loadModule('utils/subjectView.js');
const column = (type, config = {}) => ({ field: 'metadata.values', fieldDef: { type }, ...config });
test('count mode counts selected values, including zero, and excludes empty entries', () => {
const config = column('select', { displayMode: 'count' });
assert.equal(formatTableFieldValue([0, '选中', null, undefined, '', ' '], config), '2');
for (const value of [null, undefined, '', []]) assert.equal(formatTableFieldValue(value, config), '0');
assert.equal(formatTableFieldValue(0, config), '1');
assert.equal(formatTableFieldValue({ _id: 'one', title: '关联标题' }, column('reference', { displayMode: 'count' })), '1');
});
test('labels show selected option labels and populated record titles', () => {
const config = column('select', { displayMode: 'labels' });
config.fieldDef.settings = { options: [{ value: 0, label: '零号' }, { value: 'a', label: '选项 A' }, { value: 'b', label: '未选项' }] };
assert.equal(formatTableFieldValue(['0', 'a'], config), '零号、选项 A');
assert.equal(formatTableFieldValue([{ _id: 'id-1', title: '记录 A' }, { _id: 'id-2', title: '记录 B' }], column('reference', { displayMode: 'labels' })), '记录 A、记录 B');
assert.equal(formatTableFieldValue([{ name: 'risk', label: '风险标签' }], column('tag', { displayMode: 'labels' })), '风险标签');
});
test('maxTagCount alone matches streams-admin at the exact threshold', () => {
const config = column('reference', { displayConfig: { maxTagCount: 2 } });
assert.equal(formatTableFieldValue([{ title: 'A' }], config), 'A');
assert.equal(formatTableFieldValue([{ title: 'A' }, { title: 'B' }], config), '2');
assert.equal(formatTableFieldValue([], config), '');
assert.equal(formatTableFieldValue(['A'], column('reference', { displayMode: 'auto', maxTagCount: 0 })), '1');
assert.equal(getTableFieldDisplayConfig(column('reference', { displayMode: 'auto' })).maxTagCount, 3);
});
test('embedded subjects and forms use configured child labels without changing data', () => {
const value = [{ responsible_department: '审计部' }, { metadata: { responsible_department: '风险管理部' } }];
const original = JSON.stringify(value);
const config = column('subject', { displayMode: 'labels', titleField: 'responsible_department' });
assert.equal(formatTableFieldValue(value, config), '审计部、风险管理部');
assert.equal(getTableFieldDisplay(value, { ...config, displayMode: 'count' }).count, 2);
assert.equal(formatTableFieldValue([{}, {}], config), '第 1 条、第 2 条');
assert.equal(formatTableFieldValue({ form: { title: '表单' }, data: value }, column('form', { displayMode: 'count' })), '2');
assert.equal(JSON.stringify(value), original);
});
test('application defaults, view settings, and column overrides keep independent modes', () => {
const subject = { fields: [{ name: 'values', type: 'select' }] };
const view = { settings: {
displayConfig: { maxTagCount: 2 },
columns: [{ field: 'metadata.values', displayMode: 'labels' }],
} };
const original = JSON.stringify(view);
const [config] = buildViewColumns(view, subject, { displayConfig: { displayMode: 'count', maxTagCount: 5 } });
assert.equal(getTableFieldDisplayConfig(config).maxTagCount, 2);
assert.equal(formatTableFieldValue(['A', 'B'], config), 'A、B');
const [nestedConfig] = buildViewColumns({ settings: {
displayConfig: { displayMode: 'count', maxTagCount: 5 },
columns: [{ field: 'metadata.values', displayConfig: { displayMode: 'auto', maxTagCount: 2 } }],
} }, subject);
assert.equal(formatTableFieldValue(['A', 'B'], nestedConfig), '2');
const fallback = buildViewColumns({ settings: {} }, subject, { displayConfig: { displayMode: 'count' } });
assert.equal(formatTableFieldValue(['A', 'B'], fallback.find((item) => item.field === 'metadata.values')), '2');
assert.equal(JSON.stringify(view), original);
});
test('missing or invalid config preserves current behavior and does not affect scalar fields', () => {
const value = [{ responsible_department: '审计部' }];
assert.equal(formatTableFieldValue(value, column('subject')), formatFieldValue(value, { type: 'subject' }));
assert.equal(formatTableFieldValue('A', column('select', { displayMode: 'invalid', maxTagCount: -1 })), 'A');
for (const type of ['text', 'number', 'date', 'boolean', 'user', 'file', 'object']) {
assert.equal(getTableFieldDisplayConfig(column(type, { displayMode: 'count' })).isConfigured, false);
}
for (const maxTagCount of ['invalid', -1, 1.5, null, '', ' ', false, true, []]) {
assert.equal(getTableFieldDisplayConfig(column('reference', { displayConfig: { maxTagCount } })).isConfigured, false);
}
});
test('application config is read from the same layout path as streams-admin', async () => {
const requests = [];
const service = loadModule('services/tableFieldDisplayService.js', {
loadApplication: async (name) => {
requests.push(name);
return { layouts: { default: { subjects: { risk_prevention_control_measures_table: {
gridRecordConfig: { displayConfig: { maxTagCount: 2 } },
} } } } };
},
});
assert.deepEqual(await service.loadApplicationTableDisplayConfig('risk_prevention_control_measures_table'), {
displayConfig: { maxTagCount: 2 }, displayConfigError: '',
});
assert.deepEqual(requests, ['integrity_risk_prevention_new']);
assert.deepEqual(await service.loadApplicationTableDisplayConfig('integrity_risk_control_archive'), {
displayConfig: {}, displayConfigError: '',
});
});
test('unavailable application config returns a warning and usable view fallback', async () => {
const service = loadModule('services/tableFieldDisplayService.js', {
loadApplication: async () => { throw new Error('无权限'); },
});
const result = await service.loadApplicationTableDisplayConfig('risk_prevention_control_measures_table');
assert.deepEqual(result.displayConfig, {});
assert.match(result.displayConfigError, /暂使用视图默认显示.*无权限/);
});
test('shared workbench propagates app config while preserving restrictions and hidden columns', () => {
const { buildConfiguredWorkbenchView } = loadModule('services/SubjectWorkbenchService.js');
const subject = { fields: [{ name: 'values', type: 'reference' }] };
const records = [
{ id: 'a', raw: { metadata: { values: [{ title: 'A' }], allowed: 'yes' } } },
{ id: 'b', raw: { metadata: { values: [{ title: 'B' }], allowed: 'no' } } },
];
const view = { settings: {
shortTitle: true,
columns: [{ field: 'title' }, { field: 'metadata.values' }, { field: 'metadata.allowed', hide: true }],
restrictions: [{ type: 'field', field: 'metadata.allowed', comparator: '$in', value: ['yes'] }],
} };
const baseline = buildConfiguredWorkbenchView(records, {}, view, subject);
const configured = buildConfiguredWorkbenchView(records, {}, view, subject, { displayMode: 'count' });
assert.deepEqual(configured.filteredRecords, baseline.filteredRecords);
assert.deepEqual(configured.filteredRecords.map((record) => record.id), ['a']);
assert.deepEqual(configured.columns.map((item) => item.field), ['metadata.values']);
assert.equal(formatTableFieldValue(records[0].raw.metadata.values, configured.columns[0]), '1');
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment