Skip to content

Commit 7993ff7

Browse files
NilumilakHaarolean
authored andcommitted
FE: Topics: Remember user polling options (kafbat#453)
Co-authored-by: Roman Zabaluev <gpg@haarolean.dev>
1 parent c1479dc commit 7993ff7

File tree

9 files changed

+167
-37
lines changed

9 files changed

+167
-37
lines changed

frontend/src/components/Topics/Topic/Messages/Filters/Filters.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const Filters: React.FC<FiltersProps> = ({
6363
smartFilter,
6464
setSmartFilter,
6565
refreshData,
66-
} = useMessagesFilters();
66+
} = useMessagesFilters(topicName);
6767

6868
const { data: topic } = useTopicDetails({ clusterName, topicName });
6969
const [createdEditedSmartId, setCreatedEditedSmartId] = useState<string>();

frontend/src/components/Topics/Topic/Messages/Filters/__tests__/Filters.spec.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,10 @@ import { useTopicDetails } from 'lib/hooks/api/topics';
1010
import { externalTopicPayload } from 'lib/fixtures/topics';
1111
import { useSerdes } from 'lib/hooks/api/topicMessages';
1212
import { serdesPayload } from 'lib/fixtures/topicMessages';
13-
import {
14-
MessagesFilterKeys,
15-
MessagesFilterKeysTypes,
16-
} from 'lib/hooks/useMessagesFilters';
1713
import { PollingMode } from 'generated-sources';
1814
import { ModeOptions } from 'lib/hooks/filterUtils';
15+
import { MessagesFilterKeysTypes } from 'lib/types';
16+
import { MessagesFilterKeys } from 'lib/constants';
1917

2018
const closeIconMock = 'closeIconMock';
2119
const filtersSideBarMock = 'filtersSideBarMock';

frontend/src/components/common/Search/Search.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { ComponentRef, useRef } from 'react';
1+
import React, { ComponentRef, useEffect, useRef } from 'react';
22
import { useDebouncedCallback } from 'use-debounce';
33
import Input from 'components/common/Input/Input';
44
import { useSearchParams } from 'react-router-dom';
@@ -31,6 +31,12 @@ const Search: React.FC<SearchProps> = ({
3131
const [searchParams, setSearchParams] = useSearchParams();
3232
const ref = useRef<ComponentRef<'input'>>(null);
3333

34+
useEffect(() => {
35+
if (ref.current !== null && value) {
36+
ref.current.value = value;
37+
}
38+
}, [value]);
39+
3440
const handleChange = useDebouncedCallback((e) => {
3541
if (ref.current != null) {
3642
ref.current.value = e.target.value;

frontend/src/components/common/Select/Select.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useRef } from 'react';
1+
import React, { useState, useRef, useEffect } from 'react';
22
import useClickOutside from 'lib/hooks/useClickOutside';
33
import DropdownArrowIcon from 'components/common/Icons/DropdownArrowIcon';
44

@@ -42,6 +42,10 @@ const Select = <T extends object>(
4242
const [selectedOption, setSelectedOption] = useState(value);
4343
const [showOptions, setShowOptions] = useState(false);
4444

45+
useEffect(() => {
46+
setSelectedOption(value);
47+
}, [value]);
48+
4549
const showOptionsHandler = () => {
4650
if (!disabled) setShowOptions(!showOptions);
4751
};

frontend/src/lib/constants.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,23 @@ export const CONSUMER_GROUP_STATE_TOOLTIPS: Record<ConsumerGroupState, string> =
130130
DEAD: 'The group is going to be removed. It might be due to the inactivity, or the group is being migrated to different group coordinator.',
131131
UNKNOWN: '',
132132
} as const;
133+
134+
/**
135+
* @description !! Note !!
136+
* Key value should match
137+
* */
138+
export const MessagesFilterKeys = {
139+
mode: 'mode',
140+
timestamp: 'timestamp',
141+
keySerde: 'keySerde',
142+
valueSerde: 'valueSerde',
143+
limit: 'limit',
144+
offset: 'offset',
145+
stringFilter: 'stringFilter',
146+
partitions: 'partitions',
147+
smartFilterId: 'smartFilterId',
148+
activeFilterId: 'activeFilterId',
149+
activeFilterNPId: 'activeFilterNPId', // not persisted filter name to indicate the refresh
150+
cursor: 'cursor',
151+
r: 'r', // used tp force refresh of the data
152+
} as const;

frontend/src/lib/hooks/api/topicMessages.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import React, { useCallback, useRef } from 'react';
22
import { fetchEventSource } from '@microsoft/fetch-event-source';
3-
import { BASE_PARAMS, MESSAGES_PER_PAGE } from 'lib/constants';
3+
import {
4+
BASE_PARAMS,
5+
MESSAGES_PER_PAGE,
6+
MessagesFilterKeys,
7+
} from 'lib/constants';
48
import {
59
GetSerdesRequest,
610
PollingMode,
@@ -13,10 +17,7 @@ import { showServerError } from 'lib/errorHandling';
1317
import { useMutation, useQuery } from '@tanstack/react-query';
1418
import { messagesApiClient } from 'lib/api';
1519
import { useSearchParams } from 'react-router-dom';
16-
import {
17-
getCursorValue,
18-
MessagesFilterKeys,
19-
} from 'lib/hooks/useMessagesFilters';
20+
import { getCursorValue } from 'lib/hooks/useMessagesFilters';
2021
import { convertStrToPollingMode } from 'lib/hooks/filterUtils';
2122
import { useMessageFiltersStore } from 'lib/hooks/useMessageFiltersStore';
2223
import { TopicName } from 'lib/interfaces/topic';

frontend/src/lib/hooks/useMessagesFilters.ts

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,15 @@ import { useSearchParams } from 'react-router-dom';
22
import { PollingMode } from 'generated-sources';
33
import { useEffect } from 'react';
44
import { Option } from 'react-multi-select-component';
5-
import { ObjectValues } from 'lib/types';
5+
import { MessagesFilterKeys } from 'lib/constants';
66

77
import { convertStrToPollingMode, ModeOptions } from './filterUtils';
88
import {
99
AdvancedFilter,
1010
selectFilter,
1111
useMessageFiltersStore,
1212
} from './useMessageFiltersStore';
13-
14-
/**
15-
* @description !! Note !!
16-
* Key value should match
17-
* */
18-
export const MessagesFilterKeys = {
19-
mode: 'mode',
20-
timestamp: 'timestamp',
21-
keySerde: 'keySerde',
22-
valueSerde: 'valueSerde',
23-
limit: 'limit',
24-
offset: 'offset',
25-
stringFilter: 'stringFilter',
26-
partitions: 'partitions',
27-
smartFilterId: 'smartFilterId',
28-
activeFilterId: 'activeFilterId',
29-
activeFilterNPId: 'activeFilterNPId', // not persisted filter name to indicate the refresh
30-
cursor: 'cursor',
31-
r: 'r', // used tp force refresh of the data
32-
} as const;
33-
34-
export type MessagesFilterKeysTypes = ObjectValues<typeof MessagesFilterKeys>;
13+
import { useMessagesFiltersFields } from './useMessagesFiltersFields';
3514

3615
const PER_PAGE = 100;
3716

@@ -80,12 +59,18 @@ export function usePaginateTopics(initSearchParams?: URLSearchParams) {
8059
};
8160
}
8261

83-
export function useMessagesFilters() {
62+
export function useMessagesFilters(topicName: string) {
8463
const [searchParams, setSearchParams] = useSearchParams();
8564
const refreshData = useRefreshData(searchParams);
65+
const {
66+
initMessagesFiltersFields,
67+
setMessagesFiltersField,
68+
removeMessagesFiltersField,
69+
} = useMessagesFiltersFields(topicName);
8670

8771
useEffect(() => {
8872
setSearchParams((params) => {
73+
initMessagesFiltersFields(params);
8974
params.set(MessagesFilterKeys.limit, PER_PAGE.toString());
9075

9176
if (!params.get(MessagesFilterKeys.mode)) {
@@ -140,8 +125,10 @@ export function useMessagesFilters() {
140125
* */
141126
const setMode = (newMode: PollingMode) => {
142127
setSearchParams((params) => {
128+
removeMessagesFiltersField(MessagesFilterKeys.offset);
129+
removeMessagesFiltersField(MessagesFilterKeys.timestamp);
130+
setMessagesFiltersField(MessagesFilterKeys.mode, newMode);
143131
params.set(MessagesFilterKeys.mode, newMode);
144-
145132
params.delete(MessagesFilterKeys.offset);
146133
params.delete(MessagesFilterKeys.timestamp);
147134
return params;
@@ -151,13 +138,18 @@ export function useMessagesFilters() {
151138
const setTimeStamp = (newDate: Date | null) => {
152139
if (newDate === null) {
153140
setSearchParams((params) => {
141+
removeMessagesFiltersField(MessagesFilterKeys.timestamp);
154142
params.delete(MessagesFilterKeys.timestamp);
155143
return params;
156144
});
157145
return;
158146
}
159147

160148
setSearchParams((params) => {
149+
setMessagesFiltersField(
150+
MessagesFilterKeys.timestamp,
151+
newDate.getTime().toString()
152+
);
161153
params.set(MessagesFilterKeys.timestamp, newDate.getTime().toString());
162154
return params;
163155
});
@@ -166,19 +158,22 @@ export function useMessagesFilters() {
166158
const setKeySerde = (newKeySerde: string) => {
167159
setSearchParams((params) => {
168160
params.set(MessagesFilterKeys.keySerde, newKeySerde);
161+
setMessagesFiltersField(MessagesFilterKeys.keySerde, newKeySerde);
169162
return params;
170163
});
171164
};
172165

173166
const setValueSerde = (newValueSerde: string) => {
174167
setSearchParams((params) => {
168+
setMessagesFiltersField(MessagesFilterKeys.valueSerde, newValueSerde);
175169
params.set(MessagesFilterKeys.valueSerde, newValueSerde);
176170
return params;
177171
});
178172
};
179173

180174
const setOffsetValue = (newOffsetValue: string) => {
181175
setSearchParams((params) => {
176+
setMessagesFiltersField(MessagesFilterKeys.offset, newOffsetValue);
182177
params.set(MessagesFilterKeys.offset, newOffsetValue);
183178
return params;
184179
});
@@ -187,8 +182,10 @@ export function useMessagesFilters() {
187182
const setSearch = (value: string) => {
188183
setSearchParams((params) => {
189184
if (value) {
185+
setMessagesFiltersField(MessagesFilterKeys.stringFilter, value);
190186
params.set(MessagesFilterKeys.stringFilter, value);
191187
} else {
188+
removeMessagesFiltersField(MessagesFilterKeys.stringFilter);
192189
params.delete(MessagesFilterKeys.stringFilter);
193190
}
194191
return params;
@@ -200,10 +197,16 @@ export function useMessagesFilters() {
200197
params.delete(MessagesFilterKeys.partitions);
201198

202199
if (values.length) {
200+
setMessagesFiltersField(
201+
MessagesFilterKeys.partitions,
202+
values.map((v) => v.value).join(',')
203+
);
203204
params.append(
204205
MessagesFilterKeys.partitions,
205206
values.map((v) => v.value).join(',')
206207
);
208+
} else {
209+
removeMessagesFiltersField(MessagesFilterKeys.partitions);
207210
}
208211

209212
return params;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { MessagesFilterKeysTypes } from 'lib/types';
2+
import { MessagesFilterKeys } from 'lib/constants';
3+
import { useLocalStorage } from 'lib/hooks/useLocalStorage';
4+
5+
type MessagesFilterFieldsType = Pick<
6+
Partial<{
7+
[key in MessagesFilterKeysTypes]: string;
8+
}>,
9+
| 'mode'
10+
| 'offset'
11+
| 'timestamp'
12+
| 'partitions'
13+
| 'keySerde'
14+
| 'valueSerde'
15+
| 'stringFilter'
16+
>;
17+
18+
export function useMessagesFiltersFields(topicName: string) {
19+
const [messageFilters, setMessageFilters] = useLocalStorage<{
20+
[topicName: string]: MessagesFilterFieldsType;
21+
}>('message-filters-fields', {});
22+
23+
const removeMessagesFilterFields = () => {
24+
setMessageFilters((prev) => {
25+
const { [topicName]: topicFilters, ...rest } = prev || {};
26+
return rest;
27+
});
28+
};
29+
30+
const removeMessagesFiltersField = (key: keyof MessagesFilterFieldsType) => {
31+
setMessageFilters((prev) => {
32+
const { [key]: value, ...rest } = prev[topicName] || {};
33+
return { ...prev, [topicName]: rest };
34+
});
35+
};
36+
37+
const setMessagesFiltersField = (
38+
key: keyof MessagesFilterFieldsType,
39+
value: string
40+
) => {
41+
setMessageFilters((prev) => ({
42+
...prev,
43+
[topicName]: { ...prev[topicName], [key]: value },
44+
}));
45+
};
46+
47+
const initMessagesFiltersFields = (params: URLSearchParams) => {
48+
const topicMessagesFilters = messageFilters[topicName];
49+
50+
const setTopicMessageFiltersFromLocalStorage = (
51+
key: keyof MessagesFilterFieldsType
52+
) => {
53+
const value = topicMessagesFilters[key];
54+
if (value) {
55+
params.set(MessagesFilterKeys[key], value);
56+
}
57+
};
58+
59+
const setTopicMessageFiltersFromUrlParams = (
60+
key: keyof MessagesFilterFieldsType
61+
) => {
62+
const filters = params.get(MessagesFilterKeys[key]);
63+
if (filters) {
64+
setMessagesFiltersField(key, filters);
65+
}
66+
};
67+
68+
// if url params are empty and topicMessagesFilters from local storage are existing then we apply them
69+
if (params.size === 0 && !!topicMessagesFilters) {
70+
setTopicMessageFiltersFromLocalStorage(MessagesFilterKeys.mode);
71+
setTopicMessageFiltersFromLocalStorage(MessagesFilterKeys.offset);
72+
setTopicMessageFiltersFromLocalStorage(MessagesFilterKeys.timestamp);
73+
setTopicMessageFiltersFromLocalStorage(MessagesFilterKeys.partitions);
74+
setTopicMessageFiltersFromLocalStorage(MessagesFilterKeys.keySerde);
75+
setTopicMessageFiltersFromLocalStorage(MessagesFilterKeys.valueSerde);
76+
setTopicMessageFiltersFromLocalStorage(MessagesFilterKeys.stringFilter);
77+
} else {
78+
removeMessagesFilterFields();
79+
setTopicMessageFiltersFromUrlParams(MessagesFilterKeys.mode);
80+
setTopicMessageFiltersFromUrlParams(MessagesFilterKeys.offset);
81+
setTopicMessageFiltersFromUrlParams(MessagesFilterKeys.timestamp);
82+
setTopicMessageFiltersFromUrlParams(MessagesFilterKeys.partitions);
83+
setTopicMessageFiltersFromUrlParams(MessagesFilterKeys.keySerde);
84+
setTopicMessageFiltersFromUrlParams(MessagesFilterKeys.valueSerde);
85+
setTopicMessageFiltersFromUrlParams(MessagesFilterKeys.stringFilter);
86+
}
87+
};
88+
89+
return {
90+
initMessagesFiltersFields,
91+
removeMessagesFiltersField,
92+
setMessagesFiltersField,
93+
};
94+
}

frontend/src/lib/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import { MessagesFilterKeys } from './constants';
2+
13
export type ObjectValues<T extends Record<string, unknown>> = T[keyof T];
24

35
export type WithPartialKey<T, K extends keyof T> = Omit<T, K> &
46
Partial<Record<K, Partial<T[K]>>>;
7+
8+
export type MessagesFilterKeysTypes = ObjectValues<typeof MessagesFilterKeys>;

0 commit comments

Comments
 (0)