Skip to content

Commit

Permalink
feat: add line share feature (#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
praveen5959 authored Dec 18, 2024
1 parent fd76cd4 commit 7900103
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 72 deletions.
3 changes: 2 additions & 1 deletion src/pages/Stream/Views/Explore/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import useMountedState from '@/hooks/useMountedState';
import classes from '../../styles/Footer.module.css';
import { LOGS_FOOTER_HEIGHT } from '@/constants/theme';

const { setPageAndPageData, setCurrentPage, setCurrentOffset } = logsStoreReducers;
const { setPageAndPageData, setCurrentPage, setCurrentOffset, setRowNumber } = logsStoreReducers;

const TotalCount = (props: { totalCount: number }) => {
return (
Expand Down Expand Up @@ -93,6 +93,7 @@ const Footer = (props: { loaded: boolean; hasNoData: boolean; isFetchingCount: b
const { totalPages, currentOffset, currentPage, perPage, totalCount } = tableOpts;

const onPageChange = useCallback((page: number) => {
setLogsStore((store) => setRowNumber(store, ''));
setLogsStore((store) => setPageAndPageData(store, page));
}, []);

Expand Down
304 changes: 237 additions & 67 deletions src/pages/Stream/Views/Explore/StaticLogTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import type { ReactNode } from 'react';
import { Box, Menu } from '@mantine/core';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { Dispatch, ReactNode, SetStateAction } from 'react';
import EmptyBox from '@/components/Empty';
import FilterPills from '../../components/FilterPills';
import tableStyles from '../../styles/Logs.module.css';
Expand All @@ -21,8 +21,11 @@ import { Log } from '@/@types/parseable/api/query';
import { CopyIcon } from './JSONView';
import { FieldTypeMap, useStreamStore } from '../../providers/StreamProvider';
import timeRangeUtils from '@/utils/timeRangeUtils';
import { IconDotsVertical } from '@tabler/icons-react';
import { copyTextToClipboard } from '@/utils';
import { notifySuccess } from '@/utils/notification';

const { setSelectedLog } = logsStoreReducers;
const { setSelectedLog, setRowNumber } = logsStoreReducers;
const TableContainer = (props: { children: ReactNode }) => {
return <Box className={tableStyles.container}>{props.children}</Box>;
};
Expand Down Expand Up @@ -54,10 +57,23 @@ const getSanitizedValue = (value: CellType, isTimestamp: boolean) => {
return String(value);
};

const makeHeaderOpts = (headers: string[], isSecureHTTPContext: boolean, fieldTypeMap: FieldTypeMap) => {
type ContextMenuState = {
visible: boolean;
x: number;
y: number;
row: Log | null;
};

const makeHeaderOpts = (
headers: string[],
isSecureHTTPContext: boolean,
fieldTypeMap: FieldTypeMap,
rowNumber: string,
setContextMenu: Dispatch<SetStateAction<ContextMenuState>>,
) => {
return _.reduce(
headers,
(acc: { accessorKey: string; header: string; grow: boolean }[], header) => {
(acc: { accessorKey: string; header: string; grow: boolean }[], header, index) => {
const isTimestamp = _.get(fieldTypeMap, header, null) === 'timestamp';

return [
Expand All @@ -76,8 +92,38 @@ const makeHeaderOpts = (headers: string[], isSecureHTTPContext: boolean, fieldTy
})
.value();
const sanitizedValue = getSanitizedValue(value, isTimestamp);
let isFirstSelectedRow = false;
if (rowNumber) {
const [start] = rowNumber.split(':').map(Number);
isFirstSelectedRow = cell.row.index === start;
}
const isFirstColumn = index === 0;
return (
<div className={tableStyles.customCellContainer} style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
<div
className={tableStyles.customCellContainer}
style={{
marginLeft: isFirstSelectedRow && isFirstColumn ? '4px' : '',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
<div
className={tableStyles.actionIconContainer}
onClick={(event) => {
event.stopPropagation();
setContextMenu({
visible: true,
x: event.pageX,
y: event.pageY,
row: cell.row.original,
});
}}
style={{
display: isFirstSelectedRow && isFirstColumn ? 'flex' : '',
}}>
{isSecureHTTPContext
? sanitizedValue && <IconDotsVertical stroke={1.2} size={'0.8rem'} color="#545beb" />
: null}
</div>
{sanitizedValue}
<div className={tableStyles.copyIconContainer}>
{isSecureHTTPContext ? sanitizedValue && <CopyIcon value={sanitizedValue} /> : null}
Expand All @@ -91,19 +137,31 @@ const makeHeaderOpts = (headers: string[], isSecureHTTPContext: boolean, fieldTy
[],
);
};

const makeColumnVisiblityOpts = (columns: string[]) => {
return _.reduce(columns, (acc, column) => ({ ...acc, [column]: false }), {});
};

const Table = (props: { primaryHeaderHeight: number }) => {
const [{ orderedHeaders, disabledColumns, pinnedColumns, pageData, wrapDisabledColumns }, setLogsStore] =
useLogsStore((store) => store.tableOpts);
const [contextMenu, setContextMenu] = useState<ContextMenuState>({
visible: false,
x: 0,
y: 0,
row: null,
});

const contextMenuRef = useRef<HTMLDivElement>(null);
const [{ orderedHeaders, disabledColumns, pageData, wrapDisabledColumns, rowNumber }, setLogsStore] = useLogsStore(
(store) => store.tableOpts,
);
const [isSecureHTTPContext] = useAppStore((store) => store.isSecureHTTPContext);
const [fieldTypeMap] = useStreamStore((store) => store.fieldTypeMap);
const columns = useMemo(() => makeHeaderOpts(orderedHeaders, isSecureHTTPContext, fieldTypeMap), [orderedHeaders]);
const columns = useMemo(
() => makeHeaderOpts(orderedHeaders, isSecureHTTPContext, fieldTypeMap, rowNumber, setContextMenu),
[orderedHeaders, rowNumber],
);
const columnVisibility = useMemo(() => makeColumnVisiblityOpts(disabledColumns), [disabledColumns, orderedHeaders]);
const selectLog = useCallback((log: Log) => {
const selectLog = useCallback((log: Log | null) => {
if (!log) return;
const selectedText = window.getSelection()?.toString();
if (selectedText !== undefined && selectedText?.length > 0) return;

Expand All @@ -126,67 +184,179 @@ const Table = (props: { primaryHeaderHeight: number }) => {
},
[wrapDisabledColumns],
);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (contextMenuRef.current && !contextMenuRef.current.contains(event.target as Node)) {
closeContextMenu();
}
};

if (contextMenu.visible) {
document.addEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [contextMenu.visible]);

const closeContextMenu = () => setContextMenu({ visible: false, x: 0, y: 0, row: null });

const copyUrl = useCallback(() => {
copyTextToClipboard(window.location.href);
notifySuccess({ message: 'Link Copied!' });
}, [window.location.href]);

const copyJSON = useCallback(() => {
const [start, end] = rowNumber.split(':').map(Number);

const rowsToCopy = pageData.slice(start, end + 1);

copyTextToClipboard(rowsToCopy);
notifySuccess({ message: 'JSON Copied!' });
}, [rowNumber]);

const handleRowClick = (index: number, event: React.MouseEvent) => {
let newRange = `${index}:${index}`;

if ((event.ctrlKey || event.metaKey) && rowNumber) {
const [start, end] = rowNumber.split(':').map(Number);
const lastIndex = Math.max(start, end);

const startIndex = Math.min(lastIndex, index);
const endIndex = Math.max(lastIndex, index);
newRange = `${startIndex}:${endIndex}`;
setLogsStore((store) => setRowNumber(store, newRange));
} else {
if (rowNumber) {
const [start, end] = rowNumber.split(':').map(Number);
if (index >= start && index <= end) {
setLogsStore((store) => setRowNumber(store, ''));
return;
}
}

setLogsStore((store) => setRowNumber(store, newRange));
}
};

return (
<MantineReactTable
enableBottomToolbar={false}
enableTopToolbar={false}
enableColumnResizing={true}
mantineTableBodyCellProps={({ column: { id } }) => makeCellCustomStyles(id)}
mantineTableHeadRowProps={{ style: { border: 'none' } }}
mantineTableHeadCellProps={{
style: {
fontWeight: 600,
fontSize: '0.65rem',
border: 'none',
padding: '0.5rem 1rem',
},
}}
mantineTableBodyRowProps={({ row }) => {
return {
onClick: () => {
selectLog(row.original);
<>
<MantineReactTable
enableBottomToolbar={false}
enableTopToolbar={false}
enableColumnResizing
mantineTableBodyCellProps={({ column: { id } }) => makeCellCustomStyles(id)}
mantineTableHeadRowProps={{ style: { border: 'none' } }}
mantineTableHeadCellProps={{
style: {
fontWeight: 600,
fontSize: '0.65rem',
border: 'none',
padding: '0.5rem 1rem',
},
}}
mantineTableBodyRowProps={({ row }) => {
return {
onClick: (event) => {
event.preventDefault();
handleRowClick(row.index, event);
},
style: {
border: 'none',
background: row.index % 2 === 0 ? '#f8f9fa' : 'white',
backgroundColor:
rowNumber &&
(() => {
const [start, end] = rowNumber.split(':').map(Number);
return row.index >= start && row.index <= end;
})()
? '#E8EDFE'
: '',
},
};
}}
mantineTableProps={{ highlightOnHover: false }}
mantineTableHeadProps={{
style: {
border: 'none',
background: row.index % 2 === 0 ? '#f8f9fa' : 'white',
},
};
}}
mantineTableHeadProps={{
style: {
border: 'none',
},
}}
columns={columns}
data={pageData}
mantinePaperProps={{ style: { border: 'none' } }}
enablePagination={false}
enableColumnPinning={true}
initialState={{
columnPinning: {
left: pinnedColumns,
},
}}
enableStickyHeader={true}
defaultColumn={{ minSize: 100 }}
layoutMode="grid"
state={{
columnPinning: {
left: pinnedColumns,
},
columnVisibility,
columnOrder: orderedHeaders,
}}
mantineTableContainerProps={{
style: {
height: `calc(100vh - ${props.primaryHeaderHeight + LOGS_FOOTER_HEIGHT}px )`,
},
}}
renderColumnActionsMenuItems={({ column }) => {
return <Column columnName={column.id} />;
}}
/>
}}
columns={columns}
data={pageData}
mantinePaperProps={{ style: { border: 'none' } }}
enablePagination={false}
enableColumnPinning
initialState={{
columnPinning: {
left: ['rowNumber'],
},
}}
enableStickyHeader
defaultColumn={{ minSize: 100 }}
layoutMode="grid"
state={{
columnPinning: {
left: ['rowNumber'],
},
columnVisibility,
columnOrder: orderedHeaders,
}}
mantineTableContainerProps={{
style: {
height: `calc(100vh - ${props.primaryHeaderHeight + LOGS_FOOTER_HEIGHT}px )`,
},
}}
renderColumnActionsMenuItems={({ column }) => {
return <Column columnName={column.id} />;
}}
/>
{contextMenu.visible && (
<div
ref={contextMenuRef}
style={{
top: contextMenu.y,
left: contextMenu.x,
}}
className={tableStyles.contextMenuContainer}
onClick={closeContextMenu}>
<Menu opened={contextMenu.visible} onClose={closeContextMenu}>
{(() => {
const [start, end] = rowNumber.split(':').map(Number);
const rowCount = end - start + 1;

if (rowCount === 1) {
return (
<Menu.Item
onClick={() => {
selectLog(contextMenu.row);
closeContextMenu();
}}>
View JSON
</Menu.Item>
);
}

return null;
})()}
<Menu.Item
onClick={() => {
copyJSON();
closeContextMenu();
}}>
Copy JSON
</Menu.Item>
<Menu.Item
onClick={() => {
copyUrl();
closeContextMenu();
}}>
Copy permalink
</Menu.Item>
</Menu>
</div>
)}
</>
);
};

Expand Down
Loading

0 comments on commit 7900103

Please sign in to comment.