Skip to content

Commit a05709f

Browse files
authored
FE: Topics: Save field previews into local storage (#449)
1 parent 2601a9a commit a05709f

File tree

3 files changed

+98
-12
lines changed

3 files changed

+98
-12
lines changed

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

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,86 @@ import PageLoader from 'components/common/PageLoader/PageLoader';
22
import { Table } from 'components/common/table/Table/Table.styled';
33
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
44
import { TopicMessage } from 'generated-sources';
5-
import React, { useState } from 'react';
5+
import React, { useCallback, useEffect, useState } from 'react';
66
import { Button } from 'components/common/Button/Button';
77
import * as S from 'components/common/NewTable/Table.styled';
88
import { usePaginateTopics, useIsLiveMode } from 'lib/hooks/useMessagesFilters';
99
import { useMessageFiltersStore } from 'lib/hooks/useMessageFiltersStore';
10+
import useAppParams from 'lib/hooks/useAppParams';
11+
import { RouteParamsClusterTopic } from 'lib/paths';
12+
import { useLocalStorage } from 'lib/hooks/useLocalStorage';
1013

11-
import PreviewModal from './PreviewModal';
1214
import Message, { PreviewFilter } from './Message';
15+
import PreviewModal from './PreviewModal';
1316

1417
export interface MessagesTableProps {
1518
messages: TopicMessage[];
1619
isFetching: boolean;
1720
}
1821

22+
interface MessagePreviewProps {
23+
[key: string]: {
24+
keyFilters: PreviewFilter[];
25+
contentFilters: PreviewFilter[];
26+
};
27+
}
28+
1929
const MessagesTable: React.FC<MessagesTableProps> = ({
2030
messages,
2131
isFetching,
2232
}) => {
2333
const paginate = usePaginateTopics();
24-
const [previewFor, setPreviewFor] = useState<string | null>(null);
25-
34+
const [previewFor, setPreviewFor] = useState<'key' | 'content' | null>(null);
2635
const [keyFilters, setKeyFilters] = useState<PreviewFilter[]>([]);
2736
const [contentFilters, setContentFilters] = useState<PreviewFilter[]>([]);
2837
const nextCursor = useMessageFiltersStore((state) => state.nextCursor);
2938
const isLive = useIsLiveMode();
39+
const { topicName } = useAppParams<RouteParamsClusterTopic>();
40+
const [messagesPreview, setMessagesPreview] =
41+
useLocalStorage<MessagePreviewProps>('message-preview', {
42+
[topicName]: {
43+
keyFilters: [],
44+
contentFilters: [],
45+
},
46+
});
47+
48+
useEffect(() => {
49+
setKeyFilters(messagesPreview[topicName]?.keyFilters || []);
50+
setContentFilters(messagesPreview[topicName]?.contentFilters || []);
51+
}, []);
52+
53+
const setFilters = useCallback(
54+
(payload: PreviewFilter[]) => {
55+
if (previewFor === 'key') {
56+
setKeyFilters(payload);
57+
setMessagesPreview({
58+
...messagesPreview,
59+
[topicName]: {
60+
...messagesPreview[topicName],
61+
keyFilters: payload,
62+
},
63+
});
64+
} else {
65+
setContentFilters(payload);
66+
setMessagesPreview({
67+
...messagesPreview,
68+
[topicName]: {
69+
...messagesPreview[topicName],
70+
contentFilters: payload,
71+
},
72+
});
73+
}
74+
},
75+
[previewFor, messagesPreview, topicName]
76+
);
3077

3178
return (
3279
<div style={{ position: 'relative' }}>
3380
{previewFor !== null && (
3481
<PreviewModal
3582
values={previewFor === 'key' ? keyFilters : contentFilters}
3683
toggleIsOpen={() => setPreviewFor(null)}
37-
setFilters={(payload: PreviewFilter[]) =>
38-
previewFor === 'key'
39-
? setKeyFilters(payload)
40-
: setContentFilters(payload)
41-
}
84+
setFilters={setFilters}
4285
/>
4386
)}
4487
<Table isFullwidth>

frontend/src/components/Topics/Topic/Messages/__test__/MessagesTable.spec.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import MessagesTable, {
77
} from 'components/Topics/Topic/Messages/MessagesTable';
88
import { TopicMessage, TopicMessageTimestampTypeEnum } from 'generated-sources';
99
import { useIsLiveMode } from 'lib/hooks/useMessagesFilters';
10+
import useAppParams from 'lib/hooks/useAppParams';
11+
import { LOCAL_STORAGE_KEY_PREFIX } from 'lib/constants';
1012

1113
export const topicMessagePayload: TopicMessage = {
1214
partition: 29,
@@ -33,8 +35,16 @@ jest.mock('lib/hooks/useMessagesFilters', () => ({
3335
usePaginateTopics: jest.fn(),
3436
}));
3537

38+
jest.mock('lib/hooks/useAppParams', () => ({
39+
__esModule: true,
40+
default: jest.fn(),
41+
}));
42+
3643
describe('MessagesTable', () => {
3744
const renderComponent = (props?: Partial<MessagesTableProps>) => {
45+
(useAppParams as jest.Mock).mockImplementation(() => ({
46+
topicName: 'testTopic',
47+
}));
3848
return render(
3949
<MessagesTable messages={[]} isFetching={false} {...props} />
4050
);
@@ -99,4 +109,34 @@ describe('MessagesTable', () => {
99109
}
100110
});
101111
});
112+
113+
describe('should save messages preview into localstorage', () => {
114+
beforeEach(() => {
115+
renderComponent({ messages: mockTopicsMessages, isFetching: false });
116+
});
117+
118+
it('should save messages preview into localstorage', async () => {
119+
const previewButtons = screen.getAllByText('Preview');
120+
await userEvent.click(previewButtons[0]);
121+
await userEvent.type(screen.getByPlaceholderText('Field'), 'test1');
122+
await userEvent.type(screen.getByPlaceholderText('Json Path'), 'test2');
123+
await userEvent.click(screen.getByText('Save'));
124+
await userEvent.click(previewButtons[1]);
125+
await userEvent.type(screen.getByPlaceholderText('Field'), 'test3');
126+
await userEvent.type(screen.getByPlaceholderText('Json Path'), 'test4');
127+
await userEvent.click(screen.getByText('Save'));
128+
expect(
129+
global.localStorage.getItem(
130+
`${LOCAL_STORAGE_KEY_PREFIX}-message-preview`
131+
)
132+
).toEqual(
133+
JSON.stringify({
134+
testTopic: {
135+
keyFilters: [{ field: 'test1', path: 'test2' }],
136+
contentFilters: [{ field: 'test3', path: 'test4' }],
137+
},
138+
})
139+
);
140+
});
141+
});
102142
});

frontend/src/lib/hooks/useLocalStorage.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { LOCAL_STORAGE_KEY_PREFIX } from 'lib/constants';
2-
import { useState, useEffect } from 'react';
2+
import { useState, useEffect, Dispatch, SetStateAction } from 'react';
33

4-
export const useLocalStorage = (featureKey: string, defaultValue: string) => {
4+
export const useLocalStorage = <T>(
5+
featureKey: string,
6+
defaultValue: T
7+
): [T, Dispatch<SetStateAction<T>>] => {
58
const key = `${LOCAL_STORAGE_KEY_PREFIX}-${featureKey}`;
6-
const [value, setValue] = useState(() => {
9+
const [value, setValue] = useState<T>(() => {
710
const saved = localStorage.getItem(key);
811

912
if (saved !== null) {

0 commit comments

Comments
 (0)