From 24bb27faeb79fba7e489ce948134ec2e9553bf67 Mon Sep 17 00:00:00 2001 From: praveen5959 Date: Fri, 8 Nov 2024 15:11:19 +0530 Subject: [PATCH 01/64] Correlation route and page setup --- src/components/Navbar/index.tsx | 16 ++++++++++++- src/constants/routes.ts | 2 ++ src/pages/Correlation/index.tsx | 23 +++++++++++++++++++ src/pages/Correlation/utils.ts | 0 .../Stream/components/PrimaryToolbar.tsx | 19 +++++++++++++-- src/routes/elements.tsx | 15 ++++++++++++ src/routes/index.tsx | 3 +++ 7 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/pages/Correlation/index.tsx create mode 100644 src/pages/Correlation/utils.ts diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 64fbe52c..268b03f7 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -8,12 +8,20 @@ import { IconHomeStats, IconListDetails, IconLayoutDashboard, + IconHierarchy2, } from '@tabler/icons-react'; import { FC, useCallback, useEffect } from 'react'; import { useLocation, useParams } from 'react-router-dom'; import { useNavigate } from 'react-router-dom'; import { useDisclosure } from '@mantine/hooks'; -import { HOME_ROUTE, CLUSTER_ROUTE, USERS_MANAGEMENT_ROUTE, STREAM_ROUTE, DASHBOARDS_ROUTE } from '@/constants/routes'; +import { + HOME_ROUTE, + CLUSTER_ROUTE, + USERS_MANAGEMENT_ROUTE, + STREAM_ROUTE, + DASHBOARDS_ROUTE, + CORRELATION_ROUTE, +} from '@/constants/routes'; import InfoModal from './infoModal'; import { getStreamsSepcificAccess, getUserSepcificStreams } from './rolesHandler'; import Cookies from 'js-cookie'; @@ -49,6 +57,12 @@ const navItems = [ path: '/explore', route: STREAM_ROUTE, }, + { + icon: IconHierarchy2, + label: 'Correlation', + path: '/correlation', + route: CORRELATION_ROUTE, + }, ]; const previlagedActions = [ diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 462827b6..60a6c4e7 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -11,6 +11,7 @@ export const OIDC_NOT_CONFIGURED_ROUTE = '/oidc-not-configured'; export const CLUSTER_ROUTE = '/cluster'; export const STREAM_ROUTE = '/:streamName/:view?'; export const DASHBOARDS_ROUTE = '/dashboards'; +export const CORRELATION_ROUTE = '/correlation'; export const STREAM_VIEWS = ['explore', 'manage', 'live-tail']; @@ -27,4 +28,5 @@ export const PATHS = { cluster: '/cluster', manage: '/:streamName/:view?', dashboards: '/dashboards', + correlation: '/correlation', } as { [key: string]: string }; diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx new file mode 100644 index 00000000..7a90c52b --- /dev/null +++ b/src/pages/Correlation/index.tsx @@ -0,0 +1,23 @@ +import 'react-grid-layout/css/styles.css'; +import 'react-resizable/css/styles.css'; +import _ from 'lodash'; +import { useDocumentTitle } from '@mantine/hooks'; +import { Group, Stack } from '@mantine/core'; +import LogsView from '../Stream/Views/Explore/LogsView'; + +const Correlation = () => { + useDocumentTitle('Parseable | Correlation'); + + return ( + + + + + + + + + ); +}; + +export default Correlation; diff --git a/src/pages/Correlation/utils.ts b/src/pages/Correlation/utils.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/Stream/components/PrimaryToolbar.tsx b/src/pages/Stream/components/PrimaryToolbar.tsx index 3e67bb07..0e1df6c7 100644 --- a/src/pages/Stream/components/PrimaryToolbar.tsx +++ b/src/pages/Stream/components/PrimaryToolbar.tsx @@ -1,6 +1,6 @@ import { Button, SegmentedControl, Stack, Tooltip, px, rem } from '@mantine/core'; import IconButton from '@/components/Button/IconButton'; -import { IconBraces, IconFilterHeart, IconMaximize, IconTable, IconTrash } from '@tabler/icons-react'; +import { IconBraces, IconFilterHeart, IconMaximize, IconPlus, IconTable, IconTrash } from '@tabler/icons-react'; import { STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, STREAM_PRIMARY_TOOLBAR_HEIGHT } from '@/constants/theme'; import TimeRange from '@/components/Header/TimeRange'; import RefreshInterval from '@/components/Header/RefreshInterval'; @@ -10,7 +10,7 @@ import { appStoreReducers, useAppStore } from '@/layouts/MainLayout/providers/Ap import { useCallback, useEffect } from 'react'; import StreamDropdown from '@/components/Header/StreamDropdown'; import { notifications } from '@mantine/notifications'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import _ from 'lodash'; import StreamingButton from '@/components/Header/StreamingButton'; import ShareButton from '@/components/Header/ShareButton'; @@ -43,6 +43,20 @@ const SavedFiltersButton = () => { ); }; +const AddCorrelationButton = () => { + const navigate = useNavigate(); + + return ( + + ); +}; + const DeleteStreamButton = () => { const [_store, setLogsStore] = useLogsStore((_store) => null); const onClick = useCallback(() => setLogsStore(toggleDeleteModal), []); @@ -115,6 +129,7 @@ const PrimaryToolbar = () => { + diff --git a/src/routes/elements.tsx b/src/routes/elements.tsx index 573281bb..9141fd1a 100644 --- a/src/routes/elements.tsx +++ b/src/routes/elements.tsx @@ -10,6 +10,7 @@ import { StreamProvider } from '@/pages/Stream/providers/StreamProvider'; import { ClusterProvider } from '@/pages/Systems/providers/ClusterProvider'; import { DashbaordsProvider } from '@/pages/Dashboards/providers/DashboardsProvider'; import Dashboards from '@/pages/Dashboards'; +import Correlation from '@/pages/Correlation'; export const HomeElement: FC = () => { return ( @@ -31,6 +32,20 @@ export const DashboardsElement: FC = () => { ); }; +export const CorrelationElement: FC = () => { + return ( + + + + + + + + + + ); +}; + const Login = lazy(() => import('@/pages/Login')); export const LoginElement: FC = () => { diff --git a/src/routes/index.tsx b/src/routes/index.tsx index b3f30810..7115a451 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -7,6 +7,7 @@ import { CLUSTER_ROUTE, STREAM_ROUTE, DASHBOARDS_ROUTE, + CORRELATION_ROUTE, } from '@/constants/routes'; import FullPageLayout from '@/layouts/FullPageLayout'; import NotFound from '@/pages/Errors/NotFound'; @@ -21,6 +22,7 @@ import { SystemsElement, UsersElement, DashboardsElement, + CorrelationElement, } from './elements'; import AccessSpecificRoute from './AccessSpecificRoute'; import OIDCNotConFigured from '@/pages/Errors/OIDC'; @@ -33,6 +35,7 @@ const AppRouter: FC = () => { }> } /> } /> + } /> }> } /> From fdd63898ef7c24478ad0b1a34ecaf7703d3568d3 Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Wed, 13 Nov 2024 11:45:43 +0530 Subject: [PATCH 02/64] Added logTable toolbar --- src/pages/Correlation/index.tsx | 49 ++++++++++++++++--- .../Stream/components/PrimaryToolbar.tsx | 4 +- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx index 7a90c52b..5eb03727 100644 --- a/src/pages/Correlation/index.tsx +++ b/src/pages/Correlation/index.tsx @@ -2,21 +2,54 @@ import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; import _ from 'lodash'; import { useDocumentTitle } from '@mantine/hooks'; -import { Group, Stack } from '@mantine/core'; +import { Stack } from '@mantine/core'; import LogsView from '../Stream/Views/Explore/LogsView'; +import Querier from '../Stream/components/Querier'; +import SecondaryToolbar from '../Stream/components/SecondaryToolbar'; +import { + PRIMARY_HEADER_HEIGHT, + STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, + STREAM_PRIMARY_TOOLBAR_HEIGHT, +} from '@/constants/theme'; +import { MaximizeButton, SavedFiltersButton } from '../Stream/components/PrimaryToolbar'; +import TimeRange from '@/components/Header/TimeRange'; +import RefreshInterval from '@/components/Header/RefreshInterval'; +import RefreshNow from '@/components/Header/RefreshNow'; +import ShareButton from '@/components/Header/ShareButton'; const Correlation = () => { useDocumentTitle('Parseable | Correlation'); return ( - - - + +

PKB

+ + + + + + + + {/* */} + + + - - - -
+ + +
); }; diff --git a/src/pages/Stream/components/PrimaryToolbar.tsx b/src/pages/Stream/components/PrimaryToolbar.tsx index 0e1df6c7..1d2c92dc 100644 --- a/src/pages/Stream/components/PrimaryToolbar.tsx +++ b/src/pages/Stream/components/PrimaryToolbar.tsx @@ -23,13 +23,13 @@ const { toggleSavedFiltersModal } = filterStoreReducers; const renderMaximizeIcon = () => ; const renderDeleteIcon = () => ; -const MaximizeButton = () => { +export const MaximizeButton = () => { const [_appStore, setAppStore] = useAppStore((_store) => null); const onClick = useCallback(() => setAppStore(appStoreReducers.toggleMaximize), []); return ; }; -const SavedFiltersButton = () => { +export const SavedFiltersButton = () => { const [_store, setLogsStore] = useFilterStore((_store) => null); const onClick = useCallback(() => setLogsStore((store) => toggleSavedFiltersModal(store, true)), []); return ( From 66b823782c1b85b19666a4b3baca85ee432dd89a Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Wed, 13 Nov 2024 14:04:47 +0530 Subject: [PATCH 03/64] Added sidebar and collapsable config --- src/constants/theme.ts | 2 +- src/pages/Correlation/index.tsx | 68 ++++++++++++------- .../Stream/components/PrimaryToolbar.tsx | 2 +- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/constants/theme.ts b/src/constants/theme.ts index 3a092f42..4b6b0cab 100644 --- a/src/constants/theme.ts +++ b/src/constants/theme.ts @@ -12,4 +12,4 @@ export const DASHBOARDS_SIDEBAR_WIDTH = 200; export const DASHBOARD_TOOLBAR_HEIGHT = 50; export const JSON_VIEW_TOOLBAR_HEIGHT = 50; export const LOGS_CONFIG_SIDEBAR_WIDTH = 200; -export const LOGS_FOOTER_HEIGHT = 50; \ No newline at end of file +export const LOGS_FOOTER_HEIGHT = 50; diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx index 5eb03727..bb80e00a 100644 --- a/src/pages/Correlation/index.tsx +++ b/src/pages/Correlation/index.tsx @@ -1,55 +1,73 @@ import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; import _ from 'lodash'; -import { useDocumentTitle } from '@mantine/hooks'; -import { Stack } from '@mantine/core'; +import { useDisclosure, useDocumentTitle } from '@mantine/hooks'; +import { Stack, Box, Group, Button, Collapse } from '@mantine/core'; import LogsView from '../Stream/Views/Explore/LogsView'; import Querier from '../Stream/components/Querier'; import SecondaryToolbar from '../Stream/components/SecondaryToolbar'; import { PRIMARY_HEADER_HEIGHT, + SECONDARY_SIDEBAR_WIDTH, STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, STREAM_PRIMARY_TOOLBAR_HEIGHT, } from '@/constants/theme'; -import { MaximizeButton, SavedFiltersButton } from '../Stream/components/PrimaryToolbar'; +import { MaximizeButton } from '../Stream/components/PrimaryToolbar'; import TimeRange from '@/components/Header/TimeRange'; import RefreshInterval from '@/components/Header/RefreshInterval'; import RefreshNow from '@/components/Header/RefreshNow'; import ShareButton from '@/components/Header/ShareButton'; +import SideBar from '@/components/SideBar/Sidebar'; const Correlation = () => { useDocumentTitle('Parseable | Correlation'); - + const [opened, { toggle }] = useDisclosure(false); + const sideBarWidth = SECONDARY_SIDEBAR_WIDTH; return ( - -

PKB

+ + + - - - - - - - {/* */} - - + + + + +

PKB

+
+ + + + + + + + + + +
- - -
+ ); }; diff --git a/src/pages/Stream/components/PrimaryToolbar.tsx b/src/pages/Stream/components/PrimaryToolbar.tsx index 1d2c92dc..d19b1ed6 100644 --- a/src/pages/Stream/components/PrimaryToolbar.tsx +++ b/src/pages/Stream/components/PrimaryToolbar.tsx @@ -29,7 +29,7 @@ export const MaximizeButton = () => { return ; }; -export const SavedFiltersButton = () => { +const SavedFiltersButton = () => { const [_store, setLogsStore] = useFilterStore((_store) => null); const onClick = useCallback(() => setLogsStore((store) => toggleSavedFiltersModal(store, true)), []); return ( From f37f28c77f31942e49ef5b114edb3d82f9a022a3 Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Wed, 13 Nov 2024 14:07:09 +0530 Subject: [PATCH 04/64] Added sidebar and collapsable config --- src/components/SideBar/Sidebar.tsx | 110 ++++++++++++++++++ .../SideBar/styles/SideBar.module.css | 52 +++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/components/SideBar/Sidebar.tsx create mode 100644 src/components/SideBar/styles/SideBar.module.css diff --git a/src/components/SideBar/Sidebar.tsx b/src/components/SideBar/Sidebar.tsx new file mode 100644 index 00000000..d07da553 --- /dev/null +++ b/src/components/SideBar/Sidebar.tsx @@ -0,0 +1,110 @@ +import { Stack, Tooltip } from '@mantine/core'; +import classes from './styles/SideBar.module.css'; +import { IconBolt, IconFilterSearch, IconSettings2 } from '@tabler/icons-react'; +import { useCallback } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; +import { STREAM_VIEWS } from '@/constants/routes'; +import _ from 'lodash'; + +type MenuItemProps = { + setCurrentView: (view: string) => void; + currentView: string; +}; + +const AllLogsButton = (props: MenuItemProps) => { + const viewName = 'explore'; + const isActive = props.currentView === viewName; + const additionalClassNames = `${isActive ? classes.activeMenuItem : ''}`; + return ( + props.setCurrentView(viewName)} + style={{ padding: '4px 0', alignItems: 'center' }} + className={classes.menuItemContainer}> + + + + + + + ); +}; + +const ConfigButton = (props: MenuItemProps) => { + const viewName = 'manage'; + const isActive = props.currentView === viewName; + const additionalClassNames = `${isActive ? classes.activeMenuItem : ''}`; + return ( + props.setCurrentView(viewName)} + style={{ padding: '4px 0', alignItems: 'center' }} + className={classes.menuItemContainer}> + + + + + + + ); +}; + +const LiveTailMenu = (props: MenuItemProps) => { + const viewName = 'live-tail'; + const isActive = props.currentView === viewName; + const additionalClassNames = `${isActive ? classes.activeMenuItem : ''}`; + return ( + props.setCurrentView(viewName)} + className={classes.menuItemContainer} + style={{ padding: '4px 0', alignItems: 'center' }}> + + + + + + + ); +}; + +const SideBar = () => { + const [currentStream] = useAppStore((store) => store.currentStream); + const [isStandAloneMode] = useAppStore((store) => store.isStandAloneMode); + const { view } = useParams(); + const navigate = useNavigate(); + + const setCurrentView = useCallback( + (view: string) => { + if (_.includes(STREAM_VIEWS, view)) { + navigate(`/${currentStream}/${view}`); + } + }, + [currentStream], + ); + + return ( + + + + {isStandAloneMode && } + + + + ); +}; + +export default SideBar; diff --git a/src/components/SideBar/styles/SideBar.module.css b/src/components/SideBar/styles/SideBar.module.css new file mode 100644 index 00000000..b1100c3b --- /dev/null +++ b/src/components/SideBar/styles/SideBar.module.css @@ -0,0 +1,52 @@ +.container { + flex: 1; + gap: 1rem; + border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); +} + +.menuItemContainer { + cursor: pointer; +} + +.activeMenuItem { + padding: 4px; + background-color: var(--mantine-color-gray-1); + border-radius: 0.2rem; +} + +.menuIconContainer { + background-color: transparent; +} + +.menuLabel { + font-size: 0.7rem; + font-weight: 500; + padding-right: 4px; +} + +.streamDropdownContainer { + /* margin-top: 0.5rem; */ + /* padding: 0 1.25rem; */ + position: relative; +} + +.sideBarToggleContainer { + position: absolute; + left: 93%; + top: 30%; + align-items: center; + justify-content: center; + z-index: 99; +} + +.sideBarToggleIconContainer { + padding: 2rem; + border-radius: 50%; + background-color: white; + border: 1px solid var(--mantine-color-gray-2); + cursor: pointer; +} + +.icon { + color: var(--mantine-color-gray-6); +} \ No newline at end of file From 544b40052a9b5d19dd9dd2b392bb790458650dae Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Thu, 14 Nov 2024 11:34:57 +0530 Subject: [PATCH 05/64] Added new UX few items --- src/pages/Correlation/index.tsx | 101 ++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 18 deletions(-) diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx index bb80e00a..3e771a53 100644 --- a/src/pages/Correlation/index.tsx +++ b/src/pages/Correlation/index.tsx @@ -1,14 +1,13 @@ import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; import _ from 'lodash'; -import { useDisclosure, useDocumentTitle } from '@mantine/hooks'; -import { Stack, Box, Group, Button, Collapse } from '@mantine/core'; +import { useDocumentTitle } from '@mantine/hooks'; +import { Stack, Box, Pill } from '@mantine/core'; import LogsView from '../Stream/Views/Explore/LogsView'; import Querier from '../Stream/components/Querier'; import SecondaryToolbar from '../Stream/components/SecondaryToolbar'; import { PRIMARY_HEADER_HEIGHT, - SECONDARY_SIDEBAR_WIDTH, STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, STREAM_PRIMARY_TOOLBAR_HEIGHT, } from '@/constants/theme'; @@ -17,37 +16,91 @@ import TimeRange from '@/components/Header/TimeRange'; import RefreshInterval from '@/components/Header/RefreshInterval'; import RefreshNow from '@/components/Header/RefreshNow'; import ShareButton from '@/components/Header/ShareButton'; -import SideBar from '@/components/SideBar/Sidebar'; const Correlation = () => { useDocumentTitle('Parseable | Correlation'); - const [opened, { toggle }] = useDisclosure(false); - const sideBarWidth = SECONDARY_SIDEBAR_WIDTH; + // const [opened, { toggle }] = useDisclosure(false); + // const sideBarWidth = SECONDARY_SIDEBAR_WIDTH; return ( - - - - - - - -

PKB

-
+ +
+ Stream 1 + Stream 2 + Stream 3 +
+
+ Saved Correlations +
+ +
+ +
+
+
Stream A
+
+ {new Array(5).fill(0).map((key, index) => { + return
Field {index} A
; + })} +
+
+
+ Stream B +
+
+ Stream C +
+
+
Col 2
+
{ }}> - @@ -71,4 +123,17 @@ const Correlation = () => { ); }; +{ + /* + + + +

PKB

+
*/ +} +{ + /* + + */ +} export default Correlation; From baf8901dc196c04d87e1dde8240f741eceb8123c Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Thu, 14 Nov 2024 15:12:10 +0530 Subject: [PATCH 06/64] Added new desing flow for correlation --- src/pages/Correlation/index.tsx | 166 +++++++++++++++++--------------- 1 file changed, 90 insertions(+), 76 deletions(-) diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx index 3e771a53..06f9f3ed 100644 --- a/src/pages/Correlation/index.tsx +++ b/src/pages/Correlation/index.tsx @@ -2,15 +2,11 @@ import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; import _ from 'lodash'; import { useDocumentTitle } from '@mantine/hooks'; -import { Stack, Box, Pill } from '@mantine/core'; +import { Stack, Box, Pill, PillsInput, Badge } from '@mantine/core'; import LogsView from '../Stream/Views/Explore/LogsView'; import Querier from '../Stream/components/Querier'; import SecondaryToolbar from '../Stream/components/SecondaryToolbar'; -import { - PRIMARY_HEADER_HEIGHT, - STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, - STREAM_PRIMARY_TOOLBAR_HEIGHT, -} from '@/constants/theme'; +import { PRIMARY_HEADER_HEIGHT } from '@/constants/theme'; import { MaximizeButton } from '../Stream/components/PrimaryToolbar'; import TimeRange from '@/components/Header/TimeRange'; import RefreshInterval from '@/components/Header/RefreshInterval'; @@ -20,7 +16,6 @@ import ShareButton from '@/components/Header/ShareButton'; const Correlation = () => { useDocumentTitle('Parseable | Correlation'); // const [opened, { toggle }] = useDisclosure(false); - // const sideBarWidth = SECONDARY_SIDEBAR_WIDTH; return ( { position: 'relative', width: '100%', }}> +
+
Streams
+
+
Stream A
+ + React + + + React + + + React + +
+
+
Stream B
+ + React + + + React + + + React + +
+
+
Stream C
+ + React + + + React + + + React + +
+
{ overflowY: 'scroll', width: '100%', }}> - -
- Stream 1 - Stream 2 - Stream 3 -
-
- Saved Correlations -
- -
- -
-
-
Stream A
-
- {new Array(5).fill(0).map((key, index) => { - return
Field {index} A
; - })} -
-
-
- Stream B -
-
- Stream C -
-
-
Col 2
-
- + +
+
Fields
+ + + + Stream A.Status + + + Stream B.Status Code + + + Errors + + {/* */} + + +
+
+
Joins
+ + + + Stream A.Status = Stream B.Status Code + + + {/* */} + + +
+
+ + From 84ccdb8c3123366cded47ed6d9aab2247a855d4b Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Fri, 15 Nov 2024 14:44:54 +0530 Subject: [PATCH 07/64] correlation --- src/pages/Correlation/index.tsx | 179 ++++++++++++-------- src/pages/Stream/Views/Explore/LogsView.tsx | 4 +- 2 files changed, 115 insertions(+), 68 deletions(-) diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx index 06f9f3ed..31627550 100644 --- a/src/pages/Correlation/index.tsx +++ b/src/pages/Correlation/index.tsx @@ -2,20 +2,68 @@ import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; import _ from 'lodash'; import { useDocumentTitle } from '@mantine/hooks'; -import { Stack, Box, Pill, PillsInput, Badge } from '@mantine/core'; +import { Stack, Box, Pill, PillsInput, Badge, TextInput, px, Select } from '@mantine/core'; import LogsView from '../Stream/Views/Explore/LogsView'; import Querier from '../Stream/components/Querier'; import SecondaryToolbar from '../Stream/components/SecondaryToolbar'; -import { PRIMARY_HEADER_HEIGHT } from '@/constants/theme'; +import { PRIMARY_HEADER_HEIGHT, STREAM_PRIMARY_TOOLBAR_HEIGHT } from '@/constants/theme'; import { MaximizeButton } from '../Stream/components/PrimaryToolbar'; import TimeRange from '@/components/Header/TimeRange'; import RefreshInterval from '@/components/Header/RefreshInterval'; import RefreshNow from '@/components/Header/RefreshNow'; import ShareButton from '@/components/Header/ShareButton'; +import { useRef, useState } from 'react'; +import { IconSearch } from '@tabler/icons-react'; +import classes from '../../components/Header/styles/LogQuery.module.css'; const Correlation = () => { useDocumentTitle('Parseable | Correlation'); // const [opened, { toggle }] = useDisclosure(false); + const [fields, setFileds] = useState([]); + const searchIcon = ; + const [selectedStreams, setSelectedStreams] = useState([]); + const valueRef = useRef(); + + const handleChange = (value: string | null) => { + if (value === null) return; + + const selectedData = { + value, + fields: ['status', 'id', 'erros'], + }; + + setSelectedStreams((prev: any) => [...prev, selectedData]); + }; + + const removeStream = (streamName: string) => { + setSelectedStreams((prev: any) => prev.filter((stream: any) => stream.value !== streamName)); + }; + + const handleColor = (streamName: string) => { + switch (streamName) { + case 'streamA': + return 'grape'; + case 'streamB': + return 'teal'; + case 'streamA-field': + return '#DA77F1'; + case 'streamB-field': + return '#38D9A9'; + } + }; + + const addField = (field: string, streamName: string) => { + const selectedField = { + field, + streamName: `${streamName}-field`, + }; + setFileds((prev: any) => [...prev, selectedField]); + }; + + const removeField = (field: string) => { + setFileds((prev: any) => prev.filter((fieldItr: any) => fieldItr.field !== field)); + }; + return ( { gap: '20px', }}>
Streams
-
-
Stream A
- - React - - - React - - - React - -
-
-
Stream B
- - React - - - React - - - React - -
-
-
Stream C
- - React - - - React - - - React - -
+ + - {selectedStreams.map((stream: any) => { - return ( -
-
-
{stream.value}
-
removeStream(stream.value)}> - X + {Object.keys(fields).length < 4 && ( +
+ setSelectedStream(value)} + placeholder="Select Stream" + data={userSpecificStreams?.map((stream: any) => stream.name) ?? []} + /> + stream.name === selectedStream)} + onClick={addStream}> + + +
+ )} +
+ {Object.entries(fields).map(([stream, fieldsIter]: [any, any]) => { + const typedFields = fieldsIter.headers as string[]; + const totalStreams = Object.entries(fields).length; + const heightPercentage = totalStreams > 1 ? `${100 / totalStreams}%` : '100%'; + return ( +
+
+ + {stream} + + removeStream(stream)} + /> +
+
+ {typedFields.map((field: string) => { + return ( + addField(field, stream)} + /> + ); + })}
- {stream.fields.map((field: any) => { - return ( - addField(field, stream.value)} - style={{ width: '160px', display: 'flex', justifyContent: 'center' }}> - {field} - - ); - })} -
- ); - })} + ); + })} +
{
Fields
- {fields.map((field: any, index: any) => { - return ( + {Object.entries(selectedFields).map(([streamName, fields]: [any, any]) => + fields.map((field: any, index: any) => ( removeField(field.field)} - style={{ backgroundColor: handleColor(field.streamName) }}> - {field.field} + onRemove={() => removeField(field, streamName)} + style={{ backgroundColor: handleColor(streamName) }}> + {field} - ); - })} + )), + )}
@@ -182,17 +225,9 @@ const Correlation = () => { padding: '5px', height: '100%', }} - w="100%"> - - - - - - -
+ w="100%">
- - + {showLogData && }
); diff --git a/src/pages/Stream/Views/Explore/LogsView.tsx b/src/pages/Stream/Views/Explore/LogsView.tsx index 3e1b3f28..5fa46622 100644 --- a/src/pages/Stream/Views/Explore/LogsView.tsx +++ b/src/pages/Stream/Views/Explore/LogsView.tsx @@ -22,9 +22,10 @@ const LogsView = (props: { schemaLoading: boolean; infoLoading: boolean }) => { return ( - {/* {viewMode === 'table' && ( + ƒ + {viewMode === 'table' && ( - )} */} + )} {viewMode === 'table' ? : } ); diff --git a/src/routes/elements.tsx b/src/routes/elements.tsx index 9141fd1a..df0bffb6 100644 --- a/src/routes/elements.tsx +++ b/src/routes/elements.tsx @@ -8,9 +8,9 @@ import { LogsProvider } from '@/pages/Stream/providers/LogsProvider'; import { FilterProvider } from '@/pages/Stream/providers/FilterProvider'; import { StreamProvider } from '@/pages/Stream/providers/StreamProvider'; import { ClusterProvider } from '@/pages/Systems/providers/ClusterProvider'; +import { CorrelationProvider } from '@/pages/Correlation/providers/CorrelationProvider'; import { DashbaordsProvider } from '@/pages/Dashboards/providers/DashboardsProvider'; import Dashboards from '@/pages/Dashboards'; -import Correlation from '@/pages/Correlation'; export const HomeElement: FC = () => { return ( @@ -32,13 +32,16 @@ export const DashboardsElement: FC = () => { ); }; +const Correlation = lazy(() => import('@/pages/Correlation')); export const CorrelationElement: FC = () => { return ( - + + + From c532cf2c347fc81b4fa4ed12357a358ec501e72f Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Tue, 19 Nov 2024 16:55:11 +0530 Subject: [PATCH 09/64] Created Correlation store and new UX changes --- src/hooks/useCorrelationQueryLogs.tsx | 78 +++++ .../Correlation/Views/CorrelationTable.tsx | 164 +++++++++++ .../providers/CorrelationProvider.tsx | 211 +++++++++++++ .../Correlation/styles/Correlation.module.css | 55 ++++ src/pages/Correlation/styles/Logs.module.css | 276 ++++++++++++++++++ 5 files changed, 784 insertions(+) create mode 100644 src/hooks/useCorrelationQueryLogs.tsx create mode 100644 src/pages/Correlation/Views/CorrelationTable.tsx create mode 100644 src/pages/Correlation/providers/CorrelationProvider.tsx create mode 100644 src/pages/Correlation/styles/Correlation.module.css create mode 100644 src/pages/Correlation/styles/Logs.module.css diff --git a/src/hooks/useCorrelationQueryLogs.tsx b/src/hooks/useCorrelationQueryLogs.tsx new file mode 100644 index 00000000..f9021a01 --- /dev/null +++ b/src/hooks/useCorrelationQueryLogs.tsx @@ -0,0 +1,78 @@ +import { getQueryLogsWithHeaders } from '@/api/query'; +import { StatusCodes } from 'http-status-codes'; +import useMountedState from './useMountedState'; +import { useLogsStore } from '@/pages/Stream/providers/LogsProvider'; +import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; +import _ from 'lodash'; +import { AxiosError } from 'axios'; +import { useGetStreamSchema } from '@/hooks/useGetLogStreamSchema'; +import { useStreamStore } from '@/pages/Stream/providers/StreamProvider'; +import { + CORRELATION_LOAD_LIMIT, + correlationStoreReducers, + useCorrelationStore, +} from '@/pages/Correlation/providers/CorrelationProvider'; +import { notifyError } from '@/utils/notification'; + +const { setStreamData } = correlationStoreReducers; + +export const useCorrelationQueryLogs = () => { + const [error, setError] = useMountedState(null); + const [loading, setLoading] = useMountedState(false); + const [, setCorrelationStore] = useCorrelationStore((store) => store.streamData); + const [queryEngine] = useAppStore((store) => store.instanceConfig?.queryEngine); + const [streamInfo] = useStreamStore((store) => store.info); + const [currentStream] = useAppStore((store) => store.currentStream); + const timePartitionColumn = _.get(streamInfo, 'time_partition', 'p_timestamp'); + const { refetch: refetchSchema } = useGetStreamSchema({ streamName: currentStream || '' }); + const [ + { + timeRange, + tableOpts: { currentOffset }, + }, + ] = useLogsStore((store) => store); + + const defaultQueryOpts = { + queryEngine, + streamName: currentStream || '', + startTime: timeRange.startTime, + endTime: timeRange.endTime, + limit: CORRELATION_LOAD_LIMIT, + pageOffset: currentOffset, + timePartitionColumn, + }; + + const getCorrelationData = async () => { + try { + setLoading(true); + setError(null); + refetchSchema(); // fetch schema parallelly every time we fetch logs + const logsQueryRes = await (async () => { + return await getQueryLogsWithHeaders(defaultQueryOpts); + })(); + const logs = logsQueryRes.data; + const isInvalidResponse = _.isEmpty(logs) || _.isNil(logs) || logsQueryRes.status !== StatusCodes.OK; + if (isInvalidResponse) return setError('Failed to query log'); + + const { records, fields } = logs; + if (fields.length > 0) { + return setCorrelationStore((store) => setStreamData(store, currentStream || '', records, fields)); + } else { + notifyError({ message: `${currentStream} doesn't have any fields` }); + } + } catch (e) { + const axiosError = e as AxiosError; + const errorMessage = axiosError?.response?.data; + setError(_.isString(errorMessage) && !_.isEmpty(errorMessage) ? errorMessage : 'Failed to query log'); + return setCorrelationStore((store) => setStreamData(store, currentStream || '', [], [])); + } finally { + setLoading(false); + } + }; + + return { + error, + loading: loading, + getCorrelationData, + }; +}; diff --git a/src/pages/Correlation/Views/CorrelationTable.tsx b/src/pages/Correlation/Views/CorrelationTable.tsx new file mode 100644 index 00000000..cc2dbd9c --- /dev/null +++ b/src/pages/Correlation/Views/CorrelationTable.tsx @@ -0,0 +1,164 @@ +import { MantineReactTable, MRT_ColumnDef } from 'mantine-react-table'; +import { useCorrelationStore } from '../providers/CorrelationProvider'; +import { useCallback, useEffect, useState } from 'react'; +import tableStyles from '../styles/Logs.module.css'; +import { formatLogTs } from '@/pages/Stream/providers/LogsProvider'; +import timeRangeUtils from '@/utils/timeRangeUtils'; +import { CopyIcon } from '@/pages/Stream/Views/Explore/JSONView'; +import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; +import { LOGS_FOOTER_HEIGHT } from '@/constants/theme'; +import Column from '@/pages/Stream/components/Column'; +import { Log } from '@/@types/parseable/api/query'; +import { FieldTypeMap } from '@/pages/Stream/providers/StreamProvider'; +import _ from 'lodash'; + +const localTz = timeRangeUtils.getLocalTimezone(); + +type CellType = string | number | boolean | null | undefined; + +const getSanitizedValue = (value: CellType, isTimestamp: boolean) => { + if (isTimestamp) { + const timestamp = String(value).trim(); + const isValidTimestamp = !isNaN(Date.parse(timestamp)); + + if (timestamp && isValidTimestamp) { + return formatLogTs(timestamp); + } else { + return ''; + } + } + + if (value === null || value === undefined) { + return ''; + } + + if (typeof value === 'boolean') { + return value.toString(); + } + + return String(value); +}; + +const makeColumnsFromSelectedFields = ( + selectedFields: Record, + isSecureHTTPContext: boolean, + fieldTypeMap: FieldTypeMap, +) => { + return Object.entries(selectedFields).flatMap(([streamName, fields]) => + fields.map((field: string) => ({ + id: `${streamName}.${field}`, + header: `${streamName}.${field}`, + accessorKey: `${streamName}.${field}`, + grow: true, + Cell: ({ cell }: { cell: any }) => { + const value = _.get(cell.row.original, `${streamName}.${field}`, ''); + const isTimestamp = _.get(fieldTypeMap, `${streamName}.${field}`, null) === 'timestamp'; + const sanitizedValue = getSanitizedValue(value, isTimestamp); + + return ( +
+ {sanitizedValue} +
+ {isSecureHTTPContext && sanitizedValue && } +
+
+ ); + }, + })), + ); +}; + +const Table = (props: { primaryHeaderHeight: number }) => { + const [{ pageData, wrapDisabledColumns }] = useCorrelationStore((store) => store.tableOpts); + const [isSecureHTTPContext] = useAppStore((store) => store.isSecureHTTPContext); + const [columns, setColumns] = useState[]>([]); + + const [{ selectedFields }] = useCorrelationStore((store) => store); + + useEffect(() => { + const updatedColumns = makeColumnsFromSelectedFields(selectedFields, isSecureHTTPContext, { + datetime: 'text', + host: 'text', + id: 'text', + method: 'text', + p_metadata: 'text', + p_tags: 'text', + p_timestamp: 'timestamp', + referrer: 'text', + status: 'number', + 'user-identifier': 'text', + }); + setColumns(updatedColumns); + }, [selectedFields]); + + console.log('Columns:::', columns); + console.log('Rows::', pageData); + + const makeCellCustomStyles = useCallback( + (columnName: string) => { + return { + className: tableStyles.customCell, + style: { + padding: '0.5rem 1rem', + fontSize: '0.6rem', + overflow: 'hidden', + textOverflow: 'ellipsis', + display: 'table-cell', + ...(!_.includes(wrapDisabledColumns, columnName) ? { whiteSpace: 'nowrap' as 'nowrap' } : {}), + }, + }; + }, + [wrapDisabledColumns], + ); + if (columns.length == 0) return; + return ( + makeCellCustomStyles(id)} + mantineTableHeadRowProps={{ style: { border: 'none' } }} + mantineTableHeadCellProps={{ + style: { + fontWeight: 600, + fontSize: '0.65rem', + border: 'none', + padding: '0.5rem 1rem', + }, + }} + mantineTableBodyRowProps={({ row }) => { + return { + 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={{}} + enableStickyHeader={true} + defaultColumn={{ minSize: 100 }} + layoutMode="grid" + state={{}} + mantineTableContainerProps={{ + style: { + height: `calc(100vh - ${props.primaryHeaderHeight + LOGS_FOOTER_HEIGHT}px )`, + }, + }} + // renderColumnActionsMenuItems={({ column }) => { + // return ; + // }} + /> + ); +}; + +export default Table; diff --git a/src/pages/Correlation/providers/CorrelationProvider.tsx b/src/pages/Correlation/providers/CorrelationProvider.tsx new file mode 100644 index 00000000..9b084325 --- /dev/null +++ b/src/pages/Correlation/providers/CorrelationProvider.tsx @@ -0,0 +1,211 @@ +import { Log } from '@/@types/parseable/api/query'; +import initContext from '@/utils/initContext'; +import _ from 'lodash'; + +export const CORRELATION_LOAD_LIMIT = 250; + +export const STREAM_COLORS = ['#FDA4AF', '#D8B4FE', '#7DE3D3', '#FEE45E']; +export const STREAM_HEADER_COLORS = ['#9F1239', '#7E22CE', '#0F766E', '#A16207']; +export const FIELD_BACKGROUND_COLORS = ['#FFF8F8', '#F8F1FF', '#F4FFFC', '#FFFEF3']; + +const defaultSortKey = 'p_timestamp'; +const defaultSortOrder = 'desc' as 'desc'; + +type ReducerOutput = Partial; + +type CorrelationStore = { + streamData: any; + fields: any; + + selectedFields: any; + + tableOpts: { + disabledColumns: string[]; + wrapDisabledColumns: string[]; + pinnedColumns: string[]; + pageData: Log[]; + totalPages: number; + totalCount: number; + displayedCount: number; + currentPage: number; + perPage: number; + currentOffset: number; + headers: string[]; + orderedHeaders: string[]; + sortKey: string; + sortOrder: 'asc' | 'desc'; + filters: Record; + instantSearchValue: string; + configViewType: 'schema' | 'columns'; + enableWordWrap: boolean; + }; +}; + +type CorrelationStoreReducers = { + setStreamData: (store: CorrelationStore, currentStream: string, data: Log[], headers: string[]) => ReducerOutput; + deleteStreamData: (store: CorrelationStore, currentStream: string) => ReducerOutput; + setSelectedFields: (store: CorrelationStore, field: string, streamName: string) => ReducerOutput; + deleteSelectedField: (store: CorrelationStore, field: string, streamName: string) => ReducerOutput; +}; + +const initialState: CorrelationStore = { + streamData: null, + fields: {}, + selectedFields: [], + tableOpts: { + disabledColumns: [], + wrapDisabledColumns: [], + pinnedColumns: [], + pageData: [], + perPage: 50, + totalCount: 0, + displayedCount: 0, + totalPages: 0, + currentPage: 0, + currentOffset: 0, + headers: [], + orderedHeaders: [], + sortKey: defaultSortKey, + sortOrder: defaultSortOrder, + filters: {}, + instantSearchValue: '', + configViewType: 'columns', + enableWordWrap: true, + }, +}; + +const setSelectedFields = (store: CorrelationStore, field: string, streamName: string) => { + // Update selectedFields + const updatedSelectedFields = { + ...store.selectedFields, + [streamName]: store.selectedFields[streamName] + ? store.selectedFields[streamName].includes(field) + ? store.selectedFields[streamName] + : [...store.selectedFields[streamName], field] + : [field], + }; + + console.log(updatedSelectedFields); + + console.log(store.streamData); + + const recordCount = Math.min(...Object.values(store.streamData).map((stream: any) => stream.logData.length)); + + // Compute updated pageData + const updatedPageData = Array.from({ length: recordCount }, (_, index) => { + const combinedRecord: any = {}; + + for (const [stream, fields] of Object.entries(updatedSelectedFields)) { + const streamRecord = store.streamData[stream]?.logData[index]; + if (streamRecord) { + if (Array.isArray(fields)) { + fields.forEach((field) => { + combinedRecord[`${stream}.${field}`] = streamRecord[field]; + }); + } + } + } + + return combinedRecord; + }); + + // Return updated store + return { + ...store, + selectedFields: updatedSelectedFields, + tableOpts: { + ...store.tableOpts, + pageData: updatedPageData, + }, + }; +}; + +const deleteSelectedField = (store: CorrelationStore, field: string, streamName: string) => { + if (!store.selectedFields[streamName]) { + return store; + } + + const updatedFields = store.selectedFields[streamName].filter((selectedField: string) => selectedField !== field); + + const newSelectedFields = { ...store.selectedFields }; + + if (updatedFields.length > 0) { + newSelectedFields[streamName] = updatedFields; + } else { + delete newSelectedFields[streamName]; + } + + // Delete from the pageData as well + const updatedPageData = store.tableOpts.pageData.map((row: any) => { + const newRow = { ...row }; + delete newRow[`${streamName}.${field}`]; + return newRow; + }); + + return { + ...store, + selectedFields: newSelectedFields, + tableOpts: { + ...store.tableOpts, + pageData: updatedPageData, + }, + }; +}; + +const setStreamData = (store: CorrelationStore, currentStream: string, data: Log[], headers: string[]) => { + if (!currentStream) { + return { + fields: store.fields, + }; + } + + // Check the number of existing streams + const currentStreamCount = Object.keys(store.streamData || {}).length; + + // Enforce a limit of 4 streams + if (currentStreamCount >= 4 && !(currentStream in (store.streamData || {}))) { + console.warn('Stream limit reached. Cannot add more than 4 streams.'); + return store; // Return the unchanged state + } + + return { + fields: { + ...store.fields, + [currentStream]: { + headers, + color: STREAM_COLORS[currentStreamCount], + headerColor: STREAM_HEADER_COLORS[currentStreamCount], + backgroundColor: FIELD_BACKGROUND_COLORS[currentStreamCount], + }, + }, + streamData: { + ...store.streamData, + [currentStream]: { + logData: data, + }, + }, + }; +}; + +const deleteStreamData = (store: CorrelationStore, currentStream: string) => { + const newfields = { ...store.fields }; + + if (currentStream in newfields) { + delete newfields[currentStream]; + } + + return { + fields: newfields, + }; +}; + +const { Provider: CorrelationProvider, useStore: useCorrelationStore } = initContext(initialState); + +const correlationStoreReducers: CorrelationStoreReducers = { + setStreamData, + deleteStreamData, + setSelectedFields, + deleteSelectedField, +}; + +export { CorrelationProvider, useCorrelationStore, correlationStoreReducers }; diff --git a/src/pages/Correlation/styles/Correlation.module.css b/src/pages/Correlation/styles/Correlation.module.css new file mode 100644 index 00000000..1855f8bf --- /dev/null +++ b/src/pages/Correlation/styles/Correlation.module.css @@ -0,0 +1,55 @@ +.correlationWrapper { + flex: 1; + display: flex; + position: relative; + width: 100%; +} + +.correlationSideBarWrapper { + width: 230px; + border-right: 1px solid #dee2e6; + padding: 14px; + display: flex; + flex-direction: column; + gap: 15px; +} + +.streamWrapper { + display: flex; + flex-direction: column; + overflow: hidden; + padding: 12px; + border-radius: 8px; + gap: 12px; +} + +.streamNameWrapper { + display: flex; + justify-content: space-between; + align-items: center; +} + +.streamName { + font-size: 14px; + font-weight: bold; + color: #9f1239; +} + +.fieldsWrapper { + overflow-y: auto; + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.fieldItem { + width: 100%; + border-radius: 4px; + padding: 6px; + height: 24px; + align-items: center; + cursor: pointer; + display: flex; + align-items: center; + font-size: 12px; +} diff --git a/src/pages/Correlation/styles/Logs.module.css b/src/pages/Correlation/styles/Logs.module.css new file mode 100644 index 00000000..0ab4c807 --- /dev/null +++ b/src/pages/Correlation/styles/Logs.module.css @@ -0,0 +1,276 @@ +.col { + display: flex; + flex-direction: column; + width: max-content; +} + +.container { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.innerContainer { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.tableContainer { + position: relative; +} + +.pinnedTableContainer { + overflow: unset !important; +} + +.pinnedScrollView { + overflow: unset !important; +} + +.tableStyle { + white-space: nowrap; + overflow: scroll; + padding: 0; + width: 100%; +} + +.theadStyle { + position: sticky; + background-color: #fff; + top: 0; + z-index: 2; +} + +.liveTheadStyle { + position: sticky; + background-color: #fff; + top: 0; +} + +.theadStylePinned { + position: sticky; + top: 0; + background-color: #fff; + z-index: 2; +} + +.trStyle { + background-color: #fff; + &:hover { + background-color: #f1f3f5; + } + z-index: 2; + border-top: 'none !important'; + + td { + border-top: none !important; + padding: 0.5rem 1rem; + font-size: 0.6rem; + } +} + +.trEvenStyle { + cursor: pointer; + background-color: #f8f9fa; + &:hover { + background-color: #f1f3f5; + } + z-index: 2; + border-color: #ff0000; + td { + border-top: none !important; + padding: 0.5rem 1rem; + font-size: 0.6rem; + } +} + +.tdArrowContainer { + display: flex; + justify-content: center; + align-items: center; +} + +.tdArrow { + position: sticky; + right: 0; + background: inherit; + box-shadow: 0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.05) 0 0.625rem 0.9375rem -0.3125rem, + rgba(0, 0, 0, 0.04) 0 0.4375rem 0.4375rem -0.3125rem; + tr:hover & { + background-color: #f1f3f5; + } +} + +.thColumnMenuBtn { + width: 0.75rem; + height: 0.75rem; +} + +.thColumnMenuResetBtn { + margin: 0.75rem; +} + +.thColumnMenuDropdown { + /* max-height: 2rem; */ + overflow-x: hidden; + overflow-y: scroll; +} + +.thColumnMenuDragHandle { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #868e96; + padding-right: 1rem; +} + +.thColumnMenuDraggable { + display: flex; +} + +.footerContainer { + padding: 0.6rem 1rem; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + border-top: 0.0625rem solid rgba(0, 0, 0, 0.1); +} + +.errorContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.limitActive { + background: #1a237e; + font-weight: 700; + color: #fff; + &:hover { + background-color: #1a237e; + } +} + +.limitOption { + font-weight: 400; + &:hover { + color: #1a237e; + } +} + +.limitBtn { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 0.6rem; + background: white; + padding: 0.4rem 0.625rem; + border: 1px solid var(--mantine-color-gray-2); + border-radius: 0.425rem; + &:hover { + background: #e0e0e0; + } +} + +.limitBtnText { + font-size: 0.65rem; +} + +.trackStyle { + background-color: #545beb; +} + +.modalTitle { + font-size: 1.2rem; + font-weight: 600; +} + +.fieldTitle { + font-size: var(--mantine-font-size-sm); + font-weight: 500; +} + +.fieldDescription { + font-size: var(--mantine-font-size-sm); + color: var(--mantine-color-gray-6); +} + +.fieldsContainer { + border: 1px solid var(--mantine-color-gray-4); + padding: var(--mantine-spacing-md); + border-radius: 0.2rem; +} + +.alertSection { + border: 1px solid var(--mantine-color-gray-4); + border-radius: 0.2rem; + padding: 1rem; + margin: 0.6rem 0; +} + +.targetContainer { + gap: 8; + background-color: red; + padding: 1rem; + border-radius: 0.5rem; + background-color: var(--mantine-color-gray-1); +} + +.formBtn { + display: flex; + justify-content: center; + align-items: center; + background-color: white; + color: #211f1f; + border: 1px #e9ecef solid; + padding: 0.625rem 1rem; + margin-right: 0.625rem; + border-radius: rem(8px); + &:hover { + color: #211f1f; + } +} + +.infoTooltipIcon { + color: var(--mantine-color-gray-5); + cursor: pointer; +} + +.copyIcon { + color: #211f1f; +} +.copyIcon:hover {background-color: #f1f3f5; +} + + +.customCell { + position: relative; + + &:hover { + .customCellContainer { + .copyIconContainer { + display: block; + } + } + } + + .customCellContainer { + .copyIconContainer { + position: absolute; + top: 25%; + right: 0; + display: none; + } + } +} \ No newline at end of file From 71637c38707bacf1fafe2fc96f6714a1b65c8866 Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Tue, 19 Nov 2024 17:05:25 +0530 Subject: [PATCH 10/64] Moved useCorrelationQueryLogs to react-query --- src/hooks/useCorrelationQueryLogs.tsx | 63 +++++++++++++++------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/hooks/useCorrelationQueryLogs.tsx b/src/hooks/useCorrelationQueryLogs.tsx index f9021a01..ba39c093 100644 --- a/src/hooks/useCorrelationQueryLogs.tsx +++ b/src/hooks/useCorrelationQueryLogs.tsx @@ -13,12 +13,12 @@ import { useCorrelationStore, } from '@/pages/Correlation/providers/CorrelationProvider'; import { notifyError } from '@/utils/notification'; +import { useQuery } from 'react-query'; const { setStreamData } = correlationStoreReducers; export const useCorrelationQueryLogs = () => { const [error, setError] = useMountedState(null); - const [loading, setLoading] = useMountedState(false); const [, setCorrelationStore] = useCorrelationStore((store) => store.streamData); const [queryEngine] = useAppStore((store) => store.instanceConfig?.queryEngine); const [streamInfo] = useStreamStore((store) => store.info); @@ -42,37 +42,44 @@ export const useCorrelationQueryLogs = () => { timePartitionColumn, }; - const getCorrelationData = async () => { - try { - setLoading(true); - setError(null); - refetchSchema(); // fetch schema parallelly every time we fetch logs - const logsQueryRes = await (async () => { - return await getQueryLogsWithHeaders(defaultQueryOpts); - })(); - const logs = logsQueryRes.data; - const isInvalidResponse = _.isEmpty(logs) || _.isNil(logs) || logsQueryRes.status !== StatusCodes.OK; - if (isInvalidResponse) return setError('Failed to query log'); + console.log(currentStream); - const { records, fields } = logs; - if (fields.length > 0) { - return setCorrelationStore((store) => setStreamData(store, currentStream || '', records, fields)); - } else { - notifyError({ message: `${currentStream} doesn't have any fields` }); - } - } catch (e) { - const axiosError = e as AxiosError; - const errorMessage = axiosError?.response?.data; - setError(_.isString(errorMessage) && !_.isEmpty(errorMessage) ? errorMessage : 'Failed to query log'); - return setCorrelationStore((store) => setStreamData(store, currentStream || '', [], [])); - } finally { - setLoading(false); - } - }; + const { + isLoading: logsLoading, + isRefetching: logsRefetching, + refetch: getCorrelationData, + } = useQuery( + ['fetch-logs', defaultQueryOpts], + () => { + refetchSchema(); + + return getQueryLogsWithHeaders(defaultQueryOpts); + }, + { + enabled: false, + refetchOnWindowFocus: false, + onSuccess: async (data) => { + const logs = data.data; + const isInvalidResponse = _.isEmpty(logs) || _.isNil(logs) || data.status !== StatusCodes.OK; + if (isInvalidResponse) return setError('Failed to query logs'); + + const { records, fields } = logs; + if (fields.length > 0) { + return setCorrelationStore((store) => setStreamData(store, currentStream || '', records, fields)); + } else { + notifyError({ message: `${currentStream} doesn't have any fields` }); + } + }, + onError: (data: AxiosError) => { + const errorMessage = data.response?.data as string; + setError(_.isString(errorMessage) && !_.isEmpty(errorMessage) ? errorMessage : 'Failed to query logs'); + }, + }, + ); return { error, - loading: loading, + loading: logsLoading || logsRefetching, getCorrelationData, }; }; From aae5b1e631a3407421fd1e0371ee22665010cfd0 Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Wed, 20 Nov 2024 10:59:47 +0530 Subject: [PATCH 11/64] A seperate hook for schema call --- src/hooks/useCorrelationQueryLogs.tsx | 6 +-- src/hooks/useGetCorrelationStreamSchema.ts | 47 +++++++++++++++++ .../Correlation/Views/CorrelationTable.tsx | 4 +- src/pages/Correlation/index.tsx | 15 +++++- .../providers/CorrelationProvider.tsx | 51 +++++++++++++++---- .../Correlation/styles/Correlation.module.css | 2 + src/pages/Stream/Views/Explore/LogsView.tsx | 1 - 7 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 src/hooks/useGetCorrelationStreamSchema.ts diff --git a/src/hooks/useCorrelationQueryLogs.tsx b/src/hooks/useCorrelationQueryLogs.tsx index ba39c093..7d6a19e8 100644 --- a/src/hooks/useCorrelationQueryLogs.tsx +++ b/src/hooks/useCorrelationQueryLogs.tsx @@ -5,7 +5,7 @@ import { useLogsStore } from '@/pages/Stream/providers/LogsProvider'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import _ from 'lodash'; import { AxiosError } from 'axios'; -import { useGetStreamSchema } from '@/hooks/useGetLogStreamSchema'; +import { useGetStreamSchema } from '@/hooks/useGetCorrelationStreamSchema'; import { useStreamStore } from '@/pages/Stream/providers/StreamProvider'; import { CORRELATION_LOAD_LIMIT, @@ -42,8 +42,6 @@ export const useCorrelationQueryLogs = () => { timePartitionColumn, }; - console.log(currentStream); - const { isLoading: logsLoading, isRefetching: logsRefetching, @@ -65,7 +63,7 @@ export const useCorrelationQueryLogs = () => { const { records, fields } = logs; if (fields.length > 0) { - return setCorrelationStore((store) => setStreamData(store, currentStream || '', records, fields)); + return setCorrelationStore((store) => setStreamData(store, currentStream || '', records)); } else { notifyError({ message: `${currentStream} doesn't have any fields` }); } diff --git a/src/hooks/useGetCorrelationStreamSchema.ts b/src/hooks/useGetCorrelationStreamSchema.ts new file mode 100644 index 00000000..2f3a791c --- /dev/null +++ b/src/hooks/useGetCorrelationStreamSchema.ts @@ -0,0 +1,47 @@ +import { getLogStreamSchema } from '@/api/logStream'; +import { AxiosError, isAxiosError } from 'axios'; +import _ from 'lodash'; +import { useQuery } from 'react-query'; +import { useState } from 'react'; +import { correlationStoreReducers, useCorrelationStore } from '@/pages/Correlation/providers/CorrelationProvider'; + +const { setStreamSchema } = correlationStoreReducers; + +export const useGetStreamSchema = (opts: { streamName: string }) => { + const { streamName } = opts; + const [, setCorrelationStore] = useCorrelationStore((_store) => null); + + const [errorMessage, setErrorMesssage] = useState(null); + + const { isError, isSuccess, isLoading, refetch, isRefetching } = useQuery( + ['stream-schema', streamName], + () => getLogStreamSchema(streamName), + { + retry: false, + enabled: streamName !== '', + refetchOnWindowFocus: false, + onSuccess: (data) => { + setErrorMesssage(null); + + setCorrelationStore((store) => setStreamSchema(store, data.data, streamName)); + }, + onError: (data: AxiosError) => { + if (isAxiosError(data) && data.response) { + const error = data.response?.data as string; + typeof error === 'string' && setErrorMesssage(error); + } else if (data.message && typeof data.message === 'string') { + setErrorMesssage(data.message); + } + }, + }, + ); + + return { + refetch, + isSuccess, + isError, + isLoading, + errorMessage, + isRefetching, + }; +}; diff --git a/src/pages/Correlation/Views/CorrelationTable.tsx b/src/pages/Correlation/Views/CorrelationTable.tsx index cc2dbd9c..4e2b7c0c 100644 --- a/src/pages/Correlation/Views/CorrelationTable.tsx +++ b/src/pages/Correlation/Views/CorrelationTable.tsx @@ -91,8 +91,8 @@ const Table = (props: { primaryHeaderHeight: number }) => { setColumns(updatedColumns); }, [selectedFields]); - console.log('Columns:::', columns); - console.log('Rows::', pageData); + // console.log('Columns:::', columns); + // console.log('Rows::', pageData); const makeCellCustomStyles = useCallback( (columnName: string) => { diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx index cebe9a25..ed6a32b2 100644 --- a/src/pages/Correlation/index.tsx +++ b/src/pages/Correlation/index.tsx @@ -15,6 +15,7 @@ import { appStoreReducers, useAppStore } from '@/layouts/MainLayout/providers/Ap import { useCorrelationQueryLogs } from '@/hooks/useCorrelationQueryLogs'; import CorrelationTable from './Views/CorrelationTable'; import { IconPlus, IconTrashX } from '@tabler/icons-react'; +import Footer from '../Stream/Views/Explore/Footer'; const { changeStream } = appStoreReducers; const { deleteStreamData, setSelectedFields, deleteSelectedField } = correlationStoreReducers; @@ -23,11 +24,13 @@ const FieldItem = ({ headerColor, fieldName, backgroundColor, + dataType, onClick, }: { headerColor: string; fieldName: string; backgroundColor: string; + dataType: string; onClick: () => void; }) => { return ( @@ -38,6 +41,7 @@ const FieldItem = ({ {fieldName} + {dataType} ); }; @@ -129,7 +133,7 @@ const Correlation = () => { height: `calc(100vh - 220px)`, }}> {Object.entries(fields).map(([stream, fieldsIter]: [any, any]) => { - const typedFields = fieldsIter.headers as string[]; + const typedFields = Object.keys(fieldsIter.fieldTypeMap) as string[]; const totalStreams = Object.entries(fields).length; const heightPercentage = totalStreams > 1 ? `${100 / totalStreams}%` : '100%'; return ( @@ -158,12 +162,14 @@ const Correlation = () => {
{typedFields.map((field: string) => { + const dataType = fieldsIter.fieldTypeMap[field]; return ( addField(field, stream)} /> ); @@ -227,7 +233,12 @@ const Correlation = () => { }} w="100%"> - {showLogData && } + {showLogData && ( + <> + +
+ + )} ); diff --git a/src/pages/Correlation/providers/CorrelationProvider.tsx b/src/pages/Correlation/providers/CorrelationProvider.tsx index 9b084325..7f28fb6e 100644 --- a/src/pages/Correlation/providers/CorrelationProvider.tsx +++ b/src/pages/Correlation/providers/CorrelationProvider.tsx @@ -1,4 +1,5 @@ import { Log } from '@/@types/parseable/api/query'; +import { LogStreamSchemaData } from '@/@types/parseable/api/stream'; import initContext from '@/utils/initContext'; import _ from 'lodash'; @@ -42,10 +43,11 @@ type CorrelationStore = { }; type CorrelationStoreReducers = { - setStreamData: (store: CorrelationStore, currentStream: string, data: Log[], headers: string[]) => ReducerOutput; + setStreamData: (store: CorrelationStore, currentStream: string, data: Log[]) => ReducerOutput; deleteStreamData: (store: CorrelationStore, currentStream: string) => ReducerOutput; setSelectedFields: (store: CorrelationStore, field: string, streamName: string) => ReducerOutput; deleteSelectedField: (store: CorrelationStore, field: string, streamName: string) => ReducerOutput; + setStreamSchema: (store: CorrelationStore, schema: LogStreamSchemaData, streamName: string) => ReducerOutput; }; const initialState: CorrelationStore = { @@ -152,7 +154,7 @@ const deleteSelectedField = (store: CorrelationStore, field: string, streamName: }; }; -const setStreamData = (store: CorrelationStore, currentStream: string, data: Log[], headers: string[]) => { +const setStreamData = (store: CorrelationStore, currentStream: string, data: Log[]) => { if (!currentStream) { return { fields: store.fields, @@ -165,25 +167,51 @@ const setStreamData = (store: CorrelationStore, currentStream: string, data: Log // Enforce a limit of 4 streams if (currentStreamCount >= 4 && !(currentStream in (store.streamData || {}))) { console.warn('Stream limit reached. Cannot add more than 4 streams.'); - return store; // Return the unchanged state + return store; } + return { + streamData: { + ...store.streamData, + [currentStream]: { + logData: data, + }, + }, + }; +}; + +const parseType = (type: any): 'text' | 'number' | 'timestamp' => { + if (typeof type === 'object') { + if (_.get(type, 'Timestamp', null)) { + return 'timestamp'; + } else return 'text'; + // console.error('Error finding type for an object', type); + } + const lowercaseType = type.toLowerCase(); + if (lowercaseType.startsWith('int') || lowercaseType.startsWith('float') || lowercaseType.startsWith('double')) { + return 'number'; + } else { + return 'text'; + } +}; + +const setStreamSchema = (store: CorrelationStore, schema: LogStreamSchemaData, streamName: string) => { + const fieldTypeMap = schema.fields.reduce((acc, field) => { + return { ...acc, [field.name]: parseType(field.data_type) }; + }, {}); + const currentStreamCount = Object.keys(store.streamData || {}).length; + console.log('fieldTypeMap', fieldTypeMap); + return { fields: { ...store.fields, - [currentStream]: { - headers, + [streamName]: { + fieldTypeMap, color: STREAM_COLORS[currentStreamCount], headerColor: STREAM_HEADER_COLORS[currentStreamCount], backgroundColor: FIELD_BACKGROUND_COLORS[currentStreamCount], }, }, - streamData: { - ...store.streamData, - [currentStream]: { - logData: data, - }, - }, }; }; @@ -206,6 +234,7 @@ const correlationStoreReducers: CorrelationStoreReducers = { deleteStreamData, setSelectedFields, deleteSelectedField, + setStreamSchema, }; export { CorrelationProvider, useCorrelationStore, correlationStoreReducers }; diff --git a/src/pages/Correlation/styles/Correlation.module.css b/src/pages/Correlation/styles/Correlation.module.css index 1855f8bf..76d1762b 100644 --- a/src/pages/Correlation/styles/Correlation.module.css +++ b/src/pages/Correlation/styles/Correlation.module.css @@ -52,4 +52,6 @@ display: flex; align-items: center; font-size: 12px; + display: flex; + justify-content: space-between; } diff --git a/src/pages/Stream/Views/Explore/LogsView.tsx b/src/pages/Stream/Views/Explore/LogsView.tsx index 2939da57..dd765123 100644 --- a/src/pages/Stream/Views/Explore/LogsView.tsx +++ b/src/pages/Stream/Views/Explore/LogsView.tsx @@ -24,7 +24,6 @@ const LogsView = (props: { schemaLoading: boolean; infoLoading: boolean }) => { return ( - ƒ {viewMode === 'table' && ( )} From 85a72548a56d358ba2d3840af673ffbc1c925bcb Mon Sep 17 00:00:00 2001 From: Praveen K B Date: Thu, 21 Nov 2024 15:41:08 +0530 Subject: [PATCH 12/64] Added stream-prefix --- .../Correlation/Views/CorrelationFooter.tsx | 198 ++++++++++++ .../Correlation/Views/CorrelationTable.tsx | 7 - src/pages/Correlation/index.tsx | 301 ++++++++++++------ .../providers/CorrelationProvider.tsx | 130 +++++++- .../Correlation/styles/Correlation.module.css | 20 ++ .../Correlation/styles/Footer.module.css | 44 +++ 6 files changed, 587 insertions(+), 113 deletions(-) create mode 100644 src/pages/Correlation/Views/CorrelationFooter.tsx create mode 100644 src/pages/Correlation/styles/Footer.module.css diff --git a/src/pages/Correlation/Views/CorrelationFooter.tsx b/src/pages/Correlation/Views/CorrelationFooter.tsx new file mode 100644 index 00000000..0397227f --- /dev/null +++ b/src/pages/Correlation/Views/CorrelationFooter.tsx @@ -0,0 +1,198 @@ +import { FC, useCallback } from 'react'; +import { usePagination } from '@mantine/hooks'; +import { Box, Center, Group, Loader, Menu, Pagination, Stack, Tooltip } from '@mantine/core'; +import _ from 'lodash'; +import { Text } from '@mantine/core'; +import { HumanizeNumber } from '@/utils/formatBytes'; +import { IconSelector } from '@tabler/icons-react'; +import useMountedState from '@/hooks/useMountedState'; +import classes from '../styles/Footer.module.css'; +import { LOGS_FOOTER_HEIGHT } from '@/constants/theme'; +import { correlationStoreReducers, useCorrelationStore } from '@/pages/Correlation/providers/CorrelationProvider'; +import { LOG_QUERY_LIMITS, useLogsStore, LOAD_LIMIT } from '@/pages/Stream/providers/LogsProvider'; + +const { setCurrentOffset, setCurrentPage, setPageAndPageData } = correlationStoreReducers; + +const TotalCount = (props: { totalCount: number }) => { + return ( + + {HumanizeNumber(props.totalCount)} + + ); +}; + +const TotalLogsCount = (props: { hasTableLoaded: boolean; isFetchingCount: boolean; isTableEmpty: boolean }) => { + const [{ totalCount, perPage, pageData }] = useLogsStore((store) => store.tableOpts); + const displayedCount = _.size(pageData); + const showingCount = displayedCount < perPage ? displayedCount : perPage; + if (typeof totalCount !== 'number' || typeof displayedCount !== 'number') return ; + return ( + + {props.hasTableLoaded ? ( + props.isFetchingCount ? ( + + ) : ( + <> + {`Showing ${showingCount} out of`} + + records + + ) + ) : props.isTableEmpty ? null : ( + + )} + + ); +}; + +const LimitControl: FC = () => { + const [opened, setOpened] = useMountedState(false); + const [perPage, setCorrelationStore] = useCorrelationStore((store) => store.tableOpts.perPage); + + const toggle = () => { + setOpened(!opened); + }; + + const onSelect = (limit: number) => { + if (perPage !== limit) { + setCorrelationStore((store) => setPageAndPageData(store, 1, limit)); + } + }; + + return ( + + +
+ + + {perPage} + + + +
+ + {LOG_QUERY_LIMITS.map((limit) => { + return ( + onSelect(limit)}> +
+ {limit} +
+
+ ); + })} +
+
+
+ ); +}; + +const CorrelationFooter = (props: { loaded: boolean; hasNoData: boolean; isFetchingCount: boolean }) => { + const [tableOpts, setCorrelationStore] = useCorrelationStore((store) => store.tableOpts); + const { totalPages, currentOffset, currentPage, perPage, totalCount } = tableOpts; + + const onPageChange = useCallback((page: number) => { + setCorrelationStore((store) => setPageAndPageData(store, page)); + }, []); + + const pagination = usePagination({ total: totalPages ?? 1, initialPage: 1, onChange: onPageChange }); + const onChangeOffset = useCallback( + (key: 'prev' | 'next') => { + if (key === 'prev') { + const targetOffset = currentOffset - LOAD_LIMIT; + if (currentOffset < 0) return; + + if (targetOffset === 0 && currentOffset > 0) { + // hack to initiate fetch + setCorrelationStore((store) => setCurrentPage(store, 0)); + } + setCorrelationStore((store) => setCurrentOffset(store, targetOffset)); + } else { + const targetOffset = currentOffset + LOAD_LIMIT; + setCorrelationStore((store) => setCurrentOffset(store, targetOffset)); + } + }, + [currentOffset], + ); + + return ( + + + {/* */} + + + {props.loaded ? ( + { + pagination.setPage(page); + }} + size="sm"> + + { + currentOffset !== 0 && onChangeOffset('prev'); + }} + disabled={currentOffset === 0} + /> + + {pagination.range.map((page, index) => { + if (page === 'dots') { + return ; + } else { + return ( + { + pagination.setPage(page); + }}> + {(perPage ? Math.ceil(page + currentOffset / perPage) : page) ?? 1} + + ); + } + })} + + { + onChangeOffset('next'); + }} + disabled={!(currentOffset + LOAD_LIMIT < totalCount)} + /> + + + ) : null} + + + {/* {props.loaded && ( + + +
+ +
+
+ + exportHandler('CSV')} style={{ padding: '0.5rem 2.25rem 0.5rem 0.75rem' }}> + CSV + + exportHandler('JSON')} style={{ padding: '0.5rem 2.25rem 0.5rem 0.75rem' }}> + JSON + + +
+ )} */} + +
+
+ ); +}; + +export default CorrelationFooter; diff --git a/src/pages/Correlation/Views/CorrelationTable.tsx b/src/pages/Correlation/Views/CorrelationTable.tsx index 4e2b7c0c..81ba0962 100644 --- a/src/pages/Correlation/Views/CorrelationTable.tsx +++ b/src/pages/Correlation/Views/CorrelationTable.tsx @@ -3,17 +3,13 @@ import { useCorrelationStore } from '../providers/CorrelationProvider'; import { useCallback, useEffect, useState } from 'react'; import tableStyles from '../styles/Logs.module.css'; import { formatLogTs } from '@/pages/Stream/providers/LogsProvider'; -import timeRangeUtils from '@/utils/timeRangeUtils'; import { CopyIcon } from '@/pages/Stream/Views/Explore/JSONView'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import { LOGS_FOOTER_HEIGHT } from '@/constants/theme'; -import Column from '@/pages/Stream/components/Column'; import { Log } from '@/@types/parseable/api/query'; import { FieldTypeMap } from '@/pages/Stream/providers/StreamProvider'; import _ from 'lodash'; -const localTz = timeRangeUtils.getLocalTimezone(); - type CellType = string | number | boolean | null | undefined; const getSanitizedValue = (value: CellType, isTimestamp: boolean) => { @@ -91,9 +87,6 @@ const Table = (props: { primaryHeaderHeight: number }) => { setColumns(updatedColumns); }, [selectedFields]); - // console.log('Columns:::', columns); - // console.log('Rows::', pageData); - const makeCellCustomStyles = useCallback( (columnName: string) => { return { diff --git a/src/pages/Correlation/index.tsx b/src/pages/Correlation/index.tsx index ed6a32b2..84aed5e6 100644 --- a/src/pages/Correlation/index.tsx +++ b/src/pages/Correlation/index.tsx @@ -1,8 +1,6 @@ -import 'react-grid-layout/css/styles.css'; -import 'react-resizable/css/styles.css'; import _ from 'lodash'; import { useDocumentTitle } from '@mantine/hooks'; -import { Stack, Box, Pill, PillsInput, TextInput, Autocomplete, Text, ActionIcon } from '@mantine/core'; +import { Stack, Box, TextInput, Text, Select } from '@mantine/core'; import { PRIMARY_HEADER_HEIGHT, STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, @@ -14,34 +12,54 @@ import { correlationStoreReducers, useCorrelationStore } from './providers/Corre import { appStoreReducers, useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import { useCorrelationQueryLogs } from '@/hooks/useCorrelationQueryLogs'; import CorrelationTable from './Views/CorrelationTable'; -import { IconPlus, IconTrashX } from '@tabler/icons-react'; -import Footer from '../Stream/Views/Explore/Footer'; +import { + IconChartCircles, + IconClockHour5, + IconLetterASmall, + IconNumber123, + IconTrashX, + IconX, +} from '@tabler/icons-react'; +import CorrelationFooter from './Views/CorrelationFooter'; const { changeStream } = appStoreReducers; const { deleteStreamData, setSelectedFields, deleteSelectedField } = correlationStoreReducers; +const dataTypeIcons = (iconColor: string): Record => ({ + text: , + timestamp: , + number: , + boolean: , +}); + const FieldItem = ({ headerColor, fieldName, backgroundColor, + iconColor, dataType, onClick, }: { headerColor: string; fieldName: string; backgroundColor: string; - dataType: string; - onClick: () => void; + iconColor: string; + dataType?: string; + onClick?: () => void; }) => { return (
- - {fieldName} - - {dataType} + {fieldName} + {!dataType && } + {dataType && dataTypeIcons(iconColor)[dataType]}
); }; @@ -52,40 +70,30 @@ const Correlation = () => { const [{ fields, selectedFields }, setCorrelationData] = useCorrelationStore((store) => store); const { getCorrelationData } = useCorrelationQueryLogs(); const [currentStream, setAppStore] = useAppStore((store) => store.currentStream); - const [selectedStream, setSelectedStream] = useState(''); const [maximized] = useAppStore((store) => store.maximized); + const [searchText, setSearchText] = useState(''); const primaryHeaderHeight = !maximized ? PRIMARY_HEADER_HEIGHT + STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT + STREAM_SECONDARY_TOOLBAR_HRIGHT : 0; - const [showLogData, setShowLogData] = useState(false); useEffect(() => { getCorrelationData(); }, [currentStream]); - console.log(fields); + const addStream = (value: string | null) => { + if (!value) return; - const addStream = () => { - setAppStore((store) => changeStream(store, selectedStream)); - setShowLogData(true); - setSelectedStream(''); + setAppStore((store) => changeStream(store, value)); }; - const removeStream = (streamName: string) => { - setCorrelationData((store) => deleteStreamData(store, streamName)); + const filterFields = (fieldsIter: any) => { + const typedFields = Object.keys(fieldsIter.fieldTypeMap) as string[]; + if (!searchText) return typedFields; + return typedFields.filter((field) => field.toLowerCase().includes(searchText.toLowerCase())); }; - const handleColor = (streamName: string) => { - switch (streamName) { - case 'streamA': - return 'grape'; - case 'streamB': - return 'teal'; - case 'streamA-field': - return '#DA77F1'; - case 'streamB-field': - return '#38D9A9'; - } + const removeStream = (streamName: string) => { + setCorrelationData((store) => deleteStreamData(store, streamName)); }; const addField = (field: string, streamName: string) => { @@ -105,26 +113,21 @@ const Correlation = () => { w="100%" placeholder="Search Fields" key="search-fields" + value={searchText} + onChange={(e) => setSearchText(e.target.value)} /> - {Object.keys(fields).length < 4 && ( + {/* {Object.keys(fields).length < 4 && (
- setSelectedStream(value)} + { + if (value) addStream(value); // Add stream when selected, affecting Object.keys(fields).length + }} + placeholder="Select Stream 1" + style={{ width: '100%', padding: '10px' }} + data={ + userSpecificStreams?.map((stream: any) => ({ + value: stream.name, + label: stream.name, + })) ?? [] + } + /> +
+ + Add Stream 1 + +
+
+ + {/* Second box */} +
+ addStream(value)} + placeholder="Select Stream 2" + style={{ width: '100%', padding: '10px' }} + data={ + userSpecificStreams?.map((stream: any) => ({ + value: stream.name, + label: stream.name, + })) ?? [] + } + /> +
+ + Add Stream 2 + +
+
+ ))} + + )}
{ }}>
-
Fields
- -
- {Object.entries(selectedFields).map(([streamName, fields]: [any, any]) => - fields.map((field: any, index: any) => ( - removeField(field, streamName)} - style={{ backgroundColor: handleColor(streamName) }}> - {field} - - )), - )} -
-
+
0 ? 'black' : '#CBCBCB' }}>Fields
+
+ {Object.entries(selectedFields).map(([streamName, fieldsMap]: [any, any]) => + fieldsMap.map((field: any, index: any) => ( + + )), + )} +
-
Joins
- - - - Stream A.Status = Stream B.Status Code - - - +
0 ? 'black' : '#CBCBCB' }}>Joins
+
+ {/* + + + Stream A.Status = Stream B.Status Code + + + */}
{ }} w="100%">
- {showLogData && ( + {Object.keys(selectedFields).length > 0 && ( <> -