mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 13:59:06 +08:00
feat: added generic UI for scenario 1,3,4 (#6287)
* feat: added generic table component for scenario 1,3,4 * feat: added generic logic for mq detail tables and consumed for sc-1,2 * feat: added overview and details table for scenario-3 * feat: added table row clicks func * feat: resolved comments
This commit is contained in:
parent
9d90b8d19c
commit
12377be809
@ -37,4 +37,5 @@ export enum QueryParams {
|
||||
partition = 'partition',
|
||||
selectedTimelineQuery = 'selectedTimelineQuery',
|
||||
ruleType = 'ruleType',
|
||||
configDetail = 'configDetail',
|
||||
}
|
||||
|
@ -5,17 +5,33 @@ import logEvent from 'api/common/logEvent';
|
||||
import ROUTES from 'constants/routes';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { ListMinus } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { MessagingQueuesViewType } from '../MessagingQueuesUtils';
|
||||
import {
|
||||
MessagingQueuesViewType,
|
||||
MessagingQueuesViewTypeOptions,
|
||||
ProducerLatencyOptions,
|
||||
} from '../MessagingQueuesUtils';
|
||||
import { SelectLabelWithComingSoon } from '../MQCommon/MQCommon';
|
||||
import MessagingQueueOverview from '../MQDetails/MessagingQueueOverview';
|
||||
import MessagingQueuesDetails from '../MQDetails/MQDetails';
|
||||
import MessagingQueuesConfigOptions from '../MQGraph/MQConfigOptions';
|
||||
import MessagingQueuesGraph from '../MQGraph/MQGraph';
|
||||
|
||||
function MQDetailPage(): JSX.Element {
|
||||
const history = useHistory();
|
||||
const [
|
||||
selectedView,
|
||||
setSelectedView,
|
||||
] = useState<MessagingQueuesViewTypeOptions>(
|
||||
MessagingQueuesViewType.consumerLag.value,
|
||||
);
|
||||
|
||||
const [
|
||||
producerLatencyOption,
|
||||
setproducerLatencyOption,
|
||||
] = useState<ProducerLatencyOptions>(ProducerLatencyOptions.Producers);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Messaging Queues: Detail page visited', {});
|
||||
@ -39,28 +55,19 @@ function MQDetailPage(): JSX.Element {
|
||||
className="messaging-queue-options"
|
||||
defaultValue={MessagingQueuesViewType.consumerLag.value}
|
||||
popupClassName="messaging-queue-options-popup"
|
||||
onChange={(value): void => setSelectedView(value)}
|
||||
options={[
|
||||
{
|
||||
label: MessagingQueuesViewType.consumerLag.label,
|
||||
value: MessagingQueuesViewType.consumerLag.value,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<SelectLabelWithComingSoon
|
||||
label={MessagingQueuesViewType.partitionLatency.label}
|
||||
/>
|
||||
),
|
||||
label: MessagingQueuesViewType.partitionLatency.label,
|
||||
value: MessagingQueuesViewType.partitionLatency.value,
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<SelectLabelWithComingSoon
|
||||
label={MessagingQueuesViewType.producerLatency.label}
|
||||
/>
|
||||
),
|
||||
label: MessagingQueuesViewType.producerLatency.label,
|
||||
value: MessagingQueuesViewType.producerLatency.value,
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
@ -78,10 +85,21 @@ function MQDetailPage(): JSX.Element {
|
||||
</div>
|
||||
<div className="messaging-queue-main-graph">
|
||||
<MessagingQueuesConfigOptions />
|
||||
<MessagingQueuesGraph />
|
||||
{selectedView === MessagingQueuesViewType.consumerLag.value ? (
|
||||
<MessagingQueuesGraph />
|
||||
) : (
|
||||
<MessagingQueueOverview
|
||||
selectedView={selectedView}
|
||||
option={producerLatencyOption}
|
||||
setOption={setproducerLatencyOption}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="messaging-queue-details">
|
||||
<MessagingQueuesDetails />
|
||||
<MessagingQueuesDetails
|
||||
selectedView={selectedView}
|
||||
producerLatencyOption={producerLatencyOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,3 +4,42 @@
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.mq-overview-container {
|
||||
display: flex;
|
||||
padding: 24px;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 16px;
|
||||
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
background: var(--bg-ink-500);
|
||||
|
||||
.mq-overview-title {
|
||||
color: var(--bg-vanilla-200);
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.mq-details-options {
|
||||
letter-spacing: -0.06px;
|
||||
cursor: pointer;
|
||||
|
||||
.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::before {
|
||||
width: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +1,227 @@
|
||||
import './MQDetails.style.scss';
|
||||
|
||||
import { Radio } from 'antd';
|
||||
import { Dispatch, SetStateAction, useState } from 'react';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import {
|
||||
ConsumerLagDetailTitle,
|
||||
ConsumerLagDetailType,
|
||||
getMetaDataAndAPIPerView,
|
||||
MessagingQueueServiceDetailType,
|
||||
MessagingQueuesViewType,
|
||||
MessagingQueuesViewTypeOptions,
|
||||
ProducerLatencyOptions,
|
||||
SelectedTimelineQuery,
|
||||
} from '../MessagingQueuesUtils';
|
||||
import { ComingSoon } from '../MQCommon/MQCommon';
|
||||
import MessagingQueuesTable from './MQTables/MQTables';
|
||||
|
||||
const MQServiceDetailTypePerView = (
|
||||
producerLatencyOption: ProducerLatencyOptions,
|
||||
): Record<string, MessagingQueueServiceDetailType[]> => ({
|
||||
[MessagingQueuesViewType.consumerLag.value]: [
|
||||
MessagingQueueServiceDetailType.ConsumerDetails,
|
||||
MessagingQueueServiceDetailType.ProducerDetails,
|
||||
MessagingQueueServiceDetailType.NetworkLatency,
|
||||
MessagingQueueServiceDetailType.PartitionHostMetrics,
|
||||
],
|
||||
[MessagingQueuesViewType.partitionLatency.value]: [
|
||||
MessagingQueueServiceDetailType.ConsumerDetails,
|
||||
MessagingQueueServiceDetailType.ProducerDetails,
|
||||
],
|
||||
[MessagingQueuesViewType.producerLatency.value]: [
|
||||
producerLatencyOption === ProducerLatencyOptions.Consumers
|
||||
? MessagingQueueServiceDetailType.ConsumerDetails
|
||||
: MessagingQueueServiceDetailType.ProducerDetails,
|
||||
],
|
||||
});
|
||||
|
||||
interface MessagingQueuesOptionsProps {
|
||||
currentTab: MessagingQueueServiceDetailType;
|
||||
setCurrentTab: Dispatch<SetStateAction<MessagingQueueServiceDetailType>>;
|
||||
selectedView: MessagingQueuesViewTypeOptions;
|
||||
producerLatencyOption: ProducerLatencyOptions;
|
||||
}
|
||||
|
||||
function MessagingQueuesOptions({
|
||||
currentTab,
|
||||
setCurrentTab,
|
||||
}: {
|
||||
currentTab: ConsumerLagDetailType;
|
||||
setCurrentTab: Dispatch<SetStateAction<ConsumerLagDetailType>>;
|
||||
}): JSX.Element {
|
||||
const [option, setOption] = useState<ConsumerLagDetailType>(currentTab);
|
||||
selectedView,
|
||||
producerLatencyOption,
|
||||
}: MessagingQueuesOptionsProps): JSX.Element {
|
||||
const handleChange = (value: MessagingQueueServiceDetailType): void => {
|
||||
setCurrentTab(value);
|
||||
};
|
||||
|
||||
const renderRadioButtons = (): JSX.Element[] => {
|
||||
const detailTypes =
|
||||
MQServiceDetailTypePerView(producerLatencyOption)[selectedView] || [];
|
||||
return detailTypes.map((detailType) => (
|
||||
<Radio.Button
|
||||
key={detailType}
|
||||
value={detailType}
|
||||
disabled={
|
||||
detailType === MessagingQueueServiceDetailType.PartitionHostMetrics
|
||||
}
|
||||
className={
|
||||
detailType === MessagingQueueServiceDetailType.PartitionHostMetrics
|
||||
? 'disabled-option'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{ConsumerLagDetailTitle[detailType]}
|
||||
{detailType === MessagingQueueServiceDetailType.PartitionHostMetrics && (
|
||||
<ComingSoon />
|
||||
)}
|
||||
</Radio.Button>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<Radio.Group
|
||||
onChange={(value): void => {
|
||||
setOption(value.target.value);
|
||||
setCurrentTab(value.target.value);
|
||||
}}
|
||||
value={option}
|
||||
onChange={(e): void => handleChange(e.target.value)}
|
||||
value={currentTab}
|
||||
className="mq-details-options"
|
||||
>
|
||||
<Radio.Button value={ConsumerLagDetailType.ConsumerDetails} checked>
|
||||
{ConsumerLagDetailTitle[ConsumerLagDetailType.ConsumerDetails]}
|
||||
</Radio.Button>
|
||||
<Radio.Button value={ConsumerLagDetailType.ProducerDetails}>
|
||||
{ConsumerLagDetailTitle[ConsumerLagDetailType.ProducerDetails]}
|
||||
</Radio.Button>
|
||||
<Radio.Button value={ConsumerLagDetailType.NetworkLatency}>
|
||||
{ConsumerLagDetailTitle[ConsumerLagDetailType.NetworkLatency]}
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
value={ConsumerLagDetailType.PartitionHostMetrics}
|
||||
disabled
|
||||
className="disabled-option"
|
||||
>
|
||||
{ConsumerLagDetailTitle[ConsumerLagDetailType.PartitionHostMetrics]}
|
||||
<ComingSoon />
|
||||
</Radio.Button>
|
||||
{renderRadioButtons()}
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
|
||||
function MessagingQueuesDetails(): JSX.Element {
|
||||
const [currentTab, setCurrentTab] = useState<ConsumerLagDetailType>(
|
||||
ConsumerLagDetailType.ConsumerDetails,
|
||||
const checkValidityOfDetailConfigs = (
|
||||
selectedTimelineQuery: SelectedTimelineQuery,
|
||||
selectedView: MessagingQueuesViewTypeOptions,
|
||||
currentTab: MessagingQueueServiceDetailType,
|
||||
configDetails?: {
|
||||
[key: string]: string;
|
||||
},
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): boolean => {
|
||||
if (selectedView === MessagingQueuesViewType.consumerLag.value) {
|
||||
return !(
|
||||
isEmpty(selectedTimelineQuery) ||
|
||||
(!selectedTimelineQuery?.group &&
|
||||
!selectedTimelineQuery?.topic &&
|
||||
!selectedTimelineQuery?.partition)
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedView === MessagingQueuesViewType.partitionLatency.value) {
|
||||
if (isEmpty(configDetails)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTab === MessagingQueueServiceDetailType.ConsumerDetails) {
|
||||
return Boolean(configDetails?.topic && configDetails?.partition);
|
||||
}
|
||||
return Boolean(
|
||||
configDetails?.group && configDetails?.topic && configDetails?.partition,
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedView === MessagingQueuesViewType.producerLatency.value) {
|
||||
if (isEmpty(configDetails)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTab === MessagingQueueServiceDetailType.ProducerDetails) {
|
||||
return Boolean(
|
||||
configDetails?.topic &&
|
||||
configDetails?.partition &&
|
||||
configDetails?.service_name,
|
||||
);
|
||||
}
|
||||
return Boolean(configDetails?.topic && configDetails?.service_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
function MessagingQueuesDetails({
|
||||
selectedView,
|
||||
producerLatencyOption,
|
||||
}: {
|
||||
selectedView: MessagingQueuesViewTypeOptions;
|
||||
producerLatencyOption: ProducerLatencyOptions;
|
||||
}): JSX.Element {
|
||||
const [currentTab, setCurrentTab] = useState<MessagingQueueServiceDetailType>(
|
||||
MessagingQueueServiceDetailType.ConsumerDetails,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
producerLatencyOption &&
|
||||
selectedView === MessagingQueuesViewType.producerLatency.value
|
||||
) {
|
||||
setCurrentTab(
|
||||
producerLatencyOption === ProducerLatencyOptions.Consumers
|
||||
? MessagingQueueServiceDetailType.ConsumerDetails
|
||||
: MessagingQueueServiceDetailType.ProducerDetails,
|
||||
);
|
||||
}
|
||||
}, [selectedView, producerLatencyOption]);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const timelineQuery = decodeURIComponent(
|
||||
urlQuery.get(QueryParams.selectedTimelineQuery) || '',
|
||||
);
|
||||
|
||||
const timelineQueryData: SelectedTimelineQuery = useMemo(
|
||||
() => (timelineQuery ? JSON.parse(timelineQuery) : {}),
|
||||
[timelineQuery],
|
||||
);
|
||||
|
||||
const configDetails = decodeURIComponent(
|
||||
urlQuery.get(QueryParams.configDetail) || '',
|
||||
);
|
||||
|
||||
const configDetailQueryData: {
|
||||
[key: string]: string;
|
||||
} = useMemo(() => (configDetails ? JSON.parse(configDetails) : {}), [
|
||||
configDetails,
|
||||
]);
|
||||
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const serviceConfigDetails = useMemo(
|
||||
() =>
|
||||
getMetaDataAndAPIPerView({
|
||||
detailType: currentTab,
|
||||
minTime,
|
||||
maxTime,
|
||||
selectedTimelineQuery: timelineQueryData,
|
||||
configDetails: configDetailQueryData,
|
||||
}),
|
||||
[configDetailQueryData, currentTab, maxTime, minTime, timelineQueryData],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mq-details">
|
||||
<MessagingQueuesOptions
|
||||
currentTab={currentTab}
|
||||
setCurrentTab={setCurrentTab}
|
||||
selectedView={selectedView}
|
||||
producerLatencyOption={producerLatencyOption}
|
||||
/>
|
||||
<MessagingQueuesTable
|
||||
currentTab={currentTab}
|
||||
selectedView={selectedView}
|
||||
tableApi={serviceConfigDetails[selectedView].tableApi}
|
||||
validConfigPresent={checkValidityOfDetailConfigs(
|
||||
timelineQueryData,
|
||||
selectedView,
|
||||
currentTab,
|
||||
configDetailQueryData,
|
||||
)}
|
||||
tableApiPayload={serviceConfigDetails[selectedView].tableApiPayload}
|
||||
/>
|
||||
<MessagingQueuesTable currentTab={currentTab} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
.mq-tables-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.mq-table-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -31,9 +34,6 @@
|
||||
.ant-table-tbody {
|
||||
.ant-table-cell {
|
||||
max-width: 250px;
|
||||
|
||||
background-color: var(--bg-ink-400);
|
||||
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@ -63,6 +63,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mq-table {
|
||||
&.mq-overview-row-clickable {
|
||||
.ant-table-row {
|
||||
background-color: var(--bg-ink-400);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--bg-slate-400) !important;
|
||||
color: var(--bg-vanilla-400);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.mq-tables-container {
|
||||
.mq-table-title {
|
||||
|
@ -1,9 +1,10 @@
|
||||
/* eslint-disable react/require-default-props */
|
||||
import './MQTables.styles.scss';
|
||||
|
||||
import { Skeleton, Table, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import axios from 'axios';
|
||||
import { isNumber } from 'chart.js/helpers';
|
||||
import cx from 'classnames';
|
||||
import { ColumnTypeRender } from 'components/Logs/TableView/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@ -13,18 +14,21 @@ import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import {
|
||||
ConsumerLagDetailTitle,
|
||||
ConsumerLagDetailType,
|
||||
convertToTitleCase,
|
||||
MessagingQueueServiceDetailType,
|
||||
MessagingQueuesViewType,
|
||||
MessagingQueuesViewTypeOptions,
|
||||
RowData,
|
||||
SelectedTimelineQuery,
|
||||
setConfigDetail,
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import {
|
||||
ConsumerLagPayload,
|
||||
getConsumerLagDetails,
|
||||
MessagingQueueServicePayload,
|
||||
MessagingQueuesPayloadProps,
|
||||
} from './getConsumerLagDetails';
|
||||
|
||||
@ -33,7 +37,6 @@ export function getColumns(
|
||||
data: MessagingQueuesPayloadProps['payload'],
|
||||
history: History<unknown>,
|
||||
): RowData[] {
|
||||
console.log(data);
|
||||
if (data?.result?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@ -105,10 +108,25 @@ const showPaginationItem = (total: number, range: number[]): JSX.Element => (
|
||||
</>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function MessagingQueuesTable({
|
||||
currentTab,
|
||||
selectedView,
|
||||
tableApiPayload,
|
||||
tableApi,
|
||||
validConfigPresent = false,
|
||||
type = 'Detail',
|
||||
}: {
|
||||
currentTab: ConsumerLagDetailType;
|
||||
currentTab?: MessagingQueueServiceDetailType;
|
||||
selectedView: MessagingQueuesViewTypeOptions;
|
||||
tableApiPayload?: MessagingQueueServicePayload;
|
||||
tableApi: (
|
||||
props: MessagingQueueServicePayload,
|
||||
) => Promise<
|
||||
SuccessResponse<MessagingQueuesPayloadProps['payload']> | ErrorResponse
|
||||
>;
|
||||
validConfigPresent?: boolean;
|
||||
type?: 'Detail' | 'Overview';
|
||||
}): JSX.Element {
|
||||
const [columns, setColumns] = useState<any[]>([]);
|
||||
const [tableData, setTableData] = useState<any[]>([]);
|
||||
@ -118,11 +136,22 @@ function MessagingQueuesTable({
|
||||
const timelineQuery = decodeURIComponent(
|
||||
urlQuery.get(QueryParams.selectedTimelineQuery) || '',
|
||||
);
|
||||
|
||||
const timelineQueryData: SelectedTimelineQuery = useMemo(
|
||||
() => (timelineQuery ? JSON.parse(timelineQuery) : {}),
|
||||
[timelineQuery],
|
||||
);
|
||||
|
||||
const configDetails = decodeURIComponent(
|
||||
urlQuery.get(QueryParams.configDetail) || '',
|
||||
);
|
||||
|
||||
const configDetailQueryData: {
|
||||
[key: string]: string;
|
||||
} = useMemo(() => (configDetails ? JSON.parse(configDetails) : {}), [
|
||||
configDetails,
|
||||
]);
|
||||
|
||||
const paginationConfig = useMemo(
|
||||
() =>
|
||||
tableData?.length > 20 && {
|
||||
@ -134,90 +163,104 @@ function MessagingQueuesTable({
|
||||
[tableData],
|
||||
);
|
||||
|
||||
const props: ConsumerLagPayload = useMemo(
|
||||
() => ({
|
||||
start: (timelineQueryData?.start || 0) * 1e9,
|
||||
end: (timelineQueryData?.end || 0) * 1e9,
|
||||
variables: {
|
||||
partition: timelineQueryData?.partition,
|
||||
topic: timelineQueryData?.topic,
|
||||
consumer_group: timelineQueryData?.group,
|
||||
},
|
||||
detailType: currentTab,
|
||||
}),
|
||||
[currentTab, timelineQueryData],
|
||||
);
|
||||
|
||||
const handleConsumerDetailsOnError = (error: Error): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(error) ? error?.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
const { mutate: getConsumerDetails, isLoading } = useMutation(
|
||||
getConsumerLagDetails,
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
if (data.payload) {
|
||||
setColumns(getColumns(data?.payload, history));
|
||||
setTableData(getTableData(data?.payload));
|
||||
}
|
||||
},
|
||||
onError: handleConsumerDetailsOnError,
|
||||
const { mutate: getViewDetails, isLoading } = useMutation(tableApi, {
|
||||
onSuccess: (data) => {
|
||||
if (data.payload) {
|
||||
setColumns(getColumns(data?.payload, history));
|
||||
setTableData(getTableData(data?.payload));
|
||||
}
|
||||
},
|
||||
onError: handleConsumerDetailsOnError,
|
||||
});
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (validConfigPresent && tableApiPayload) {
|
||||
getViewDetails(tableApiPayload);
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[currentTab, selectedView, tableApiPayload],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => getConsumerDetails(props), [currentTab, props]);
|
||||
const [selectedRowKey, setSelectedRowKey] = useState<React.Key>();
|
||||
const [, setSelectedRows] = useState<any>();
|
||||
const location = useLocation();
|
||||
|
||||
const isLogEventCalled = useRef<boolean>(false);
|
||||
const onRowClick = (record: { [key: string]: string }): void => {
|
||||
const selectedKey = record.key;
|
||||
|
||||
const isEmptyDetails = (timelineQueryData: SelectedTimelineQuery): boolean => {
|
||||
const isEmptyDetail =
|
||||
isEmpty(timelineQueryData) ||
|
||||
(!timelineQueryData?.group &&
|
||||
!timelineQueryData?.topic &&
|
||||
!timelineQueryData?.partition);
|
||||
if (`${selectedKey}_${selectedView}` === selectedRowKey) {
|
||||
setSelectedRowKey(undefined);
|
||||
setSelectedRows({});
|
||||
setConfigDetail(urlQuery, location, history, {});
|
||||
} else {
|
||||
setSelectedRowKey(`${selectedKey}_${selectedView}`);
|
||||
setSelectedRows(record);
|
||||
|
||||
if (!isEmptyDetail && !isLogEventCalled.current) {
|
||||
logEvent('Messaging Queues: More details viewed', {
|
||||
'tab-option': ConsumerLagDetailTitle[currentTab],
|
||||
variables: {
|
||||
group: timelineQueryData?.group,
|
||||
topic: timelineQueryData?.topic,
|
||||
partition: timelineQueryData?.partition,
|
||||
},
|
||||
});
|
||||
isLogEventCalled.current = true;
|
||||
if (!isEmpty(record)) {
|
||||
setConfigDetail(urlQuery, location, history, record);
|
||||
}
|
||||
}
|
||||
return isEmptyDetail;
|
||||
};
|
||||
|
||||
const subtitle =
|
||||
selectedView === MessagingQueuesViewType.consumerLag.value
|
||||
? `${timelineQueryData?.group || ''} ${timelineQueryData?.topic || ''} ${
|
||||
timelineQueryData?.partition || ''
|
||||
}`
|
||||
: `${configDetailQueryData?.service_name || ''} ${
|
||||
configDetailQueryData?.topic || ''
|
||||
} ${configDetailQueryData?.partition || ''}`;
|
||||
|
||||
return (
|
||||
<div className="mq-tables-container">
|
||||
{isEmptyDetails(timelineQueryData) ? (
|
||||
{!validConfigPresent ? (
|
||||
<div className="no-data-style">
|
||||
<Typography.Text>
|
||||
Click on a co-ordinate above to see the details
|
||||
{selectedView === MessagingQueuesViewType.consumerLag.value
|
||||
? 'Click on a co-ordinate above to see the details'
|
||||
: 'Click on a row above to see the details'}
|
||||
</Typography.Text>
|
||||
<Skeleton />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="mq-table-title">
|
||||
{ConsumerLagDetailTitle[currentTab]}
|
||||
<div className="mq-table-subtitle">{`${timelineQueryData?.group || ''} ${
|
||||
timelineQueryData?.topic || ''
|
||||
} ${timelineQueryData?.partition || ''}`}</div>
|
||||
</div>
|
||||
{currentTab && (
|
||||
<div className="mq-table-title">
|
||||
{ConsumerLagDetailTitle[currentTab]}
|
||||
<div className="mq-table-subtitle">{subtitle}</div>
|
||||
</div>
|
||||
)}
|
||||
<Table
|
||||
className="mq-table"
|
||||
className={cx(
|
||||
'mq-table',
|
||||
type !== 'Detail' ? 'mq-overview-row-clickable' : '',
|
||||
)}
|
||||
pagination={paginationConfig}
|
||||
size="middle"
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
bordered={false}
|
||||
loading={isLoading}
|
||||
onRow={(record): any =>
|
||||
type !== 'Detail'
|
||||
? {
|
||||
onClick: (): void => onRowClick(record),
|
||||
}
|
||||
: {}
|
||||
}
|
||||
rowClassName={(record): any =>
|
||||
`${record.key}_${selectedView}` === selectedRowKey
|
||||
? 'ant-table-row-selected'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -2,18 +2,19 @@ import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { ConsumerLagDetailType } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { MessagingQueueServiceDetailType } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
export interface ConsumerLagPayload {
|
||||
export interface MessagingQueueServicePayload {
|
||||
start?: number | string;
|
||||
end?: number | string;
|
||||
variables: {
|
||||
partition?: string;
|
||||
topic?: string;
|
||||
consumer_group?: string;
|
||||
service_name?: string;
|
||||
};
|
||||
detailType: ConsumerLagDetailType;
|
||||
detailType?: MessagingQueueServiceDetailType | 'producer' | 'consumer';
|
||||
}
|
||||
|
||||
export interface MessagingQueuesPayloadProps {
|
||||
@ -36,7 +37,7 @@ export interface MessagingQueuesPayloadProps {
|
||||
}
|
||||
|
||||
export const getConsumerLagDetails = async (
|
||||
props: ConsumerLagPayload,
|
||||
props: MessagingQueueServicePayload,
|
||||
): Promise<
|
||||
SuccessResponse<MessagingQueuesPayloadProps['payload']> | ErrorResponse
|
||||
> => {
|
||||
|
@ -0,0 +1,39 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { MessagingQueueServiceDetailType } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import {
|
||||
MessagingQueueServicePayload,
|
||||
MessagingQueuesPayloadProps,
|
||||
} from './getConsumerLagDetails';
|
||||
|
||||
export const getPartitionLatencyDetails = async (
|
||||
props: MessagingQueueServicePayload,
|
||||
): Promise<
|
||||
SuccessResponse<MessagingQueuesPayloadProps['payload']> | ErrorResponse
|
||||
> => {
|
||||
const { detailType, ...rest } = props;
|
||||
let endpoint = '';
|
||||
if (detailType === MessagingQueueServiceDetailType.ConsumerDetails) {
|
||||
endpoint = `/messaging-queues/kafka/partition-latency/consumer`;
|
||||
} else {
|
||||
endpoint = `/messaging-queues/kafka/consumer-lag/producer-details`;
|
||||
}
|
||||
try {
|
||||
const response = await axios.post(endpoint, {
|
||||
...rest,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG);
|
||||
}
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import {
|
||||
MessagingQueueServicePayload,
|
||||
MessagingQueuesPayloadProps,
|
||||
} from './getConsumerLagDetails';
|
||||
|
||||
export const getPartitionLatencyOverview = async (
|
||||
props: Omit<MessagingQueueServicePayload, 'detailType' | 'variables'>,
|
||||
): Promise<
|
||||
SuccessResponse<MessagingQueuesPayloadProps['payload']> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`/messaging-queues/kafka/partition-latency/overview`,
|
||||
{
|
||||
...props,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG);
|
||||
}
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import {
|
||||
MessagingQueueServicePayload,
|
||||
MessagingQueuesPayloadProps,
|
||||
} from './getConsumerLagDetails';
|
||||
|
||||
export const getTopicThroughputDetails = async (
|
||||
props: MessagingQueueServicePayload,
|
||||
): Promise<
|
||||
SuccessResponse<MessagingQueuesPayloadProps['payload']> | ErrorResponse
|
||||
> => {
|
||||
const { detailType, ...rest } = props;
|
||||
const endpoint = `/messaging-queues/kafka/topic-throughput/${detailType}`;
|
||||
try {
|
||||
const response = await axios.post(endpoint, {
|
||||
...rest,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG);
|
||||
}
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import {
|
||||
MessagingQueueServicePayload,
|
||||
MessagingQueuesPayloadProps,
|
||||
} from './getConsumerLagDetails';
|
||||
|
||||
export const getTopicThroughputOverview = async (
|
||||
props: Omit<MessagingQueueServicePayload, 'variables'>,
|
||||
): Promise<
|
||||
SuccessResponse<MessagingQueuesPayloadProps['payload']> | ErrorResponse
|
||||
> => {
|
||||
const { detailType, start, end } = props;
|
||||
console.log(detailType);
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`messaging-queues/kafka/topic-throughput/${detailType}`,
|
||||
{
|
||||
start,
|
||||
end,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG);
|
||||
}
|
||||
};
|
@ -0,0 +1,102 @@
|
||||
import './MQDetails.style.scss';
|
||||
|
||||
import { Radio } from 'antd';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import {
|
||||
MessagingQueuesViewType,
|
||||
MessagingQueuesViewTypeOptions,
|
||||
ProducerLatencyOptions,
|
||||
} from '../MessagingQueuesUtils';
|
||||
import { MessagingQueueServicePayload } from './MQTables/getConsumerLagDetails';
|
||||
import { getPartitionLatencyOverview } from './MQTables/getPartitionLatencyOverview';
|
||||
import { getTopicThroughputOverview } from './MQTables/getTopicThroughputOverview';
|
||||
import MessagingQueuesTable from './MQTables/MQTables';
|
||||
|
||||
type SelectedViewType = keyof typeof MessagingQueuesViewType;
|
||||
|
||||
function PartitionLatencyTabs({
|
||||
option,
|
||||
setOption,
|
||||
}: {
|
||||
option: ProducerLatencyOptions;
|
||||
setOption: Dispatch<SetStateAction<ProducerLatencyOptions>>;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<Radio.Group
|
||||
onChange={(e): void => setOption(e.target.value)}
|
||||
value={option}
|
||||
className="mq-details-options"
|
||||
>
|
||||
<Radio.Button
|
||||
value={ProducerLatencyOptions.Producers}
|
||||
key={ProducerLatencyOptions.Producers}
|
||||
>
|
||||
{ProducerLatencyOptions.Producers}
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
value={ProducerLatencyOptions.Consumers}
|
||||
key={ProducerLatencyOptions.Consumers}
|
||||
>
|
||||
{ProducerLatencyOptions.Consumers}
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
|
||||
const getTableApi = (selectedView: MessagingQueuesViewTypeOptions): any => {
|
||||
if (selectedView === MessagingQueuesViewType.producerLatency.value) {
|
||||
return getTopicThroughputOverview;
|
||||
}
|
||||
return getPartitionLatencyOverview;
|
||||
};
|
||||
|
||||
function MessagingQueueOverview({
|
||||
selectedView,
|
||||
option,
|
||||
setOption,
|
||||
}: {
|
||||
selectedView: MessagingQueuesViewTypeOptions;
|
||||
option: ProducerLatencyOptions;
|
||||
setOption: Dispatch<SetStateAction<ProducerLatencyOptions>>;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const tableApiPayload: MessagingQueueServicePayload = {
|
||||
variables: {},
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
detailType:
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
selectedView === MessagingQueuesViewType.producerLatency.value
|
||||
? option === ProducerLatencyOptions.Producers
|
||||
? 'producer'
|
||||
: 'consumer'
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mq-overview-container">
|
||||
{selectedView === MessagingQueuesViewType.producerLatency.value ? (
|
||||
<PartitionLatencyTabs option={option} setOption={setOption} />
|
||||
) : (
|
||||
<div className="mq-overview-title">
|
||||
{MessagingQueuesViewType[selectedView as SelectedViewType].label}
|
||||
</div>
|
||||
)}
|
||||
<MessagingQueuesTable
|
||||
selectedView={selectedView}
|
||||
tableApiPayload={tableApiPayload}
|
||||
tableApi={getTableApi(selectedView)}
|
||||
validConfigPresent
|
||||
type="Overview"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default MessagingQueueOverview;
|
@ -106,6 +106,8 @@
|
||||
|
||||
.mq-details-options {
|
||||
letter-spacing: -0.06px;
|
||||
cursor: pointer;
|
||||
|
||||
.ant-radio-button-wrapper {
|
||||
border-color: var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-400);
|
||||
|
@ -3,12 +3,21 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetWidgetQueryBuilderProps } from 'container/MetricsApplication/types';
|
||||
import { History, Location } from 'history';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
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';
|
||||
|
||||
import {
|
||||
getConsumerLagDetails,
|
||||
MessagingQueueServicePayload,
|
||||
MessagingQueuesPayloadProps,
|
||||
} from './MQDetails/MQTables/getConsumerLagDetails';
|
||||
import { getPartitionLatencyDetails } from './MQDetails/MQTables/getPartitionLatencyDetails';
|
||||
import { getTopicThroughputDetails } from './MQDetails/MQTables/getTopicThroughputDetails';
|
||||
|
||||
export const KAFKA_SETUP_DOC_LINK =
|
||||
'https://signoz.io/docs/messaging-queues/kafka?utm_source=product&utm_medium=kafka-get-started';
|
||||
|
||||
@ -24,14 +33,17 @@ export type RowData = {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
||||
export enum ConsumerLagDetailType {
|
||||
export enum MessagingQueueServiceDetailType {
|
||||
ConsumerDetails = 'consumer-details',
|
||||
ProducerDetails = 'producer-details',
|
||||
NetworkLatency = 'network-latency',
|
||||
PartitionHostMetrics = 'partition-host-metric',
|
||||
}
|
||||
|
||||
export const ConsumerLagDetailTitle: Record<ConsumerLagDetailType, string> = {
|
||||
export const ConsumerLagDetailTitle: Record<
|
||||
MessagingQueueServiceDetailType,
|
||||
string
|
||||
> = {
|
||||
'consumer-details': 'Consumer Groups Details',
|
||||
'producer-details': 'Producer Details',
|
||||
'network-latency': 'Network Latency',
|
||||
@ -205,21 +217,130 @@ export function setSelectedTimelineQuery(
|
||||
history.replace(generatedUrl);
|
||||
}
|
||||
|
||||
export enum MessagingQueuesViewTypeOptions {
|
||||
ConsumerLag = 'consumerLag',
|
||||
PartitionLatency = 'partitionLatency',
|
||||
ProducerLatency = 'producerLatency',
|
||||
ConsumerLatency = 'consumerLatency',
|
||||
}
|
||||
|
||||
export const MessagingQueuesViewType = {
|
||||
consumerLag: {
|
||||
label: 'Consumer Lag view',
|
||||
value: 'consumerLag',
|
||||
value: MessagingQueuesViewTypeOptions.ConsumerLag,
|
||||
},
|
||||
partitionLatency: {
|
||||
label: 'Partition Latency view',
|
||||
value: 'partitionLatency',
|
||||
value: MessagingQueuesViewTypeOptions.PartitionLatency,
|
||||
},
|
||||
producerLatency: {
|
||||
label: 'Producer Latency view',
|
||||
value: 'producerLatency',
|
||||
value: MessagingQueuesViewTypeOptions.ProducerLatency,
|
||||
},
|
||||
consumerLatency: {
|
||||
label: 'Consumer latency view',
|
||||
value: 'consumerLatency',
|
||||
value: MessagingQueuesViewTypeOptions.ConsumerLatency,
|
||||
},
|
||||
};
|
||||
|
||||
export function setConfigDetail(
|
||||
urlQuery: URLSearchParams,
|
||||
location: Location<unknown>,
|
||||
history: History<unknown>,
|
||||
paramsToSet?: {
|
||||
[key: string]: string;
|
||||
},
|
||||
): void {
|
||||
// remove "key" and its value from the paramsToSet object
|
||||
const { key, ...restParamsToSet } = paramsToSet || {};
|
||||
|
||||
if (!isEmpty(restParamsToSet)) {
|
||||
const configDetail = {
|
||||
...restParamsToSet,
|
||||
};
|
||||
urlQuery.set(
|
||||
QueryParams.configDetail,
|
||||
encodeURIComponent(JSON.stringify(configDetail)),
|
||||
);
|
||||
} else {
|
||||
urlQuery.delete(QueryParams.configDetail);
|
||||
}
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.replace(generatedUrl);
|
||||
}
|
||||
|
||||
export enum ProducerLatencyOptions {
|
||||
Producers = 'Producers',
|
||||
Consumers = 'Consumers',
|
||||
}
|
||||
|
||||
interface MetaDataAndAPI {
|
||||
tableApiPayload: MessagingQueueServicePayload;
|
||||
tableApi: (
|
||||
props: MessagingQueueServicePayload,
|
||||
) => Promise<
|
||||
SuccessResponse<MessagingQueuesPayloadProps['payload']> | ErrorResponse
|
||||
>;
|
||||
}
|
||||
interface MetaDataAndAPIPerView {
|
||||
detailType: MessagingQueueServiceDetailType;
|
||||
selectedTimelineQuery: SelectedTimelineQuery;
|
||||
configDetails?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
minTime: number;
|
||||
maxTime: number;
|
||||
}
|
||||
|
||||
export const getMetaDataAndAPIPerView = (
|
||||
metaDataProps: MetaDataAndAPIPerView,
|
||||
): Record<string, MetaDataAndAPI> => {
|
||||
const {
|
||||
detailType,
|
||||
minTime,
|
||||
maxTime,
|
||||
selectedTimelineQuery,
|
||||
configDetails,
|
||||
} = metaDataProps;
|
||||
return {
|
||||
[MessagingQueuesViewType.consumerLag.value]: {
|
||||
tableApiPayload: {
|
||||
start: (selectedTimelineQuery?.start || 0) * 1e9,
|
||||
end: (selectedTimelineQuery?.end || 0) * 1e9,
|
||||
variables: {
|
||||
partition: selectedTimelineQuery?.partition,
|
||||
topic: selectedTimelineQuery?.topic,
|
||||
consumer_group: selectedTimelineQuery?.group,
|
||||
},
|
||||
detailType,
|
||||
},
|
||||
tableApi: getConsumerLagDetails,
|
||||
},
|
||||
[MessagingQueuesViewType.partitionLatency.value]: {
|
||||
tableApiPayload: {
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
variables: {
|
||||
partition: configDetails?.partition,
|
||||
topic: configDetails?.topic,
|
||||
consumer_group: configDetails?.group,
|
||||
},
|
||||
detailType,
|
||||
},
|
||||
tableApi: getPartitionLatencyDetails,
|
||||
},
|
||||
[MessagingQueuesViewType.producerLatency.value]: {
|
||||
tableApiPayload: {
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
variables: {
|
||||
partition: configDetails?.partition,
|
||||
topic: configDetails?.topic,
|
||||
service_name: configDetails?.service_name,
|
||||
},
|
||||
detailType,
|
||||
},
|
||||
tableApi: getTopicThroughputDetails,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user