feat(ui): hide alert details when idle

This commit is contained in:
Łukasz Mierzwa
2020-12-01 09:50:20 +00:00
committed by Łukasz Mierzwa
parent 3eb9d0a222
commit 2bc7b6fef8
11 changed files with 186 additions and 65 deletions

View File

@@ -6,6 +6,10 @@
- Docker images reported version as `dev` #2479.
### Changed
- Alert groups will be rendered fewer details when idle.
## v0.76
### Fixed

View File

@@ -150,4 +150,55 @@ describe("<GroupFooter />", () => {
const tree = MountedGroupFooter();
expect(toDiffableHtml(tree.html())).not.toMatch(/@receiver:/);
});
it("doesn't render silences when showSilences=false", () => {
for (let index = 0; index < group.alerts.length; index++) {
group.alerts[index].alertmanager[0].silencedBy = ["123456789"];
}
group.shared.silences = { default: ["123456789"] };
alertStore.data.silences = {
default: {
"123456789": MockSilence(),
},
};
alertStore.data.silences["default"]["123456789"].id = "123456789";
const tree = mount(
<GroupFooter
group={group}
alertmanagers={["default"]}
afterUpdate={MockAfterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
showSilences={false}
/>,
{
wrappingComponent: ThemeContext.Provider,
wrappingComponentProps: { value: MockThemeContext },
}
);
expect(
tree.find("div.components-grid-alertgrid-alertgroup-shared-silence")
).toHaveLength(0);
});
it("doesn't render annotations when showAnnotations=false", () => {
const tree = mount(
<GroupFooter
group={group}
alertmanagers={["default"]}
afterUpdate={MockAfterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
showAnnotations={false}
/>,
{
wrappingComponent: ThemeContext.Provider,
wrappingComponentProps: { value: MockThemeContext },
}
);
expect(tree.find("RenderLinkAnnotation")).toHaveLength(0);
expect(tree.find("RenderNonLinkAnnotation")).toHaveLength(0);
});
});

View File

@@ -16,21 +16,33 @@ const GroupFooter: FC<{
afterUpdate: () => void;
alertStore: AlertStore;
silenceFormStore: SilenceFormStore;
}> = ({ group, alertmanagers, afterUpdate, alertStore, silenceFormStore }) => {
showAnnotations?: boolean;
showSilences?: boolean;
}> = ({
group,
alertmanagers,
afterUpdate,
alertStore,
silenceFormStore,
showAnnotations = true,
showSilences = true,
}) => {
return (
<div className="card-footer components-grid-alertgrid-alertgroup-footer px-2 py-1">
<div className="mb-1">
{group.shared.annotations
.filter((a) => a.isLink === false)
.map((a) => (
<RenderNonLinkAnnotation
key={a.name}
name={a.name}
value={a.value}
visible={a.visible}
afterUpdate={afterUpdate}
/>
))}
{showAnnotations
? group.shared.annotations
.filter((a) => a.isLink === false)
.map((a) => (
<RenderNonLinkAnnotation
key={a.name}
name={a.name}
value={a.value}
visible={a.visible}
afterUpdate={afterUpdate}
/>
))
: null}
</div>
{Object.entries(group.shared.labels).map(([name, value]) => (
<FilteringLabel
@@ -55,12 +67,18 @@ const GroupFooter: FC<{
alertStore={alertStore}
/>
) : null}
{group.shared.annotations
.filter((a) => a.isLink === true)
.map((a) => (
<RenderLinkAnnotation key={a.name} name={a.name} value={a.value} />
))}
{Object.keys(group.shared.silences).length === 0 ? null : (
{showAnnotations
? group.shared.annotations
.filter((a) => a.isLink === true)
.map((a) => (
<RenderLinkAnnotation
key={a.name}
name={a.name}
value={a.value}
/>
))
: null}
{Object.keys(group.shared.silences).length === 0 ? null : showSilences ? (
<div className="components-grid-alertgrid-alertgroup-shared-silence rounded-0 border-0">
{Object.entries(group.shared.silences).map(([cluster, silences]) =>
silences.map((silenceID) => (
@@ -75,7 +93,7 @@ const GroupFooter: FC<{
))
)}
</div>
)}
) : null}
</div>
);
};

View File

@@ -205,6 +205,13 @@ describe("<AlertGroup />", () => {
expect(tree.find("ul.list-group")).toHaveLength(0);
});
it("renders reduced details when idle", () => {
MockAlerts(10);
alertStore.ui.setIsIdle(true);
const tree = MountedAlertGroup(jest.fn(), true, 10, MockThemeContext);
expect(tree.find("Alert")).toHaveLength(1);
});
it("is collapsed by default on desktop when defaultCollapseState=collapsed", () => {
// set window.innerWidth to 2k to mock a desktop window
ValidateCollapse(2048, "collapsed", true);

View File

@@ -6,6 +6,7 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
import { faEllipsisH } from "@fortawesome/free-solid-svg-icons/faEllipsisH";
import { APIAlertT, APIAlertGroupT, AlertStateT } from "Models/APITypes";
import { Settings } from "Stores/Settings";
@@ -201,41 +202,57 @@ const AlertGroup: FC<{
{isCollapsed ? null : (
<div className="card-body px-2 py-1 components-grid-alertgrid-card">
<ul className="list-group">
{group.alerts.slice(0, alertsToRender).map((alert) => (
<Alert
key={alert.id}
group={group}
alert={alert}
showAlertmanagers={
showAlertmanagers && !showAlertmanagersInFooter
}
showReceiver={
alertStore.data.receivers.length > 1 &&
group.alerts.length === 1
}
afterUpdate={afterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
setIsMenuOpen={setIsMenuOpen}
/>
))}
{group.alerts
.slice(0, alertStore.ui.isIdle ? 1 : alertsToRender)
.map((alert) => (
<Alert
key={alert.id}
group={group}
alert={alert}
showAlertmanagers={
showAlertmanagers && !showAlertmanagersInFooter
}
showReceiver={
alertStore.data.receivers.length > 1 &&
group.alerts.length === 1
}
afterUpdate={afterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
setIsMenuOpen={setIsMenuOpen}
/>
))}
{group.alerts.length > defaultRenderCount ? (
<li className="list-group-item border-0 p-0 text-center bg-transparent">
<LoadButton
icon={faMinus}
action={loadLess}
tooltip="Show fewer alerts in this group"
/>
<small className="text-muted mx-2">
{Math.min(alertsToRender, group.alerts.length)}
{" of "}
{group.alerts.length}
</small>
<LoadButton
icon={faPlus}
action={loadMore}
tooltip="Show more alerts in this group"
/>
<li
className="list-group-item border-0 p-0 text-center bg-transparent"
style={{
lineHeight: alertStore.ui.isIdle ? "1rem" : undefined,
}}
>
{alertStore.ui.isIdle ? (
<FontAwesomeIcon
icon={faEllipsisH}
className="text-muted"
/>
) : (
<React.Fragment>
<LoadButton
icon={faMinus}
action={loadLess}
tooltip="Show fewer alerts in this group"
/>
<small className="text-muted mx-2">
{Math.min(alertsToRender, group.alerts.length)}
{" of "}
{group.alerts.length}
</small>
<LoadButton
icon={faPlus}
action={loadMore}
tooltip="Show more alerts in this group"
/>
</React.Fragment>
)}
</li>
) : null}
</ul>
@@ -248,6 +265,8 @@ const AlertGroup: FC<{
afterUpdate={afterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
showAnnotations={!alertStore.ui.isIdle}
showSilences={!alertStore.ui.isIdle}
/>
) : null}
</div>

View File

@@ -25,7 +25,7 @@ const FilterBarConfiguration: FC<{
className="custom-control-label cursor-pointer mr-3"
htmlFor="configuration-autohide"
>
Hide filter bar when idle
Hide filter bar and alert details when idle
</label>
</span>
</div>

View File

@@ -13,7 +13,7 @@ exports[`<FilterBarConfiguration /> matches snapshot with default values 1`] = `
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-autohide\\"
>
Hide filter bar when idle
Hide filter bar and alert details when idle
</label>
</span>
</div>

View File

@@ -108,7 +108,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-autohide\\"
>
Hide filter bar when idle
Hide filter bar and alert details when idle
</label>
</span>
</div>

View File

@@ -127,7 +127,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
<label class=\\"custom-control-label cursor-pointer mr-3\\"
for=\\"configuration-autohide\\"
>
Hide filter bar when idle
Hide filter bar and alert details when idle
</label>
</span>
</div>

View File

@@ -27,7 +27,6 @@ const NavBar: FC<{
silenceFormStore: SilenceFormStore;
fixedTop?: boolean;
}> = ({ alertStore, settingsStore, silenceFormStore, fixedTop = true }) => {
const [isIdle, setIsIdle] = useState<boolean>(false);
const [containerClass, setContainerClass] = useState<string>("visible");
const context = React.useContext(ThemeContext);
@@ -44,12 +43,12 @@ const NavBar: FC<{
);
const onActive = useCallback(() => {
setIsIdle(false);
}, []);
alertStore.ui.setIsIdle(false);
}, [alertStore.ui]);
const onIdle = useCallback(() => {
setIsIdle(true);
}, []);
alertStore.ui.setIsIdle(true);
}, [alertStore.ui]);
const { pause, reset } = useIdleTimer({
timeout: IsMobile() ? MobileIdleTimeout : DesktopIdleTimeout,
@@ -60,7 +59,7 @@ const NavBar: FC<{
useEffect(() => {
let timer: number;
if (isIdle) {
if (alertStore.ui.isIdle) {
timer = window.setTimeout(
() => updateBodyPaddingTop(true),
context.animations.duration
@@ -69,7 +68,12 @@ const NavBar: FC<{
updateBodyPaddingTop(false);
}
return () => window.clearTimeout(timer);
}, [height, updateBodyPaddingTop, isIdle, context.animations.duration]);
}, [
height,
updateBodyPaddingTop,
alertStore.ui.isIdle,
context.animations.duration,
]);
useEffect(
() =>
@@ -89,7 +93,7 @@ const NavBar: FC<{
<div className={`container p-0 m-0 mw-100 ${containerClass}`}>
<CSSTransition
classNames="components-animation-navbar"
in={!isIdle}
in={!alertStore.ui.isIdle}
timeout={context.animations.duration}
onEntering={() => {}}
onExited={() => {}}

View File

@@ -192,12 +192,18 @@ interface AlertStoreStatusT {
stop: () => void;
}
interface AlertStoreUIT {
isIdle: boolean;
setIsIdle: (val: boolean) => void;
}
class AlertStore {
filters: AlertStoreFiltersT;
data: AlertStoreDataT;
info: AlertStoreInfoT;
settings: AlertStoreSettingsT;
status: AlertStoreStatusT;
ui: AlertStoreUIT;
constructor(initialFilters: null | string[]) {
this.filters = observable(
@@ -474,6 +480,18 @@ class AlertStore {
{ name: "Store status" }
);
this.ui = observable(
{
isIdle: false as boolean,
setIsIdle(val: boolean) {
this.isIdle = val;
},
},
{
setIsIdle: action.bound,
}
);
if (initialFilters !== null) this.filters.setFilters(initialFilters);
}