Skip to content

Commit

Permalink
Save load filters (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
vish9812 authored Dec 6, 2024
1 parent c3cea4b commit 2221c50
Show file tree
Hide file tree
Showing 5 changed files with 616 additions and 103 deletions.
28 changes: 28 additions & 0 deletions ui/src/components/filters/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import useViewModel, {
SearchTerm,
FiltersProps,
GridsRefs,
savedFiltersNames,
} from "./useViewModel";
import { AgGridSolidRef } from "ag-grid-solid";
import { GridOptions } from "ag-grid-community";
Expand Down Expand Up @@ -55,6 +56,7 @@ function Filters(props: FiltersProps) {
};

const {
savedFilterName,
filters,
msgs,
httpCodes,
Expand All @@ -64,11 +66,15 @@ function Filters(props: FiltersProps) {
addedLogs,
removedLogs,
setFilters,
setSavedFilterName,
handleFiltersChange,
handleLogsSelectionChanged,
handleErrorsOnlyChange,
handleResetClick,
handleNewSearchTerm,
handleSaveFilter,
handleLoadFilter,
handleDeleteFilters,
} = useViewModel(props);

const commonGridOptions: GridOptions<GroupedMsg> = {
Expand Down Expand Up @@ -251,6 +257,28 @@ function Filters(props: FiltersProps) {
<Alert severity="info">
N-Logs works only with the below "selection" filters.
</Alert>
<TextField
label="Filter Name"
value={savedFilterName()}
onChange={(_, val) => setSavedFilterName(val)}
/>
<Button variant="contained" onClick={handleSaveFilter}>
Save Filters
</Button>
<FormControlLabel
control={
<Select
class="app-select"
options={savedFiltersNames()}
onChange={(val) => handleLoadFilter(val)}
/>
}
label="Load Filter"
labelPlacement="start"
/>
<Button variant="contained" onClick={handleDeleteFilters}>
Delete Filters
</Button>
</Stack>
</Grid>
<Grid item xs={12} container spacing={2}>
Expand Down
145 changes: 142 additions & 3 deletions ui/src/components/filters/useViewModel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { createRoot } from "solid-js";
import useViewModel, { GridsRefs, defaultFilters } from "./useViewModel";
import useViewModel, {
FiltersData,
GridsRefs,
defaultFilters,
savedFilterKey,
} from "./useViewModel";
import { FiltersProps } from "./useViewModel";
import comparer from "@al/services/comparer";
import LogData, { Summary } from "@al/models/logData";
Expand Down Expand Up @@ -62,6 +67,7 @@ describe("useViewModel", () => {
};

comparer.removed = [summary.msgs[0], summary.msgs[2]];
comparer.unchanged = [summary.msgs[1]];
comparer.added = [
{
logs: [{}, {}, {}, {}, {}],
Expand All @@ -88,15 +94,17 @@ describe("useViewModel", () => {
});

afterEach(() => {
vi.restoreAllMocks();
vi.clearAllMocks();
});

test("initial values", () => {
createRoot((dispose) => {
const vm = useViewModel(null as any);

expect(vm.savedFilterName(), "savedFilterName").toEqual("");
expect(vm.addedLogs(), "addedLogs").toEqual(comparer.added);
expect(vm.removedLogs(), "removedLogs").toEqual(comparer.removed);
expect(vm.unchangedLogs(), "unchangedLogs").toEqual(comparer.unchanged);
expect(vm.msgs(), "msgs").toEqual(comparer.last().summary.msgs);
expect(vm.httpCodes(), "httpCodes").toEqual(
comparer.last().summary.httpCodes
Expand All @@ -109,6 +117,118 @@ describe("useViewModel", () => {
});
});

describe("manageSavedFilters", () => {
const filterName = "test_filter";

const filterToSave: FiltersData = {
startTime: "2023-10-20T01:00:00.000Z",
endTime: "2023-10-20T11:00:00.000Z",
errorsOnly: true,
logs: [
{
some_key: "some_value",
},
],
regex: "^some.*regex$",
terms: [
{
and: true,
contains: true,
field: "some_field",
value: "ands",
},
],
firstN: 1,
lastN: 1,
};

const expectedSavedFilter = { ...filterToSave, logs: [] };

beforeEach(() => {
localStorage.clear();
});

test("handleSaveFilter", () => {
createRoot((dispose) => {
const vm = useViewModel(props);

vm.setFilters(filterToSave);
vm.setSavedFilterName(filterName);
vm.handleSaveFilter();

expect(vm.savedFilterName(), "savedFilterName").toEqual(filterName);
expect(localStorage.length, "localStorage.length").toEqual(1);
const loadedFilterStr = localStorage.getItem(
savedFilterKey(filterName)
);
expect(loadedFilterStr, "loadedFilterStr").toBeTruthy();
const loadedFilterJSON = JSON.parse(loadedFilterStr!);
expect(loadedFilterJSON, "loadedFilterJSON").toEqual(
expectedSavedFilter
);

dispose();
});
});

test("handleDeleteFilters", () => {
createRoot((dispose) => {
const vm = useViewModel(props);

vm.setFilters(filterToSave);
vm.setSavedFilterName(filterName);

expect(localStorage.length, "localStorage.length-pre-save").toEqual(0);
vm.handleSaveFilter();

expect(localStorage.length, "localStorage.length-pre-clear").toEqual(1);
vm.handleDeleteFilters();
expect(localStorage.length, "localStorage.length-post-clear").toEqual(
0
);
expect(vm.savedFilterName(), "savedFilterName").toEqual("");

dispose();
});
});

describe("handleLoadFilter", () => {
test("whenNoFilterIsAvailableToLoad", () => {
createRoot((dispose) => {
const vm = useViewModel(props);

vm.handleLoadFilter(filterName);

expect(vm.savedFilterName(), "savedFilterName").toEqual("");
expect(vm.filters, "filters").toEqual(defaultFilters());
expect(props.onFiltersChange, "onFiltersChange").toBeCalledTimes(0);

dispose();
});
});

test("whenAFilterIsAvailableToLoad", () => {
createRoot((dispose) => {
const vm = useViewModel(props);

localStorage.setItem(
savedFilterKey(filterName),
JSON.stringify(expectedSavedFilter)
);
vm.handleLoadFilter(filterName);

expect(vm.savedFilterName(), "savedFilterName").toEqual(filterName);
expect(vm.filters, "filters").toEqual(expectedSavedFilter);
expect(props.onFiltersChange, "onFiltersChange").toBeCalledWith(
vm.filters
);

dispose();
});
});
});
});

test("handleFiltersChange", () => {
createRoot((dispose) => {
const vm = useViewModel(props);
Expand Down Expand Up @@ -137,6 +257,7 @@ describe("useViewModel", () => {
plugins: getGrid() as any,
added: getGrid() as any,
removed: getGrid() as any,
unchanged: getGrid() as any,
};

const vm = useViewModel(props);
Expand All @@ -149,11 +270,15 @@ describe("useViewModel", () => {
}));
expect(vm.filters.regex, "regex").toEqual("some regex");

vm.setSavedFilterName("test_filter")

vm.handleResetClick(gridsRefs);

expect(vm.savedFilterName(), "savedFilterName").toEqual("");
expect(vm.filters, "filters").toEqual(defaultFilters());
expect(vm.addedLogs(), "addedLogs").toEqual(comparer.added);
expect(vm.removedLogs(), "removedLogs").toEqual(comparer.removed);
expect(vm.unchangedLogs(), "unchangedLogs").toEqual(comparer.unchanged);
expect(vm.msgs(), "msgs").toEqual(comparer.last().summary.msgs);
expect(vm.httpCodes(), "httpCodes").toEqual(
comparer.last().summary.httpCodes
Expand Down Expand Up @@ -241,6 +366,14 @@ describe("useViewModel", () => {
],
},
} as any,
unchanged: {
api: {
getSelectedRows: () => [
{ logs: [{ id: 2 }, { id: 11 }, { id: 4 }] },
{ logs: [{ id: 5 }] },
],
},
} as any,
removed: undefined as any,
};

Expand All @@ -256,8 +389,9 @@ describe("useViewModel", () => {
{ id: 4 },
{ id: 5 },
{ id: 6 },
// { id: 7 }, // both skipped due to firstN=1 and lastN=1
// { id: 7 }, // all 3 are skipped due to firstN=1 and lastN=1, so only first 1 and last 1 logs are selected
// { id: 10 },
// { id: 11 },
{ id: 20 },
{ id: 30 },
];
Expand All @@ -284,10 +418,14 @@ describe("useViewModel", () => {
const errPlugins = [summary.plugins[1]];
const errAddedTopLogs = [comparer.added[1]];
const errRemovedTopLogs = [comparer.removed[1]];
const errUnchangedTopLogs = [comparer.unchanged[0]];

expect(vm.filters.errorsOnly, "errorsOnly").toEqual(checked);
expect(vm.addedLogs(), "addedLogs").toEqual(errAddedTopLogs);
expect(vm.removedLogs(), "removedLogs").toEqual(errRemovedTopLogs);
expect(vm.unchangedLogs(), "unchangedLogs").toEqual(
errUnchangedTopLogs
);
expect(vm.msgs(), "msgs").toEqual(errMsgs);
expect(vm.httpCodes(), "httpCodes").toEqual(errHTTPCodes);
expect(vm.jobs(), "jobs").toEqual(errJobs);
Expand All @@ -310,6 +448,7 @@ describe("useViewModel", () => {
expect(vm.filters.errorsOnly, "errorsOnly").toEqual(checked);
expect(vm.addedLogs(), "addedLogs").toEqual(comparer.added);
expect(vm.removedLogs(), "removedLogs").toEqual(comparer.removed);
expect(vm.unchangedLogs(), "unchangedLogs").toEqual(comparer.unchanged);
expect(vm.msgs(), "msgs").toEqual(summary.msgs);
expect(vm.httpCodes(), "httpCodes").toEqual(summary.httpCodes);
expect(vm.jobs(), "jobs").toEqual(summary.jobs);
Expand Down
53 changes: 50 additions & 3 deletions ui/src/components/filters/useViewModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,24 @@ function defaultFilters(): FiltersData {
};
}

const savedFiltersKeyPrefix = "saved_filters|";

function savedFilterKey(filterName: string): string {
return savedFiltersKeyPrefix + filterName;
}

function savedFiltersKeys(): string[] {
return Object.keys(localStorage).filter((key) =>
key.startsWith(savedFiltersKeyPrefix)
);
}

function savedFiltersNames(): string[] {
return savedFiltersKeys().map((key) => key.split("|")[1]);
}

function useViewModel(props: FiltersProps) {
const [savedFilterName, setSavedFilterName] = createSignal("");
const [filters, setFilters] = createStore(defaultFilters());
const [msgs, setMsgs] = createSignal(comparer.last().summary.msgs);
const [httpCodes, setHTTPCodes] = createSignal(
Expand All @@ -68,11 +85,32 @@ function useViewModel(props: FiltersProps) {
const [addedLogs, setAddedLogs] = createSignal(comparer.added);
const [removedLogs, setRemovedLogs] = createSignal(comparer.removed);

function handleSaveFilter() {
// save filters except the logs key
const filterStr = JSON.stringify({ ...filters, logs: [] });
localStorage.setItem(savedFilterKey(savedFilterName()), filterStr);
}

function handleLoadFilter(filterName: string) {
const filtersStr = localStorage.getItem(savedFilterKey(filterName));
if (!filtersStr) return;

setSavedFilterName(filterName);
setFilters(JSON.parse(filtersStr));
handleFiltersChange();
}

function handleDeleteFilters() {
savedFiltersKeys().forEach((key) => localStorage.removeItem(key));
setSavedFilterName("");
}

function handleFiltersChange() {
props.onFiltersChange(filters);
}

function handleResetClick(gridsRefs: GridsRefs) {
setSavedFilterName("");
setFilters(defaultFilters());
handleErrorsOnlyChange(false);

Expand Down Expand Up @@ -150,7 +188,7 @@ function useViewModel(props: FiltersProps) {
setHTTPCodes(comparer.last().summary.httpCodes);
setJobs(comparer.last().summary.jobs);
setPlugins(comparer.last().summary.plugins);
setUnchangedLogs(comparer.added);
setUnchangedLogs(comparer.unchanged);
setAddedLogs(comparer.added);
setRemovedLogs(comparer.removed);
}
Expand All @@ -173,6 +211,7 @@ function useViewModel(props: FiltersProps) {
}

return {
savedFilterName,
filters,
msgs,
httpCodes,
Expand All @@ -182,14 +221,22 @@ function useViewModel(props: FiltersProps) {
addedLogs,
removedLogs,
setFilters,
setSavedFilterName,
handleFiltersChange,
handleLogsSelectionChanged,
handleErrorsOnlyChange,
handleResetClick,
handleNewSearchTerm,
handleSaveFilter,
handleLoadFilter,
handleDeleteFilters,
};
}

export default useViewModel;
export { defaultFilters };
export {
defaultFilters,
savedFiltersNames,
savedFilterKey,
};
export type { SearchTerm, FiltersData, FiltersProps, GridsRefs };
Loading

0 comments on commit 2221c50

Please sign in to comment.