mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
chore(ui): drop react-reveal animations
This library is not maintained anymore and it causes small glitches when rendering a lot of alerts - scrolling groups into view doesn't always trigger animation and they stay hidden.
This commit is contained in:
committed by
Łukasz Mierzwa
parent
46d49f2404
commit
2f74561809
@@ -18,6 +18,10 @@ import {
|
||||
ReactSelectStyles,
|
||||
} from "Components/Theme/ReactSelect";
|
||||
|
||||
import { config } from "react-transition-group";
|
||||
|
||||
config.disabled = true;
|
||||
|
||||
const { percyAddon, serializeStories } = createPercyAddon();
|
||||
setAddon(percyAddon);
|
||||
|
||||
@@ -36,7 +40,6 @@ addDecorator((story) => {
|
||||
value={{
|
||||
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Light),
|
||||
animations: {
|
||||
in: true,
|
||||
duration: 0,
|
||||
},
|
||||
}}
|
||||
@@ -58,7 +61,6 @@ addDecorator((story) => {
|
||||
value={{
|
||||
reactSelectStyles: ReactSelectStyles(ReactSelectColors.Dark),
|
||||
animations: {
|
||||
in: true,
|
||||
duration: 0,
|
||||
},
|
||||
}}
|
||||
|
||||
Generated
+18
-8
@@ -12422,6 +12422,11 @@
|
||||
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
|
||||
"dev": true
|
||||
},
|
||||
"intersection-observer": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.11.0.tgz",
|
||||
"integrity": "sha512-KZArj2QVnmdud9zTpKf279m2bbGfG+4/kn16UU0NL3pTVl52ZHiJ9IRNSsnn6jaHrL9EGLFM5eWjTx2fz/+zoQ=="
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@@ -18966,6 +18971,14 @@
|
||||
"prop-types": "^15.5.8"
|
||||
}
|
||||
},
|
||||
"react-intersection-observer": {
|
||||
"version": "8.26.2",
|
||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.26.2.tgz",
|
||||
"integrity": "sha512-GmSjLNK+oV7kS+BHfrJSaA4wF61ELA33gizKHmN+tk59UT6/aW8kkqvlrFGPwxGoaIzLKS2evfG5fgkw5MIIsg==",
|
||||
"requires": {
|
||||
"tiny-invariant": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -19061,14 +19074,6 @@
|
||||
"resize-observer-polyfill": "^1.5.1"
|
||||
}
|
||||
},
|
||||
"react-reveal": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-reveal/-/react-reveal-1.2.2.tgz",
|
||||
"integrity": "sha512-JCv3fAoU6Z+Lcd8U48bwzm4pMZ79qsedSXYwpwt6lJNtj/v5nKJYZZbw3yhaQPPgYePo3Y0NOCoYOq/jcsisuw==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"react-scripts": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.1.tgz",
|
||||
@@ -21682,6 +21687,11 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"tiny-invariant": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
|
||||
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
|
||||
},
|
||||
"tlds": {
|
||||
"version": "1.207.0",
|
||||
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.207.0.tgz",
|
||||
|
||||
+2
-1
@@ -24,6 +24,7 @@
|
||||
"date-fns": "2.14.0",
|
||||
"favico.js": "0.3.10",
|
||||
"fontfaceobserver": "2.1.0",
|
||||
"intersection-observer": "0.11.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.throttle": "4.1.1",
|
||||
@@ -44,13 +45,13 @@
|
||||
"react-hotkeys-hook": "2.1.4",
|
||||
"react-idle-timer": "4.2.12",
|
||||
"react-input-range": "1.3.0",
|
||||
"react-intersection-observer": "8.26.2",
|
||||
"react-js-pagination": "3.0.3",
|
||||
"react-json-pretty": "2.2.0",
|
||||
"react-linkify": "0.2.2",
|
||||
"react-media-hook": "0.4.7",
|
||||
"react-popper": "2.2.3",
|
||||
"react-resize-detector": "5.0.6",
|
||||
"react-reveal": "1.2.2",
|
||||
"react-scripts": "3.4.1",
|
||||
"react-select": "3.1.0",
|
||||
"react-transition-group": "4.4.1",
|
||||
|
||||
+1
-2
@@ -136,8 +136,7 @@ const App: FunctionComponent<AppProps> = ({ defaultFilters, uiDefaults }) => {
|
||||
? ReactSelectStyles(ReactSelectColors.Dark)
|
||||
: ReactSelectStyles(ReactSelectColors.Light),
|
||||
animations: {
|
||||
enabled: true,
|
||||
duration: 1000,
|
||||
duration: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
exports[`<CenteredMessage /> matches snapshot 1`] = `
|
||||
"
|
||||
<h1 class=\\"display-1 text-placeholder screen-center\\">
|
||||
<div class=\\"react-reveal\\">
|
||||
<div>
|
||||
Foo
|
||||
</div>
|
||||
<div>
|
||||
Foo
|
||||
</div>
|
||||
</h1>
|
||||
"
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import React from "react";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const CenteredMessage = ({ children, className }) => {
|
||||
const theme = React.useContext(ThemeContext);
|
||||
const context = React.useContext(ThemeContext);
|
||||
return (
|
||||
<h1
|
||||
className={`${
|
||||
className ? className : "display-1 text-placeholder"
|
||||
} screen-center`}
|
||||
<CSSTransition
|
||||
in={true}
|
||||
appear={true}
|
||||
classNames="components-animation-fade"
|
||||
timeout={context.animations.duration}
|
||||
>
|
||||
<Fade in={theme.animations.in} duration={theme.animations.duration}>
|
||||
<h1
|
||||
className={`${
|
||||
className ? className : "display-1 text-placeholder"
|
||||
} screen-center`}
|
||||
>
|
||||
{children}
|
||||
</Fade>
|
||||
</h1>
|
||||
</h1>
|
||||
</CSSTransition>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ exports[`<Alert /> matches snapshot when inhibited 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span class=\\"Linkify\\">
|
||||
<span style=\\"opacity: 1;\\">
|
||||
<span>
|
||||
some long text
|
||||
</span>
|
||||
</span>
|
||||
@@ -175,7 +175,7 @@ exports[`<Alert /> matches snapshot with showAlertmanagers=false showReceiver=fa
|
||||
</span>
|
||||
</span>
|
||||
<span class=\\"Linkify\\">
|
||||
<span style=\\"opacity: 1;\\">
|
||||
<span>
|
||||
some long text
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
|
||||
|
||||
import Linkify from "react-linkify";
|
||||
|
||||
import Flash from "react-reveal/Flash";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt";
|
||||
@@ -11,6 +11,7 @@ import { faSearchPlus } from "@fortawesome/free-solid-svg-icons/faSearchPlus";
|
||||
import { faSearchMinus } from "@fortawesome/free-solid-svg-icons/faSearchMinus";
|
||||
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
|
||||
const RenderNonLinkAnnotation = memo(
|
||||
({ name, value, visible, afterUpdate }) => {
|
||||
@@ -26,40 +27,43 @@ const RenderNonLinkAnnotation = memo(
|
||||
}
|
||||
});
|
||||
|
||||
const { ref, props } = useFlashTransition(value);
|
||||
|
||||
const className =
|
||||
"mb-1 p-1 bg-light d-inline-block rounded components-grid-annotation text-break mw-100";
|
||||
|
||||
if (!isVisible) {
|
||||
return (
|
||||
<TooltipWrapper title="Click to show annotation value">
|
||||
<div
|
||||
className={`${className} cursor-pointer`}
|
||||
onClick={() => setIsVisible(!isVisible)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSearchPlus} className="mr-1" />
|
||||
{name}
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipWrapper title="Click the icon to hide annotation value">
|
||||
<div key={name} className={className}>
|
||||
<span onClick={() => setIsVisible(false)} className="cursor-pointer">
|
||||
<FontAwesomeIcon icon={faSearchMinus} className="mr-1" />
|
||||
<span className="text-muted">{name}: </span>
|
||||
</span>
|
||||
<Linkify
|
||||
properties={{
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
}}
|
||||
>
|
||||
<Flash spy={value}>
|
||||
<span>{value}</span>
|
||||
</Flash>
|
||||
</Linkify>
|
||||
<TooltipWrapper title="Toggle annotation value">
|
||||
<div
|
||||
className={`${className}${isVisible ? "" : " cursor-pointer"}`}
|
||||
onClick={isVisible ? undefined : () => setIsVisible(!isVisible)}
|
||||
>
|
||||
{isVisible ? (
|
||||
<React.Fragment>
|
||||
<span
|
||||
onClick={() => setIsVisible(false)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<FontAwesomeIcon icon={faSearchMinus} className="mr-1" />
|
||||
<span className="text-muted">{name}: </span>
|
||||
</span>
|
||||
<Linkify
|
||||
properties={{
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
}}
|
||||
>
|
||||
<CSSTransition {...props}>
|
||||
<span ref={ref}>{value}</span>
|
||||
</CSSTransition>
|
||||
</Linkify>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<FontAwesomeIcon icon={faSearchPlus} className="mr-1" />
|
||||
{name}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
|
||||
+3
-5
@@ -28,7 +28,7 @@ exports[`<GroupFooter /> matches snapshot 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span class=\\"Linkify\\">
|
||||
<span style=\\"opacity: 1;\\">
|
||||
<span>
|
||||
This is summary
|
||||
</span>
|
||||
</span>
|
||||
@@ -157,7 +157,7 @@ exports[`<GroupFooter /> mathes snapshot when silence is rendered 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span class=\\"Linkify\\">
|
||||
<span style=\\"opacity: 1;\\">
|
||||
<span>
|
||||
This is summary
|
||||
</span>
|
||||
</span>
|
||||
@@ -255,9 +255,7 @@ exports[`<GroupFooter /> mathes snapshot when silence is rendered 1`] = `
|
||||
link
|
||||
</a>
|
||||
<div class=\\"components-grid-alertgrid-alertgroup-shared-silence rounded-0 border-0\\">
|
||||
<div class=\\"react-reveal card my-1 components-managed-silence\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<div class=\\"card my-1 components-managed-silence\\">
|
||||
<div class=\\"card-header rounded-0 border-bottom-0 px-3\\">
|
||||
<div class=\\"d-flex flex-row\\">
|
||||
<div class=\\"flex-shrink-0 flex-grow-0\\">
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import React, { useEffect, useCallback, useRef, useState } from "react";
|
||||
import React, { useEffect, useCallback, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
|
||||
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
|
||||
@@ -15,7 +13,6 @@ import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { BackgroundClassMap } from "Common/Colors";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { GroupHeader } from "./GroupHeader";
|
||||
import { Alert } from "./Alert";
|
||||
import { GroupFooter } from "./GroupFooter";
|
||||
@@ -158,20 +155,9 @@ const AlertGroup = ({
|
||||
}
|
||||
}
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
const mountRef = useRef(null);
|
||||
|
||||
const [fadeDone, setFadeDone] = useState(false);
|
||||
|
||||
return useObserver(() => (
|
||||
<div
|
||||
ref={mountRef}
|
||||
className={`components-grid-alertgrid-alertgroup ${
|
||||
mountRef.current && fadeDone
|
||||
? "components-animation-fade-appear-done"
|
||||
: ""
|
||||
}`}
|
||||
className="components-grid-alertgrid-alertgroup"
|
||||
style={{
|
||||
width: groupWidth,
|
||||
zIndex: isMenuOpen ? 100 : null,
|
||||
@@ -180,82 +166,73 @@ const AlertGroup = ({
|
||||
settingsStore.alertGroupConfig.config.defaultRenderCount
|
||||
}
|
||||
>
|
||||
<Fade
|
||||
in={context.animations.in}
|
||||
duration={context.animations.duration}
|
||||
wait={context.animations.duration}
|
||||
onReveal={() => setFadeDone(true)}
|
||||
<div
|
||||
className={`card ${cardBackgroundClass}`}
|
||||
data-colortitlebar={settingsStore.alertGroupConfig.config.colorTitleBar}
|
||||
>
|
||||
<div
|
||||
className={`card ${cardBackgroundClass}`}
|
||||
data-colortitlebar={
|
||||
settingsStore.alertGroupConfig.config.colorTitleBar
|
||||
}
|
||||
>
|
||||
<GroupHeader
|
||||
isCollapsed={isCollapsed}
|
||||
setIsCollapsed={setIsCollapsed}
|
||||
<GroupHeader
|
||||
isCollapsed={isCollapsed}
|
||||
setIsCollapsed={setIsCollapsed}
|
||||
group={group}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
themedCounters={themedCounters}
|
||||
setIsMenuOpen={setIsMenuOpen}
|
||||
gridLabelValue={gridLabelValue}
|
||||
/>
|
||||
{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.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>
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{isCollapsed === false && group.alerts.length > 1 ? (
|
||||
<GroupFooter
|
||||
group={group}
|
||||
alertmanagers={footerAlertmanagers}
|
||||
afterUpdate={afterUpdate}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
themedCounters={themedCounters}
|
||||
setIsMenuOpen={setIsMenuOpen}
|
||||
gridLabelValue={gridLabelValue}
|
||||
/>
|
||||
{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.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>
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{isCollapsed === false && group.alerts.length > 1 ? (
|
||||
<GroupFooter
|
||||
group={group}
|
||||
alertmanagers={footerAlertmanagers}
|
||||
afterUpdate={afterUpdate}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</Fade>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
|
||||
@@ -102,24 +102,6 @@ describe("<AlertGroup />", () => {
|
||||
tree.unmount();
|
||||
});
|
||||
|
||||
it("appends components-animation-fade-appear-done class after first mount", () => {
|
||||
MockAlerts(5);
|
||||
const tree = MountedAlertGroup(jest.fn(), true);
|
||||
expect(
|
||||
tree
|
||||
.find("div.components-grid-alertgrid-alertgroup")
|
||||
.hasClass("components-animation-fade-appear-done")
|
||||
).toBe(false);
|
||||
tree.setProps({});
|
||||
act(() => tree.find("RevealBase").at(0).props().onReveal());
|
||||
tree.update();
|
||||
expect(
|
||||
tree
|
||||
.find("div.components-grid-alertgrid-alertgroup")
|
||||
.hasClass("components-animation-fade-appear-done")
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("renders Alertmanager cluster labels in footer if showAlertmanagersInFooter=true", () => {
|
||||
MockAlerts(2);
|
||||
const tree = MountedAlertGroup(jest.fn(), true).find("AlertGroup");
|
||||
|
||||
@@ -5,7 +5,8 @@ import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
import TransitionGroup from "react-transition-group/TransitionGroup";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import FontFaceObserver from "fontfaceobserver";
|
||||
|
||||
@@ -31,6 +32,7 @@ const Grid = ({
|
||||
grid,
|
||||
outerPadding,
|
||||
}) => {
|
||||
const context = React.useContext(ThemeContext);
|
||||
const { ref, repack } = useGrid(gridSizesConfig);
|
||||
const debouncedRepack = useCallback(debounce(repack, 10), [repack]);
|
||||
|
||||
@@ -93,18 +95,22 @@ const Grid = ({
|
||||
}
|
||||
}, [grid.alertGroups.length, groupsToRender]);
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return useObserver(() => (
|
||||
<React.Fragment>
|
||||
{grid.labelName !== "" && (
|
||||
<CSSTransition
|
||||
key={grid.labelValue}
|
||||
in={grid.labelName !== ""}
|
||||
classNames="components-animation-fade"
|
||||
timeout={context.animations.duration}
|
||||
unmountOnExit
|
||||
>
|
||||
<Swimlane
|
||||
alertStore={alertStore}
|
||||
grid={grid}
|
||||
isExpanded={isExpanded}
|
||||
onToggle={onCollapseClick}
|
||||
/>
|
||||
)}
|
||||
</CSSTransition>
|
||||
<div
|
||||
className="components-grid"
|
||||
ref={ref}
|
||||
@@ -114,49 +120,60 @@ const Grid = ({
|
||||
paddingRight: outerPadding + "px",
|
||||
}}
|
||||
>
|
||||
{isExpanded || grid.labelName === ""
|
||||
? grid.alertGroups
|
||||
.slice(0, groupsToRender)
|
||||
.map((group) => (
|
||||
<AlertGroup
|
||||
<TransitionGroup component={null} appear enter exit>
|
||||
{isExpanded || grid.labelName === ""
|
||||
? grid.alertGroups.slice(0, groupsToRender).map((group) => (
|
||||
<CSSTransition
|
||||
key={group.id}
|
||||
group={group}
|
||||
showAlertmanagers={
|
||||
Object.keys(alertStore.data.upstreams.clusters).length > 1
|
||||
}
|
||||
afterUpdate={debouncedRepack}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
groupWidth={groupWidth}
|
||||
gridLabelValue={grid.labelValue}
|
||||
/>
|
||||
classNames="components-animation-fade"
|
||||
timeout={context.animations.duration}
|
||||
onEntering={repack}
|
||||
onExited={debouncedRepack}
|
||||
unmountOnExit
|
||||
>
|
||||
<AlertGroup
|
||||
group={group}
|
||||
showAlertmanagers={
|
||||
Object.keys(alertStore.data.upstreams.clusters).length > 1
|
||||
}
|
||||
afterUpdate={debouncedRepack}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
groupWidth={groupWidth}
|
||||
gridLabelValue={grid.labelValue}
|
||||
/>
|
||||
</CSSTransition>
|
||||
))
|
||||
: []}
|
||||
: []}
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
{isExpanded && grid.alertGroups.length > groupsToRender && (
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<div className="flex-shrink-1 flex-grow-1 text-center">
|
||||
<Fade
|
||||
in={context.animations.in}
|
||||
duration={context.animations.duration}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary mb-3"
|
||||
onClick={() =>
|
||||
setGroupsToRender(
|
||||
Math.min(groupsToRender + 30, grid.alertGroups.length)
|
||||
)
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon className="mr-2" icon={faAngleDoubleDown} />
|
||||
Load more
|
||||
</button>
|
||||
</Fade>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<TransitionGroup component={null} enter exit>
|
||||
{isExpanded && grid.alertGroups.length > groupsToRender && (
|
||||
<CSSTransition
|
||||
classNames="components-animation-fade"
|
||||
timeout={context.animations.duration}
|
||||
unmountOnExit
|
||||
>
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<div className="flex-shrink-1 flex-grow-1 text-center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary mb-3"
|
||||
onClick={() =>
|
||||
setGroupsToRender(
|
||||
Math.min(groupsToRender + 30, grid.alertGroups.length)
|
||||
)
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon className="mr-2" icon={faAngleDoubleDown} />
|
||||
Load more
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faTh } from "@fortawesome/free-solid-svg-icons/faTh";
|
||||
|
||||
@@ -12,62 +10,57 @@ import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const Swimlane = ({ alertStore, grid, isExpanded, onToggle }) => {
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<Fade in={context.animations.in} duration={context.animations.duration}>
|
||||
<h5 className="components-grid-swimlane d-flex flex-row justify-content-between rounded px-2 py-1 mt-2 mb-0 border border-dark">
|
||||
<span className="flex-shrink-0 flex-grow-0">
|
||||
<span className="badge components-label px-0 ml-1 mr-3">
|
||||
<FontAwesomeIcon icon={faTh} className="text-muted" />
|
||||
</span>
|
||||
<h5 className="components-grid-swimlane d-flex flex-row justify-content-between rounded px-2 py-1 mt-2 mb-0 border border-dark">
|
||||
<span className="flex-shrink-0 flex-grow-0">
|
||||
<span className="badge components-label px-0 ml-1 mr-3">
|
||||
<FontAwesomeIcon icon={faTh} className="text-muted" />
|
||||
</span>
|
||||
<span className="flex-shrink-1 flex-grow-1" style={{ minWidth: "0px" }}>
|
||||
{grid.labelName !== "" && grid.labelValue !== "" && (
|
||||
<FilteringLabel
|
||||
key={grid.labelValue}
|
||||
name={grid.labelName}
|
||||
value={grid.labelValue}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="flex-shrink-0 flex-grow-0 ml-2 mr-0">
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="unprocessed"
|
||||
counter={grid.stateCount.unprocessed}
|
||||
themed={true}
|
||||
</span>
|
||||
<span className="flex-shrink-1 flex-grow-1" style={{ minWidth: "0px" }}>
|
||||
{grid.labelName !== "" && grid.labelValue !== "" && (
|
||||
<FilteringLabel
|
||||
key={grid.labelValue}
|
||||
name={grid.labelName}
|
||||
value={grid.labelValue}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="suppressed"
|
||||
counter={grid.stateCount.suppressed}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="active"
|
||||
counter={grid.stateCount.active}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<span
|
||||
className="text-muted cursor-pointer badge px-0 components-label ml-2 mr-1"
|
||||
onClick={onToggle}
|
||||
>
|
||||
<TooltipWrapper title="Click to toggle this grid details or Alt+Click to toggle all grids">
|
||||
<ToggleIcon isOpen={isExpanded} />
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="flex-shrink-0 flex-grow-0 ml-2 mr-0">
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="unprocessed"
|
||||
counter={grid.stateCount.unprocessed}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="suppressed"
|
||||
counter={grid.stateCount.suppressed}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="active"
|
||||
counter={grid.stateCount.active}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<span
|
||||
className="text-muted cursor-pointer badge px-0 components-label ml-2 mr-1"
|
||||
onClick={onToggle}
|
||||
>
|
||||
<TooltipWrapper title="Click to toggle this grid details or Alt+Click to toggle all grids">
|
||||
<ToggleIcon isOpen={isExpanded} />
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</h5>
|
||||
</Fade>
|
||||
</span>
|
||||
</h5>
|
||||
);
|
||||
};
|
||||
Swimlane.propTypes = {
|
||||
|
||||
@@ -157,14 +157,14 @@ const MockGroupList = (count, alertPerGroup) => {
|
||||
|
||||
describe("<Grid />", () => {
|
||||
it("renders only first 50 alert groups", () => {
|
||||
MockGroupList(60, 5);
|
||||
MockGroupList(55, 5);
|
||||
const tree = MountedGrid();
|
||||
const alertGroups = tree.find("AlertGroup");
|
||||
expect(alertGroups).toHaveLength(50);
|
||||
});
|
||||
|
||||
it("appends 30 groups after clicking 'Load More' button", () => {
|
||||
MockGroupList(100, 5);
|
||||
MockGroupList(85, 5);
|
||||
const tree = MountedGrid();
|
||||
tree.find("button").simulate("click");
|
||||
const alertGroups = tree.find("AlertGroup");
|
||||
@@ -186,6 +186,8 @@ describe("<Grid />", () => {
|
||||
});
|
||||
|
||||
it("click on the grid toggle toggles all groups", () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
MockGroupList(10, 3);
|
||||
const tree = MountedGrid();
|
||||
const grid = MockGrid();
|
||||
@@ -201,12 +203,18 @@ describe("<Grid />", () => {
|
||||
expect(tree.find("AlertGroup")).toHaveLength(10);
|
||||
|
||||
tree.find("span.cursor-pointer").at(0).simulate("click");
|
||||
act(() => jest.runOnlyPendingTimers());
|
||||
tree.update();
|
||||
expect(tree.find("AlertGroup")).toHaveLength(0);
|
||||
expect(tree.find("div.components-grid-alertgrid-alertgroup")).toHaveLength(
|
||||
0
|
||||
);
|
||||
|
||||
tree.find("span.cursor-pointer").at(0).simulate("click");
|
||||
act(() => jest.runOnlyPendingTimers());
|
||||
tree.update();
|
||||
expect(tree.find("AlertGroup")).toHaveLength(10);
|
||||
expect(tree.find("div.components-grid-alertgrid-alertgroup")).toHaveLength(
|
||||
10
|
||||
);
|
||||
});
|
||||
|
||||
it("renders filter badge for grids with a value", () => {
|
||||
|
||||
@@ -3,22 +3,20 @@
|
||||
exports[`<EmptyGrid /> matches snapshot 1`] = `
|
||||
"
|
||||
<h1 class=\\"display-1 text-placeholder screen-center\\">
|
||||
<div class=\\"react-reveal\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"mug-hot\\"
|
||||
class=\\"svg-inline--fa fa-mug-hot fa-w-16 screen-center-icon-big text-placeholder\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"mug-hot\\"
|
||||
class=\\"svg-inline--fa fa-mug-hot fa-w-16 screen-center-icon-big text-placeholder\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M127.1 146.5c1.3 7.7 8 13.5 16 13.5h16.5c9.8 0 17.6-8.5 16.3-18-3.8-28.2-16.4-54.2-36.6-74.7-14.4-14.7-23.6-33.3-26.4-53.5C111.8 5.9 105 0 96.8 0H80.4C70.6 0 63 8.5 64.1 18c3.9 31.9 18 61.3 40.6 84.4 12 12.2 19.7 27.5 22.4 44.1zm112 0c1.3 7.7 8 13.5 16 13.5h16.5c9.8 0 17.6-8.5 16.3-18-3.8-28.2-16.4-54.2-36.6-74.7-14.4-14.7-23.6-33.3-26.4-53.5C223.8 5.9 217 0 208.8 0h-16.4c-9.8 0-17.5 8.5-16.3 18 3.9 31.9 18 61.3 40.6 84.4 12 12.2 19.7 27.5 22.4 44.1zM400 192H32c-17.7 0-32 14.3-32 32v192c0 53 43 96 96 96h192c53 0 96-43 96-96h16c61.8 0 112-50.2 112-112s-50.2-112-112-112zm0 160h-16v-96h16c26.5 0 48 21.5 48 48s-21.5 48-48 48z\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M127.1 146.5c1.3 7.7 8 13.5 16 13.5h16.5c9.8 0 17.6-8.5 16.3-18-3.8-28.2-16.4-54.2-36.6-74.7-14.4-14.7-23.6-33.3-26.4-53.5C111.8 5.9 105 0 96.8 0H80.4C70.6 0 63 8.5 64.1 18c3.9 31.9 18 61.3 40.6 84.4 12 12.2 19.7 27.5 22.4 44.1zm112 0c1.3 7.7 8 13.5 16 13.5h16.5c9.8 0 17.6-8.5 16.3-18-3.8-28.2-16.4-54.2-36.6-74.7-14.4-14.7-23.6-33.3-26.4-53.5C223.8 5.9 217 0 208.8 0h-16.4c-9.8 0-17.5 8.5-16.3 18 3.9 31.9 18 61.3 40.6 84.4 12 12.2 19.7 27.5 22.4 44.1zM400 192H32c-17.7 0-32 14.3-32 32v192c0 53 43 96 96 96h192c53 0 96-43 96-96h16c61.8 0 112-50.2 112-112s-50.2-112-112-112zm0 160h-16v-96h16c26.5 0 48 21.5 48 48s-21.5 48-48 48z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</path>
|
||||
</svg>
|
||||
</h1>
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`<FatalError /> matches snapshot 1`] = `
|
||||
"
|
||||
<h1 class=\\"display-1 text-placeholder screen-center\\">
|
||||
<div class=\\"react-reveal container-fluid text-center\\">
|
||||
<div class=\\"container-fluid text-center\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`<ReloadNeeded /> matches snapshot 1`] = `
|
||||
"
|
||||
<h1 class=\\"display-1 text-placeholder screen-center\\">
|
||||
<div class=\\"react-reveal container-fluid text-center\\">
|
||||
<div class=\\"container-fluid text-center\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`<UpgradeNeeded /> matches snapshot 1`] = `
|
||||
"
|
||||
<h1 class=\\"display-1 text-placeholder screen-center\\">
|
||||
<div class=\\"react-reveal container-fluid text-center\\">
|
||||
<div class=\\"container-fluid text-center\\">
|
||||
<div class=\\"shake-slow shake-constant mb-4\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
|
||||
@@ -3,12 +3,13 @@ import PropTypes from "prop-types";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import Flash from "react-reveal/Flash";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { QueryOperators, FormatQuery } from "Common/Query";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { GetClassAndStyle } from "Components/Labels/Utils";
|
||||
import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
|
||||
// Same as FilteringLabel but for labels that are counters (usually @state)
|
||||
// and only renders a pill badge with the counter, it doesn't render anything
|
||||
@@ -23,6 +24,8 @@ const FilteringCounterBadge = observer(
|
||||
alwaysVisible,
|
||||
defaultColor,
|
||||
}) => {
|
||||
const { ref, props } = useFlashTransition(counter);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(event) => {
|
||||
// left click => apply foo=bar filter
|
||||
@@ -52,8 +55,9 @@ const FilteringCounterBadge = observer(
|
||||
<TooltipWrapper
|
||||
title={`Click to only show ${name}=${value} alerts or Alt+Click to hide them`}
|
||||
>
|
||||
<Flash spy={counter}>
|
||||
<CSSTransition {...props}>
|
||||
<span
|
||||
ref={ref}
|
||||
className={
|
||||
themed
|
||||
? cs.className
|
||||
@@ -68,7 +72,7 @@ const FilteringCounterBadge = observer(
|
||||
>
|
||||
{counter}
|
||||
</span>
|
||||
</Flash>
|
||||
</CSSTransition>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ const validateStyle = (value, themed) => {
|
||||
themed={themed}
|
||||
/>
|
||||
);
|
||||
// opacity=>1 is set by react-reveal/Flash
|
||||
expect(tree.find("span").prop("style")).toEqual({ opacity: 1 });
|
||||
expect(tree.find("span").prop("style")).toEqual({});
|
||||
};
|
||||
|
||||
const validateOnClick = (value, themed, isNegative) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import parseISO from "date-fns/parseISO";
|
||||
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
import Flash from "react-reveal/Flash";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons/faEdit";
|
||||
@@ -22,14 +22,18 @@ import { QueryOperators } from "Common/Query";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { RenderLinkAnnotation } from "Components/Grid/AlertGrid/AlertGroup/Annotation";
|
||||
import { DateFromNow } from "Components/DateFromNow";
|
||||
import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
import { DeleteSilence } from "./DeleteSilence";
|
||||
|
||||
const SilenceIDCopyButton = ({ id }) => {
|
||||
const [clickCount, setClickCount] = useState(0);
|
||||
const { ref, props } = useFlashTransition(clickCount);
|
||||
|
||||
return (
|
||||
<TooltipWrapper title="Copy silence ID to the clipboard">
|
||||
<Flash spy={clickCount} duration={500}>
|
||||
<CSSTransition {...props}>
|
||||
<span
|
||||
ref={ref}
|
||||
className="badge badge-secondary px-1 mr-1 components-label cursor-pointer"
|
||||
onClick={() => {
|
||||
copy(id);
|
||||
@@ -38,7 +42,7 @@ const SilenceIDCopyButton = ({ id }) => {
|
||||
>
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
</span>
|
||||
</Flash>
|
||||
</CSSTransition>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,9 +47,7 @@ exports[`<SilenceComment /> Matches snapshot when collapsed 1`] = `
|
||||
<div style=\\"display: inline-block; max-width: 100%;\\"
|
||||
class=\\" tooltip-trigger\\"
|
||||
>
|
||||
<span class=\\" badge-primary badge-pill components-label-with-hover components-label badge\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<span class=\\"badge-primary badge-pill components-label-with-hover components-label badge\\">
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
@@ -124,9 +122,7 @@ exports[`<SilenceComment /> Matches snapshot when collapsed and multiple cluster
|
||||
<div style=\\"display: inline-block; max-width: 100%;\\"
|
||||
class=\\" tooltip-trigger\\"
|
||||
>
|
||||
<span class=\\" badge-primary badge-pill components-label-with-hover components-label badge\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<span class=\\"badge-primary badge-pill components-label-with-hover components-label badge\\">
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
@@ -185,9 +181,7 @@ exports[`<SilenceComment /> Matches snapshot when collapsed and multiple cluster
|
||||
<div style=\\"display: inline-block; max-width: 100%;\\"
|
||||
class=\\" tooltip-trigger\\"
|
||||
>
|
||||
<span class=\\" badge-primary badge-pill components-label-with-hover components-label badge\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<span class=\\"badge-primary badge-pill components-label-with-hover components-label badge\\">
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
@@ -246,9 +240,7 @@ exports[`<SilenceComment /> Matches snapshot when expanded 1`] = `
|
||||
<div style=\\"display: inline-block; max-width: 100%;\\"
|
||||
class=\\" tooltip-trigger\\"
|
||||
>
|
||||
<span class=\\" badge-primary badge-pill components-label-with-hover components-label badge\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<span class=\\"badge-primary badge-pill components-label-with-hover components-label badge\\">
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
exports[`<ManagedSilence /> matches snapshot when collapsed 1`] = `
|
||||
"
|
||||
<div class=\\"react-reveal card my-1 components-managed-silence\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<div class=\\"card my-1 components-managed-silence\\">
|
||||
<div class=\\"card-header rounded-0 border-bottom-0 px-3\\">
|
||||
<div class=\\"d-flex flex-row\\">
|
||||
<div class=\\"flex-shrink-0 flex-grow-0\\">
|
||||
@@ -51,9 +49,7 @@ exports[`<ManagedSilence /> matches snapshot when collapsed 1`] = `
|
||||
<div style=\\"display: inline-block; max-width: 100%;\\"
|
||||
class=\\" tooltip-trigger\\"
|
||||
>
|
||||
<span class=\\" badge-primary badge-pill components-label-with-hover components-label badge\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<span class=\\"badge-primary badge-pill components-label-with-hover components-label badge\\">
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
@@ -82,9 +78,7 @@ exports[`<ManagedSilence /> matches snapshot when collapsed 1`] = `
|
||||
|
||||
exports[`<ManagedSilence /> matches snapshot with expaned details 1`] = `
|
||||
"
|
||||
<div class=\\"react-reveal card my-1 components-managed-silence\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<div class=\\"card my-1 components-managed-silence\\">
|
||||
<div class=\\"card-header rounded-0 border-bottom-0 px-3\\">
|
||||
<div class=\\"d-flex flex-row\\">
|
||||
<div class=\\"flex-shrink-0 flex-grow-0\\">
|
||||
@@ -118,9 +112,7 @@ exports[`<ManagedSilence /> matches snapshot with expaned details 1`] = `
|
||||
<div style=\\"display: inline-block; max-width: 100%;\\"
|
||||
class=\\" tooltip-trigger\\"
|
||||
>
|
||||
<span class=\\" badge-primary badge-pill components-label-with-hover components-label badge\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<span class=\\"badge-primary badge-pill components-label-with-hover components-label badge\\">
|
||||
123
|
||||
</span>
|
||||
</div>
|
||||
@@ -207,9 +199,7 @@ exports[`<ManagedSilence /> matches snapshot with expaned details 1`] = `
|
||||
<div style=\\"display: inline-block; max-width: 100%;\\"
|
||||
class=\\" tooltip-trigger\\"
|
||||
>
|
||||
<span class=\\" badge badge-secondary px-1 mr-1 components-label cursor-pointer\\"
|
||||
style=\\"opacity: 1;\\"
|
||||
>
|
||||
<span class=\\"badge badge-secondary px-1 mr-1 components-label cursor-pointer\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
|
||||
import { APISilence } from "Models/API";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore, SilenceTabNames } from "Stores/SilenceFormStore";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { SilenceComment } from "./SilenceComment";
|
||||
import { SilenceDetails } from "./SilenceDetails";
|
||||
|
||||
@@ -41,37 +38,32 @@ const ManagedSilence = ({
|
||||
silenceFormStore.toggle.show();
|
||||
};
|
||||
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<Fade in={context.animations.in} duration={context.animations.duration}>
|
||||
<div className="card my-1 components-managed-silence">
|
||||
<div className="card-header rounded-0 border-bottom-0 px-3">
|
||||
<SilenceComment
|
||||
alertStore={alertStore}
|
||||
<div className="card my-1 components-managed-silence">
|
||||
<div className="card-header rounded-0 border-bottom-0 px-3">
|
||||
<SilenceComment
|
||||
alertStore={alertStore}
|
||||
cluster={cluster}
|
||||
silence={silence}
|
||||
alertCount={alertCount}
|
||||
alertCountAlwaysVisible={alertCountAlwaysVisible}
|
||||
collapsed={!showDetails}
|
||||
collapseToggle={() => setShowDetails(!showDetails)}
|
||||
/>
|
||||
</div>
|
||||
{showDetails ? (
|
||||
<div className="card-body pt-0">
|
||||
<SilenceDetails
|
||||
cluster={cluster}
|
||||
silence={silence}
|
||||
alertCount={alertCount}
|
||||
alertCountAlwaysVisible={alertCountAlwaysVisible}
|
||||
collapsed={!showDetails}
|
||||
collapseToggle={() => setShowDetails(!showDetails)}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
onEditSilence={onEditSilence}
|
||||
isUpper={isNested}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showDetails ? (
|
||||
<div className="card-body pt-0">
|
||||
<SilenceDetails
|
||||
cluster={cluster}
|
||||
silence={silence}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
onEditSilence={onEditSilence}
|
||||
isUpper={isNested}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Fade>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ManagedSilence.propTypes = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import ReactResizeDetector from "react-resize-detector";
|
||||
|
||||
import IdleTimer from "react-idle-timer";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
@@ -96,7 +96,15 @@ const NavBar = ({ alertStore, settingsStore, silenceFormStore, fixedTop }) => {
|
||||
timeout={IsMobile() ? MobileIdleTimeout : DesktopIdleTimeout}
|
||||
>
|
||||
<div className={`container p-0 m-0 mw-100 ${containerClass}`}>
|
||||
<Fade top when={!isIdle}>
|
||||
<CSSTransition
|
||||
classNames="components-animation-navbar"
|
||||
in={!isIdle}
|
||||
timeout={context.animations.duration}
|
||||
onEntering={() => {}}
|
||||
onExited={() => {}}
|
||||
enter
|
||||
exit
|
||||
>
|
||||
<nav
|
||||
className={`navbar navbar-expand navbar-dark p-1 bg-primary-transparent d-inline-block ${
|
||||
fixedTop ? "fixed-top" : "w-100"
|
||||
@@ -129,7 +137,7 @@ const NavBar = ({ alertStore, settingsStore, silenceFormStore, fixedTop }) => {
|
||||
settingsStore={settingsStore}
|
||||
/>
|
||||
</nav>
|
||||
</Fade>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
</IdleTimer>
|
||||
));
|
||||
|
||||
@@ -3,12 +3,13 @@ import PropTypes from "prop-types";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import Flash from "react-reveal/Flash";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { Modal } from "Components/Modal";
|
||||
|
||||
@@ -24,11 +25,14 @@ const OverviewModal = ({ alertStore }) => {
|
||||
|
||||
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
|
||||
|
||||
const { ref, props } = useFlashTransition(alertStore.info.totalAlerts);
|
||||
|
||||
return useObserver(() => (
|
||||
<React.Fragment>
|
||||
<TooltipWrapper title="Show alert overview">
|
||||
<Flash spy={alertStore.info.totalAlerts}>
|
||||
<CSSTransition {...props}>
|
||||
<div
|
||||
ref={ref}
|
||||
className={`text-center d-inline-block cursor-pointer navbar-brand m-0 components-navbar-button ${
|
||||
isVisible ? "border-info" : ""
|
||||
}`}
|
||||
@@ -36,7 +40,7 @@ const OverviewModal = ({ alertStore }) => {
|
||||
>
|
||||
{alertStore.info.totalAlerts}
|
||||
</div>
|
||||
</Flash>
|
||||
</CSSTransition>
|
||||
</TooltipWrapper>
|
||||
<Modal size="xl" isOpen={isVisible} toggleOpen={toggle}>
|
||||
<React.Suspense
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
@@ -34,14 +34,19 @@ FetchError.propTypes = {
|
||||
};
|
||||
|
||||
const Placeholder = ({ content }) => {
|
||||
const theme = React.useContext(ThemeContext);
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<Fade in={theme.animations.in} duration={theme.animations.duration}>
|
||||
<CSSTransition
|
||||
in={true}
|
||||
appear={true}
|
||||
classNames="components-animation-fade"
|
||||
timeout={context.animations.duration}
|
||||
>
|
||||
<div className="jumbotron bg-transparent">
|
||||
<h1 className="display-5 text-placeholder text-center">{content}</h1>
|
||||
</div>
|
||||
</Fade>
|
||||
</CSSTransition>
|
||||
);
|
||||
};
|
||||
Placeholder.propTypes = {
|
||||
|
||||
@@ -3,10 +3,10 @@ import PropTypes from "prop-types";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import Flash from "react-reveal/Flash";
|
||||
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
|
||||
import { faUser } from "@fortawesome/free-solid-svg-icons/faUser";
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { QueryOperators } from "Common/Query";
|
||||
import { useFlashTransition } from "Hooks/useFlashTransition";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
import { AlertManagerInput } from "./AlertManagerInput";
|
||||
@@ -44,6 +45,8 @@ const ShareButton = ({ silenceFormStore }) => {
|
||||
window.location.pathname,
|
||||
].join("");
|
||||
|
||||
const { ref, props } = useFlashTransition(clickCount);
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="input-group mb-3">
|
||||
<div className="input-group-prepend">
|
||||
@@ -59,7 +62,7 @@ const ShareButton = ({ silenceFormStore }) => {
|
||||
value={`${baseURL}?m=${silenceFormStore.data.toBase64}`}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<div className="input-group-append">
|
||||
<div ref={ref} className="input-group-append">
|
||||
<span
|
||||
className="input-group-text cursor-pointer"
|
||||
onClick={() => {
|
||||
@@ -68,9 +71,9 @@ const ShareButton = ({ silenceFormStore }) => {
|
||||
}}
|
||||
>
|
||||
<TooltipWrapper title="Copy to clipboard">
|
||||
<Flash spy={clickCount} duration={500}>
|
||||
<CSSTransition {...props}>
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
</Flash>
|
||||
</CSSTransition>
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
import { useInView } from "react-intersection-observer";
|
||||
|
||||
const defaultProps = {
|
||||
in: false,
|
||||
classNames: "components-animation-flash",
|
||||
timeout: 800,
|
||||
appear: false,
|
||||
enter: false,
|
||||
exit: false,
|
||||
};
|
||||
|
||||
const useFlashTransition = (flashOn) => {
|
||||
const mountRef = useRef(false);
|
||||
const [ref, inView] = useInView();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const [props, setProps] = useState(defaultProps);
|
||||
|
||||
useEffect(() => {
|
||||
if (mountRef.current) {
|
||||
setIsPending(true);
|
||||
} else {
|
||||
mountRef.current = true;
|
||||
}
|
||||
}, [flashOn]);
|
||||
|
||||
useEffect(() => {
|
||||
setProps({
|
||||
...defaultProps,
|
||||
in: isPending && inView,
|
||||
enter: isPending && inView,
|
||||
onEntered: () => setIsPending(false),
|
||||
});
|
||||
}, [inView, isPending]);
|
||||
|
||||
return { ref, props };
|
||||
};
|
||||
|
||||
export { useFlashTransition, defaultProps };
|
||||
@@ -0,0 +1,97 @@
|
||||
import { renderHook, act } from "@testing-library/react-hooks";
|
||||
|
||||
import { useInView } from "react-intersection-observer";
|
||||
|
||||
import { useFlashTransition, defaultProps } from "./useFlashTransition";
|
||||
|
||||
describe("useFlashTransition", () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useInView.setInView(true);
|
||||
});
|
||||
|
||||
it("does nothing when value changes but element is out of viewport", () => {
|
||||
useInView.setInView(false);
|
||||
|
||||
let value = 0;
|
||||
const { result, rerender } = renderHook(() => useFlashTransition(value));
|
||||
expect(result.current.props).toMatchObject(defaultProps);
|
||||
|
||||
value = 1;
|
||||
rerender();
|
||||
expect(result.current.props).toMatchObject(defaultProps);
|
||||
});
|
||||
|
||||
it("flashes when value changes and element is in viewport", () => {
|
||||
useInView.setInView(true);
|
||||
|
||||
let value = 2;
|
||||
const { result, rerender } = renderHook(() => useFlashTransition(value));
|
||||
|
||||
value = 3;
|
||||
rerender();
|
||||
expect(result.current.props).toMatchObject({
|
||||
...defaultProps,
|
||||
in: true,
|
||||
enter: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("flashes when value changes and element moves into viewport", () => {
|
||||
useInView.setInView(false);
|
||||
|
||||
let value = 2;
|
||||
const { result, rerender } = renderHook(() => useFlashTransition(value));
|
||||
|
||||
value = 3;
|
||||
rerender();
|
||||
expect(result.current.props).toMatchObject(defaultProps);
|
||||
|
||||
act(() => useInView.setInView(true));
|
||||
rerender();
|
||||
expect(result.current.props).toMatchObject({
|
||||
...defaultProps,
|
||||
in: true,
|
||||
enter: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("stops flashing props.onEntered is called", () => {
|
||||
useInView.setInView(true);
|
||||
|
||||
let value = 2;
|
||||
const { result, rerender } = renderHook(() => useFlashTransition(value));
|
||||
|
||||
value = 3;
|
||||
rerender();
|
||||
expect(result.current.props).toMatchObject({
|
||||
...defaultProps,
|
||||
in: true,
|
||||
enter: true,
|
||||
});
|
||||
|
||||
act(() => result.current.props.onEntered());
|
||||
expect(result.current.props).toMatchObject(defaultProps);
|
||||
});
|
||||
|
||||
it("unmounts cleanly when not flashing", () => {
|
||||
useInView.setInView(false);
|
||||
|
||||
const { unmount } = renderHook(() => useFlashTransition(4));
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("unmounts cleanly when flashing", () => {
|
||||
useInView.setInView(false);
|
||||
|
||||
let value = 5;
|
||||
const { rerender, unmount } = renderHook(() => useFlashTransition(value));
|
||||
|
||||
value = 6;
|
||||
rerender();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,12 @@
|
||||
.components-grid-alertgrid-alertgroup.components-animation-fade-appear-done {
|
||||
will-change: transform;
|
||||
transform: translateZ(0);
|
||||
|
||||
transition-property: transform;
|
||||
transition-duration: 0.4s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.components-grid-alertgrid-alertgroup {
|
||||
padding: 0.3rem;
|
||||
|
||||
&.components-animation-fade-appear-done,
|
||||
&.components-animation-fade-enter-done {
|
||||
transition-property: transform;
|
||||
transition-duration: 0.4s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
}
|
||||
|
||||
.components-grid-alertgrid-alertgroup-shared-silence {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
$duration: 0.5s;
|
||||
|
||||
.components-animation-fade-appear,
|
||||
.components-animation-fade-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
.components-animation-fade-appear-active,
|
||||
.components-animation-fade-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity $duration ease-in;
|
||||
}
|
||||
|
||||
.components-animation-fade-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
.components-animation-fade-exit-active {
|
||||
opacity: 0.01;
|
||||
transition: opacity $duration ease-out;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
$duration: 800ms;
|
||||
|
||||
.components-animation-flash-appear,
|
||||
.components-animation-flash-enter {
|
||||
animation-name: flash;
|
||||
animation-duration: $duration;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
75% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
$duration: 0.5s;
|
||||
|
||||
.components-animation-navbar-appear,
|
||||
.components-animation-navbar-enter {
|
||||
opacity: 0.01;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
.components-animation-navbar-appear-active,
|
||||
.components-animation-navbar-enter-active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition: opacity $duration ease-out, transform $duration ease-out;
|
||||
}
|
||||
|
||||
.components-animation-navbar-exit {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.components-animation-navbar-exit-active {
|
||||
opacity: 0.01;
|
||||
transform: translateY(-100%);
|
||||
transition: opacity $duration ease-out, transform $duration ease-out;
|
||||
}
|
||||
@@ -98,6 +98,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/AlertGroup";
|
||||
@import "Styles/Components/BaseLabel";
|
||||
@import "Styles/Components/CenteredMessage";
|
||||
@import "Styles/Components/Fade";
|
||||
@import "Styles/Components/FilterInputLabel";
|
||||
@import "Styles/Components/LabelWithPercent";
|
||||
@import "Styles/Components/InputRange";
|
||||
@@ -105,6 +106,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/Modal";
|
||||
@import "Styles/Components/NavBar";
|
||||
@import "Styles/Components/FilterInput";
|
||||
@import "Styles/Components/Flash";
|
||||
@import "Styles/Components/DateTimeSelect";
|
||||
@import "Styles/Components/MountModal";
|
||||
@import "Styles/Components/Placeholder";
|
||||
@@ -113,6 +115,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/DropdownSlide";
|
||||
@import "Styles/Components/History";
|
||||
@import "Styles/Components/HistoryLabel";
|
||||
@import "Styles/Components/NavBarSlide";
|
||||
@import "Styles/Components/SilenceModal";
|
||||
@import "Styles/Components/Pagination";
|
||||
@import "Styles/Components/Tooltip";
|
||||
|
||||
@@ -80,6 +80,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/AlertGroup";
|
||||
@import "Styles/Components/BaseLabel";
|
||||
@import "Styles/Components/CenteredMessage";
|
||||
@import "Styles/Components/Fade";
|
||||
@import "Styles/Components/FilterInputLabel";
|
||||
@import "Styles/Components/LabelWithPercent";
|
||||
@import "Styles/Components/InputRange";
|
||||
@@ -87,6 +88,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/Modal";
|
||||
@import "Styles/Components/NavBar";
|
||||
@import "Styles/Components/FilterInput";
|
||||
@import "Styles/Components/Flash";
|
||||
@import "Styles/Components/DateTimeSelect";
|
||||
@import "Styles/Components/MountModal";
|
||||
@import "Styles/Components/Placeholder";
|
||||
@@ -95,6 +97,7 @@ $color-default: #708090;
|
||||
@import "Styles/Components/DropdownSlide";
|
||||
@import "Styles/Components/History";
|
||||
@import "Styles/Components/HistoryLabel";
|
||||
@import "Styles/Components/NavBarSlide";
|
||||
@import "Styles/Components/SilenceModal";
|
||||
@import "Styles/Components/Pagination";
|
||||
@import "Styles/Components/Tooltip";
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
|
||||
const MockThemeContext = {
|
||||
animations: {
|
||||
in: undefined,
|
||||
duration: 500,
|
||||
},
|
||||
isDark: false,
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { useRef } from "react";
|
||||
|
||||
const mock = {
|
||||
value: true,
|
||||
};
|
||||
|
||||
const useInView = () => {
|
||||
const ref = useRef(null);
|
||||
return [ref, mock.value];
|
||||
};
|
||||
useInView.setInView = (val) => {
|
||||
mock.value = val;
|
||||
};
|
||||
|
||||
export { useInView };
|
||||
@@ -4,6 +4,9 @@
|
||||
import "react-app-polyfill/ie11";
|
||||
import "react-app-polyfill/stable";
|
||||
|
||||
// https://www.npmjs.com/package/react-intersection-observer#polyfill
|
||||
import "intersection-observer";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
|
||||
@@ -20,10 +20,7 @@ for (const level of ["error", "warn", "info", "log", "trace"]) {
|
||||
// https://reactjs.org/blog/2019/08/08/react-v16.9.0.html#new-deprecations
|
||||
const reactDeprecationWarning = /.*has been renamed, and is not recommended for use.*/;
|
||||
global.console[level] = (message, ...args) => {
|
||||
if (
|
||||
reactDeprecationWarning.test(message) === false &&
|
||||
message !== "react-reveal - animation failed"
|
||||
) {
|
||||
if (reactDeprecationWarning.test(message) === false) {
|
||||
throw new Error(`message=${message} args=${args}`);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user