feat(analysis): 网页汇总表自定义列
- analyze 支持 custom_columns:白名单表达式树由服务端 Decimal 求值, 自定义列进入汇总、分组行与明细(缺失保持不可用,不转零) - 新增「选择网页自定义列」弹窗:从定义库(含共享)多选最多 10 列, 应用后进入主分析表格与热力图;公式工作簿可一键导出网页已选列 - 请求去重纳入自定义列;Playwright 覆盖选择后显示与数值断言
This commit is contained in:
+35
-20
@@ -180,38 +180,53 @@ test("桌面:真实导入、异常、下钻、证据、设置、Copilot、导
|
||||
await page.route("**/formula-design", async (route) => {
|
||||
designCalls++;
|
||||
await route.fulfill({
|
||||
json: designCalls === 1
|
||||
? { complete: false, questions: ["缺失值和零分母如何处理?"], column: null }
|
||||
: {
|
||||
complete: true,
|
||||
questions: [],
|
||||
column: {
|
||||
name: "收入成本差",
|
||||
meaning: "实际收入减成本",
|
||||
scope: "所选分组与期间",
|
||||
missing: "结果不可用(#N/A);零分母不可用",
|
||||
expression: {
|
||||
operator: "subtract",
|
||||
left: { metric: "revenue" },
|
||||
right: { metric: "cost" },
|
||||
json:
|
||||
designCalls === 1
|
||||
? {
|
||||
complete: false,
|
||||
questions: ["缺失值和零分母如何处理?"],
|
||||
column: null,
|
||||
}
|
||||
: {
|
||||
complete: true,
|
||||
questions: [],
|
||||
column: {
|
||||
name: "收入成本差",
|
||||
meaning: "实际收入减成本",
|
||||
scope: "所选分组与期间",
|
||||
missing: "结果不可用(#N/A);零分母不可用",
|
||||
expression: {
|
||||
operator: "subtract",
|
||||
left: { metric: "revenue" },
|
||||
right: { metric: "cost" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
await page.getByRole("button", { name: "生成公式工作簿" }).click();
|
||||
const formulaDialog = page.getByRole("dialog", { name: "生成公式工作簿" });
|
||||
await formulaDialog.getByRole("checkbox").check();
|
||||
await formulaDialog.getByLabel("列计算描述").fill("新增收入成本差,缺失或零分母显示不可用");
|
||||
await formulaDialog
|
||||
.getByLabel("列计算描述")
|
||||
.fill("新增收入成本差,缺失或零分母显示不可用");
|
||||
await formulaDialog.getByRole("button", { name: "发送描述" }).click();
|
||||
await expect(formulaDialog).toContainText("缺失值和零分母如何处理?");
|
||||
await formulaDialog.getByLabel("列计算描述").fill("缺失项和零分母都按不可用处理");
|
||||
await formulaDialog
|
||||
.getByLabel("列计算描述")
|
||||
.fill("缺失项和零分母都按不可用处理");
|
||||
await formulaDialog.getByRole("button", { name: "发送描述" }).click();
|
||||
await formulaDialog.getByRole("button", { name: "确认并生成公式工作簿" }).click();
|
||||
await expect(formulaDialog.getByRole("status")).toContainText("生成完成", { timeout: 30000 });
|
||||
await formulaDialog
|
||||
.getByRole("button", { name: "确认并生成公式工作簿" })
|
||||
.click();
|
||||
await expect(formulaDialog.getByRole("status")).toContainText("生成完成", {
|
||||
timeout: 30000,
|
||||
});
|
||||
const formulaDownload = page.waitForEvent("download");
|
||||
await formulaDialog.getByRole("button", { name: "下载公式工作簿" }).click();
|
||||
expect((await formulaDownload).suggestedFilename()).toBe("traceiq-formula-analysis.xlsx");
|
||||
expect((await formulaDownload).suggestedFilename()).toBe(
|
||||
"traceiq-formula-analysis.xlsx",
|
||||
);
|
||||
await formulaDialog.getByLabel("关闭").click();
|
||||
});
|
||||
test("手机:登录、总览、设置、Copilot 布局无横向溢出", async ({ page }) => {
|
||||
|
||||
@@ -85,6 +85,36 @@ def test_metrics_and_analysis_preserve_roi_refunds_and_distinct_customers(worksp
|
||||
assert unavailable["self_revenue"] == 0
|
||||
|
||||
|
||||
def test_analysis_computes_saved_custom_columns_in_summary_and_rows(workspace):
|
||||
ds, member, *_ = workspace
|
||||
column = {
|
||||
"name": "收入成本差",
|
||||
"meaning": "收入减成本",
|
||||
"scope": "按分组计算",
|
||||
"missing": "结果不可用(#N/A);零分母不可用",
|
||||
"expression": {
|
||||
"operator": "subtract",
|
||||
"left": {"metric": "revenue"},
|
||||
"right": {"metric": "cost"},
|
||||
},
|
||||
}
|
||||
result = analyze(
|
||||
ds,
|
||||
member,
|
||||
{
|
||||
"start": "2026-08",
|
||||
"end": "2026-08",
|
||||
"comparison": "yoy",
|
||||
"group": "department",
|
||||
"custom_columns": [column],
|
||||
},
|
||||
default_preferences(ds),
|
||||
)
|
||||
assert result["summary"]["custom_0"]["current"] == Decimal("45")
|
||||
assert result["summary"]["custom_0"]["baseline"] == Decimal("90")
|
||||
assert result["rows"][0]["values"]["custom_0"]["current"] == Decimal("60")
|
||||
|
||||
|
||||
def test_dataset_and_evidence_are_organization_and_department_scoped(workspace):
|
||||
ds, _, _, _, other_client = workspace
|
||||
url = f"/api/datasets/{ds.pk}"
|
||||
|
||||
Reference in New Issue
Block a user