Skip to content
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

FE: Messages: Implement messages export #740

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Prev Previous commit
Next Next commit
linter warnings fix
alexanderlz committed Jan 5, 2025
commit eef61073a4ccd4843671f3cf0f5cf227ea80ce90
117 changes: 69 additions & 48 deletions frontend/src/components/Topics/Topic/Messages/MessagesTable.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import PageLoader from 'components/common/PageLoader/PageLoader';
import { Table } from 'components/common/table/Table/Table.styled';
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
import { TopicMessage } from 'generated-sources';
import React, { useState, useMemo } from 'react';
import { format } from 'date-fns';
import { Button } from 'components/common/Button/Button';
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, { SelectOption } from 'components/common/Select/Select';

import PreviewModal from './PreviewModal';
import Message, { PreviewFilter } from './Message';
import PageLoader from "components/common/PageLoader/PageLoader";

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

GitHub Actions / build-and-test / build-and-test

Replace `"components/common/PageLoader/PageLoader"` with `'components/common/PageLoader/PageLoader'`
import { Table } from "components/common/table/Table/Table.styled";

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

GitHub Actions / build-and-test / build-and-test

Replace `"components/common/table/Table/Table.styled"` with `'components/common/table/Table/Table.styled'`
import TableHeaderCell from "components/common/table/TableHeaderCell/TableHeaderCell";

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

GitHub Actions / build-and-test / build-and-test

Replace `"components/common/table/TableHeaderCell/TableHeaderCell"` with `'components/common/table/TableHeaderCell/TableHeaderCell'`
import { TopicMessage } from "generated-sources";

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

GitHub Actions / build-and-test / build-and-test

Replace `"generated-sources"` with `'generated-sources'`
import React, { useState, useMemo } from "react";

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

GitHub Actions / build-and-test / build-and-test

Replace `"react"` with `'react'`
import { format } from "date-fns";

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

GitHub Actions / build-and-test / build-and-test

Replace `"date-fns"` with `'date-fns'`
import { Button } from "components/common/Button/Button";

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

GitHub Actions / build-and-test / build-and-test

Replace `"components/common/Button/Button"` with `'components/common/Button/Button'`
import * as S from "components/common/NewTable/Table.styled";

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

GitHub Actions / build-and-test / build-and-test

Replace `"components/common/NewTable/Table.styled"` with `'components/common/NewTable/Table.styled'`
import { usePaginateTopics, useIsLiveMode } from "lib/hooks/useMessagesFilters";

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

GitHub Actions / build-and-test / build-and-test

Replace `"lib/hooks/useMessagesFilters"` with `'lib/hooks/useMessagesFilters'`
import { useMessageFiltersStore } from "lib/hooks/useMessageFiltersStore";

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

GitHub Actions / build-and-test / build-and-test

Replace `"lib/hooks/useMessageFiltersStore"` with `'lib/hooks/useMessageFiltersStore'`
import useDataSaver from "lib/hooks/useDataSaver";
import Select, { SelectOption } from "components/common/Select/Select";

import PreviewModal from "./PreviewModal";
import Message, { PreviewFilter } from "./Message";

export interface MessagesTableProps {
messages: TopicMessage[];
@@ -24,20 +24,19 @@
Offset: number;
Key: string | undefined;
Partition: number;
Headers: { [key: string]: string | undefined; } | undefined;
Headers: { [key: string]: string | undefined } | undefined;
Timestamp: Date;
}

type DownloadFormat = 'json' | 'csv';
type DownloadFormat = "json" | "csv";

function padCurrentDateTimeString(): string {
const now: Date = new Date();
const dateTimeString:string = format(now, 'yyyy-MM-dd HH:mm:ss');
const dateTimeString: string = format(now, "yyyy-MM-dd HH:mm:ss");

return `_${dateTimeString}`;
}


const MessagesTable: React.FC<MessagesTableProps> = ({
messages,
isFetching,
@@ -50,16 +49,16 @@
const nextCursor = useMessageFiltersStore((state) => state.nextCursor);
const isLive = useIsLiveMode();

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

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

const baseFileName = `topic-messages${padCurrentDateTimeString()}`;

const savedMessagesJson: MessageData[] = messages.map(message => ({
const savedMessagesJson: MessageData[] = messages.map((message) => ({
Value: message.content,
Offset: message.offset,
Key: message.key,
@@ -70,47 +69,68 @@

const convertToCSV = useMemo(() => {
return (messagesData: MessageData[]) => {
const headers = ['Value', 'Offset', 'Key', 'Partition', 'Headers', 'Timestamp'] as const;
const rows = messagesData.map(msg =>
headers.map(header => {
const value = msg[header];
if (header === 'Headers') {
return JSON.stringify(value || {});
}
return String(value ?? '');
}).join(',')
const headers = [
"Value",
"Offset",
"Key",
"Partition",
"Headers",
"Timestamp",
] as const;
const rows = messagesData.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');
return [headers.join(","), ...rows].join("\n");
};
}, []);

const jsonSaver = useDataSaver(`${baseFileName}.json`, JSON.stringify(savedMessagesJson, null, '\t'));
const csvSaver = useDataSaver(`${baseFileName}.csv`, convertToCSV(savedMessagesJson));

const jsonSaver = useDataSaver(
`${baseFileName}.json`,
JSON.stringify(savedMessagesJson, null, "\t"),
);
const csvSaver = useDataSaver(
`${baseFileName}.csv`,
convertToCSV(savedMessagesJson),
);

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

const handleDownload = () => {
if (selectedFormat === 'json') {
if (selectedFormat === "json") {
jsonSaver.saveFile();
} else {
csvSaver.saveFile();
}
};

return (
<div style={{ position: 'relative' }}>
<div style={{ display: 'flex', gap: '8px', marginLeft: '1rem', marginBottom: '1rem' }}>
<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"
minWidth="70px"
selectSize="M"
placeholder="Select format to download"
disabled={isFetching || messages.length === 0}
/>
@@ -126,10 +146,10 @@

{previewFor !== null && (
<PreviewModal
values={previewFor === 'key' ? keyFilters : contentFilters}
values={previewFor === "key" ? keyFilters : contentFilters}
toggleIsOpen={() => setPreviewFor(null)}
setFilters={(payload: PreviewFilter[]) =>
previewFor === 'key'
previewFor === "key"
? setKeyFilters(payload)
: setContentFilters(payload)
}
@@ -145,18 +165,18 @@
<TableHeaderCell
title="Key"
previewText={`Preview ${
keyFilters.length ? `(${keyFilters.length} selected)` : ''
keyFilters.length ? `(${keyFilters.length} selected)` : ""
}`}
onPreview={() => setPreviewFor('key')}
onPreview={() => setPreviewFor("key")}
/>
<TableHeaderCell
title="Value"
previewText={`Preview ${
contentFilters.length
? `(${contentFilters.length} selected)`
: ''
: ""
}`}
onPreview={() => setPreviewFor('content')}
onPreview={() => setPreviewFor("content")}
/>
<TableHeaderCell> </TableHeaderCell>
</tr>
@@ -169,7 +189,7 @@
message.timestamp,
message.key,
message.partition,
].join('-')}
].join("-")}
message={message}
keyFilters={keyFilters}
contentFilters={contentFilters}
@@ -206,3 +226,4 @@
};

export default MessagesTable;