9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
213 lines
7.0 KiB
JavaScript
213 lines
7.0 KiB
JavaScript
/**
|
|
* ONES filters/peek 响应解析(tasks / my-tasks 共用)
|
|
*/
|
|
import { CliError } from '@jackwener/opencli/errors';
|
|
/** ONES task 里 field_values 常为 [{ field_uuid, value }, ...] */
|
|
function pickTitleFromFieldValuesArray(fv) {
|
|
if (!Array.isArray(fv))
|
|
return '';
|
|
for (const item of fv) {
|
|
if (!item || typeof item !== 'object')
|
|
continue;
|
|
const row = item;
|
|
const fu = String(row.field_uuid ?? '');
|
|
if (!fu.startsWith('field'))
|
|
continue;
|
|
const v = row.value;
|
|
if (typeof v === 'string' && v.trim())
|
|
return v.trim();
|
|
if (Array.isArray(v) && v.length && typeof v[0] === 'string' && v[0].trim())
|
|
return v[0].trim();
|
|
}
|
|
return '';
|
|
}
|
|
export function pickTaskTitle(e) {
|
|
for (const k of ['summary', 'name', 'title', 'subject']) {
|
|
const v = e[k];
|
|
if (typeof v === 'string' && v.trim())
|
|
return v.trim();
|
|
}
|
|
const fromArr = pickTitleFromFieldValuesArray(e.field_values);
|
|
if (fromArr)
|
|
return fromArr;
|
|
const fv = e.field_values;
|
|
if (fv && typeof fv === 'object' && !Array.isArray(fv)) {
|
|
const o = fv;
|
|
for (const k of ['field001', 'field002', 'field003']) {
|
|
const v = o[k];
|
|
if (typeof v === 'string' && v.trim())
|
|
return v.trim();
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
/** 表格里标题别撑爆终端 */
|
|
export function ellipsizeCell(s, max = 64) {
|
|
const t = s.trim();
|
|
if (t.length <= max)
|
|
return t;
|
|
return `${t.slice(0, max - 1)}…`;
|
|
}
|
|
/** 辅助列:长 uuid 缩略,完整值见 -f json */
|
|
export function briefUuid(id, head = 6, tail = 4) {
|
|
if (!id)
|
|
return '';
|
|
if (id.length <= head + tail + 1)
|
|
return id;
|
|
return `${id.slice(0, head)}…${id.slice(-tail)}`;
|
|
}
|
|
export function formatStamp(v) {
|
|
if (v == null || v === '')
|
|
return '';
|
|
const n = Number(v);
|
|
if (Number.isNaN(n))
|
|
return String(v);
|
|
const ms = n > 1e14 ? Math.floor(n / 1000) : n > 1e12 ? n : n * 1000;
|
|
try {
|
|
return new Date(ms).toISOString().replace('T', ' ').slice(0, 19);
|
|
}
|
|
catch {
|
|
return String(v);
|
|
}
|
|
}
|
|
export function flattenPeekGroups(parsed, limit) {
|
|
if (!Array.isArray(parsed.groups)) {
|
|
throw new CliError('FETCH_ERROR', 'Unexpected filters/peek response (missing groups)', 'Try -f json; check team UUID and API version.');
|
|
}
|
|
const groups = parsed.groups;
|
|
const rows = [];
|
|
for (const g of groups) {
|
|
const entries = Array.isArray(g.entries) ? g.entries : [];
|
|
for (const e of entries) {
|
|
rows.push(e);
|
|
if (rows.length >= limit)
|
|
break;
|
|
}
|
|
if (rows.length >= limit)
|
|
break;
|
|
}
|
|
return rows.slice(0, limit);
|
|
}
|
|
function fieldArrayFirstString(fv, fieldUuid) {
|
|
if (!Array.isArray(fv))
|
|
return '';
|
|
for (const item of fv) {
|
|
if (!item || typeof item !== 'object')
|
|
continue;
|
|
const row = item;
|
|
if (String(row.field_uuid ?? '') !== fieldUuid)
|
|
continue;
|
|
const v = row.value;
|
|
if (typeof v === 'string')
|
|
return v;
|
|
if (Array.isArray(v) && v[0] != null)
|
|
return String(v[0]);
|
|
}
|
|
return '';
|
|
}
|
|
function fvRecord(e) {
|
|
const fv = e.field_values;
|
|
return fv && typeof fv === 'object' && !Array.isArray(fv) ? fv : null;
|
|
}
|
|
/** 工作项状态 uuid(用于查 task_statuses 得中文名) */
|
|
export function getTaskStatusRawId(e) {
|
|
const fv = e.field_values;
|
|
const fvObj = fvRecord(e);
|
|
if (typeof e.status_uuid === 'string')
|
|
return e.status_uuid;
|
|
return fieldArrayFirstString(fv, 'field016') || (fvObj ? String(fvObj.field016 ?? '') : '');
|
|
}
|
|
/** 项目 uuid */
|
|
export function getTaskProjectRawId(e) {
|
|
const fv = e.field_values;
|
|
const fvObj = fvRecord(e);
|
|
if (typeof e.project_uuid === 'string')
|
|
return e.project_uuid;
|
|
return fieldArrayFirstString(fv, 'field006') || (fvObj ? String(fvObj.field006 ?? '') : '');
|
|
}
|
|
/**
|
|
* Project API 里 assess/total/remaining_manhour 多为**定点整数**(与 Web 上「小时」不一致);
|
|
* 常见换算:raw / 1e5 ≈ 小时。若你方实例不同,可设 `ONES_MANHOUR_SCALE`(默认 100000)。
|
|
*/
|
|
export function onesManhourScale() {
|
|
const raw = Number(process.env.ONES_MANHOUR_SCALE?.trim());
|
|
if (Number.isFinite(raw) && raw > 0)
|
|
return raw;
|
|
return 1e5;
|
|
}
|
|
/** 界面/h 小数 → API 内 manhour 整数(与列表「工时」列同一刻度) */
|
|
export function hoursToOnesManhourRaw(hours) {
|
|
if (!Number.isFinite(hours) || hours <= 0)
|
|
return 0;
|
|
return Math.max(1, Math.round(hours * onesManhourScale()));
|
|
}
|
|
function formatHoursShort(hours) {
|
|
if (!Number.isFinite(hours))
|
|
return '';
|
|
const snapped = Math.round(hours * 1e6) / 1e6;
|
|
const near = Math.round(snapped);
|
|
if (Math.abs(snapped - near) < 1e-5)
|
|
return `${near}h`;
|
|
const t = Math.round(snapped * 10) / 10;
|
|
return Number.isInteger(t) ? `${t}h` : `${t.toFixed(1)}h`;
|
|
}
|
|
function formatManhourSegment(label, v) {
|
|
if (v == null || v === '')
|
|
return null;
|
|
const n = Number(v);
|
|
if (!Number.isFinite(n))
|
|
return null;
|
|
const hours = n / onesManhourScale();
|
|
return `${label}${formatHoursShort(hours)}`;
|
|
}
|
|
export function formatTaskManhourSummary(e) {
|
|
const parts = [];
|
|
const a = formatManhourSegment('估', e.assess_manhour);
|
|
const t = formatManhourSegment('登', e.total_manhour);
|
|
const r = formatManhourSegment('余', e.remaining_manhour);
|
|
if (a)
|
|
parts.push(a);
|
|
if (t)
|
|
parts.push(t);
|
|
if (r)
|
|
parts.push(r);
|
|
return parts.length ? parts.join(' ') : '—';
|
|
}
|
|
export function mapTaskEntry(e, labels) {
|
|
const statusId = getTaskStatusRawId(e);
|
|
const projectId = getTaskProjectRawId(e);
|
|
const fullUuid = String(e.uuid ?? '');
|
|
const title = ellipsizeCell(pickTaskTitle(e));
|
|
const briefIfLong = (s) => (s.length > 14 ? briefUuid(s) : s);
|
|
const statusLabel = labels?.statusByUuid?.get(statusId) ?? briefIfLong(statusId);
|
|
const projectLabel = labels?.projectByUuid?.get(projectId) ?? briefIfLong(projectId);
|
|
return {
|
|
title,
|
|
status: ellipsizeCell(statusLabel, 20),
|
|
project: ellipsizeCell(projectLabel, 40),
|
|
uuid: fullUuid,
|
|
updated: formatStamp(e.server_update_stamp),
|
|
工时: ellipsizeCell(formatTaskManhourSummary(e), 36),
|
|
};
|
|
}
|
|
export function defaultPeekBody(query) {
|
|
return {
|
|
with_boards: false,
|
|
boards: null,
|
|
query,
|
|
group_by: '',
|
|
sort: [{ create_time: { order: 'desc' } }],
|
|
include_subtasks: false,
|
|
include_status_uuid: true,
|
|
include_issue_type: false,
|
|
include_project_uuid: true,
|
|
is_show_derive: false,
|
|
};
|
|
}
|
|
export function parsePeekLimit(value, fallback) {
|
|
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
return fallback;
|
|
return Math.max(1, Math.min(500, parsed));
|
|
}
|