吸收数字员工管理端通知视图
This commit is contained in:
@@ -687,7 +687,7 @@ const PLAN_SCHEDULES: Record<string, { expression: string; nextRun: string; sche
|
||||
|
||||
const BUSINESS_VIEW_SOURCE = 'qimingclaw-runtime-projection';
|
||||
|
||||
type BusinessViewKind = 'plans' | 'tasks' | 'runs' | 'events' | 'artifacts' | 'approvals' | 'route-decisions' | 'audits';
|
||||
type BusinessViewKind = 'plans' | 'tasks' | 'runs' | 'events' | 'artifacts' | 'approvals' | 'route-decisions' | 'notifications' | 'audits';
|
||||
type BusinessViewRow = Record<string, unknown>;
|
||||
|
||||
export function isQimingclawDigitalApiEnabled(): boolean {
|
||||
@@ -771,7 +771,7 @@ export async function handleQimingclawDigitalApi<T>(
|
||||
const approvalId = decodeURIComponent(approvalActionMatch[1] || '');
|
||||
return await handleApprovalAction(approvalId, parseOptionalJsonBody(options.body), await readQimingclawSnapshot()) as T;
|
||||
}
|
||||
const businessViewMatch = pathname.match(/^\/api\/digital-employee\/(plans|tasks|runs|events|artifacts|approvals|route-decisions|audits)$/);
|
||||
const businessViewMatch = pathname.match(/^\/api\/digital-employee\/(plans|tasks|runs|events|artifacts|approvals|route-decisions|notifications|audits)$/);
|
||||
if (method === 'GET' && businessViewMatch) {
|
||||
return buildBusinessViewResponse(businessViewMatch[1] as BusinessViewKind, await readQimingclawSnapshot(), url.searchParams) as T;
|
||||
}
|
||||
@@ -2507,13 +2507,16 @@ function buildBusinessViewResponse(kind: BusinessViewKind, snapshot: QimingclawS
|
||||
artifacts: businessArtifactRows(snapshot),
|
||||
approvals: businessApprovalRows(snapshot),
|
||||
'route-decisions': routeDecisionRows(snapshot),
|
||||
notifications: notificationRows(snapshot, notificationAdapterState(snapshot)),
|
||||
audits: auditRows(snapshot),
|
||||
};
|
||||
const rows = kind === 'route-decisions'
|
||||
? filterRouteDecisionRows(rowsByKind[kind], searchParams)
|
||||
: kind === 'audits'
|
||||
? filterAuditRows(rowsByKind[kind], searchParams)
|
||||
: filterBusinessRows(rowsByKind[kind], searchParams);
|
||||
: kind === 'notifications'
|
||||
? filterNotificationRows(rowsByKind[kind], searchParams)
|
||||
: kind === 'audits'
|
||||
? filterAuditRows(rowsByKind[kind], searchParams)
|
||||
: filterBusinessRows(rowsByKind[kind], searchParams);
|
||||
return {
|
||||
...paginateBusinessRows(rows, searchParams),
|
||||
source: BUSINESS_VIEW_SOURCE,
|
||||
@@ -2555,6 +2558,50 @@ function filterRouteDecisionRows(rows: BusinessViewRow[], searchParams: URLSearc
|
||||
.filter((row) => !policyResult || stringValue(row.policy_result) === policyResult);
|
||||
}
|
||||
|
||||
function notificationRows(snapshot: QimingclawSnapshot | null, state: AdapterState): BusinessViewRow[] {
|
||||
const preferences = notificationPreferences(state);
|
||||
return buildNotificationCandidates(snapshot, state)
|
||||
.map((notification) => ({
|
||||
notification_id: notification.notification_id,
|
||||
kind: notification.kind,
|
||||
level: notification.level,
|
||||
status: notification.status,
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
plan_id: notification.plan_id,
|
||||
task_id: notification.task_id,
|
||||
correlation_id: notification.correlation_id,
|
||||
created_at: notification.created_at,
|
||||
updated_at: notification.replied_at || notification.dismissed_at || notification.read_at || notification.created_at,
|
||||
read_at: notification.read_at ?? null,
|
||||
dismissed_at: notification.dismissed_at ?? null,
|
||||
replied_at: notification.replied_at ?? null,
|
||||
muted_by_preferences: !notificationMatchesPreferences(notification, preferences),
|
||||
entity_type: 'notification',
|
||||
entity_id: notification.notification_id,
|
||||
source: BUSINESS_VIEW_SOURCE,
|
||||
}))
|
||||
.sort((left, right) => stringValue(right.created_at).localeCompare(stringValue(left.created_at)) || stringValue(right.notification_id).localeCompare(stringValue(left.notification_id)));
|
||||
}
|
||||
|
||||
function filterNotificationRows(rows: BusinessViewRow[], searchParams: URLSearchParams): BusinessViewRow[] {
|
||||
const id = stringValue(searchParams.get('id'));
|
||||
const kind = stringValue(searchParams.get('kind'));
|
||||
const level = stringValue(searchParams.get('level'));
|
||||
const muted = stringValue(searchParams.get('muted')).toLowerCase();
|
||||
return filterBusinessRows(rows, searchParams)
|
||||
.filter((row) => !id || stringValue(row.notification_id) === id || stringValue(row.correlation_id) === id || stringValue(row.entity_id) === id)
|
||||
.filter((row) => !kind || stringValue(row.kind) === kind)
|
||||
.filter((row) => !level || stringValue(row.level) === level)
|
||||
.filter((row) => {
|
||||
if (!muted) return true;
|
||||
const mutedValue = row.muted_by_preferences === true;
|
||||
if (['1', 'true', 'yes'].includes(muted)) return mutedValue;
|
||||
if (['0', 'false', 'no'].includes(muted)) return !mutedValue;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function routeDecisionFromRuntimePayload(payload: Record<string, unknown>, event: QimingclawEventRecord): RouteDecisionRecord {
|
||||
const decision = asRecord(payload.routeDecision ?? payload.route_decision ?? payload) ?? {};
|
||||
return {
|
||||
@@ -3185,6 +3232,7 @@ function businessRowEntityIds(row: BusinessViewRow): string[] {
|
||||
row.event_id,
|
||||
row.artifact_id,
|
||||
row.approval_id,
|
||||
row.notification_id,
|
||||
row.report_id,
|
||||
]);
|
||||
}
|
||||
@@ -3468,6 +3516,11 @@ function buildNotificationsResponse(status: string | null, snapshot: QimingclawS
|
||||
}
|
||||
|
||||
function buildNotifications(snapshot: QimingclawSnapshot | null, state: AdapterState): UserNotification[] {
|
||||
return buildNotificationCandidates(snapshot, state)
|
||||
.filter((notification) => notificationMatchesPreferences(notification, state.notificationPreferences));
|
||||
}
|
||||
|
||||
function buildNotificationCandidates(snapshot: QimingclawSnapshot | null, state: AdapterState): UserNotification[] {
|
||||
const notifications = dedupeNotifications([
|
||||
...approvalNotifications(snapshot),
|
||||
...approvalActionNotifications(snapshot),
|
||||
@@ -3482,7 +3535,6 @@ function buildNotifications(snapshot: QimingclawSnapshot | null, state: AdapterS
|
||||
const overlays = state.notificationStateById ?? {};
|
||||
return notifications
|
||||
.map((notification) => applyNotificationOverlay(notification, overlays[notification.notification_id]))
|
||||
.filter((notification) => notificationMatchesPreferences(notification, state.notificationPreferences))
|
||||
.sort((left, right) => right.created_at.localeCompare(left.created_at) || right.notification_id.localeCompare(left.notification_id));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user