diff --git a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss index efd668ffe1..af325a2d25 100644 --- a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss +++ b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss @@ -27,7 +27,7 @@ line-height: 18px; letter-spacing: 0.08em; text-align: left; - color: var(--bg-slate-200, #52575c); + color: #52575c; } .menu-items { @@ -65,7 +65,7 @@ padding: 12px; .title { - color: var(--bg-slate-200, #52575c); + color: #52575c; font-family: Inter; font-size: 11px; font-style: normal; @@ -149,7 +149,7 @@ } .title { - color: var(--bg-slate-200, #52575c); + color: #52575c; font-family: Inter; font-size: 11px; font-style: normal; diff --git a/frontend/src/container/DownloadV2/DownloadV2.styles.scss b/frontend/src/container/DownloadV2/DownloadV2.styles.scss new file mode 100644 index 0000000000..850c1c7d16 --- /dev/null +++ b/frontend/src/container/DownloadV2/DownloadV2.styles.scss @@ -0,0 +1,84 @@ +.download-logs-popover { + .ant-popover-inner { + border-radius: 4px; + border: 1px solid var(--bg-slate-400); + background: linear-gradient( + 139deg, + rgba(18, 19, 23, 0.8) 0%, + rgba(18, 19, 23, 0.9) 98.68% + ); + box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(20px); + padding: 12px 18px 12px 14px; + + .download-logs-content { + display: flex; + flex-direction: column; + gap: 8px; + align-items: flex-start; + + .action-btns { + padding: 4px 0px !important; + width: 159px; + display: flex; + align-items: center; + color: var(--bg-vanilla-400); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 0.14px; + gap: 6px; + + .ant-btn-icon { + margin-inline-end: 0px; + } + } + + .action-btns:hover { + &.ant-btn-text { + background-color: rgba(171, 189, 255, 0.04) !important; + } + } + + .export-heading { + color: #52575c; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 163.636% */ + letter-spacing: 0.88px; + text-transform: uppercase; + } + } + } +} + +.lightMode { + .download-logs-popover { + .ant-popover-inner { + border: 1px solid var(--bg-vanilla-300); + background: linear-gradient( + 139deg, + rgba(255, 255, 255, 0.8) 0%, + rgba(255, 255, 255, 0.9) 98.68% + ); + + box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2); + + .download-logs-content { + .action-btns { + color: var(--bg-ink-400); + } + .action-btns:hover { + &.ant-btn-text { + background-color: var(--bg-vanilla-300) !important; + } + } + .export-heading { + color: var(--bg-ink-200); + } + } + } + } +} diff --git a/frontend/src/container/DownloadV2/DownloadV2.tsx b/frontend/src/container/DownloadV2/DownloadV2.tsx new file mode 100644 index 0000000000..95630efcb9 --- /dev/null +++ b/frontend/src/container/DownloadV2/DownloadV2.tsx @@ -0,0 +1,84 @@ +import './DownloadV2.styles.scss'; + +import { Button, Popover, Typography } from 'antd'; +import { Excel } from 'antd-table-saveas-excel'; +import { FileDigit, FileDown, Sheet } from 'lucide-react'; +import { unparse } from 'papaparse'; + +import { DownloadProps } from './DownloadV2.types'; + +function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element { + const downloadExcelFile = (): void => { + const headers = Object.keys(Object.assign({}, ...data)).map((item) => { + const updatedTitle = item + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + return { + title: updatedTitle, + dataIndex: item, + }; + }); + const excel = new Excel(); + excel + .addSheet(fileName) + .addColumns(headers) + .addDataSource(data, { + str2Percent: true, + }) + .saveAs(`${fileName}.xlsx`); + }; + + const downloadCsvFile = (): void => { + const csv = unparse(data); + const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const csvUrl = URL.createObjectURL(csvBlob); + const downloadLink = document.createElement('a'); + downloadLink.href = csvUrl; + downloadLink.download = `${fileName}.csv`; + downloadLink.click(); + downloadLink.remove(); + }; + + return ( + + Export As + } + type="text" + onClick={downloadExcelFile} + className="action-btns" + > + Excel (.xlsx) + + } + type="text" + onClick={downloadCsvFile} + className="action-btns" + > + CSV + + + } + > + } + /> + + ); +} + +Download.defaultProps = { + isLoading: undefined, +}; + +export default Download; diff --git a/frontend/src/container/DownloadV2/DownloadV2.types.ts b/frontend/src/container/DownloadV2/DownloadV2.types.ts new file mode 100644 index 0000000000..0757ed62fd --- /dev/null +++ b/frontend/src/container/DownloadV2/DownloadV2.types.ts @@ -0,0 +1,10 @@ +export type DownloadOptions = { + isDownloadEnabled: boolean; + fileName: string; +}; + +export type DownloadProps = { + data: Record[]; + isLoading?: boolean; + fileName: string; +}; diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index a12fd80997..e07450229a 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -14,6 +14,7 @@ import { PANEL_TYPES, } from 'constants/queryBuilder'; import { DEFAULT_PER_PAGE_VALUE } from 'container/Controls/config'; +import Download from 'container/DownloadV2/DownloadV2'; import ExplorerOptions from 'container/ExplorerOptions/ExplorerOptions'; import GoToTop from 'container/GoToTop'; import LogsExplorerChart from 'container/LogsExplorerChart'; @@ -21,6 +22,7 @@ import LogsExplorerList from 'container/LogsExplorerList'; import LogsExplorerTable from 'container/LogsExplorerTable'; import { useOptionsMenu } from 'container/OptionsMenu'; import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView'; +import dayjs from 'dayjs'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils'; import { LogTimeRange } from 'hooks/logs/types'; @@ -33,8 +35,9 @@ import useClickOutside from 'hooks/useClickOutside'; import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange'; import { useNotifications } from 'hooks/useNotifications'; import useUrlQueryData from 'hooks/useUrlQueryData'; +import { FlatLogData } from 'lib/logs/flatLogData'; import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData'; -import { defaultTo, isEmpty } from 'lodash-es'; +import { defaultTo, isEmpty, omit } from 'lodash-es'; import { Sliders } from 'lucide-react'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -523,6 +526,23 @@ function LogsExplorerViews({ }, }); + const flattenLogData = useMemo( + () => + logs.map((log) => { + const timestamp = + typeof log.timestamp === 'string' + ? dayjs(log.timestamp).format() + : dayjs(log.timestamp / 1e6).format(); + + return FlatLogData({ + timestamp, + body: log.body, + ...omit(log, 'timestamp', 'body'), + }); + }), + [logs], + ); + return ( {showHistogram && ( @@ -578,6 +598,11 @@ function LogsExplorerViews({ {selectedPanelType === PANEL_TYPES.LIST && ( +