From 140533b7906defa37f7f50a8f3079c19d482ed43 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:36:56 +0530 Subject: [PATCH] feat: added Messaging queue detail page (#5690) * feat: added Messaging queue detail page * feat: added MQDetails - tables - consumer, producer & network latency * feat: added MQConfigOption - with dummy responses * feat: configured query-range and autocomplete against the staging setup * feat: added queryparams and linked config options with graph * feat: added shareable link, cleanup code and connected details table with graph * feat: fixed comments * Messaging queue overview (#5782) * feat: added messaging queue overview page * feat: added get-started links * feat: fixed comments * feat: messaging queue misc tasks (#5785) * feat: added lightMode styles * feat: misc fix * feat: misc fix * feat: added customer tooltip info text * feat: removed reset btn until the funcitonality is clear * feat: fixed comments * feat: fixed comments and added onDragSelect * feat: added placeholder doc link for get-started for non-cloud --- frontend/public/locales/en/titles.json | 3 +- frontend/src/AppRoutes/pageComponents.ts | 12 + frontend/src/AppRoutes/routes.ts | 16 + frontend/src/constants/query.ts | 4 + frontend/src/constants/reactQueryKeys.ts | 1 + frontend/src/constants/routes.ts | 2 + .../constants/shortcuts/globalShortcuts.ts | 3 + frontend/src/container/AppLayout/index.tsx | 6 +- .../GridCard/WidgetGraphComponent.tsx | 2 + .../GridCardLayout/GridCard/index.tsx | 2 + .../GridCardLayout/GridCard/types.ts | 2 + .../container/PanelWrapper/PanelWrapper.tsx | 2 + .../PanelWrapper/UplotPanelWrapper.tsx | 3 + .../PanelWrapper/panelWrapper.types.ts | 1 + frontend/src/container/SideNav/SideNav.tsx | 5 + frontend/src/container/SideNav/config.ts | 2 + frontend/src/container/SideNav/menuItems.tsx | 6 + .../container/TopNav/Breadcrumbs/index.tsx | 1 + .../TopNav/DateTimeSelectionV2/config.ts | 2 + .../src/lib/uPlotLib/getUplotChartOptions.ts | 11 +- .../src/lib/uPlotLib/plugins/onClickPlugin.ts | 25 +- .../src/lib/uPlotLib/plugins/tooltipPlugin.ts | 8 +- .../MQCommon/MQCommon.styles.scss | 34 ++ .../MessagingQueues/MQCommon/MQCommon.tsx | 46 ++ .../MQDetailPage/MQDetailPage.tsx | 72 +++ .../MessagingQueues/MQDetailPage/index.tsx | 3 + .../MQDetails/MQDetails.style.scss | 6 + .../MessagingQueues/MQDetails/MQDetails.tsx | 67 +++ .../MQDetails/MQTables/MQTables.styles.scss | 99 ++++ .../MQDetails/MQTables/MQTables.tsx | 210 +++++++++ .../MQTables/getConsumerLagDetails.ts | 61 +++ .../MQGraph/MQConfigOptions.styles.scss | 4 + .../MQGraph/MQConfigOptions.tsx | 235 ++++++++++ .../pages/MessagingQueues/MQGraph/MQGraph.tsx | 88 ++++ .../MQGraph/useGetAllConfigOptions.ts | 49 ++ .../MessagingQueues.styles.scss | 424 ++++++++++++++++++ .../pages/MessagingQueues/MessagingQueues.tsx | 171 +++++++ .../MessagingQueues/MessagingQueuesUtils.ts | 206 +++++++++ frontend/src/pages/MessagingQueues/index.tsx | 3 + frontend/src/utils/permission/index.ts | 2 + 40 files changed, 1893 insertions(+), 6 deletions(-) create mode 100644 frontend/src/pages/MessagingQueues/MQCommon/MQCommon.styles.scss create mode 100644 frontend/src/pages/MessagingQueues/MQCommon/MQCommon.tsx create mode 100644 frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx create mode 100644 frontend/src/pages/MessagingQueues/MQDetailPage/index.tsx create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.styles.scss create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQTables/getConsumerLagDetails.ts create mode 100644 frontend/src/pages/MessagingQueues/MQGraph/MQConfigOptions.styles.scss create mode 100644 frontend/src/pages/MessagingQueues/MQGraph/MQConfigOptions.tsx create mode 100644 frontend/src/pages/MessagingQueues/MQGraph/MQGraph.tsx create mode 100644 frontend/src/pages/MessagingQueues/MQGraph/useGetAllConfigOptions.ts create mode 100644 frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss create mode 100644 frontend/src/pages/MessagingQueues/MessagingQueues.tsx create mode 100644 frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts create mode 100644 frontend/src/pages/MessagingQueues/index.tsx diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index f77bf0e85a..4aa2b65dc0 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -49,5 +49,6 @@ "TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views", "DEFAULT": "Open source Observability Platform | SigNoz", "SHORTCUTS": "SigNoz | Shortcuts", - "INTEGRATIONS": "SigNoz | Integrations" + "INTEGRATIONS": "SigNoz | Integrations", + "MESSAGING_QUEUES": "SigNoz | Messaging Queues" } diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 9275e7d6f6..bce075cef3 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -204,3 +204,15 @@ export const InstalledIntegrations = Loadable( /* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage' ), ); + +export const MessagingQueues = Loadable( + () => + import(/* webpackChunkName: "MessagingQueues" */ 'pages/MessagingQueues'), +); + +export const MQDetailPage = Loadable( + () => + import( + /* webpackChunkName: "MQDetailPage" */ 'pages/MessagingQueues/MQDetailPage' + ), +); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 4fd421ffba..98fdbed392 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -23,6 +23,8 @@ import { LogsExplorer, LogsIndexToFields, LogsSaveViews, + MessagingQueues, + MQDetailPage, MySettings, NewDashboardPage, OldLogsExplorer, @@ -351,6 +353,20 @@ const routes: AppRoutes[] = [ isPrivate: true, key: 'INTEGRATIONS', }, + { + path: ROUTES.MESSAGING_QUEUES, + exact: true, + component: MessagingQueues, + key: 'MESSAGING_QUEUES', + isPrivate: true, + }, + { + path: ROUTES.MESSAGING_QUEUES_DETAIL, + exact: true, + component: MQDetailPage, + key: 'MESSAGING_QUEUES_DETAIL', + isPrivate: true, + }, ]; export const SUPPORT_ROUTE: AppRoutes = { diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index 9b731ca089..3ee0a39634 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -32,4 +32,8 @@ export enum QueryParams { relativeTime = 'relativeTime', alertType = 'alertType', ruleId = 'ruleId', + consumerGrp = 'consumerGrp', + topic = 'topic', + partition = 'partition', + selectedTimelineQuery = 'selectedTimelineQuery', } diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts index 63fc205d81..52ae235ef6 100644 --- a/frontend/src/constants/reactQueryKeys.ts +++ b/frontend/src/constants/reactQueryKeys.ts @@ -8,4 +8,5 @@ export const REACT_QUERY_KEY = { GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS', DELETE_DASHBOARD: 'DELETE_DASHBOARD', LOGS_PIPELINE_PREVIEW: 'LOGS_PIPELINE_PREVIEW', + GET_CONSUMER_LAG_DETAILS: 'GET_CONSUMER_LAG_DETAILS', }; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index ef73184a86..8f76cd0386 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -54,6 +54,8 @@ const ROUTES = { WORKSPACE_LOCKED: '/workspace-locked', SHORTCUTS: '/shortcuts', INTEGRATIONS: '/integrations', + MESSAGING_QUEUES: '/messaging-queues', + MESSAGING_QUEUES_DETAIL: '/messaging-queues/detail', } as const; export default ROUTES; diff --git a/frontend/src/constants/shortcuts/globalShortcuts.ts b/frontend/src/constants/shortcuts/globalShortcuts.ts index 81420fc830..4ab7752fac 100644 --- a/frontend/src/constants/shortcuts/globalShortcuts.ts +++ b/frontend/src/constants/shortcuts/globalShortcuts.ts @@ -9,6 +9,7 @@ export const GlobalShortcuts = { NavigateToDashboards: 'd+shift', NavigateToAlerts: 'a+shift', NavigateToExceptions: 'e+shift', + NavigateToMessagingQueues: 'm+shift', }; export const GlobalShortcutsName = { @@ -19,6 +20,7 @@ export const GlobalShortcutsName = { NavigateToDashboards: 'shift+d', NavigateToAlerts: 'shift+a', NavigateToExceptions: 'shift+e', + NavigateToMessagingQueues: 'shift+m', }; export const GlobalShortcutsDescription = { @@ -29,4 +31,5 @@ export const GlobalShortcutsDescription = { NavigateToDashboards: 'Navigate to dashboards page', NavigateToAlerts: 'Navigate to alerts page', NavigateToExceptions: 'Navigate to Exceptions page', + NavigateToMessagingQueues: 'Navigate to Messaging Queues page', }; diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 0f267976d2..3ee58efd7c 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -241,6 +241,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element { const isTracesView = (): boolean => routeKey === 'TRACES_EXPLORER' || routeKey === 'TRACES_SAVE_VIEWS'; + const isMessagingQueues = (): boolean => + routeKey === 'MESSAGING_QUEUES' || routeKey === 'MESSAGING_QUEUES_DETAIL'; + const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD'; const isDashboardView = (): boolean => { /** @@ -329,7 +332,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element { isTracesView() || isDashboardView() || isDashboardWidgetView() || - isDashboardListView() + isDashboardListView() || + isMessagingQueues() ? 0 : '0 1rem', }} diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx index d0e16857be..b76c7c9f73 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -47,6 +47,7 @@ function WidgetGraphComponent({ setRequestData, onClickHandler, onDragSelect, + customTooltipElement, }: WidgetGraphComponentProps): JSX.Element { const [deleteModal, setDeleteModal] = useState(false); const [hovered, setHovered] = useState(false); @@ -335,6 +336,7 @@ function WidgetGraphComponent({ onClickHandler={onClickHandler} onDragSelect={onDragSelect} tableProcessedDataRef={tableProcessedDataRef} + customTooltipElement={customTooltipElement} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index 8e34e32879..d7d2e729cb 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -33,6 +33,7 @@ function GridCardGraph({ version, onClickHandler, onDragSelect, + customTooltipElement, }: GridCardGraphProps): JSX.Element { const dispatch = useDispatch(); const [errorMessage, setErrorMessage] = useState(); @@ -215,6 +216,7 @@ function GridCardGraph({ setRequestData={setRequestData} onClickHandler={onClickHandler} onDragSelect={onDragSelect} + customTooltipElement={customTooltipElement} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts index 1235a26440..d0edede5a1 100644 --- a/frontend/src/container/GridCardLayout/GridCard/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -31,6 +31,7 @@ export interface WidgetGraphComponentProps { setRequestData?: Dispatch>; onClickHandler?: OnClickPluginOpts['onClick']; onDragSelect: (start: number, end: number) => void; + customTooltipElement?: HTMLDivElement; } export interface GridCardGraphProps { @@ -42,6 +43,7 @@ export interface GridCardGraphProps { variables?: Dashboard['data']['variables']; version?: string; onDragSelect: (start: number, end: number) => void; + customTooltipElement?: HTMLDivElement; } export interface GetGraphVisibilityStateOnLegendClickProps { diff --git a/frontend/src/container/PanelWrapper/PanelWrapper.tsx b/frontend/src/container/PanelWrapper/PanelWrapper.tsx index fdc20f7d30..ed105b3948 100644 --- a/frontend/src/container/PanelWrapper/PanelWrapper.tsx +++ b/frontend/src/container/PanelWrapper/PanelWrapper.tsx @@ -15,6 +15,7 @@ function PanelWrapper({ onDragSelect, selectedGraph, tableProcessedDataRef, + customTooltipElement, }: PanelWrapperProps): JSX.Element { const Component = PanelTypeVsPanelWrapper[ selectedGraph || widget.panelTypes @@ -37,6 +38,7 @@ function PanelWrapper({ onDragSelect={onDragSelect} selectedGraph={selectedGraph} tableProcessedDataRef={tableProcessedDataRef} + customTooltipElement={customTooltipElement} /> ); } diff --git a/frontend/src/container/PanelWrapper/UplotPanelWrapper.tsx b/frontend/src/container/PanelWrapper/UplotPanelWrapper.tsx index dc404adabb..ff231b563a 100644 --- a/frontend/src/container/PanelWrapper/UplotPanelWrapper.tsx +++ b/frontend/src/container/PanelWrapper/UplotPanelWrapper.tsx @@ -30,6 +30,7 @@ function UplotPanelWrapper({ onClickHandler, onDragSelect, selectedGraph, + customTooltipElement, }: PanelWrapperProps): JSX.Element { const { toScrollWidgetId, setToScrollWidgetId } = useDashboard(); const isDarkMode = useIsDarkMode(); @@ -126,6 +127,7 @@ function UplotPanelWrapper({ stackBarChart: widget?.stackedBarChart, hiddenGraph, setHiddenGraph, + customTooltipElement, }), [ widget?.id, @@ -147,6 +149,7 @@ function UplotPanelWrapper({ selectedGraph, currentQuery, hiddenGraph, + customTooltipElement, ], ); diff --git a/frontend/src/container/PanelWrapper/panelWrapper.types.ts b/frontend/src/container/PanelWrapper/panelWrapper.types.ts index 6fd993f377..7d5e3122e8 100644 --- a/frontend/src/container/PanelWrapper/panelWrapper.types.ts +++ b/frontend/src/container/PanelWrapper/panelWrapper.types.ts @@ -23,6 +23,7 @@ export type PanelWrapperProps = { onDragSelect: (start: number, end: number) => void; selectedGraph?: PANEL_TYPES; tableProcessedDataRef?: React.MutableRefObject; + customTooltipElement?: HTMLDivElement; }; export type TooltipData = { diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx index d4ad27908c..1ba863d8ec 100644 --- a/frontend/src/container/SideNav/SideNav.tsx +++ b/frontend/src/container/SideNav/SideNav.tsx @@ -347,6 +347,10 @@ function SideNav({ onClickHandler(ROUTES.ALL_DASHBOARD, null), ); + registerShortcut(GlobalShortcuts.NavigateToMessagingQueues, () => + onClickHandler(ROUTES.MESSAGING_QUEUES, null), + ); + registerShortcut(GlobalShortcuts.NavigateToAlerts, () => onClickHandler(ROUTES.LIST_ALL_ALERT, null), ); @@ -362,6 +366,7 @@ function SideNav({ deregisterShortcut(GlobalShortcuts.NavigateToDashboards); deregisterShortcut(GlobalShortcuts.NavigateToAlerts); deregisterShortcut(GlobalShortcuts.NavigateToExceptions); + deregisterShortcut(GlobalShortcuts.NavigateToMessagingQueues); }; }, [deregisterShortcut, onClickHandler, onCollapse, registerShortcut]); diff --git a/frontend/src/container/SideNav/config.ts b/frontend/src/container/SideNav/config.ts index 95028b0a05..37e9db4d9f 100644 --- a/frontend/src/container/SideNav/config.ts +++ b/frontend/src/container/SideNav/config.ts @@ -48,4 +48,6 @@ export const routeConfig: Record = { [ROUTES.TRACE_EXPLORER]: [QueryParams.resourceAttributes], [ROUTES.LOGS_PIPELINES]: [QueryParams.resourceAttributes], [ROUTES.WORKSPACE_LOCKED]: [QueryParams.resourceAttributes], + [ROUTES.MESSAGING_QUEUES]: [QueryParams.resourceAttributes], + [ROUTES.MESSAGING_QUEUES_DETAIL]: [QueryParams.resourceAttributes], }; diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index 2039e4df8e..1566141735 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -10,6 +10,7 @@ import { FileKey2, Layers2, LayoutGrid, + ListMinus, MessageSquare, Receipt, Route, @@ -86,6 +87,11 @@ const menuItems: SidebarItem[] = [ label: 'Dashboards', icon: , }, + { + key: ROUTES.MESSAGING_QUEUES, + label: 'Messaging Queues', + icon: , + }, { key: ROUTES.LIST_ALL_ALERT, label: 'Alerts', diff --git a/frontend/src/container/TopNav/Breadcrumbs/index.tsx b/frontend/src/container/TopNav/Breadcrumbs/index.tsx index 4ab1e945d4..9efd50d2c3 100644 --- a/frontend/src/container/TopNav/Breadcrumbs/index.tsx +++ b/frontend/src/container/TopNav/Breadcrumbs/index.tsx @@ -27,6 +27,7 @@ const breadcrumbNameMap: Record = { [ROUTES.BILLING]: 'Billing', [ROUTES.SUPPORT]: 'Support', [ROUTES.WORKSPACE_LOCKED]: 'Workspace Locked', + [ROUTES.MESSAGING_QUEUES]: 'Messaging Queues', }; function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element { diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts index 473107265e..b652a68202 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts @@ -208,6 +208,8 @@ export const routesToSkip = [ ROUTES.DASHBOARD, ROUTES.DASHBOARD_WIDGET, ROUTES.SERVICE_TOP_LEVEL_OPERATIONS, + ROUTES.MESSAGING_QUEUES, + ROUTES.MESSAGING_QUEUES_DETAIL, ]; export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS]; diff --git a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts index 29904f0d6a..5000b0f0d8 100644 --- a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts +++ b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts @@ -53,6 +53,7 @@ export interface GetUPlotChartOptions { [key: string]: boolean; }> >; + customTooltipElement?: HTMLDivElement; } /** the function converts series A , series B , series C to @@ -154,6 +155,7 @@ export const getUPlotChartOptions = ({ stackBarChart: stackChart, hiddenGraph, setHiddenGraph, + customTooltipElement, }: GetUPlotChartOptions): uPlot.Options => { const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale); @@ -209,9 +211,16 @@ export const getUPlotChartOptions = ({ }, }, plugins: [ - tooltipPlugin({ apiResponse, yAxisUnit, stackBarChart, isDarkMode }), + tooltipPlugin({ + apiResponse, + yAxisUnit, + stackBarChart, + isDarkMode, + customTooltipElement, + }), onClickPlugin({ onClick: onClickHandler, + apiResponse, }), ], hooks: { diff --git a/frontend/src/lib/uPlotLib/plugins/onClickPlugin.ts b/frontend/src/lib/uPlotLib/plugins/onClickPlugin.ts index 7dfbbe9b47..fafc4afe64 100644 --- a/frontend/src/lib/uPlotLib/plugins/onClickPlugin.ts +++ b/frontend/src/lib/uPlotLib/plugins/onClickPlugin.ts @@ -1,10 +1,16 @@ +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; + export interface OnClickPluginOpts { onClick: ( xValue: number, yValue: number, mouseX: number, mouseY: number, + data?: { + [key: string]: string; + }, ) => void; + apiResponse?: MetricRangePayloadProps; } function onClickPlugin(opts: OnClickPluginOpts): uPlot.Plugin { @@ -22,9 +28,24 @@ function onClickPlugin(opts: OnClickPluginOpts): uPlot.Plugin { const xValue = u.posToVal(event.offsetX, 'x'); const yValue = u.posToVal(event.offsetY, 'y'); - opts.onClick(xValue, yValue, mouseX, mouseY); - }; + let metric = {}; + const { series } = u; + const apiResult = opts.apiResponse?.data?.result || []; + // this is to get the metric value of the focused series + if (Array.isArray(series) && series.length > 0) { + series.forEach((item, index) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (item?.show && item?._focus) { + const { metric: focusedMetric } = apiResult[index - 1] || []; + metric = focusedMetric; + } + }); + } + + opts.onClick(xValue, yValue, mouseX, mouseY, metric); + }; u.over.addEventListener('click', handleClick); }, destroy: (u: uPlot) => { diff --git a/frontend/src/lib/uPlotLib/plugins/tooltipPlugin.ts b/frontend/src/lib/uPlotLib/plugins/tooltipPlugin.ts index 16178fe5f0..882bf73db0 100644 --- a/frontend/src/lib/uPlotLib/plugins/tooltipPlugin.ts +++ b/frontend/src/lib/uPlotLib/plugins/tooltipPlugin.ts @@ -222,6 +222,7 @@ type ToolTipPluginProps = { isMergedSeries?: boolean; stackBarChart?: boolean; isDarkMode: boolean; + customTooltipElement?: HTMLDivElement; }; const tooltipPlugin = ({ @@ -232,7 +233,9 @@ const tooltipPlugin = ({ isMergedSeries, stackBarChart, isDarkMode, -}: ToolTipPluginProps): any => { + customTooltipElement, +}: // eslint-disable-next-line sonarjs/cognitive-complexity +ToolTipPluginProps): any => { let over: HTMLElement; let bound: HTMLElement; let bLeft: any; @@ -298,6 +301,9 @@ const tooltipPlugin = ({ isMergedSeries, stackBarChart, ); + if (customTooltipElement) { + content.appendChild(customTooltipElement); + } overlay.appendChild(content); placement(overlay, anchor, 'right', 'start', { bound }); } diff --git a/frontend/src/pages/MessagingQueues/MQCommon/MQCommon.styles.scss b/frontend/src/pages/MessagingQueues/MQCommon/MQCommon.styles.scss new file mode 100644 index 0000000000..426be83a43 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQCommon/MQCommon.styles.scss @@ -0,0 +1,34 @@ +.coming-soon { + display: inline-flex; + padding: 4px 8px; + border-radius: 20px; + border: 1px solid rgba(173, 127, 88, 0.2); + background: rgba(173, 127, 88, 0.1); + justify-content: center; + align-items: center; + gap: 5px; + + &__text { + color: var(--text-sienna-400); + font-size: 10px; + font-weight: 500; + letter-spacing: -0.05px; + line-height: normal; + } + &__icon { + display: flex; + } +} + +.tooltip-overlay { + text-wrap: nowrap; + .ant-tooltip-inner { + width: max-content; + } +} + +.select-label-with-coming-soon { + display: flex; + align-items: center; + justify-content: space-between; +} diff --git a/frontend/src/pages/MessagingQueues/MQCommon/MQCommon.tsx b/frontend/src/pages/MessagingQueues/MQCommon/MQCommon.tsx new file mode 100644 index 0000000000..1d5394a14a --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQCommon/MQCommon.tsx @@ -0,0 +1,46 @@ +/* eslint-disable react/destructuring-assignment */ +import './MQCommon.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Tooltip } from 'antd'; +import { DefaultOptionType } from 'antd/es/select'; +import { Info } from 'lucide-react'; + +export function ComingSoon(): JSX.Element { + return ( + +
+
Coming Soon
+
+ +
+
+
+ ); +} + +export function SelectMaxTagPlaceholder( + omittedValues: Partial[], +): JSX.Element { + return ( + value).join(', ')}> + + {omittedValues.length} + + ); +} + +export function SelectLabelWithComingSoon({ + label, +}: { + label: string; +}): JSX.Element { + return ( +
+ {label} +
+ ); +} diff --git a/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx b/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx new file mode 100644 index 0000000000..17baff2790 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx @@ -0,0 +1,72 @@ +import '../MessagingQueues.styles.scss'; + +import { Select, Typography } from 'antd'; +import ROUTES from 'constants/routes'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { ListMinus } from 'lucide-react'; +import { useHistory } from 'react-router-dom'; + +import { SelectLabelWithComingSoon } from '../MQCommon/MQCommon'; +import MessagingQueuesDetails from '../MQDetails/MQDetails'; +import MessagingQueuesConfigOptions from '../MQGraph/MQConfigOptions'; +import MessagingQueuesGraph from '../MQGraph/MQGraph'; + +enum MessagingQueueViewType { + consumerLag = 'consumerLag', + avgPartitionLatency = 'avgPartitionLatency', + avgProducerLatency = 'avgProducerLatency', +} + +function MQDetailPage(): JSX.Element { + const history = useHistory(); + + return ( +
+
+ + history.push(ROUTES.MESSAGING_QUEUES)} + className="message-queue-text" + > + Messaging Queues + +
+
+
+ Kafka / views / + + Loading... + + ) : ( + No Consumer Groups found + ) + } + onChange={(value): void => { + handleConsumerGrpSearch(''); + setQueryParamsForConfigOptions( + value, + urlQuery, + history, + location, + QueryParams.consumerGrp, + ); + resetTabularConfigDetailsOnChange(); + }} + /> + + Loading... + + ) : ( + No Partitions found + ) + } + onChange={(value): void => { + handlePartitionSearch(''); + setQueryParamsForConfigOptions( + value, + urlQuery, + history, + location, + QueryParams.partition, + ); + resetTabularConfigDetailsOnChange(); + }} + /> +
+ +
+ ); +} + +export default MessagingQueuesConfigOptions; diff --git a/frontend/src/pages/MessagingQueues/MQGraph/MQGraph.tsx b/frontend/src/pages/MessagingQueues/MQGraph/MQGraph.tsx new file mode 100644 index 0000000000..cd7cdd74c4 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQGraph/MQGraph.tsx @@ -0,0 +1,88 @@ +import { QueryParams } from 'constants/query'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { ViewMenuAction } from 'container/GridCardLayout/config'; +import GridCard from 'container/GridCardLayout/GridCard'; +import { Card } from 'container/GridCardLayout/styles'; +import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import useUrlQuery from 'hooks/useUrlQuery'; +import { useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import { useHistory, useLocation } from 'react-router-dom'; +import { UpdateTimeInterval } from 'store/actions'; + +import { + getFiltersFromConfigOptions, + getWidgetQuery, + setSelectedTimelineQuery, +} from '../MessagingQueuesUtils'; + +function MessagingQueuesGraph(): JSX.Element { + const isDarkMode = useIsDarkMode(); + + const urlQuery = useUrlQuery(); + const consumerGrp = urlQuery.get(QueryParams.consumerGrp) || ''; + const topic = urlQuery.get(QueryParams.topic) || ''; + const partition = urlQuery.get(QueryParams.partition) || ''; + + const filterItems = useMemo( + () => getFiltersFromConfigOptions(consumerGrp, topic, partition), + [consumerGrp, topic, partition], + ); + + const widgetData = useMemo( + () => getWidgetQueryBuilder(getWidgetQuery({ filterItems })), + [filterItems], + ); + const history = useHistory(); + const location = useLocation(); + + const messagingQueueCustomTooltipText = (): HTMLDivElement => { + const customText = document.createElement('div'); + customText.textContent = 'Click on co-ordinate to view details'; + customText.style.paddingTop = '8px'; + customText.style.paddingBottom = '2px'; + customText.style.color = '#fff'; + return customText; + }; + + const { pathname } = useLocation(); + const dispatch = useDispatch(); + + const onDragSelect = useCallback( + (start: number, end: number) => { + const startTimestamp = Math.trunc(start); + const endTimestamp = Math.trunc(end); + + urlQuery.set(QueryParams.startTime, startTimestamp.toString()); + urlQuery.set(QueryParams.endTime, endTimestamp.toString()); + const generatedUrl = `${pathname}?${urlQuery.toString()}`; + history.push(generatedUrl); + + if (startTimestamp !== endTimestamp) { + dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); + } + }, + [dispatch, history, pathname, urlQuery], + ); + + return ( + + { + setSelectedTimelineQuery(urlQuery, xValue, location, history, data); + }} + onDragSelect={onDragSelect} + customTooltipElement={messagingQueueCustomTooltipText()} + /> + + ); +} + +export default MessagingQueuesGraph; diff --git a/frontend/src/pages/MessagingQueues/MQGraph/useGetAllConfigOptions.ts b/frontend/src/pages/MessagingQueues/MQGraph/useGetAllConfigOptions.ts new file mode 100644 index 0000000000..c11c0f85f5 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQGraph/useGetAllConfigOptions.ts @@ -0,0 +1,49 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { DefaultOptionType } from 'antd/es/select'; +import { getAttributesValues } from 'api/queryBuilder/getAttributesValues'; +import { useQuery } from 'react-query'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { DataSource } from 'types/common/queryBuilder'; + +export interface ConfigOptions { + attributeKey: string; + searchText?: string; +} + +export interface GetAllConfigOptionsResponse { + options: DefaultOptionType[]; + isFetching: boolean; +} + +export function useGetAllConfigOptions( + props: ConfigOptions, +): GetAllConfigOptionsResponse { + const { attributeKey, searchText } = props; + + const { data, isLoading } = useQuery( + ['attributesValues', attributeKey, searchText], + async () => { + const { payload } = await getAttributesValues({ + aggregateOperator: 'avg', + dataSource: DataSource.METRICS, + aggregateAttribute: 'kafka_consumer_group_lag', + attributeKey, + searchText: searchText ?? '', + filterAttributeKeyDataType: DataTypes.String, + tagType: 'tag', + }); + + if (payload) { + const values = Object.values(payload).find((el) => !!el) || []; + const options: DefaultOptionType[] = values.map((val: string) => ({ + label: val, + value: val, + })); + return options; + } + return []; + }, + ); + + return { options: data ?? [], isFetching: isLoading }; +} diff --git a/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss b/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss new file mode 100644 index 0000000000..cdf3e0023a --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss @@ -0,0 +1,424 @@ +.messaging-queue-container { + .messaging-breadcrumb { + display: flex; + padding: 0px 16px; + align-items: center; + gap: 8px; + padding-top: 10px; + padding-bottom: 8px; + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: -0.07px; + + border-bottom: 1px solid var(--bg-slate-400); + + .message-queue-text { + cursor: pointer; + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: -0.07px; + } + } + + .messaging-header { + display: flex; + min-height: 48px; + padding: 10px 16px; + justify-content: space-between; + align-items: center; + + color: var(--bg-vanilla-400); + font-family: 'Geist Mono'; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.25px; + + border-bottom: 1px solid var(--bg-slate-500); + + .header-config { + display: flex; + gap: 10px; + align-items: center; + + .messaging-queue-options { + .ant-select-selector { + display: flex; + height: 32px; + padding: 6px 6px 6px 8px; + align-items: center; + gap: 4px; + + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + } + } + } + } + + .messaging-queue-main-graph { + display: flex; + padding: 24px 16px; + flex-direction: column; + gap: 16px; + + .config-options { + display: flex; + align-items: center; + gap: 8px; + + .config-select-option { + .ant-select-selector { + display: flex; + min-height: 32px; + align-items: center; + gap: 16px; + min-width: 164px; + + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + } + } + } + + .mq-graph { + height: 420px; + padding: 24px 24px 0 24px; + } + + border-bottom: 1px solid var(--bg-slate-500); + } + + .messaging-queue-details { + display: flex; + padding: 16px; + + .mq-details-options { + letter-spacing: -0.06px; + .ant-radio-button-wrapper { + border-color: var(--bg-slate-400); + color: var(--bg-vanilla-400); + } + .ant-radio-button-wrapper-checked { + background: var(--bg-slate-400); + color: var(--bg-vanilla-100); + } + .ant-radio-button-wrapper-disabled { + background: var(--bg-ink-400); + color: var(--bg-slate-200); + } + .ant-radio-button-wrapper::before { + width: 0px; + } + + .disabled-option { + .coming-soon { + margin-left: 8px; + } + } + } + } +} + +.messaging-queue-options-popup { + width: 260px !important; +} + +.messaging-overview { + padding: 24px 16px 10px 16px; + + .overview-text { + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; + letter-spacing: -0.08px; + margin: 0; + } + + .overview-subtext { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: -0.07px; + margin: 0; + margin-top: 4px; + } + + .overview-doc-area { + margin: 16px 0 28px 0; + display: flex; + + .middle-card { + border-left: none !important; + border-right: none !important; + } + + .overview-info-card { + display: flex; + width: 376px; + min-height: 176px; + padding: 18px 20px 20px 20px; + flex-direction: column; + justify-content: space-between; + border: 1px solid var(--bg-slate-500); + border-radius: 2px; + + .card-title { + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 13px; + font-style: normal; + font-weight: 600; + line-height: 22px; + letter-spacing: 0.52px; + text-transform: uppercase; + margin: 0; + } + + .card-info-text { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: -0.07px; + margin: 0; + margin-top: 4px; + } + + .button-grp { + display: flex; + gap: 8px; + + .ant-btn { + min-width: 80px; + } + + .ant-btn-default { + background-color: var(--bg-slate-400); + border: none; + box-shadow: none; + } + } + } + } + + .summary-section { + display: flex; + + .summary-card { + display: flex; + padding: 12px; + flex-direction: column; + align-items: flex-start; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); + width: 337px; + height: 283px; + border-radius: 2px; + + .summary-title { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 24px; + + > p { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 13px; + font-style: normal; + font-weight: 500; /* 169.231% */ + letter-spacing: 0.52px; + text-transform: uppercase; + margin: 0; + } + + .time-value { + display: flex; + gap: 4px; + align-items: center; + + > p { + color: var(--bg-slate-200); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 600; + line-height: 22px; + letter-spacing: 0.48px; + text-transform: uppercase; + } + } + } + + .view-detail-btn { + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + } + } + } + + .coming-soon-card { + background: var(--bg-ink-500) !important; + border-left: none !important; + } +} + +.overview-confirm-modal { + background-color: var(--bg-ink-500); + padding: 0; + border-radius: 4px; + + .ant-modal-content { + background-color: var(--bg-ink-300); + .ant-modal-confirm-content { + color: var(--bg-vanilla-100); + } + + .ant-modal-confirm-body-wrapper { + display: flex; + flex-direction: column; + height: 150px; + justify-content: space-between; + } + } +} + +.lightMode { + .messaging-queue-container { + .messaging-breadcrumb { + color: var(--bg-ink-400); + border-bottom: 1px solid var(--bg-vanilla-300); + } + .messaging-header { + color: var(--bg-ink-400); + border-bottom: 1px solid var(--bg-vanilla-300); + + .header-config { + .messaging-queue-options { + .ant-select-selector { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + } + } + } + } + + .messaging-queue-main-graph { + .config-options { + .config-select-option { + .ant-select-selector { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + } + } + } + + border-bottom: 1px solid var(--bg-vanilla-300); + } + + .messaging-queue-details { + .mq-details-options { + .ant-radio-button-wrapper { + border-color: var(--bg-vanilla-300); + color: var(--bg-slate-200); + } + .ant-radio-button-wrapper-checked { + color: var(--bg-slate-200); + background: var(--bg-vanilla-300); + } + .ant-radio-button-wrapper-disabled { + background: var(--bg-vanilla-100); + color: var(--bg-vanilla-400); + } + } + } + } + + .messaging-overview { + .overview-text { + color: var(--bg-slate-200); + } + + .overview-subtext { + color: var(--bg-slate-300); + } + + .overview-doc-area { + .overview-info-card { + border: 1px solid var(--bg-vanilla-300); + + .card-title { + color: var(--bg-slate-200); + } + + .card-info-text { + color: var(--bg-slate-300); + } + + .button-grp { + .ant-btn-default { + background-color: var(--bg-vanilla-100); + } + } + } + } + + .summary-section { + .summary-card { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + + .summary-title { + > p { + color: var(--bg-slate-200); + } + + .time-value { + > p { + color: var(--bg-slate-200); + } + } + } + } + } + + .coming-soon-card { + background: var(--bg-vanilla-200) !important; + } + } + + .overview-confirm-modal { + background-color: var(--bg-vanilla-100); + + .ant-modal-content { + background-color: var(--bg-vanilla-100); + .ant-modal-confirm-content { + color: var(--bg-slate-200); + } + } + } +} diff --git a/frontend/src/pages/MessagingQueues/MessagingQueues.tsx b/frontend/src/pages/MessagingQueues/MessagingQueues.tsx new file mode 100644 index 0000000000..0a7d6eebdd --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MessagingQueues.tsx @@ -0,0 +1,171 @@ +import './MessagingQueues.styles.scss'; + +import { ExclamationCircleFilled } from '@ant-design/icons'; +import { Color } from '@signozhq/design-tokens'; +import { Button, Modal } from 'antd'; +import ROUTES from 'constants/routes'; +import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { Calendar, ListMinus } from 'lucide-react'; +import { useHistory } from 'react-router-dom'; +import { isCloudUser } from 'utils/app'; + +import { KAFKA_SETUP_DOC_LINK } from './MessagingQueuesUtils'; +import { ComingSoon } from './MQCommon/MQCommon'; + +function MessagingQueues(): JSX.Element { + const history = useHistory(); + + const { confirm } = Modal; + + const showConfirm = (): void => { + confirm({ + icon: , + content: + 'Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.', + className: 'overview-confirm-modal', + onOk() { + history.push(ROUTES.MESSAGING_QUEUES_DETAIL); + }, + okText: 'Proceed', + }); + }; + + const isCloudUserVal = isCloudUser(); + + const getStartedRedirect = (link: string): void => { + if (isCloudUserVal) { + history.push(link); + } else { + window.open(KAFKA_SETUP_DOC_LINK, '_blank'); + } + }; + + return ( +
+
+ + Messaging Queues +
+
+
Kafka / Overview
+ +
+
+

+ Start sending data in as little as 20 minutes +

+

Connect and Monitor Your Data Streams

+
+
+
+

Configure Consumer

+

+ Connect your consumer and producer data sources to start monitoring. +

+
+
+ +
+
+
+
+

Configure Producer

+

+ Connect your consumer and producer data sources to start monitoring. +

+
+
+ +
+
+
+
+

Monitor kafka

+

+ Set up your Kafka monitoring to track consumer and producer activities. +

+
+
+ +
+
+
+
+
+
+

Consumer Lag

+
+ +

1D

+
+
+
+ +
+
+
+
+

Avg. Partition latency

+
+ +

1D

+
+
+
+ +
+
+
+
+

Avg. Partition latency

+
+ +

1D

+
+
+
+ +
+
+
+
+

Avg. Partition latency

+
+ +

1D

+
+
+
+ +
+
+
+
+
+ ); +} + +export default MessagingQueues; diff --git a/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts new file mode 100644 index 0000000000..91501e9c5c --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts @@ -0,0 +1,206 @@ +import { QueryParams } from 'constants/query'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetWidgetQueryBuilderProps } from 'container/MetricsApplication/types'; +import { History, Location } from 'history'; +import { isEmpty } from 'lodash-es'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { EQueryType } from 'types/common/dashboard'; +import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuid } from 'uuid'; + +export const KAFKA_SETUP_DOC_LINK = + 'https://github.com/shivanshuraj1333/kafka-opentelemetry-instrumentation/tree/master'; + +export function convertToTitleCase(text: string): string { + return text + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); +} + +export type RowData = { + key: string | number; + [key: string]: string | number; +}; + +export enum ConsumerLagDetailType { + ConsumerDetails = 'consumer-details', + ProducerDetails = 'producer-details', + NetworkLatency = 'network-latency', + PartitionHostMetrics = 'partition-host-metric', +} + +export const ConsumerLagDetailTitle: Record = { + 'consumer-details': 'Consumer Groups Details', + 'producer-details': 'Producer Details', + 'network-latency': 'Network Latency', + 'partition-host-metric': 'Partition Host Metrics', +}; + +export function createWidgetFilterItem( + key: string, + value: string, +): TagFilterItem { + const id = `${key}--string--tag--false`; + + return { + id: uuid(), + key: { + key, + dataType: DataTypes.String, + type: 'tag', + isColumn: false, + isJSON: false, + id, + }, + op: '=', + value, + }; +} + +export function getFiltersFromConfigOptions( + consumerGrp?: string, + topic?: string, + partition?: string, +): TagFilterItem[] { + const configOptions = [ + { key: 'group', values: consumerGrp?.split(',') }, + { key: 'topic', values: topic?.split(',') }, + { key: 'partition', values: partition?.split(',') }, + ]; + return configOptions.reduce( + (accumulator, { key, values }) => { + if (values && !isEmpty(values.filter((item) => item !== ''))) { + accumulator.push( + ...values.map((value) => createWidgetFilterItem(key, value)), + ); + } + return accumulator; + }, + [], + ); +} + +export function getWidgetQuery({ + filterItems, +}: { + filterItems: TagFilterItem[]; +}): GetWidgetQueryBuilderProps { + return { + title: 'Consumer Lag', + panelTypes: PANEL_TYPES.TIME_SERIES, + fillSpans: false, + yAxisUnit: 'none', + query: { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: { + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'kafka_consumer_group_lag--float64--Gauge--true', + isColumn: true, + isJSON: false, + key: 'kafka_consumer_group_lag', + type: 'Gauge', + }, + aggregateOperator: 'max', + dataSource: DataSource.METRICS, + disabled: false, + expression: 'A', + filters: { + items: filterItems || [], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'group--string--tag--false', + isColumn: false, + isJSON: false, + key: 'group', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'topic--string--tag--false', + isColumn: false, + isJSON: false, + key: 'topic', + type: 'tag', + }, + { + dataType: DataTypes.String, + id: 'partition--string--tag--false', + isColumn: false, + isJSON: false, + key: 'partition', + type: 'tag', + }, + ], + having: [], + legend: '{{group}}-{{topic}}-{{partition}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'avg', + stepInterval: 60, + timeAggregation: 'max', + }, + ], + queryFormulas: [], + }, + clickhouse_sql: [], + id: uuid(), + }, + }; +} + +export const convertToNanoseconds = (timestamp: number): bigint => + BigInt((timestamp * 1e9).toFixed(0)); + +export const getStartAndEndTimesInMilliseconds = ( + timestamp: number, +): { start: number; end: number } => { + const FIVE_MINUTES_IN_MILLISECONDS = 5 * 60 * 1000; // 5 minutes in milliseconds - check with Shivanshu once + + const start = Math.floor(timestamp); + const end = Math.floor(start + FIVE_MINUTES_IN_MILLISECONDS); + + return { start, end }; +}; + +export interface SelectedTimelineQuery { + group?: string; + partition?: string; + topic?: string; + start?: number; + end?: number; +} + +export function setSelectedTimelineQuery( + urlQuery: URLSearchParams, + timestamp: number, + location: Location, + history: History, + data?: { + [key: string]: string; + }, +): void { + const selectedTimelineQuery: SelectedTimelineQuery = { + group: data?.group, + partition: data?.partition, + topic: data?.topic, + ...getStartAndEndTimesInMilliseconds(timestamp), + }; + urlQuery.set( + QueryParams.selectedTimelineQuery, + encodeURIComponent(JSON.stringify(selectedTimelineQuery)), + ); + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.replace(generatedUrl); +} diff --git a/frontend/src/pages/MessagingQueues/index.tsx b/frontend/src/pages/MessagingQueues/index.tsx new file mode 100644 index 0000000000..cc59152b17 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/index.tsx @@ -0,0 +1,3 @@ +import MessagingQueues from './MessagingQueues'; + +export default MessagingQueues; diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index 83f8370944..1845e77941 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -52,6 +52,8 @@ export const routePermission: Record = { ALL_CHANNELS: ['ADMIN', 'EDITOR', 'VIEWER'], INGESTION_SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'], ALL_DASHBOARD: ['ADMIN', 'EDITOR', 'VIEWER'], + MESSAGING_QUEUES: ['ADMIN', 'EDITOR', 'VIEWER'], + MESSAGING_QUEUES_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'], ALL_ERROR: ['ADMIN', 'EDITOR', 'VIEWER'], APPLICATION: ['ADMIN', 'EDITOR', 'VIEWER'], CHANNELS_EDIT: ['ADMIN'],