mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
feat(ui): add a quick-access label select for multi-grid
This commit is contained in:
committed by
Łukasz Mierzwa
parent
e5b93eb8b3
commit
df82e6a232
@@ -5,6 +5,8 @@
|
||||
### Added
|
||||
|
||||
- Add `/robots.txt` to block search engine crawlers.
|
||||
- Easily change multi-grid source label via quick access dropdown on the grid
|
||||
header.
|
||||
|
||||
## v0.79
|
||||
|
||||
|
||||
@@ -108,12 +108,16 @@ describe("<AlertMenu />", () => {
|
||||
const toggle = tree.find("span.cursor-pointer");
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(1);
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(0);
|
||||
@@ -130,7 +134,9 @@ describe("<AlertMenu />", () => {
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(1);
|
||||
|
||||
tree.find("a.dropdown-item").at(0).simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(0);
|
||||
|
||||
@@ -108,12 +108,16 @@ describe("<GroupMenu />", () => {
|
||||
const toggle = tree.find("span.cursor-pointer");
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(1);
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(0);
|
||||
@@ -131,7 +135,9 @@ describe("<GroupMenu />", () => {
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(1);
|
||||
|
||||
tree.find("div.dropdown-item").at(0).simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(0);
|
||||
|
||||
@@ -127,6 +127,7 @@ const Grid: FC<{
|
||||
>
|
||||
<Swimlane
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
grid={grid}
|
||||
isExpanded={isExpanded}
|
||||
onToggle={onCollapseClick}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import fetchMock from "fetch-mock";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { GridLabelSelect } from "./GridLabelSelect";
|
||||
|
||||
let settingsStore: Settings;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.mock("*", {
|
||||
body: JSON.stringify([]),
|
||||
});
|
||||
|
||||
settingsStore = new Settings(null);
|
||||
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
const MountedGridLabelSelect = () => {
|
||||
return mount(<GridLabelSelect settingsStore={settingsStore} />);
|
||||
};
|
||||
|
||||
describe("<GridLabelSelect />", () => {
|
||||
it("select dropdown is hidden by default", async () => {
|
||||
const promise = Promise.resolve();
|
||||
const tree = MountedGridLabelSelect();
|
||||
expect(tree.find("div.components-grid-label-select-menu")).toHaveLength(0);
|
||||
await act(() => promise);
|
||||
});
|
||||
|
||||
it("clicking toggle renders select dropdown", async () => {
|
||||
const promise = Promise.resolve();
|
||||
const tree = MountedGridLabelSelect();
|
||||
const toggle = tree.find("span.components-grid-label-select-dropdown");
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("div.components-grid-label-select-menu")).toHaveLength(1);
|
||||
await act(() => promise);
|
||||
});
|
||||
|
||||
it("clicking toggle twice hides select dropdown", async () => {
|
||||
const promise = Promise.resolve();
|
||||
const tree = MountedGridLabelSelect();
|
||||
const toggle = tree.find("span.components-grid-label-select-dropdown");
|
||||
|
||||
toggle.simulate("click");
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(tree.find("div.components-grid-label-select-menu")).toHaveLength(1);
|
||||
|
||||
toggle.simulate("click");
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div.components-grid-label-select-menu")).toHaveLength(0);
|
||||
await act(() => promise);
|
||||
});
|
||||
|
||||
it("clicking outside hides select dropdown", async () => {
|
||||
const promise = Promise.resolve();
|
||||
const tree = MountedGridLabelSelect();
|
||||
const toggle = tree.find("span.components-grid-label-select-dropdown");
|
||||
|
||||
toggle.simulate("click");
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(tree.find("div.components-grid-label-select-menu")).toHaveLength(1);
|
||||
|
||||
const clickEvent = document.createEvent("MouseEvents");
|
||||
clickEvent.initEvent("mousedown", true, true);
|
||||
act(() => {
|
||||
document.dispatchEvent(clickEvent);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div.components-grid-label-select-menu")).toHaveLength(0);
|
||||
await act(() => promise);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, {
|
||||
FC,
|
||||
Ref,
|
||||
CSSProperties,
|
||||
useRef,
|
||||
useState,
|
||||
useCallback,
|
||||
} from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { Manager, Reference, Popper } from "react-popper";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { CommonPopperModifiers } from "Common/Popper";
|
||||
import { DropdownSlide } from "Components/Animations/DropdownSlide";
|
||||
import { useOnClickOutside } from "Hooks/useOnClickOutside";
|
||||
import { GridLabelName } from "Components/MainModal/Configuration/GridLabelName";
|
||||
|
||||
const NullContainer: FC = () => null;
|
||||
|
||||
const Dropdown: FC<{
|
||||
popperPlacement?: string;
|
||||
popperRef?: Ref<HTMLDivElement>;
|
||||
popperStyle?: CSSProperties;
|
||||
settingsStore: Settings;
|
||||
}> = ({ popperPlacement, popperRef, popperStyle, settingsStore }) => {
|
||||
return (
|
||||
<div
|
||||
className="dropdown-menu d-block shadow components-grid-label-select-menu border-0 p-0"
|
||||
ref={popperRef}
|
||||
style={{
|
||||
fontSize: "1rem",
|
||||
fontWeight: "normal",
|
||||
...popperStyle,
|
||||
}}
|
||||
data-placement={popperPlacement}
|
||||
>
|
||||
<GridLabelName
|
||||
settingsStore={settingsStore}
|
||||
isOpen={true}
|
||||
selectComponents={{
|
||||
ClearIndicator: null,
|
||||
IndicatorSeparator: null,
|
||||
DropdownIndicator: null,
|
||||
ValueContainer: NullContainer,
|
||||
Control: NullContainer,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GridLabelSelect: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = observer(({ settingsStore }) => {
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
const hide = useCallback(() => setIsVisible(false), []);
|
||||
const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]);
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useOnClickOutside(ref, hide, isVisible);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="components-label badge pl-1 pr-2">
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
className="border-0 rounded-0 bg-inherit cursor-pointer px-1 py-0 components-grid-label-select-dropdown"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon className="text-muted" icon={faCaretDown} />
|
||||
</span>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={isVisible} unmountOnExit>
|
||||
<Popper modifiers={CommonPopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<Dropdown
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
settingsStore={settingsStore}
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export { GridLabelSelect };
|
||||
@@ -4,18 +4,21 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faTh } from "@fortawesome/free-solid-svg-icons/faTh";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { APIGridT } from "Models/APITypes";
|
||||
import FilteringLabel from "Components/Labels/FilteringLabel";
|
||||
import FilteringCounterBadge from "Components/Labels/FilteringCounterBadge";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
import { GridLabelSelect } from "./GridLabelSelect";
|
||||
|
||||
const Swimlane: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
grid: APIGridT;
|
||||
isExpanded: boolean;
|
||||
onToggle: (event: MouseEvent) => void;
|
||||
}> = ({ alertStore, grid, isExpanded, onToggle }) => {
|
||||
}> = ({ alertStore, settingsStore, grid, isExpanded, onToggle }) => {
|
||||
return (
|
||||
<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">
|
||||
@@ -23,7 +26,7 @@ const Swimlane: FC<{
|
||||
<FontAwesomeIcon icon={faTh} className="text-muted" />
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex-shrink-1 flex-grow-1" style={{ minWidth: "0px" }}>
|
||||
<span className="flex-shrink-1 flex-grow-0" style={{ minWidth: "0px" }}>
|
||||
{grid.labelName !== "" && grid.labelValue !== "" && (
|
||||
<FilteringLabel
|
||||
key={grid.labelValue}
|
||||
@@ -33,6 +36,14 @@ const Swimlane: FC<{
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
{grid.labelName !== "" && grid.labelValue !== "" && (
|
||||
<span
|
||||
className="flex-shrink-0 flex-grow-1 px-0"
|
||||
style={{ minWidth: "0px" }}
|
||||
>
|
||||
<GridLabelSelect settingsStore={settingsStore} />
|
||||
</span>
|
||||
)}
|
||||
<span className="flex-shrink-0 flex-grow-0 ml-2 mr-0">
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
|
||||
@@ -248,7 +248,7 @@ describe("<Grid />", () => {
|
||||
tree.setProps({ grid: grid });
|
||||
expect(tree.find("AlertGroup")).toHaveLength(10);
|
||||
|
||||
tree.find("span.cursor-pointer").at(0).simulate("click");
|
||||
tree.find("span.cursor-pointer").at(1).simulate("click");
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
@@ -257,7 +257,7 @@ describe("<Grid />", () => {
|
||||
0
|
||||
);
|
||||
|
||||
tree.find("span.cursor-pointer").at(0).simulate("click");
|
||||
tree.find("span.cursor-pointer").at(1).simulate("click");
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
@@ -625,7 +625,7 @@ describe("<AlertGrid />", () => {
|
||||
.find("Grid")
|
||||
.at(0)
|
||||
.find("span.cursor-pointer")
|
||||
.at(0)
|
||||
.at(1)
|
||||
.simulate("click", { altKey: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import Creatable from "react-select/creatable";
|
||||
import { GroupTypeBase, SelectComponentsConfig } from "react-select";
|
||||
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
import { FormatBackendURI } from "Stores/AlertStore";
|
||||
@@ -24,7 +25,11 @@ const staticValues = [
|
||||
|
||||
const GridLabelName: FC<{
|
||||
settingsStore: Settings;
|
||||
}> = ({ settingsStore }) => {
|
||||
isOpen?: boolean | undefined;
|
||||
selectComponents?:
|
||||
| Partial<SelectComponentsConfig<OptionT, false, GroupTypeBase<OptionT>>>
|
||||
| undefined;
|
||||
}> = ({ settingsStore, isOpen = undefined, selectComponents = undefined }) => {
|
||||
const { response } = useFetchGet<string[]>(
|
||||
FormatBackendURI(`labelNames.json`)
|
||||
);
|
||||
@@ -51,6 +56,8 @@ const GridLabelName: FC<{
|
||||
onChange={(option) => {
|
||||
settingsStore.multiGridConfig.config.gridLabel = (option as OptionT).value;
|
||||
}}
|
||||
menuIsOpen={isOpen}
|
||||
components={selectComponents}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,7 +47,9 @@ const PopulateHistory = (tree: ReactWrapper, count: number) => {
|
||||
AppliedFilter("baz", "=~", `bar${i}`),
|
||||
]);
|
||||
tree.update();
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,11 +76,15 @@ describe("<History />", () => {
|
||||
const toggle = tree.find("button.cursor-pointer");
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(1);
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(0);
|
||||
await act(() => promise);
|
||||
@@ -93,7 +99,9 @@ describe("<History />", () => {
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(1);
|
||||
|
||||
tree.find(".component-history-button").at(0).simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(0);
|
||||
await act(() => promise);
|
||||
@@ -158,7 +166,9 @@ describe("<HistoryMenu />", () => {
|
||||
expect(alertStore.filters.values).toHaveLength(1);
|
||||
|
||||
button.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(alertStore.filters.values).toHaveLength(2);
|
||||
expect(alertStore.filters.values[0]).toMatchObject({ raw: "foo=bar1" });
|
||||
expect(alertStore.filters.values[1]).toMatchObject({ raw: "baz=~bar1" });
|
||||
@@ -229,7 +239,9 @@ describe("<HistoryMenu />", () => {
|
||||
expect(button.text()).toBe("Save filters");
|
||||
|
||||
button.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(settingsStore.savedFilters.config.filters).toHaveLength(2);
|
||||
expect(settingsStore.savedFilters.config.filters).toContain("foo=bar");
|
||||
expect(settingsStore.savedFilters.config.filters).toContain("bar=~baz");
|
||||
@@ -243,7 +255,9 @@ describe("<HistoryMenu />", () => {
|
||||
const button = tree.find(".component-history-button").at(1);
|
||||
expect(button.text()).toBe("Reset filters");
|
||||
button.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
expect(settingsStore.savedFilters.config.filters).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -256,7 +270,9 @@ describe("<HistoryMenu />", () => {
|
||||
const button = tree.find(".component-history-button").at(2);
|
||||
expect(button.text()).toBe("Clear history");
|
||||
button.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("button.dropdown-item")).toHaveLength(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user