diff --git a/Common/Typings/elkjs.d.ts b/Common/Typings/elkjs.d.ts index a2b2f85e7d..aa719eceee 100644 --- a/Common/Typings/elkjs.d.ts +++ b/Common/Typings/elkjs.d.ts @@ -25,6 +25,6 @@ declare module "elkjs/lib/elk.bundled.js" { } export default class ELK { - layout(graph: ElkNode): Promise; + public layout(graph: ElkNode): Promise; } } diff --git a/Common/UI/Components/Graphs/ServiceDependencyGraph.tsx b/Common/UI/Components/Graphs/ServiceDependencyGraph.tsx index d8bce3e7b4..e99f1b9def 100644 --- a/Common/UI/Components/Graphs/ServiceDependencyGraph.tsx +++ b/Common/UI/Components/Graphs/ServiceDependencyGraph.tsx @@ -36,9 +36,13 @@ export interface ServiceDependencyGraphProps { const ServiceDependencyGraph: FunctionComponent = ( props: ServiceDependencyGraphProps, ): ReactElement => { - const computeLuminance = (r: number, g: number, b: number): number => { - const transform = (v: number): number => { - const c = v / 255; + const computeLuminance: (r: number, g: number, b: number) => number = ( + r: number, + g: number, + b: number, + ): number => { + const transform: (v: number) => number = (v: number): number => { + const c: number = v / 255; return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); }; const R: number = transform(r); @@ -47,7 +51,7 @@ const ServiceDependencyGraph: FunctionComponent = ( return 0.2126 * R + 0.7152 * G + 0.0722 * B; }; - const getContrastText = (bg?: string): string => { + const getContrastText: (bg?: string) => string = (bg?: string): string => { if (!bg) { return "#111827"; // gray-900 } @@ -73,7 +77,7 @@ const ServiceDependencyGraph: FunctionComponent = ( if (hex.length === 3) { hex = hex .split("") - .map((c) => { + .map((c: string): string => { return c + c; }) .join(""); @@ -91,21 +95,25 @@ const ServiceDependencyGraph: FunctionComponent = ( const [rfNodes, setRfNodes] = useState([]); const [rfEdges, setRfEdges] = useState([]); - useEffect(() => { - const elk = new ELK(); + useEffect((): void => { + const elk: ELK = new ELK(); // fixed node dimensions for layout (px) - const NODE_WIDTH = 220; - const NODE_HEIGHT = 56; + const NODE_WIDTH: number = 220; + const NODE_HEIGHT: number = 56; - const sortedServices = [...props.services].sort((a, b) => { - return a.name.localeCompare(b.name) || a.id.localeCompare(b.id); - }); - const sortedDeps = [...props.dependencies].sort((a, b) => { - if (a.fromServiceId === b.fromServiceId) { - return a.toServiceId.localeCompare(b.toServiceId); - } - return a.fromServiceId.localeCompare(b.fromServiceId); - }); + const sortedServices: Array = [...props.services].sort( + (a: ServiceNodeData, b: ServiceNodeData): number => { + return a.name.localeCompare(b.name) || a.id.localeCompare(b.id); + }, + ); + const sortedDeps: Array = [...props.dependencies].sort( + (a: ServiceEdgeData, b: ServiceEdgeData): number => { + if (a.fromServiceId === b.fromServiceId) { + return a.toServiceId.localeCompare(b.toServiceId); + } + return a.fromServiceId.localeCompare(b.fromServiceId); + }, + ); const elkGraph: ElkNode = { id: "root", @@ -116,7 +124,7 @@ const ServiceDependencyGraph: FunctionComponent = ( "elk.spacing.nodeNode": "60", "elk.edgeRouting": "POLYLINE", }, - children: sortedServices.map((svc: ServiceNodeData) => { + children: sortedServices.map((svc: ServiceNodeData): ElkNode => { return { id: svc.id, width: NODE_WIDTH, @@ -132,35 +140,39 @@ const ServiceDependencyGraph: FunctionComponent = ( }), }; - const layout = async (): Promise => { + const layout: () => Promise = async (): Promise => { try { const res: any = await elk.layout(elkGraph as any); - const placedNodes: Node[] = (res.children || []).map((child: any) => { - const svc: ServiceNodeData | undefined = sortedServices.find((s) => { - return s.id === child.id; - }); - const background: string = svc?.color || "#ffffff"; - const textColor: string = getContrastText(background); - return { - id: child.id || "", - data: { label: svc?.name || "" }, - position: { x: child.x || 0, y: child.y || 0 }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - style: { - borderRadius: 8, - padding: 8, - border: "1px solid rgba(0,0,0,0.08)", - background, - color: textColor, - boxShadow: "0 1px 2px rgba(16,24,40,.05)", - width: NODE_WIDTH, - height: NODE_HEIGHT, - }, - } as Node; - }); + const placedNodes: Node[] = (res.children || []).map( + (child: any): Node => { + const svc: ServiceNodeData | undefined = sortedServices.find( + (s: ServiceNodeData): boolean => { + return s.id === child.id; + }, + ); + const background: string = svc?.color || "#ffffff"; + const textColor: string = getContrastText(background); + return { + id: child.id || "", + data: { label: svc?.name || "" }, + position: { x: child.x || 0, y: child.y || 0 }, + sourcePosition: Position.Right, + targetPosition: Position.Left, + style: { + borderRadius: 8, + padding: 8, + border: "1px solid rgba(0,0,0,0.08)", + background, + color: textColor, + boxShadow: "0 1px 2px rgba(16,24,40,.05)", + width: NODE_WIDTH, + height: NODE_HEIGHT, + }, + } as Node; + }, + ); - const stroke = "#94a3b8"; // slate-400 + const stroke: string = "#94a3b8"; // slate-400 const placedEdges: Edge[] = sortedDeps.map( (dep: ServiceEdgeData): Edge => { return { @@ -177,39 +189,41 @@ const ServiceDependencyGraph: FunctionComponent = ( setRfNodes(placedNodes); setRfEdges(placedEdges); - } catch (e) { + } catch { // Fallback: deterministic grid by name - const sorted = sortedServices; - const COLS = 4; - const GAP_X = 260; - const GAP_Y = 120; - const nodes: Node[] = sorted.map((svc: ServiceNodeData, i: number) => { - const col = i % COLS; - const row = Math.floor(i / COLS); - const x = col * GAP_X; - const y = row * GAP_Y; - const background: string = svc.color || "#ffffff"; - const textColor: string = getContrastText(background); - return { - id: svc.id, - data: { label: svc.name }, - position: { x, y }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - style: { - borderRadius: 8, - padding: 8, - border: "1px solid rgba(0,0,0,0.08)", - background, - color: textColor, - boxShadow: "0 1px 2px rgba(16,24,40,.05)", - width: NODE_WIDTH, - height: NODE_HEIGHT, - }, - }; - }); - const stroke = "#94a3b8"; - const edges: Edge[] = sortedDeps.map((dep: ServiceEdgeData) => { + const sorted: Array = sortedServices; + const COLS: number = 4; + const GAP_X: number = 260; + const GAP_Y: number = 120; + const nodes: Node[] = sorted.map( + (svc: ServiceNodeData, i: number): Node => { + const col: number = i % COLS; + const row: number = Math.floor(i / COLS); + const x: number = col * GAP_X; + const y: number = row * GAP_Y; + const background: string = svc.color || "#ffffff"; + const textColor: string = getContrastText(background); + return { + id: svc.id, + data: { label: svc.name }, + position: { x, y }, + sourcePosition: Position.Right, + targetPosition: Position.Left, + style: { + borderRadius: 8, + padding: 8, + border: "1px solid rgba(0,0,0,0.08)", + background, + color: textColor, + boxShadow: "0 1px 2px rgba(16,24,40,.05)", + width: NODE_WIDTH, + height: NODE_HEIGHT, + }, + }; + }, + ); + const stroke: string = "#94a3b8"; + const edges: Edge[] = sortedDeps.map((dep: ServiceEdgeData): Edge => { return { id: `e-${dep.fromServiceId}-${dep.toServiceId}`, source: dep.fromServiceId, @@ -226,7 +240,6 @@ const ServiceDependencyGraph: FunctionComponent = ( }; layout(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.services, props.dependencies]); return ( @@ -250,7 +263,7 @@ const ServiceDependencyGraph: FunctionComponent = ( connectOnClick={false} > { + nodeColor={(n: Node): string => { return ( (n.style as any)?.background || (n.data as any)?.color || diff --git a/Dashboard/src/Components/NotificationLogs/CallLogsTable.tsx b/Dashboard/src/Components/NotificationLogs/CallLogsTable.tsx index 95115dd55a..c0b651bcf9 100644 --- a/Dashboard/src/Components/NotificationLogs/CallLogsTable.tsx +++ b/Dashboard/src/Components/NotificationLogs/CallLogsTable.tsx @@ -112,7 +112,9 @@ const CallLogsTable: FunctionComponent = ( props.noItemsMessage || (props.singularName ? `No call logs for this ${props.singularName}.` - : "No call logs.") + : props.pluralName + ? `No ${props.pluralName.toLowerCase()} call logs.` + : "No call logs.") } showRefreshButton={true} columns={props.columns || defaultColumns} diff --git a/Dashboard/src/Components/NotificationLogs/EmailLogsTable.tsx b/Dashboard/src/Components/NotificationLogs/EmailLogsTable.tsx index 76db3beb3f..e24c6043b9 100644 --- a/Dashboard/src/Components/NotificationLogs/EmailLogsTable.tsx +++ b/Dashboard/src/Components/NotificationLogs/EmailLogsTable.tsx @@ -113,7 +113,9 @@ const EmailLogsTable: FunctionComponent = ( props.noItemsMessage || (props.singularName ? `No email logs for this ${props.singularName}.` - : "No email logs.") + : props.pluralName + ? `No ${props.pluralName.toLowerCase()} email logs.` + : "No email logs.") } showRefreshButton={true} columns={props.columns || defaultColumns} diff --git a/Dashboard/src/Components/NotificationLogs/PushLogsTable.tsx b/Dashboard/src/Components/NotificationLogs/PushLogsTable.tsx index bf62da4713..9dc4011aa9 100644 --- a/Dashboard/src/Components/NotificationLogs/PushLogsTable.tsx +++ b/Dashboard/src/Components/NotificationLogs/PushLogsTable.tsx @@ -119,7 +119,9 @@ const PushLogsTable: FunctionComponent = ( props.noItemsMessage || (props.singularName ? `No Push logs for this ${props.singularName}.` - : "No Push logs.") + : props.pluralName + ? `No ${props.pluralName.toLowerCase()} Push logs.` + : "No Push logs.") } showRefreshButton={true} columns={props.columns || defaultColumns} diff --git a/Dashboard/src/Components/NotificationLogs/SmsLogsTable.tsx b/Dashboard/src/Components/NotificationLogs/SmsLogsTable.tsx index da6297b14f..5f16725ace 100644 --- a/Dashboard/src/Components/NotificationLogs/SmsLogsTable.tsx +++ b/Dashboard/src/Components/NotificationLogs/SmsLogsTable.tsx @@ -112,7 +112,9 @@ const SmsLogsTable: FunctionComponent = ( props.noItemsMessage || (props.singularName ? `No SMS logs for this ${props.singularName}.` - : "No SMS logs.") + : props.pluralName + ? `No ${props.pluralName.toLowerCase()} SMS logs.` + : "No SMS logs.") } showRefreshButton={true} columns={props.columns || defaultColumns} diff --git a/Dashboard/src/Components/NotificationLogs/WorkspaceLogsTable.tsx b/Dashboard/src/Components/NotificationLogs/WorkspaceLogsTable.tsx index 213711c9fb..75d2ca11f2 100644 --- a/Dashboard/src/Components/NotificationLogs/WorkspaceLogsTable.tsx +++ b/Dashboard/src/Components/NotificationLogs/WorkspaceLogsTable.tsx @@ -138,7 +138,9 @@ const WorkspaceLogsTable: FunctionComponent = ( props.noItemsMessage || (props.singularName ? `No Workspace logs for this ${props.singularName}.` - : "No Workspace logs.") + : props.pluralName + ? `No ${props.pluralName.toLowerCase()} Workspace logs.` + : "No Workspace logs.") } showRefreshButton={true} columns={props.columns || defaultColumns}