mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
feat(ui): hide alert details when idle
This commit is contained in:
committed by
Łukasz Mierzwa
parent
3eb9d0a222
commit
2bc7b6fef8
@@ -6,6 +6,10 @@
|
||||
|
||||
- Docker images reported version as `dev` #2479.
|
||||
|
||||
### Changed
|
||||
|
||||
- Alert groups will be rendered fewer details when idle.
|
||||
|
||||
## v0.76
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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={() => {}}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user