Alert source links:
{alert.alertmanager.map((am) => (
@@ -166,43 +165,39 @@ const AlertMenu: FC<{
const rootRef = useRef
(null);
useOnClickOutside(rootRef, hide, !isHidden);
+ const { x, y, reference, floating, strategy } = useFloating({
+ placement: "bottom-start",
+ middleware: [shift(), flip(), offset(5)],
+ });
+
return (
-
-
- {({ ref }) => (
-
-
-
-
- )}
-
-
-
- {({ placement, ref, style }) => (
-
- )}
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx
index 4630bebee..891ead84f 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.tsx
@@ -191,9 +191,10 @@ describe("", () => {
const MountedMenuContent = (group: APIAlertGroupT) => {
return mount(
;
- popperStyle?: CSSProperties;
+ x: number | null;
+ y: number | null;
+ floating: Ref | null;
+ strategy: CSSProperties["position"];
group: APIAlertGroupT;
afterClick: () => void;
alertStore: AlertStore;
silenceFormStore: SilenceFormStore;
}> = ({
- popperPlacement,
- popperRef,
- popperStyle,
+ x,
+ y,
+ floating,
+ strategy,
group,
afterClick,
alertStore,
@@ -94,9 +90,12 @@ const MenuContent: FC<{
{actions.length ? (
<>
@@ -165,39 +164,35 @@ const GroupMenu: FC<{
const rootRef = useRef
(null);
useOnClickOutside(rootRef, hide, !isHidden);
+ const { x, y, reference, floating, strategy } = useFloating({
+ placement: "bottom-start",
+ middleware: [shift(), flip(), offset(5)],
+ });
+
return (
-
-
- {({ ref }) => (
-
-
-
- )}
-
-
-
- {({ placement, ref, style }) => (
-
- )}
-
-
-
+
+
+
+
+
+
);
};
diff --git a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx
index 4975e4bf5..9c4c8136a 100644
--- a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx
+++ b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.tsx
@@ -9,7 +9,7 @@ import React, {
import { observer } from "mobx-react-lite";
-import { Manager, Reference, Popper } from "react-popper";
+import { useFloating, shift, flip, offset } from "@floating-ui/react-dom";
import type { OnChangeValue } from "react-select";
import AsyncSelect from "react-select/async";
@@ -20,7 +20,6 @@ import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
import type { AlertStore } from "Stores/AlertStore";
import type { Settings } from "Stores/Settings";
import type { APIGridT } from "Models/APITypes";
-import { CommonPopperModifiers } from "Common/Popper";
import { StringToOption, OptionT } from "Common/Select";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
import { ThemeContext } from "Components/Theme";
@@ -90,17 +89,19 @@ const GridLabelNameSelect: FC<{
};
const Dropdown: FC<{
- popperPlacement?: string;
- popperRef?: Ref;
- popperStyle?: CSSProperties;
+ x: number | null;
+ y: number | null;
+ floating: Ref | null;
+ strategy: CSSProperties["position"];
alertStore: AlertStore;
settingsStore: Settings;
grid: APIGridT;
onClose: () => void;
}> = ({
- popperPlacement,
- popperRef,
- popperStyle,
+ x,
+ y,
+ floating,
+ strategy,
alertStore,
settingsStore,
grid,
@@ -109,13 +110,14 @@ const Dropdown: FC<{
return (
(null);
useOnClickOutside(ref, hide, isVisible);
+ const { x, y, reference, floating, strategy } = useFloating({
+ placement: "bottom",
+ middleware: [shift(), flip(), offset(5)],
+ });
+
return (
-
-
- {({ ref }) => (
-
-
-
- )}
-
-
-
- {({ placement, ref, style }) => (
-
- )}
-
-
-
+
+
+
+
+
+
);
});
diff --git a/ui/src/Components/NavBar/FilterInput/History.test.tsx b/ui/src/Components/NavBar/FilterInput/History.test.tsx
index 761c78b6f..f14fb9f83 100644
--- a/ui/src/Components/NavBar/FilterInput/History.test.tsx
+++ b/ui/src/Components/NavBar/FilterInput/History.test.tsx
@@ -234,7 +234,8 @@ describe("", () => {
await act(() => promise);
});
- it("clicking on 'Save filters' saves current filter set to Settings", () => {
+ it("clicking on 'Save filters' saves current filter set to Settings", async () => {
+ const promise = Promise.resolve();
alertStore.filters.setFilterValues([
AppliedFilter("foo", "=", "bar"),
AppliedFilter("bar", "=~", "baz"),
@@ -252,9 +253,11 @@ describe("", () => {
expect(settingsStore.savedFilters.config.filters).toHaveLength(2);
expect(settingsStore.savedFilters.config.filters).toContain("foo=bar");
expect(settingsStore.savedFilters.config.filters).toContain("bar=~baz");
+ await act(() => promise);
});
- it("clicking on 'Reset filters' clears current filter set in Settings", () => {
+ it("clicking on 'Reset filters' clears current filter set in Settings", async () => {
+ const promise = Promise.resolve();
settingsStore.savedFilters.save(["foo=bar"]);
const tree = MountedHistory();
tree.find("button.cursor-pointer").simulate("click");
@@ -266,6 +269,7 @@ describe("", () => {
jest.runOnlyPendingTimers();
});
expect(settingsStore.savedFilters.config.filters).toHaveLength(0);
+ await act(() => promise);
});
it("clicking on 'Clear history' clears the history", async () => {
diff --git a/ui/src/Components/NavBar/FilterInput/History.tsx b/ui/src/Components/NavBar/FilterInput/History.tsx
index e893f43bf..83f1b4b52 100644
--- a/ui/src/Components/NavBar/FilterInput/History.tsx
+++ b/ui/src/Components/NavBar/FilterInput/History.tsx
@@ -1,19 +1,19 @@
import {
FC,
Ref,
- CSSProperties,
useEffect,
useRef,
useState,
useCallback,
ReactNode,
+ CSSProperties,
} from "react";
import { action } from "mobx";
import { observer } from "mobx-react-lite";
import { localStored } from "mobx-stored";
-import { Manager, Reference, Popper } from "react-popper";
+import { useFloating, shift, flip, offset, size } from "@floating-ui/react-dom";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -26,7 +26,6 @@ import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
import type { AlertStore, FilterT } from "Stores/AlertStore";
import type { Settings } from "Stores/Settings";
import { IsMobile } from "Common/Device";
-import { CommonPopperModifiers } from "Common/Popper";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
import HistoryLabel from "Components/Labels/HistoryLabel";
import { useOnClickOutside } from "Hooks/useOnClickOutside";
@@ -69,18 +68,24 @@ const ActionButton: FC<{
);
const HistoryMenu: FC<{
- popperPlacement?: string;
- popperRef?: Ref;
- popperStyle?: CSSProperties;
+ x: number | null;
+ y: number | null;
+ floating: Ref | null;
+ strategy: CSSProperties["position"];
+ maxWidth: number | null;
+ maxHeight: number | null;
filters: ReduceFilterT[][];
alertStore: AlertStore;
settingsStore: Settings;
afterClick: () => void;
onClear: () => void;
}> = ({
- popperPlacement,
- popperRef,
- popperStyle,
+ x,
+ y,
+ floating,
+ strategy,
+ maxWidth,
+ maxHeight,
filters,
alertStore,
settingsStore,
@@ -92,9 +97,14 @@ const HistoryMenu: FC<{
return (
@@ -185,41 +195,52 @@ const History: FC<{
// this will be dumped to local storage via mobx-stored
const [history] = useState(new HistoryStorage());
const [isVisible, setIsVisible] = useState(false);
+ const [maxWidth, setMaxWidth] = useState(null);
+ const [maxHeight, setMaxHeight] = useState(null);
const hide = useCallback(() => setIsVisible(false), []);
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
- const mountRef = useRef(false);
+ const { x, y, reference, floating, strategy } = useFloating({
+ placement: "bottom-end",
+ middleware: [
+ shift(),
+ flip(),
+ offset(5),
+ size({
+ apply({ width, height }) {
+ setMaxWidth(width);
+ setMaxHeight(height);
+ },
+ }),
+ ],
+ });
// every time this component updates we will rewrite history
// (if there are changes)
useEffect(() => {
- if (mountRef.current) {
- // we don't store unapplied (we only have raw text for those, we need
- // name & value for coloring) or invalid filters
- // also check for value, name might be missing for fuzzy filters, but
- // the value should always be set
- const validAppliedFilters = alertStore.filters.values
- .filter((f) => f.applied && f.isValid && f.value)
- .map((f) => ReduceFilter(f));
+ // we don't store unapplied (we only have raw text for those, we need
+ // name & value for coloring) or invalid filters
+ // also check for value, name might be missing for fuzzy filters, but
+ // the value should always be set
+ const validAppliedFilters = alertStore.filters.values
+ .filter((f) => f.applied && f.isValid && f.value)
+ .map((f) => ReduceFilter(f));
- // don't store empty filters in history
- if (validAppliedFilters.length === 0) return;
- // make a JSON dump for comparing later with what's already stored
- const filtersJSON = JSON.stringify(validAppliedFilters);
+ // don't store empty filters in history
+ if (validAppliedFilters.length === 0) return;
+ // make a JSON dump for comparing later with what's already stored
+ const filtersJSON = JSON.stringify(validAppliedFilters);
- // rewrite history putting current filter set on top, this will move
- // it up if user selects a filter set that was already in history
- const newHistory = [
- ...[validAppliedFilters],
- ...history.config.filters.filter(
- (f) => JSON.stringify(f) !== filtersJSON
- ),
- ].slice(0, 8);
- history.setFilters(newHistory);
- } else {
- mountRef.current = true;
- }
- });
+ // rewrite history putting current filter set on top, this will move
+ // it up if user selects a filter set that was already in history
+ const newHistory = [
+ ...[validAppliedFilters],
+ ...history.config.filters.filter(
+ (f) => JSON.stringify(f) !== filtersJSON
+ ),
+ ].slice(0, 8);
+ history.setFilters(newHistory);
+ }, [history, alertStore.filters.values]);
const ref = useRef(null);
useOnClickOutside(ref, hide, isVisible);
@@ -234,45 +255,34 @@ const History: FC<{
ref={ref}
className="input-group-text border-0 rounded-0 bg-inherit px-0"
>
- ReduceFilter(f))
- .join(" ")}
+
+
+
+
+ {
+ history.setFilters([]);
+ }}
+ alertStore={alertStore}
+ settingsStore={settingsStore}
+ afterClick={hide}
+ x={x}
+ y={y}
+ floating={floating}
+ strategy={strategy}
+ maxWidth={maxWidth}
+ maxHeight={maxHeight}
+ />
+
);
});
diff --git a/ui/src/Components/NavBar/index.stories.tsx b/ui/src/Components/NavBar/index.stories.tsx
index 2a83d8e7c..e2909267b 100644
--- a/ui/src/Components/NavBar/index.stories.tsx
+++ b/ui/src/Components/NavBar/index.stories.tsx
@@ -172,9 +172,12 @@ storiesOf("NavBar", module).add("NavBar", () => {
fixedTop={false}
/>
{}}
- popperStyle={{}}
+ x={0}
+ y={0}
+ floating={null}
+ strategy={"absolute"}
+ maxWidth={null}
+ maxHeight={null}
filters={history}
onClear={() => {}}
alertStore={alertStore}
diff --git a/ui/src/Components/TooltipWrapper/index.test.tsx b/ui/src/Components/TooltipWrapper/index.test.tsx
index ed4fe165d..5f5ff1654 100644
--- a/ui/src/Components/TooltipWrapper/index.test.tsx
+++ b/ui/src/Components/TooltipWrapper/index.test.tsx
@@ -29,7 +29,7 @@ describe("TooltipWrapper", () => {
expect(tree.find("div.foo").text()).toBe("Hover me");
});
- it("on non-touch devices it renders tooltip on mouseOver and hides on mouseLeave", () => {
+ it("on non-touch devices it renders tooltip on mouseOver and hides on mouseLeave", async () => {
const tree = mount(
Hover me
@@ -49,9 +49,13 @@ describe("TooltipWrapper", () => {
});
tree.update();
expect(tree.find("div.tooltip")).toHaveLength(0);
+
+ await act(async () => {
+ jest.runAllTimers();
+ });
});
- it("on touch devices it renders tooltip on touchStart and hides on touchEnd", () => {
+ it("on touch devices it renders tooltip on touchStart and hides on touchEnd", async () => {
const tree = mount(
Hover me
@@ -77,6 +81,10 @@ describe("TooltipWrapper", () => {
});
tree.update();
expect(tree.find("div.tooltip")).toHaveLength(0);
+
+ await act(async () => {
+ jest.runAllTimers();
+ });
});
it("hides the tooltip after click and show again on mouseOver", () => {
diff --git a/ui/src/Components/TooltipWrapper/index.tsx b/ui/src/Components/TooltipWrapper/index.tsx
index f473f8ee3..2db7b32f3 100644
--- a/ui/src/Components/TooltipWrapper/index.tsx
+++ b/ui/src/Components/TooltipWrapper/index.tsx
@@ -3,7 +3,7 @@ import { createPortal } from "react-dom";
import { CSSTransition } from "react-transition-group";
-import { usePopper } from "react-popper";
+import { useFloating, shift, flip } from "@floating-ui/react-dom";
import { useSupportsTouch } from "Hooks/useSupportsTouch";
@@ -12,20 +12,9 @@ const TooltipWrapper: FC<{
children: ReactNode;
className?: string;
}> = ({ title, children, className }) => {
- const [referenceElement, setReferenceElement] = useState(
- null
- );
- const [popperElement, setPopperElement] = useState(null);
- const { styles, attributes } = usePopper(referenceElement, popperElement, {
+ const { x, y, reference, floating, strategy } = useFloating({
placement: "top",
- modifiers: [
- {
- name: "preventOverflow",
- options: {
- rootBoundary: "viewport",
- },
- },
- ],
+ middleware: [shift(), flip()],
});
const supportsTouch = useSupportsTouch();
@@ -69,7 +58,7 @@ const TooltipWrapper: FC<{
onTouchStart={supportsTouch ? showTooltip : undefined}
onTouchCancel={supportsTouch ? hideTooltip : undefined}
onTouchEnd={supportsTouch ? hideTooltip : undefined}
- ref={setReferenceElement}
+ ref={reference}
className={`${className ? className : ""} tooltip-trigger`}
>
{children}
@@ -86,9 +75,12 @@ const TooltipWrapper: FC<{
>
{title}
diff --git a/ui/src/Styles/Components/_History.scss b/ui/src/Styles/Components/_History.scss
index 6aad5348c..ec8142446 100644
--- a/ui/src/Styles/Components/_History.scss
+++ b/ui/src/Styles/Components/_History.scss
@@ -1,5 +1,6 @@
.dropdown-menu.components-navbar-historymenu {
white-space: nowrap;
+ overflow: scroll;
}
.dropdown-menu.components-navbar-historymenu > .dropdown-item {
diff --git a/ui/src/__mocks__/popper.js.ts b/ui/src/__mocks__/popper.js.ts
deleted file mode 100644
index 19b1571bb..000000000
--- a/ui/src/__mocks__/popper.js.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-// https://github.com/FezVrasta/popper.js/issues/478#issuecomment-341506071
-
-export default class Popper {
- constructor() {
- return {
- destroy: () => {},
- scheduleUpdate: () => {},
- };
- }
-}
diff --git a/ui/src/setupTests.ts b/ui/src/setupTests.ts
index 2eced79ee..b828bc3fb 100644
--- a/ui/src/setupTests.ts
+++ b/ui/src/setupTests.ts
@@ -50,7 +50,7 @@ global.console.trace = consoleHandler;
FetchRetryConfig.minTimeout = 2;
FetchRetryConfig.maxTimeout = 10;
-// usePopper uses useLayoutEffect but that fails in enzyme
+// floating-ui uses useLayoutEffect but that fails in enzyme
React.useLayoutEffect = React.useEffect;
beforeEach(() => {