Skip to content

FE: Messages: Implement messages export #740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Changes from 2 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b008fa9
Merge pull request #1 from kafbat/main
alexanderlz Dec 29, 2024
c55f8c0
add an option to save all messages as csv/json, Issue #688
alexanderlz Dec 30, 2024
252c8a9
linter issues fix
alexanderlz Dec 30, 2024
1aab310
lint fixes
alexanderlz Dec 30, 2024
9817ee5
linter fixes + comments
alexanderlz Jan 5, 2025
628ecf1
forgot missing date-fns in package.json
alexanderlz Jan 5, 2025
b393c2d
forgot missing date-fns in pnpm
alexanderlz Jan 5, 2025
d841a95
linter
alexanderlz Jan 5, 2025
eef6107
linter warnings fix
alexanderlz Jan 5, 2025
cc31f80
linter warnings fix ->> single-quote
alexanderlz Jan 5, 2025
578982d
linter warnings fix (hopefully last)
alexanderlz Jan 5, 2025
0615fc5
Merge branch 'main' into issues/688
alexanderlz Jan 5, 2025
f0f3508
Merge branch 'main' into issues/688
alexanderlz Jan 22, 2025
8ef27ad
Merge branch 'main' into issues/688
alexanderlz Feb 12, 2025
a5f925b
Update MessagesTable.tsx
alexanderlz Feb 12, 2025
bff1aa0
Update MessagesTable.tsx
alexanderlz Feb 12, 2025
f8aa582
redesign the export messages button look and feel
alexanderlz Jul 13, 2025
38abcd0
redesign the export messages button look and feel, linter errors
alexanderlz Jul 13, 2025
66c67ee
redesign the export messages button look and feel, linter errors #2
alexanderlz Jul 13, 2025
297f754
Merge branch 'main' into issues/688
alexanderlz Jul 18, 2025
7819ca6
Update Filters.tsx
alexanderlz Jul 18, 2025
416c57a
Merge branch 'main' into issues/688
germanosin Jul 23, 2025
55a8fa3
replace the icon according to the discussion
alexanderlz Jul 31, 2025
b0c419c
Merge branch 'issues/688' of https://github.com/alexanderlz/kafka-ui …
alexanderlz Jul 31, 2025
72cc645
linter warning
alexanderlz Aug 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import * as S from 'components/common/NewTable/Table.styled';
import { usePaginateTopics, useIsLiveMode } from 'lib/hooks/useMessagesFilters';
import { useMessageFiltersStore } from 'lib/hooks/useMessageFiltersStore';
import useDataSaver from 'lib/hooks/useDataSaver';
import Select from 'components/common/Select/Select';

Check failure on line 11 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

'components/common/Select/Select' imported multiple times
import { SelectOption } from 'components/common/Select/Select';

Check failure on line 12 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

'components/common/Select/Select' imported multiple times

import PreviewModal from './PreviewModal';
import Message, { PreviewFilter } from './Message';
Expand All @@ -16,6 +19,33 @@
isFetching: boolean;
}

interface MessageData {
Value: string | undefined;
Offset: number;
Key: string | undefined;
Partition: number;
Headers: { [key: string]: string | undefined; } | undefined;

Check warning on line 27 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Delete `;`
Timestamp: Date;
}

type DownloadFormat = 'json' | 'csv';

function padCurrentDateTimeString(): string {
const now: Date = new Date();

const year: string = now.getFullYear().toString();
const month: string = (now.getMonth() + 1).toString().padStart(2, '0');
const day: string = now.getDate().toString().padStart(2, '0');
const hours: string = now.getHours().toString().padStart(2, '0');
const minutes: string = now.getMinutes().toString().padStart(2, '0');
const seconds: string = now.getSeconds().toString().padStart(2, '0');

const dateTimeString: string = `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;

return `_${dateTimeString}`;
}


Check warning on line 48 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Delete `⏎`
const MessagesTable: React.FC<MessagesTableProps> = ({
messages,
isFetching,
Expand All @@ -28,8 +58,80 @@
const nextCursor = useMessageFiltersStore((state) => state.nextCursor);
const isLive = useIsLiveMode();

const [selectedFormat, setSelectedFormat] = useState<DownloadFormat>('json');

const formatOptions: SelectOption<DownloadFormat>[] = [
{ label: 'JSON', value: 'json' },
{ label: 'CSV', value: 'csv' }

Check warning on line 65 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Insert `,`
];

const handleFormatSelect = (format: DownloadFormat) => {
setSelectedFormat(format);
};

const handleDownload = () => {

Check warning on line 73 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Replace `⏎··const·savedMessagesJson:·MessageData[]·=·messages.map(message` with `····const·savedMessagesJson:·MessageData[]·=·messages.map((message)`
const savedMessagesJson: MessageData[] = messages.map(message => ({
Value: message.content,

Check warning on line 75 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Replace `····` with `······`
Offset: message.offset,

Check warning on line 76 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Insert `··`
Key: message.key,

Check warning on line 77 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Replace `····` with `······`
Partition: message.partition,

Check warning on line 78 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Insert `··`
Headers: message.headers,

Check warning on line 79 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Replace `····` with `······`
Timestamp: message.timestamp,

Check warning on line 80 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Insert `··`
}));

const convertToCSV = (messages: MessageData[]) => {

Check failure on line 83 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

'messages' is already declared in the upper scope on line 50 column 3
const headers = ['Value', 'Offset', 'Key', 'Partition', 'Headers', 'Timestamp'] as const;
type Header = typeof headers[number];

Check failure on line 85 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

'Header' is defined but never used

Check failure on line 85 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

'Header' is defined but never used

const rows = messages.map(msg =>
headers.map(header => {
const value = msg[header];
if (header === 'Headers') {
return JSON.stringify(value || {});
}
return String(value ?? '');
}).join(',')
);

return [headers.join(','), ...rows].join('\n');
};

const baseFileName = 'topic-messages'+padCurrentDateTimeString();

Check failure on line 100 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Unexpected string concatenation
const jsonSaver = useDataSaver(baseFileName+'.json', JSON.stringify(savedMessagesJson, null, '\t'));

Check failure on line 101 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

React Hook "useDataSaver" is called in function "handleDownload" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

Check failure on line 101 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Unexpected string concatenation
const csvSaver = useDataSaver(baseFileName+'.csv', convertToCSV(savedMessagesJson));

Check failure on line 102 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

React Hook "useDataSaver" is called in function "handleDownload" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

Check failure on line 102 in frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx

View workflow job for this annotation

GitHub Actions / build / build-and-test

Unexpected string concatenation

if (selectedFormat === 'json') {
jsonSaver.saveFile();
} else {
csvSaver.saveFile();
}
};

return (
<div style={{ position: 'relative' }}>
<div style={{ display: 'flex', gap: '8px', marginLeft: '1rem', marginBottom: '1rem' }}>
<Select<DownloadFormat>
id="download-format"
name="download-format"
onChange={handleFormatSelect}
options={formatOptions}
value={selectedFormat}
minWidth="70px"
selectSize="M"
placeholder="Select format to download"
disabled={isFetching || messages.length === 0}
/>
<Button
disabled={isFetching || messages.length === 0}
buttonType="secondary"
buttonSize="M"
onClick={handleDownload}
>
Download All Messages
</Button>
</div>

{previewFor !== null && (
<PreviewModal
values={previewFor === 'key' ? keyFilters : contentFilters}
Expand Down
Loading