Skip to content

Commit 17f0624

Browse files
authored
Key val search (#13)
* add key value search support * allow n-logs filter to run on each key down
1 parent a307a79 commit 17f0624

File tree

9 files changed

+129
-20
lines changed

9 files changed

+129
-20
lines changed

cmd/bun.lockb

0 Bytes
Binary file not shown.

cmd/src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,7 @@ Example:
8989
9090
Caution: Processing multiple files will need at least twice the space as the logs files size.
9191
For example, if you are analyzing 4GB of logs make sure you have 8GB of *free* RAM left for smoother processing.
92+
93+
A few utility commands can also be found here - https://github.com/vish9812/analog?tab=readme-ov-file#utility-commands.
9294
`);
9395
}

ui/src/components/filters/index.tsx

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
Alert,
23
Button,
34
Divider,
45
FormControlLabel,
@@ -15,19 +16,21 @@ import useViewModel, {
1516
import { AgGridSolidRef } from "ag-grid-solid";
1617
import { GridOptions } from "ag-grid-community";
1718
import { Accessor, For, Show } from "solid-js";
18-
import { Select } from "@thisbeyond/solid-select";
19+
import { Select, createOptions } from "@thisbeyond/solid-select";
1920
import { IconButton } from "@suid/material";
2021
import AddIcon from "@suid/icons-material/Add";
2122
import RemoveIcon from "@suid/icons-material/Remove";
2223
import comparer from "@al/services/comparer";
2324
import { GroupedMsg } from "@al/models/logData";
2425
import GroupedMsgGrid from "../groupedMsgGrid";
26+
import timesUtils from "@al/utils/times";
2527

2628
const texts = {
2729
and: "AND",
2830
or: "OR",
2931
contains: "Contains",
3032
notContains: "Not Contains",
33+
allFields: "All Fields",
3134
};
3235

3336
interface GridsOptions {
@@ -114,16 +117,14 @@ function Filters(props: FiltersProps) {
114117
},
115118
};
116119

117-
function handleEnterKey(e: KeyboardEvent) {
120+
function handleFiltersEnterKey(e: KeyboardEvent) {
118121
if (e.key === "Enter") {
119122
handleFiltersChange();
120123
}
121124
}
122125

123-
function handleNLogsEnter(e: KeyboardEvent) {
124-
if (e.key === "Enter") {
125-
handleLogsSelectionChanged(gridsRefs);
126-
}
126+
function handleNLogsKeyDown(e: KeyboardEvent) {
127+
timesUtils.debounce(handleLogsSelectionChanged, 600)(gridsRefs);
127128
}
128129

129130
function getSimpleSearchHTML(term: SearchTerm, i: Accessor<number>) {
@@ -143,13 +144,26 @@ function Filters(props: FiltersProps) {
143144
setFilters("terms", i(), "contains", val === texts.contains)
144145
}
145146
/>
147+
<Select
148+
class="app-select"
149+
initialValue={texts.allFields}
150+
{...createOptions([texts.allFields, ...comparer.last().keys])}
151+
onChange={(val) =>
152+
setFilters(
153+
"terms",
154+
i(),
155+
"field",
156+
val === texts.allFields ? "" : val
157+
)
158+
}
159+
/>
146160
<TextField
147161
label="Search"
148162
value={term.value}
149163
onChange={(_, val) =>
150164
setFilters("terms", i(), "value", val.toLowerCase())
151165
}
152-
onKeyDown={handleEnterKey}
166+
onKeyDown={handleFiltersEnterKey}
153167
/>
154168
</>
155169
);
@@ -163,19 +177,19 @@ function Filters(props: FiltersProps) {
163177
label="Start Time(Inclusive)"
164178
value={filters.startTime}
165179
onChange={(_, val) => setFilters("startTime", val)}
166-
onKeyDown={handleEnterKey}
180+
onKeyDown={handleFiltersEnterKey}
167181
/>
168182
<TextField
169183
label="End Time(Exclusive)"
170184
value={filters.endTime}
171185
onChange={(_, val) => setFilters("endTime", val)}
172-
onKeyDown={handleEnterKey}
186+
onKeyDown={handleFiltersEnterKey}
173187
/>
174188
<TextField
175189
label="Regex Search"
176190
value={filters.regex}
177191
onChange={(_, val) => setFilters("regex", val)}
178-
onKeyDown={handleEnterKey}
192+
onKeyDown={handleFiltersEnterKey}
179193
/>
180194
<For each={filters.terms}>{getSimpleSearchHTML}</For>
181195
<IconButton color="primary" onClick={() => handleNewSearchTerm(true)}>
@@ -220,16 +234,19 @@ function Filters(props: FiltersProps) {
220234
onChange={(_, val) =>
221235
setFilters("firstN", isNaN(+val) || +val < 0 ? 0 : +val)
222236
}
223-
onKeyDown={handleNLogsEnter}
237+
onKeyDown={handleNLogsKeyDown}
224238
/>
225239
<TextField
226240
label="Last N Logs"
227241
value={filters.lastN}
228242
onChange={(_, val) =>
229243
setFilters("lastN", isNaN(+val) || +val < 0 ? 0 : +val)
230244
}
231-
onKeyDown={handleNLogsEnter}
245+
onKeyDown={handleNLogsKeyDown}
232246
/>
247+
<Alert severity="info">
248+
N-Logs works only with the below "selection" filters.
249+
</Alert>
233250
</Stack>
234251
</Grid>
235252
<Grid item xs={12} container spacing={2}>

ui/src/components/filters/useViewModel.test.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,18 @@ describe("useViewModel", () => {
235235
} as any,
236236
added: {
237237
api: {
238-
getSelectedRows: () => [{ logs: [{ id: 1 }, { id: 7 }] }],
238+
getSelectedRows: () => [
239+
{ logs: [{ id: 1 }, { id: 7 }, { id: 10 }, { id: 20 }] },
240+
{ logs: [{ id: 3 }, { id: 30 }] },
241+
],
239242
},
240243
} as any,
241244
removed: undefined as any,
242245
};
243246

244247
const vm = useViewModel(props);
248+
vm.setFilters("firstN", 1);
249+
vm.setFilters("lastN", 1);
245250
vm.handleLogsSelectionChanged(gridsRefs);
246251

247252
const logs = [
@@ -251,11 +256,14 @@ describe("useViewModel", () => {
251256
{ id: 4 },
252257
{ id: 5 },
253258
{ id: 6 },
254-
{ id: 7 },
259+
// { id: 7 }, // both skipped due to firstN=1 and lastN=1
260+
// { id: 10 },
261+
{ id: 20 },
262+
{ id: 30 },
255263
];
256264
expect(vm.filters.logs, "filters.logs").toEqual(logs);
257265
expect(props.onFiltersChange, "onFiltersChange").toBeCalledWith({
258-
...defaultFilters(),
266+
...vm.filters,
259267
logs,
260268
});
261269

@@ -326,6 +334,7 @@ describe("useViewModel", () => {
326334
{
327335
and: true,
328336
contains: true,
337+
field: "",
329338
value: "",
330339
},
331340
]);

ui/src/components/filters/useViewModel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface GridsRefs {
2020
interface SearchTerm {
2121
and: boolean;
2222
contains: boolean;
23+
field: string;
2324
value: string;
2425
}
2526

@@ -43,6 +44,7 @@ function defaultFilters(): FiltersData {
4344
{
4445
and: true,
4546
contains: true,
47+
field: "",
4648
value: "",
4749
},
4850
],
@@ -148,6 +150,7 @@ function useViewModel(props: FiltersProps) {
148150
setFilters("terms", filters.terms.length, {
149151
and: true,
150152
contains: true,
153+
field: "",
151154
value: "",
152155
});
153156
return;

ui/src/pages/analyze/useViewModel.test.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,19 @@ describe("useViewModel", () => {
6868
[LogData.logKeys.timestamp]: "2023-10-20T10:00:00.000Z",
6969
[LogData.logKeys.fullData]: "test one two contains check four",
7070
[LogData.logKeys.msg]: "test one two contains check four",
71+
random_key_1: "Random Value",
7172
},
7273
{
7374
[LogData.logKeys.id]: "24",
7475
[LogData.logKeys.timestamp]: "2023-10-20T10:00:00.000Z",
75-
[LogData.logKeys.fullData]: "test one two ands check four",
76-
[LogData.logKeys.msg]: "test one two ands check four",
76+
[LogData.logKeys.fullData]: "test one two ands four",
77+
[LogData.logKeys.msg]: "test one two ands four",
78+
},
79+
{
80+
[LogData.logKeys.id]: "25",
81+
[LogData.logKeys.timestamp]: "2023-10-20T10:00:00.000Z",
82+
[LogData.logKeys.fullData]: "test one two ands four",
83+
[LogData.logKeys.msg]: "test one two ands four",
7784
},
7885
{
7986
[LogData.logKeys.id]: "30",
@@ -88,6 +95,7 @@ describe("useViewModel", () => {
8895
.mockReturnValueOnce(true)
8996
.mockReturnValueOnce(true)
9097
.mockReturnValueOnce(true)
98+
.mockReturnValueOnce(true)
9199
.mockReturnValueOnce(false);
92100

93101
vi.spyOn(timesUtils, "diffMinutes").mockReturnValue(100);
@@ -105,6 +113,7 @@ describe("useViewModel", () => {
105113
endTime: "2023-10-20T11:00:00.000Z",
106114
errorsOnly: true,
107115
logs: [
116+
comparer.last().logs[0],
108117
comparer.last().logs[1],
109118
comparer.last().logs[2],
110119
comparer.last().logs[3],
@@ -115,19 +124,30 @@ describe("useViewModel", () => {
115124
{
116125
and: true,
117126
contains: true,
127+
field: "",
118128
value: "ands",
119129
},
120130
{
121131
and: true,
122132
contains: false,
133+
field: "",
123134
value: "msg",
124135
},
125136
{
126137
and: false,
127138
contains: true,
139+
field: "",
128140
value: "check",
129141
},
142+
{
143+
and: false,
144+
contains: true,
145+
field: "random_key_2",
146+
value: "random value",
147+
},
130148
],
149+
firstN: 0,
150+
lastN: 0,
131151
};
132152

133153
const vm = useViewModel();
@@ -136,6 +156,7 @@ describe("useViewModel", () => {
136156
expect(vm.rows(), "rows").toEqual([
137157
comparer.last().logs[2],
138158
comparer.last().logs[3],
159+
comparer.last().logs[4],
139160
]);
140161

141162
expect(mockUseJumper.reset).toHaveBeenCalledOnce();

ui/src/pages/analyze/useViewModel.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,30 @@ function useViewModel() {
5151
}
5252
if (keep && filtersData.terms) {
5353
const ands = filtersData.terms.filter((t) => t.and);
54+
5455
let currCondition = true;
5556
const updateCurrCondition = (term: SearchTerm) => {
57+
const field = term.field;
5658
const val = term.value.trim();
57-
if (val) {
59+
if (field && !val) {
5860
currCondition = term.contains
59-
? fullData.includes(val)
60-
: !fullData.includes(val);
61+
? log[field] !== undefined
62+
: log[field] === undefined;
63+
64+
return;
65+
}
66+
67+
if (val) {
68+
if (field) {
69+
const fieldVal = (log[field] && log[field].toString().toLowerCase()) || "";
70+
currCondition = term.contains
71+
? fieldVal.includes(val)
72+
: !fieldVal.includes(val);
73+
} else {
74+
currCondition = term.contains
75+
? fullData.includes(val)
76+
: !fullData.includes(val);
77+
}
6178
}
6279
};
6380

ui/src/utils/times.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import timesUtils from "./times";
2+
import { Mock } from "vitest";
23

34
test.each`
45
date1 | date2 | expected
@@ -11,3 +12,29 @@ test.each`
1112
expect(timesUtils.diffMinutes(date1, date2)).toBe(expected);
1213
}
1314
);
15+
16+
describe("debounce", () => {
17+
let mockFn: Mock;
18+
let debouncedFn: Function;
19+
20+
beforeEach(() => {
21+
vi.useFakeTimers();
22+
mockFn = vi.fn();
23+
debouncedFn = timesUtils.debounce(mockFn, 20);
24+
});
25+
26+
it("should not call function until delay is over", () => {
27+
debouncedFn();
28+
expect(mockFn).toHaveBeenCalledTimes(0);
29+
30+
vi.advanceTimersByTime(15); // Fast forward time to within the debounce duration
31+
expect(mockFn).toHaveBeenCalledTimes(0);
32+
});
33+
34+
it("should call function after delay has passed", () => {
35+
debouncedFn();
36+
37+
vi.advanceTimersByTime(21); // Fast forward time beyond the debounce duration
38+
expect(mockFn).toHaveBeenCalledTimes(1);
39+
});
40+
});

ui/src/utils/times.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,20 @@ function diffMinutes(date1: Date, date2: Date) {
44
return Math.abs(Math.round(diff));
55
}
66

7+
function debounce(fn: Function, delayMS: number): Function {
8+
let timer: ReturnType<typeof setTimeout>;
9+
10+
return function (...args: any[]) {
11+
clearTimeout(timer);
12+
13+
timer = setTimeout(() => {
14+
fn(...args);
15+
}, delayMS);
16+
};
17+
}
18+
719
const timesUtils = {
820
diffMinutes,
21+
debounce,
922
};
1023
export default timesUtils;

0 commit comments

Comments
 (0)