d4Metrics.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const DEFAULT_API_BASE_URL = "http://localhost:8080";
  2. export const D4_METRICS_PATH = "/api/d4/metrics";
  3. export type D4EventName =
  4. | "D4_COLLAB_CONFLICT_CREATED"
  5. | "D4_COLLAB_CONFLICT_RESOLVED"
  6. | "D4_COLLAB_EFFECTIVE"
  7. | "D4_COMM_MESSAGE_RESPONSE"
  8. | "D4_COMM_MESSAGE_SENT"
  9. | "D4_TASK_ASSIGNED"
  10. | "D4_TASK_COMPLETED";
  11. export interface D4MetricsQuery {
  12. from?: string;
  13. to?: string;
  14. projectId?: number | string;
  15. taskType?: string;
  16. userId?: number | string;
  17. slaMs?: number | string;
  18. }
  19. export interface D4MetricsResponse {
  20. from: string;
  21. to: string;
  22. filter: {
  23. projectId: number | null;
  24. taskType: string | null;
  25. userId: number | null;
  26. slaMs: number | null;
  27. };
  28. taskCompletion: {
  29. assignedCount: number;
  30. completedCount: number;
  31. completionRate: number | null;
  32. onTimeCompletedCount: number | null;
  33. onTimeCompletionRate: number | null;
  34. };
  35. communication: {
  36. sentMessageCount: number;
  37. respondedMessageCount: number;
  38. averageResponseMs: number | null;
  39. };
  40. collaboration: {
  41. effectiveCollaborationCount: number;
  42. };
  43. conflict: {
  44. createdCount: number;
  45. resolvedCount: number;
  46. resolutionRate: number | null;
  47. };
  48. eventCounts: Record<D4EventName | string, number>;
  49. scan: {
  50. indexPattern: string;
  51. scannedEvents: number;
  52. maxEventsToScan: number;
  53. truncated: boolean;
  54. };
  55. }
  56. export const D4_EVENT_LABELS: Record<D4EventName, string> = {
  57. D4_COLLAB_CONFLICT_CREATED: "协作冲突创建",
  58. D4_COLLAB_CONFLICT_RESOLVED: "协作冲突解决",
  59. D4_COLLAB_EFFECTIVE: "有效协作",
  60. D4_COMM_MESSAGE_RESPONSE: "沟通响应",
  61. D4_COMM_MESSAGE_SENT: "沟通发送",
  62. D4_TASK_ASSIGNED: "任务分配",
  63. D4_TASK_COMPLETED: "任务完成",
  64. };
  65. export function getApiBaseUrl() {
  66. return (process.env.NEXT_PUBLIC_SEEC_ANALYSIS_API_BASE_URL || DEFAULT_API_BASE_URL).replace(/\/$/, "");
  67. }
  68. export function toD4UserId(userId: string | null | undefined) {
  69. if (!userId) {
  70. return undefined;
  71. }
  72. const parsed = Number(userId);
  73. return Number.isSafeInteger(parsed) ? parsed : undefined;
  74. }
  75. export function buildD4MetricsUrl(query: D4MetricsQuery = {}) {
  76. const url = new URL(`${getApiBaseUrl()}${D4_METRICS_PATH}`);
  77. Object.entries(query).forEach(([key, value]) => {
  78. if (value !== undefined && value !== null && `${value}`.trim() !== "") {
  79. url.searchParams.set(key, `${value}`);
  80. }
  81. });
  82. return url.toString();
  83. }
  84. export async function fetchD4Metrics(query: D4MetricsQuery = {}, signal?: AbortSignal) {
  85. const response = await fetch(buildD4MetricsUrl(query), {
  86. method: "GET",
  87. headers: {
  88. Accept: "application/json",
  89. },
  90. signal,
  91. });
  92. if (!response.ok) {
  93. let message = `D4 指标请求失败 (${response.status})`;
  94. try {
  95. const body = (await response.json()) as { message?: string; error?: string };
  96. message = body.message || body.error || message;
  97. } catch {
  98. // Keep the status based message when the response body is not JSON.
  99. }
  100. throw new Error(message);
  101. }
  102. return (await response.json()) as D4MetricsResponse;
  103. }