Skip to content

Commit

Permalink
Merge branch 'main' into feat/tristate-toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
johnflank authored Sep 2, 2021
2 parents b7a26d6 + 134db81 commit 6a46559
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 98 deletions.
143 changes: 143 additions & 0 deletions src/components/Canary/aggregate.js
Original file line number Diff line number Diff line change
@@ -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))
}
}
10 changes: 5 additions & 5 deletions src/components/Canary/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,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 = filterChecksByLabels(checks, labelFilters); // filters checks by its 'include/exclude' filters
checks = orderBy(checks, CanarySorter);
Expand All @@ -179,11 +184,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);
Expand Down
3 changes: 3 additions & 0 deletions src/components/Canary/renderers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Canary/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
Expand Down
106 changes: 14 additions & 92 deletions src/components/Canary/table.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState, useEffect } from "react";
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,
Expand Down Expand Up @@ -84,87 +84,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 (
Expand All @@ -181,25 +107,21 @@ function TableGroupRow({ title, items, onClick, showIcon, ...rest }) {
<td className="px-6 py-3 w-full">
<div className="flex items-center select-none">
<FaChevronRight
className={`${
expanded ? "transform rotate-90" : ""
} mr-4 duration-75`}
className={`${expanded ? "transform rotate-90" : ""
} mr-4 duration-75`}
/>
{showIcon && (
<Icon
name={aggregatedIcon || aggregatedType}
className="inline mr-3"
size="xl"
/>
)}
{title}
<Title check={aggregated} />
</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) => (
Expand Down

0 comments on commit 6a46559

Please sign in to comment.