Add dashboard filter context to remove prop drilling
This commit is contained in:
67
frontend/src/providers/DashboardFilterProvider.tsx
Normal file
67
frontend/src/providers/DashboardFilterProvider.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
|
||||
import { getDataSource } from "../utils/queryUtils";
|
||||
import type { BanOriginFilter, TimeRange } from "../types/ban";
|
||||
|
||||
interface DashboardFilterContextValue {
|
||||
/** Currently selected dashboard time-range preset. */
|
||||
timeRange: TimeRange;
|
||||
/** Currently selected dashboard origin filter. */
|
||||
originFilter: BanOriginFilter;
|
||||
/** Derived data source for the active time-range. */
|
||||
source: "fail2ban" | "archive";
|
||||
/** Update the selected time-range preset. */
|
||||
setTimeRange: (value: TimeRange) => void;
|
||||
/** Update the selected origin filter. */
|
||||
setOriginFilter: (value: BanOriginFilter) => void;
|
||||
}
|
||||
|
||||
const DashboardFilterContext = createContext<DashboardFilterContextValue | null>(null);
|
||||
|
||||
const DEFAULT_FILTERS: DashboardFilterContextValue = {
|
||||
timeRange: "24h",
|
||||
originFilter: "all",
|
||||
source: "fail2ban",
|
||||
setTimeRange: (): void => {
|
||||
// no-op when no provider is present
|
||||
},
|
||||
setOriginFilter: (): void => {
|
||||
// no-op when no provider is present
|
||||
},
|
||||
};
|
||||
|
||||
interface DashboardFilterProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function DashboardFilterProvider({ children }: DashboardFilterProviderProps): React.JSX.Element {
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>(DEFAULT_FILTERS.timeRange);
|
||||
const [originFilter, setOriginFilter] = useState<BanOriginFilter>(DEFAULT_FILTERS.originFilter);
|
||||
|
||||
const setTimeRangeCallback = useCallback((value: TimeRange): void => {
|
||||
setTimeRange(value);
|
||||
}, []);
|
||||
|
||||
const setOriginFilterCallback = useCallback((value: BanOriginFilter): void => {
|
||||
setOriginFilter(value);
|
||||
}, []);
|
||||
|
||||
const source = useMemo(() => getDataSource(timeRange), [timeRange]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
timeRange,
|
||||
originFilter,
|
||||
source,
|
||||
setTimeRange: setTimeRangeCallback,
|
||||
setOriginFilter: setOriginFilterCallback,
|
||||
}),
|
||||
[timeRange, originFilter, source, setTimeRangeCallback, setOriginFilterCallback],
|
||||
);
|
||||
|
||||
return <DashboardFilterContext.Provider value={value}>{children}</DashboardFilterContext.Provider>;
|
||||
}
|
||||
|
||||
export function useDashboardFilters(): DashboardFilterContextValue {
|
||||
const context = useContext(DashboardFilterContext);
|
||||
return context ?? DEFAULT_FILTERS;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { DashboardFilterBar } from "../../components/DashboardFilterBar";
|
||||
import { DashboardFilterProvider, useDashboardFilters } from "../DashboardFilterProvider";
|
||||
|
||||
function DashboardFilterSummary(): React.JSX.Element {
|
||||
const { timeRange, originFilter } = useDashboardFilters();
|
||||
return <div data-testid="dashboard-filters">{`${timeRange}-${originFilter}`}</div>;
|
||||
}
|
||||
|
||||
describe("DashboardFilterProvider", () => {
|
||||
it("provides filter state to DashboardFilterBar and updates when the user clicks", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<DashboardFilterProvider>
|
||||
<DashboardFilterBar />
|
||||
<DashboardFilterSummary />
|
||||
</DashboardFilterProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("dashboard-filters")).toHaveTextContent("24h-all");
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /Last 7 days/i }));
|
||||
expect(screen.getByTestId("dashboard-filters")).toHaveTextContent("7d-all");
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /Blocklist/i }));
|
||||
expect(screen.getByTestId("dashboard-filters")).toHaveTextContent("7d-blocklist");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user