PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / mcp / components / KpiRow.js

KpiRow.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/mcp/components/KpiRow.js

148 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React from "react";
2 import { __, sprintf } from "@wordpress/i18n";
3 import { useQuery } from "@tanstack/react-query";
4
5 import { getApps, getConnection, getHealth } from "../utils/api";
6 import { formatDate, relativeTime, toMillis } from "../utils/format";
7 import { APPS_KEY } from "./ConnectedApps";
8 import { CONNECTION_KEY } from "./ConnectionConfig";
9 import { HEALTH_KEY } from "./ConnectionHealth";
10 import { IconClock, IconCode, IconGrid, IconPlug } from "./Icons";
11
12 /**
13 * The four KPI tiles (ADR-062).
14 *
15 * Every value is read from a payload the page already fetches — the same three
16 * React Query keys the sections below use, so this row costs no extra request:
17 * an identical key and query function is served from the shared cache.
18 *
19 * **No sparklines and no delta chips.** The approved mock-up draws both, and
20 * they are decoration over data that does not exist: the server keeps one
21 * `last_used` stamp per credential and no history whatsoever, so a trend line
22 * here could only be invented.
23 *
24 * @since 4.9.0
25 */
26
27 /**
28 * One tile. The accent arrives as custom properties so the stylesheet decides
29 * what happens to it in dark mode, the way the client cards do (ADR-054).
30 */
31 const Tile = ({ label, Icon, accent, accentDark, value, valueSm, note }) => (
32 <div className="bda-kpi bd-mcp-kpi">
33 <div className="bda-kpi-top">
34 <span className="bda-kpi-label">{label}</span>
35 <span
36 className="bda-kpi-ico bd-mcp-kpi__ico"
37 style={{
38 "--bd-accent": accent,
39 "--bd-accent-dark": accentDark || accent,
40 "--bd-accent-tint": `${accent}1a`,
41 }}
42 >
43 <Icon size={14} />
44 </span>
45 </div>
46 <div className="bda-kpi-row">
47 <span className={`bda-kpi-value${valueSm ? " bd-mcp-kpi__value--sm" : ""}`}>{value}</span>
48 {note && <span className="bd-mcp-kpi__note">{note}</span>}
49 </div>
50 </div>
51 );
52
53 /**
54 * The analytics skeleton for a tile that is still waiting on its query
55 * (`KpiSkeleton` in Pro's `analytics/components/common/ui.js`), so the row
56 * arrives at its final height in one step instead of growing under the reader.
57 */
58 const TileSkeleton = () => (
59 <div className="bda-kpi bd-mcp-kpi">
60 <div className="bda-skel" style={{ width: "50%", height: 10, marginBottom: 12 }} />
61 <div className="bda-kpi-row">
62 <div className="bda-skel" style={{ width: 120, height: 26 }} />
63 </div>
64 </div>
65 );
66
67 export default function KpiRow({ status }) {
68 const connection = useQuery({ queryKey: [CONNECTION_KEY], queryFn: getConnection });
69 const health = useQuery({ queryKey: [HEALTH_KEY], queryFn: getHealth });
70 const apps = useQuery({ queryKey: [APPS_KEY], queryFn: getApps });
71
72 const connectedAt = connection.data?.connected_at || null;
73 const toolCount = health.data?.abilities?.betterdocs;
74 const appRows = Array.isArray(apps.data?.oauth_apps) ? apps.data.oauth_apps : [];
75
76 // The newest stamp anything holds: the connection token's own last use, and
77 // each OAuth app's. There is no other record of activity on this server.
78 // Compared as milliseconds, but carried forward in the shape it arrived in —
79 // `relativeTime` does its own conversion, and handing it a value already in
80 // milliseconds would multiply it by a thousand a second time.
81 const lastActivity = [connection.data?.last_used, ...appRows.map((app) => app.last_used)]
82 .map((value) => ({ value, ms: toMillis(value) }))
83 .filter((stamp) => stamp.ms !== null)
84 .reduce((newest, stamp) => (newest === null || stamp.ms > newest.ms ? stamp : newest), null);
85
86 return (
87 <div className="bda-kpi-grid bd-mcp-kpis">
88 {connection.isLoading ? (
89 <TileSkeleton />
90 ) : (
91 <Tile
92 label={__("Server status", "betterdocs")}
93 Icon={IconPlug}
94 accent="#00b884"
95 value={status.label}
96 valueSm
97 note={
98 connectedAt
99 ? sprintf(
100 /* translators: %s: the date this site's connection was established. */
101 __("since %s", "betterdocs"),
102 formatDate(connectedAt),
103 )
104 : null
105 }
106 />
107 )}
108
109 {health.isLoading || typeof toolCount !== "number" ? (
110 <TileSkeleton />
111 ) : (
112 <Tile
113 label={__("Registered tools", "betterdocs")}
114 Icon={IconCode}
115 accent="#3b82f6"
116 accentDark="#60a5fa"
117 value={String(toolCount)}
118 />
119 )}
120
121 {apps.isLoading ? (
122 <TileSkeleton />
123 ) : (
124 <Tile
125 label={__("Connected apps", "betterdocs")}
126 Icon={IconGrid}
127 accent="#8b5cf6"
128 accentDark="#a78bfa"
129 value={String(appRows.length)}
130 />
131 )}
132
133 {connection.isLoading || apps.isLoading ? (
134 <TileSkeleton />
135 ) : (
136 <Tile
137 label={__("Last activity", "betterdocs")}
138 Icon={IconClock}
139 accent="#f59e0b"
140 accentDark="#fbbf24"
141 value={relativeTime(lastActivity ? lastActivity.value : null)}
142 valueSm
143 />
144 )}
145 </div>
146 );
147 }
148