Skip to content

Commit

Permalink
feat: Canary Tabs. Check filtering based on tab selection.
Browse files Browse the repository at this point in the history
  • Loading branch information
johnflank committed Oct 7, 2021
1 parent 7efbad3 commit bd27f65
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 8 deletions.
33 changes: 26 additions & 7 deletions src/components/Canary/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CanaryDescription } from "./description";
import { StatCard } from "../StatCard";
import { Modal } from "../Modal";
import { Title } from "./renderers";
import { CanaryTabs, filterChecksByTabSelection } from "./tabs";

export class Canary extends React.Component {
constructor(props) {
Expand All @@ -26,6 +27,7 @@ export class Canary extends React.Component {
this.modal = React.createRef();
this.fetch = this.fetch.bind(this);
this.select = this.select.bind(this);
this.handleTabSelect = this.handleTabSelect.bind(this);
this.setChecks = this.setChecks.bind(this);
this.history = history;
this.unhistory = () => {};
Expand All @@ -43,6 +45,7 @@ export class Canary extends React.Component {
exclude: [],
include: []
},
selectedTab: null,
checks: props.checks ? props.checks : []
};
}
Expand Down Expand Up @@ -75,6 +78,13 @@ export class Canary extends React.Component {
this.timer = null;
}

handleTabSelect(tabSelection) {
this.setState({
// eslint-disable-next-line react/no-unused-state
selectedTab: tabSelection
});
}

setChecks(checks) {
if (checks.checks) {
// FIXME unify pipeline for demo and remote
Expand Down Expand Up @@ -115,9 +125,10 @@ export class Canary extends React.Component {
labelFilters,
urlState,
checks: stateChecks,
labels
labels,
selectedTab
} = state;
const { hidePassing, layout } = urlState;
const { hidePassing, layout, tabBy } = urlState;

// first filter for pass/fail
let checks = filterChecks(stateChecks, hidePassing, []);
Expand All @@ -130,6 +141,10 @@ export class Canary extends React.Component {
// filter the subset down
checks = Object.values(filterChecksByLabels(checks, labelFilters)); // filters checks by its 'include/exclude' filters
checks = orderBy(checks, CanarySorter);

const tabChecks = [...checks]; // list of checks used to generate tabs

checks = filterChecksByTabSelection(tabBy, selectedTab, checks);
const passed = reduce(
checks,
(sum, c) => (isHealthy(c) ? sum + 1 : sum),
Expand All @@ -145,14 +160,18 @@ export class Canary extends React.Component {
return (
<div className="w-full flex flex-col-reverse lg:flex-row">
{/* middle panel */}
<div className="w-full">
<div className="w-full m-6">
<CanaryTabs
className={layout === "table" ? "relative z-20 -mb-3" : ""}
checks={tabChecks}
tabBy={tabBy}
setTabSelection={this.handleTabSelect}
/>
{layout === "card" && (
<div className="m-6">
<CanaryCards checks={checks} onClick={this.select} />
</div>
<CanaryCards checks={checks} onClick={this.select} />
)}
{layout === "table" && (
<div className="m-6 mt-0 relative">
<div className="mt-0 z-10 relative">
<div
className="sticky top-0 h-6 bg-white z-10"
style={{ marginLeft: "-1px", width: "calc(100% + 2px)" }}
Expand Down
139 changes: 139 additions & 0 deletions src/components/Canary/tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useEffect, useState } from "react";

const defaultTabs = {
all: {
key: "all",
value: "all",
label: "All"
}
};

// filter checks according to the 'tabBy' and selected tab
export function filterChecksByTabSelection(tabBy, selectedTab, checks) {
let filteredChecks = checks;
if (selectedTab !== "all") {
if (tabBy === "namespace") {
// if filter by namespace, show only selected namespace
filteredChecks = checks.filter((o) => o.namespace === selectedTab);
} else {
// filtered by non-boolean labels
filteredChecks = checks.filter(
(o) =>
Object.prototype.hasOwnProperty.call(o.labels, tabBy) &&
o.labels[tabBy] === selectedTab
);
}
}
return filteredChecks;
}

export function generateTabs(tabBy, checks) {
let tabs = defaultTabs;
if (tabBy === "namespace") {
const namespaces = [...new Set(checks.map((o) => o.namespace))];
tabs = {
...tabs,
...namespaces.reduce(
(acc, namespace) => ({
...acc,
[namespace]: {
key: namespace,
value: namespace,
label: namespace
}
}),
{}
)
};
} else {
// generate tabs by non boolean label
const label = tabBy;
const matchingLabelValues = checks.reduce((acc, o) => {
const labelKeys = Object.keys(o.labels);
if (labelKeys.length > 0 && labelKeys.includes(label)) {
acc = { ...acc, [o.labels[label]]: null };
}
return acc;
}, {});
tabs = {
...tabs,
...Object.keys(matchingLabelValues).reduce(
(acc, labelValue) => ({
...acc,
[labelValue]: {
key: labelValue,
value: labelValue,
label: labelValue
}
}),
{}
)
};
}
return tabs;
}

export function CanaryTabs({ checks, tabBy, setTabSelection, ...rest }) {
const [tabs, setTabs] = useState(generateTabs(tabBy, checks));
const [selectedTab, setSelectedTab] = useState(Object.values(tabs)[0].value);

// changes in checks and tabBy(dropdown) will generate new tabs
useEffect(() => {
setTabs(generateTabs(tabBy, checks));
}, [checks, tabBy]);

// checking if previously selected tab still exists in newly-generated tabs
useEffect(() => {
// if it doesnt exist, reset to the first tab value
if (!Object.prototype.hasOwnProperty.call(tabs, selectedTab)) {
setSelectedTab(Object.values(tabs)[0].value);
}
}, [tabs, selectedTab]);

// changes in selected tab will trigger setTabSelection callback
useEffect(() => {
setTabSelection(selectedTab);
}, [selectedTab, setTabSelection]);

return (
<Tabs
{...rest}
tabs={Object.values(tabs)}
value={selectedTab}
onClick={(tab) => setSelectedTab(tab.value)}
/>
);
}

function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}

export function Tabs({ tabs, value, onClick, className, ...rest }) {
return (
<div
className={`flex flex-wrap ${className} border-b border-gray-300`}
aria-label="Tabs"
{...rest}
>
{tabs.map((tab) => (
<button
type="button"
key={tab.value}
onClick={() => onClick(tab)}
style={{
marginBottom: "-1px",
borderColor: tab.value === value ? "" : "transparent",
borderBottomColor: tab.value === value ? "white" : ""
}}
className={classNames(
tab.value === value ? "text-gray-900" : "text-gray-500",
"px-4 py-2 mt-2 font-medium text-sm rounded-t-md border border-gray-300 hover:text-gray-900"
)}
>
{tab.label}
</button>
))}
</div>
);
}
2 changes: 1 addition & 1 deletion src/components/Modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class Modal extends React.Component {
<Dialog
as="div"
auto-reopen="true"
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
onClose={this.hide}
>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
Expand Down

0 comments on commit bd27f65

Please sign in to comment.