From a6c45a0a4aa2cea698d19b64c1e06c975c9f22e0 Mon Sep 17 00:00:00 2001 From: Saul Date: Mon, 30 Aug 2021 11:09:49 +0200 Subject: [PATCH 1/4] fix: fixes update on tablerowgroup --- src/components/Canary/table.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Canary/table.js b/src/components/Canary/table.js index f620c966c..841711e91 100644 --- a/src/components/Canary/table.js +++ b/src/components/Canary/table.js @@ -1,6 +1,6 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { FaChevronRight } from "react-icons/fa"; -import { Title, Uptime, Latency, Percentage, Duration } from "./renderers"; +import { Title, Uptime, Latency } from "./renderers"; import { StatusList } from "./status"; import { Icon } from "../Icon"; @@ -157,7 +157,7 @@ function TableGroupRow({ title, items, onClick, showIcon, ...rest }) { const [aggregatedType, setAggregatedType] = useState(null); const [aggregatedStatuses, setAggregatedStatuses] = useState(null); - useState(() => { + useEffect(() => { const iconsList = items.map((item) => item.icon); const typesList = items.map((item) => item.type); const statusLists = items.map((item) => item.checkStatuses); From 1f176c323c0fbf112075bc27487e4170c8cc907c Mon Sep 17 00:00:00 2001 From: Moshe Immerman Date: Tue, 31 Aug 2021 23:10:37 +0200 Subject: [PATCH 2/4] fix: aggregation --- src/components/Canary/aggregate.js | 143 +++++++++++++++++++++++++++++ src/components/Canary/table.js | 105 +++------------------ 2 files changed, 157 insertions(+), 91 deletions(-) create mode 100644 src/components/Canary/aggregate.js diff --git a/src/components/Canary/aggregate.js b/src/components/Canary/aggregate.js new file mode 100644 index 000000000..a64895740 --- /dev/null +++ b/src/components/Canary/aggregate.js @@ -0,0 +1,143 @@ + +import { reduce } from "lodash"; +import { getPercentage } from "./utils"; + + +// if all check icons are similar in a given iconList, use that icon. +// if there are different icons, use a icon that indicate 'mixing'. +function aggregateIcon(iconList) { + iconList = iconList.filter((icon) => icon !== ""); + if (iconList.length === 0) { + return null; + } + let icon = iconList[0]; + for (let i = 0; i < iconList.length; i += 1) { + if (iconList[i] !== icon) { + icon = "multiple"; + break; + } + } + return icon; +} + +// if all check types are similar in a given typeList, use that type. +// if there are different types, use a type that indicate 'mixing'. +function aggregateType(typeList) { + typeList = typeList.filter((type) => type !== ""); + if (typeList.length === 0) { + return null; + } + let type = typeList[0]; + for (let i = 0; i < typeList.length; i += 1) { + if (typeList[i] !== type) { + type = "multiple"; + break; + } + } + return type; +} + +// calculate the average health of all checks with valid statuses +// returns a simplified list of statuses that indicates the overall health. +function aggregateStatuses(statusLists) { + const allStatuses = statusLists + .filter((item) => item !== null && item.length > 0) + .flatMap((item) => item); + + + const count = reduce( + statusLists, + (sum, i) => Math.max(sum, i == null ? 0 : i.length), + 0 + ); + + const aggregated = []; + for (let i = 0; i < count; i++) { + const allPass = reduce( + statusLists, + (sum, list) => sum && list != null && list.length >= i && list[i] && list[i].status, + true + ); + const allFail = reduce( + statusLists, + (sum, list) => sum && list != null && list.length >= i && list[i] && !list[i].status, + true + ); + if (allPass) { + aggregated.push({ + id: aggregated.length, + status: true + }); + } else if (allFail) { + aggregated.push({ + id: aggregated.length, + status: false + }); + } else { + aggregated.push({ + id: aggregated.length, + mixed: true + }); + } + } + return aggregated; +} + +// The uptime for a group, is defined as the minimum uptime with the group +function minUptime(items) { + return reduce( + items, + (old, item) => { + console.log( + old, + item, + getPercentage(old.uptime), + getPercentage(item.uptime) + ); + if (getPercentage(old.uptime) > getPercentage(item.uptime)) { + return item; + } + return old; + }, + { passed: 0, failed: 0 } + ); +} + +// The uptime for a group, is defined as the minimum uptime with the group +function sumUptime(items) { + return reduce( + items, + (old, item) => { + old.passed += item.uptime.passed; + old.failed += item.uptime.failed; + return old; + }, + { passed: 0, failed: 0 } + ); +} + +function avgLatency(items) { + const total = reduce(items, (sum, i) => sum + i.latency.rolling1h, 0); + return total / items.length; +} + +export function aggregate(title, items) { + if (items == null) { + return { + } + } + let _title = title + if (items.length > 1) { + _title += ` (${items.length})` + } + return { + description: _title, + icon: aggregateIcon(items.map((item) => item.icon)), + latency: { + rolling1h: avgLatency(items) + }, + uptime: sumUptime(items), + checkStatuses: aggregateStatuses(items.map((item) => item.checkStatuses)), + type: aggregateType(items.map((item) => item.type)) + } +} diff --git a/src/components/Canary/table.js b/src/components/Canary/table.js index 841711e91..314058832 100644 --- a/src/components/Canary/table.js +++ b/src/components/Canary/table.js @@ -2,7 +2,8 @@ import React, { useEffect, useState } from "react"; import { FaChevronRight } from "react-icons/fa"; import { Title, Uptime, Latency } from "./renderers"; import { StatusList } from "./status"; -import { Icon } from "../Icon"; +import { aggregate } from "./aggregate" + export function CanaryTable({ className, @@ -84,87 +85,13 @@ export function CanaryTable({ ); } -// if all check icons are similar in a given iconList, use that icon. -// if there are different icons, use a icon that indicate 'mixing'. -function aggregateIcon(iconList) { - iconList = iconList.filter((icon) => icon !== ""); - if (iconList.length === 0) { - return null; - } - let icon = iconList[0]; - for (let i = 0; i < iconList.length; i += 1) { - if (iconList[i] !== icon) { - icon = "multiple"; - break; - } - } - return icon; -} - -// if all check types are similar in a given typeList, use that type. -// if there are different types, use a type that indicate 'mixing'. -function aggregateType(typeList) { - typeList = typeList.filter((type) => type !== ""); - if (typeList.length === 0) { - return null; - } - let type = typeList[0]; - for (let i = 0; i < typeList.length; i += 1) { - if (typeList[i] !== type) { - type = "multiple"; - break; - } - } - return type; -} - -// calculate the average health of all checks with valid statuses -// returns a simplified list of statuses that indicates the overall health. -function aggregateStatuses(statusLists) { - const allStatuses = statusLists - .filter((item) => item !== null && item.length > 0) - .flatMap((item) => item); - let n = 0; - allStatuses.forEach((item) => { - if (item.status) { - n += 1; - } - }); - const scorePercentage = n / allStatuses.length; - const scoreSimple = Math.floor(scorePercentage * 5); - - const aggregated = []; - for (let i = 0; i < scoreSimple; i += 1) { - aggregated.push({ - id: aggregated.length, - invalid: false, - status: true - }); - } - while (aggregated.length < 5) { - aggregated.push({ - id: aggregated.length, - invalid: false, - status: false - }); - } - return aggregated; -} function TableGroupRow({ title, items, onClick, showIcon, ...rest }) { const [expanded, setExpanded] = useState(false); - const [aggregatedIcon, setAggregatedIcon] = useState(null); - const [aggregatedType, setAggregatedType] = useState(null); - const [aggregatedStatuses, setAggregatedStatuses] = useState(null); + const [aggregated, setAggregated] = useState(null); useEffect(() => { - const iconsList = items.map((item) => item.icon); - const typesList = items.map((item) => item.type); - const statusLists = items.map((item) => item.checkStatuses); - - setAggregatedIcon(aggregateIcon(iconsList)); - setAggregatedType(aggregateType(typesList)); - setAggregatedStatuses(aggregateStatuses(statusLists)); + setAggregated(aggregate(title, items)) }, [items, items.length]); return ( @@ -181,25 +108,21 @@ function TableGroupRow({ title, items, onClick, showIcon, ...rest }) {
- {showIcon && ( - - )} - {title} + </div> </td> <td className="px-6 py-2 whitespace-nowrap"> - <StatusList checkStatuses={aggregatedStatuses} /> + <StatusList check={aggregated} /> + </td> + <td className="px-6 py-2 whitespace-nowrap"> + <Uptime check={aggregated} /> + </td> + <td className="px-6 py-2 whitespace-nowrap"> + <Latency check={aggregated} /> </td> - <td /> - <td /> </tr> {expanded && items.map((item) => ( From dc6911519e66b4ea200e5868b4ee11c1201acd90 Mon Sep 17 00:00:00 2001 From: Moshe Immerman <moshe@flanksource.com> Date: Tue, 31 Aug 2021 23:11:16 +0200 Subject: [PATCH 3/4] fix: nil checks --- src/components/Canary/index.js | 10 +++++----- src/components/Canary/renderers.js | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/Canary/index.js b/src/components/Canary/index.js index 3593217a6..72dbed3cc 100644 --- a/src/components/Canary/index.js +++ b/src/components/Canary/index.js @@ -154,6 +154,11 @@ export class Canary extends React.Component { let checks = filterChecks(stateChecks, hidePassing, []); // get labels for the new subset const labels = getLabels(checks); + const passedAll = reduce( + stateChecks, + (sum, c) => (isHealthy(c) ? sum + 1 : sum), + 0 + ); // filter the subset down checks = filterChecks(checks, this.stateHidePassing, selectedLabels); checks = orderBy(checks, CanarySorter); @@ -162,11 +167,6 @@ export class Canary extends React.Component { (sum, c) => (isHealthy(c) ? sum + 1 : sum), 0 ); - const passedAll = reduce( - checks, - (sum, c) => (isHealthy(c) ? sum + 1 : sum), - 0 - ); // generate available grouping selections for dropdown menu const groupSelections = getGroupSelections(checks); diff --git a/src/components/Canary/renderers.js b/src/components/Canary/renderers.js index c8e3ed7d3..87c7337c2 100644 --- a/src/components/Canary/renderers.js +++ b/src/components/Canary/renderers.js @@ -100,6 +100,9 @@ export function Percentage({ val, upper, lower }) { } export function Title({ check, showIcon = true }) { + if (check == null) { + return <span className="bg-red-400" > null</span> + } return ( <> {showIcon && ( From 134db819b4f77fadb9eedb1716106d8e02706aa7 Mon Sep 17 00:00:00 2001 From: Moshe Immerman <moshe@flanksource.com> Date: Tue, 31 Aug 2021 23:11:29 +0200 Subject: [PATCH 4/4] fix: unique key for status --- src/components/Canary/status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Canary/status.js b/src/components/Canary/status.js index a97df3322..07fa5f37c 100644 --- a/src/components/Canary/status.js +++ b/src/components/Canary/status.js @@ -6,7 +6,7 @@ export function StatusList({ check, checkStatuses }) { <> {check.checkStatuses.map((status) => ( <CanaryStatus - key={`${status.time}-${status.duration}-${status.message}`} + key={status.id} status={status} className="mr-0.5" />