mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 12:36:02 +08:00
feat: added healthcheck and attribute checklist component for Kafka (#6371)
* feat: added healthcheck and attribute checklist component for Kafka * feat: corrected the onboardingapi payload * feat: added missing configuration button at overview and onboarding flow
This commit is contained in:
parent
352296c6cd
commit
7086470ce2
@ -16,11 +16,13 @@ export interface OnboardingStatusResponse {
|
|||||||
const getOnboardingStatus = async (props: {
|
const getOnboardingStatus = async (props: {
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
|
endpointService?: string;
|
||||||
}): Promise<SuccessResponse<OnboardingStatusResponse> | ErrorResponse> => {
|
}): Promise<SuccessResponse<OnboardingStatusResponse> | ErrorResponse> => {
|
||||||
|
const { endpointService, ...rest } = props;
|
||||||
try {
|
try {
|
||||||
const response = await ApiBaseInstance.post(
|
const response = await ApiBaseInstance.post(
|
||||||
'/messaging-queues/kafka/onboarding/consumers',
|
`/messaging-queues/kafka/onboarding/${endpointService || 'consumers'}`,
|
||||||
props,
|
rest,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -14,6 +14,7 @@ import { useQueryService } from 'hooks/useQueryService';
|
|||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
|
import MessagingQueueHealthCheck from 'pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck';
|
||||||
import { getAttributeDataFromOnboardingStatus } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
import { getAttributeDataFromOnboardingStatus } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
@ -33,6 +34,9 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
|
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
const getStartedSource = urlQuery.get(QueryParams.getStartedSource);
|
const getStartedSource = urlQuery.get(QueryParams.getStartedSource);
|
||||||
|
const getStartedSourceService = urlQuery.get(
|
||||||
|
QueryParams.getStartedSourceService,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
serviceName,
|
serviceName,
|
||||||
@ -74,10 +78,14 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
data: onbData,
|
data: onbData,
|
||||||
error: onbErr,
|
error: onbErr,
|
||||||
isFetching: onbFetching,
|
isFetching: onbFetching,
|
||||||
} = useOnboardingStatus({
|
} = useOnboardingStatus(
|
||||||
enabled: getStartedSource === 'kafka',
|
{
|
||||||
refetchInterval: pollInterval,
|
enabled: getStartedSource === 'kafka',
|
||||||
});
|
refetchInterval: pollInterval,
|
||||||
|
},
|
||||||
|
getStartedSourceService || '',
|
||||||
|
'query-key-onboarding-status',
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
shouldRetryOnboardingCall,
|
shouldRetryOnboardingCall,
|
||||||
@ -326,18 +334,30 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
|
|
||||||
<div className="status">
|
<div className="status">
|
||||||
{isQueryServiceLoading && <LoadingOutlined />}
|
{isQueryServiceLoading && <LoadingOutlined />}
|
||||||
{!isQueryServiceLoading && isReceivingData && (
|
{!isQueryServiceLoading &&
|
||||||
<>
|
isReceivingData &&
|
||||||
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
(getStartedSource !== 'kafka' ? (
|
||||||
<span> Success </span>
|
<>
|
||||||
</>
|
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
||||||
)}
|
<span> Success </span>
|
||||||
{!isQueryServiceLoading && !isReceivingData && (
|
</>
|
||||||
<>
|
) : (
|
||||||
<CloseCircleTwoTone twoToneColor="#e84749" />
|
<MessagingQueueHealthCheck
|
||||||
<span> Failed </span>
|
serviceToInclude={[getStartedSourceService || '']}
|
||||||
</>
|
/>
|
||||||
)}
|
))}
|
||||||
|
{!isQueryServiceLoading &&
|
||||||
|
!isReceivingData &&
|
||||||
|
(getStartedSource !== 'kafka' ? (
|
||||||
|
<>
|
||||||
|
<CloseCircleTwoTone twoToneColor="#e84749" />
|
||||||
|
<span> Failed </span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<MessagingQueueHealthCheck
|
||||||
|
serviceToInclude={[getStartedSourceService || '']}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="details-info">
|
<div className="details-info">
|
||||||
|
@ -9,7 +9,10 @@ import cx from 'classnames';
|
|||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
||||||
import { useCases } from 'container/OnboardingContainer/OnboardingContainer';
|
import {
|
||||||
|
ModulesMap,
|
||||||
|
useCases,
|
||||||
|
} from 'container/OnboardingContainer/OnboardingContainer';
|
||||||
import {
|
import {
|
||||||
getDataSources,
|
getDataSources,
|
||||||
getSupportedFrameworks,
|
getSupportedFrameworks,
|
||||||
@ -49,6 +52,9 @@ export default function DataSource(): JSX.Element {
|
|||||||
updateSelectedFramework,
|
updateSelectedFramework,
|
||||||
} = useOnboardingContext();
|
} = useOnboardingContext();
|
||||||
|
|
||||||
|
const isKafkaAPM =
|
||||||
|
getStartedSource === 'kafka' && selectedModule?.id === ModulesMap.APM;
|
||||||
|
|
||||||
const [supportedDataSources, setSupportedDataSources] = useState<
|
const [supportedDataSources, setSupportedDataSources] = useState<
|
||||||
DataSourceType[]
|
DataSourceType[]
|
||||||
>([]);
|
>([]);
|
||||||
@ -155,14 +161,14 @@ export default function DataSource(): JSX.Element {
|
|||||||
className={cx(
|
className={cx(
|
||||||
'supported-language',
|
'supported-language',
|
||||||
selectedDataSource?.name === dataSource.name ? 'selected' : '',
|
selectedDataSource?.name === dataSource.name ? 'selected' : '',
|
||||||
getStartedSource === 'kafka' &&
|
isKafkaAPM &&
|
||||||
!messagingQueueKakfaSupportedDataSources.includes(dataSource?.id || '')
|
!messagingQueueKakfaSupportedDataSources.includes(dataSource?.id || '')
|
||||||
? 'disabled'
|
? 'disabled'
|
||||||
: '',
|
: '',
|
||||||
)}
|
)}
|
||||||
key={dataSource.name}
|
key={dataSource.name}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
if (getStartedSource !== 'kafka') {
|
if (!isKafkaAPM) {
|
||||||
updateSelectedFramework(null);
|
updateSelectedFramework(null);
|
||||||
updateSelectedEnvironment(null);
|
updateSelectedEnvironment(null);
|
||||||
updateSelectedDataSource(dataSource);
|
updateSelectedDataSource(dataSource);
|
||||||
|
@ -252,7 +252,7 @@ import APM_java_springBoot_docker_recommendedSteps_runApplication from '../Modul
|
|||||||
import APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-installOtelCollector.md';
|
import APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-installOtelCollector.md';
|
||||||
import APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-instrumentApplication.md';
|
import APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-instrumentApplication.md';
|
||||||
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication.md';
|
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication.md';
|
||||||
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producer from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md';
|
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producers from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producers.md';
|
||||||
// SpringBoot-LinuxAMD64-quickstart
|
// SpringBoot-LinuxAMD64-quickstart
|
||||||
import APM_java_springBoot_linuxAMD64_quickStart_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-instrumentApplication.md';
|
import APM_java_springBoot_linuxAMD64_quickStart_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-instrumentApplication.md';
|
||||||
import APM_java_springBoot_linuxAMD64_quickStart_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-runApplication.md';
|
import APM_java_springBoot_linuxAMD64_quickStart_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-runApplication.md';
|
||||||
@ -1054,7 +1054,7 @@ export const ApmDocFilePaths = {
|
|||||||
APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector,
|
APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector,
|
||||||
APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication,
|
APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication,
|
||||||
APM_java_springBoot_kubernetes_recommendedSteps_runApplication,
|
APM_java_springBoot_kubernetes_recommendedSteps_runApplication,
|
||||||
APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producer,
|
APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producers,
|
||||||
|
|
||||||
// SpringBoot-LinuxAMD64-recommended
|
// SpringBoot-LinuxAMD64-recommended
|
||||||
APM_java_springBoot_linuxAMD64_recommendedSteps_setupOtelCollector,
|
APM_java_springBoot_linuxAMD64_recommendedSteps_setupOtelCollector,
|
||||||
|
@ -8,15 +8,22 @@ type UseOnboardingStatus = (
|
|||||||
options?: UseQueryOptions<
|
options?: UseQueryOptions<
|
||||||
SuccessResponse<OnboardingStatusResponse> | ErrorResponse
|
SuccessResponse<OnboardingStatusResponse> | ErrorResponse
|
||||||
>,
|
>,
|
||||||
|
endpointService?: string,
|
||||||
|
queryKey?: string,
|
||||||
) => UseQueryResult<SuccessResponse<OnboardingStatusResponse> | ErrorResponse>;
|
) => UseQueryResult<SuccessResponse<OnboardingStatusResponse> | ErrorResponse>;
|
||||||
|
|
||||||
export const useOnboardingStatus: UseOnboardingStatus = (options) =>
|
export const useOnboardingStatus: UseOnboardingStatus = (
|
||||||
|
options,
|
||||||
|
endpointService,
|
||||||
|
queryKey,
|
||||||
|
) =>
|
||||||
useQuery<SuccessResponse<OnboardingStatusResponse> | ErrorResponse>({
|
useQuery<SuccessResponse<OnboardingStatusResponse> | ErrorResponse>({
|
||||||
queryKey: ['onboardingStatus'],
|
queryKey: [queryKey || `onboardingStatus-${endpointService}`],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
getOnboardingStatus({
|
getOnboardingStatus({
|
||||||
start: (Date.now() - 15 * 60 * 1000) * 1_000_000,
|
start: (Date.now() - 15 * 60 * 1000) * 1_000_000,
|
||||||
end: Date.now() * 1_000_000,
|
end: Date.now() * 1_000_000,
|
||||||
|
endpointService,
|
||||||
}),
|
}),
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,206 @@
|
|||||||
|
import './MessagingQueueHealthCheck.styles.scss';
|
||||||
|
|
||||||
|
import { CaretDownOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Spin,
|
||||||
|
Tooltip,
|
||||||
|
Tree,
|
||||||
|
TreeDataNode,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus';
|
||||||
|
import { Bolt, Check, OctagonAlert, X } from 'lucide-react';
|
||||||
|
import { ReactNode, useEffect, useState } from 'react';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
interface AttributeCheckListProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onboardingStatusResponses: {
|
||||||
|
title: string;
|
||||||
|
data: OnboardingStatusResponse['data'];
|
||||||
|
errorMsg?: string;
|
||||||
|
}[];
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AttributesFilters {
|
||||||
|
ALL = 'all',
|
||||||
|
SUCCESS = 'success',
|
||||||
|
ERROR = 'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
function ErrorTitleAndKey({
|
||||||
|
title,
|
||||||
|
errorMsg,
|
||||||
|
isLeaf,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
errorMsg?: string;
|
||||||
|
isLeaf?: boolean;
|
||||||
|
}): TreeDataNode {
|
||||||
|
return {
|
||||||
|
key: `${title}-key-${uuid()}`,
|
||||||
|
title: (
|
||||||
|
<div className="attribute-error-title">
|
||||||
|
<Typography.Text className="tree-text" ellipsis={{ tooltip: title }}>
|
||||||
|
{title}
|
||||||
|
</Typography.Text>
|
||||||
|
<Tooltip title={errorMsg}>
|
||||||
|
<div className="attribute-error-warning">
|
||||||
|
<OctagonAlert size={14} />
|
||||||
|
Fix
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
isLeaf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttributeLabels({ title }: { title: ReactNode }): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="attribute-label">
|
||||||
|
<Bolt size={14} />
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeTitleAndKey({
|
||||||
|
title,
|
||||||
|
isLeaf,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
isLeaf?: boolean;
|
||||||
|
}): TreeDataNode {
|
||||||
|
return {
|
||||||
|
key: `${title}-key-${uuid()}`,
|
||||||
|
title: (
|
||||||
|
<div className="attribute-success-title">
|
||||||
|
<Typography.Text className="tree-text" ellipsis={{ tooltip: title }}>
|
||||||
|
{title}
|
||||||
|
</Typography.Text>
|
||||||
|
{isLeaf && (
|
||||||
|
<div className="success-attribute-icon">
|
||||||
|
<Tooltip title="Success">
|
||||||
|
<Check size={14} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
isLeaf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTreeDataNodes(
|
||||||
|
response: OnboardingStatusResponse['data'],
|
||||||
|
): TreeDataNode[] {
|
||||||
|
return response
|
||||||
|
.map((item) => {
|
||||||
|
if (item.attribute) {
|
||||||
|
if (item.status === '1') {
|
||||||
|
return treeTitleAndKey({ title: item.attribute, isLeaf: true });
|
||||||
|
}
|
||||||
|
if (item.status === '0') {
|
||||||
|
return ErrorTitleAndKey({
|
||||||
|
title: item.attribute,
|
||||||
|
errorMsg: item.error_message || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as TreeDataNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttributeCheckList({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
onboardingStatusResponses,
|
||||||
|
loading,
|
||||||
|
}: AttributeCheckListProps): JSX.Element {
|
||||||
|
const [filter, setFilter] = useState<AttributesFilters>(AttributesFilters.ALL);
|
||||||
|
const [treeData, setTreeData] = useState<TreeDataNode[]>([]);
|
||||||
|
|
||||||
|
const handleFilterChange = (value: AttributesFilters): void => {
|
||||||
|
setFilter(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const filteredData = onboardingStatusResponses.map((response) => {
|
||||||
|
if (response.errorMsg) {
|
||||||
|
return ErrorTitleAndKey({
|
||||||
|
title: response.title,
|
||||||
|
errorMsg: response.errorMsg,
|
||||||
|
isLeaf: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let filteredData = response.data;
|
||||||
|
|
||||||
|
if (filter === AttributesFilters.SUCCESS) {
|
||||||
|
filteredData = response.data.filter((item) => item.status === '1');
|
||||||
|
} else if (filter === AttributesFilters.ERROR) {
|
||||||
|
filteredData = response.data.filter((item) => item.status === '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...treeTitleAndKey({ title: response.title }),
|
||||||
|
children: generateTreeDataNodes(filteredData),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setTreeData(filteredData);
|
||||||
|
}, [filter, onboardingStatusResponses]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Kafka Service Attributes"
|
||||||
|
open={visible}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={false}
|
||||||
|
className="mq-health-check-modal"
|
||||||
|
closeIcon={<X size={14} />}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<div className="loader-container">
|
||||||
|
<Spin indicator={<LoadingOutlined spin />} size="large" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="modal-content">
|
||||||
|
<Select
|
||||||
|
defaultValue={AttributesFilters.ALL}
|
||||||
|
className="attribute-select"
|
||||||
|
onChange={handleFilterChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: AttributesFilters.ALL,
|
||||||
|
label: AttributeLabels({ title: 'Attributes: All' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: AttributesFilters.SUCCESS,
|
||||||
|
label: AttributeLabels({ title: 'Attributes: Success' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: AttributesFilters.ERROR,
|
||||||
|
label: AttributeLabels({ title: 'Attributes: Error' }),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Tree
|
||||||
|
showLine
|
||||||
|
switcherIcon={<CaretDownOutlined />}
|
||||||
|
treeData={treeData}
|
||||||
|
height={450}
|
||||||
|
className="attribute-tree"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AttributeCheckList;
|
@ -0,0 +1,165 @@
|
|||||||
|
.mq-health-check-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
.ant-modal-close {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-header {
|
||||||
|
border-bottom: 1px solid var(--bg-slate-500);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
.ant-modal-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
|
||||||
|
.attribute-select {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
width: 170px;
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
display: flex;
|
||||||
|
height: 28px !important;
|
||||||
|
padding: 8px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-tree {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
width: 328px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tree {
|
||||||
|
.ant-tree-title {
|
||||||
|
.attribute-error-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--bg-amber-400);
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
.tree-text {
|
||||||
|
color: var(--bg-amber-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-error-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-success-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
.success-attribute-icon {
|
||||||
|
width: 44px;
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tree-treenode {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.ant-tree-node-content-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 380px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
height: 156px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
background: var(--bg-slate-500);
|
||||||
|
|
||||||
|
&.missing-config-btn {
|
||||||
|
background: rgba(255, 205, 86, 0.1);
|
||||||
|
color: var(--bg-amber-400);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--bg-amber-300) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-btn-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-right: 1px solid rgba(255, 215, 120, 0.1);
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
import './MessagingQueueHealthCheck.styles.scss';
|
||||||
|
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { useOnboardingStatus } from 'hooks/messagingQueue / onboarding/useOnboardingStatus';
|
||||||
|
import { Bolt, FolderTree } from 'lucide-react';
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { MessagingQueueHealthCheckService } from '../MessagingQueuesUtils';
|
||||||
|
import AttributeCheckList from './AttributeCheckList';
|
||||||
|
|
||||||
|
interface MessagingQueueHealthCheckProps {
|
||||||
|
serviceToInclude: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function MessagingQueueHealthCheck({
|
||||||
|
serviceToInclude,
|
||||||
|
}: MessagingQueueHealthCheckProps): JSX.Element {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [checkListOpen, setCheckListOpen] = useState(false);
|
||||||
|
|
||||||
|
// Consumer Data
|
||||||
|
const {
|
||||||
|
data: consumerData,
|
||||||
|
error: consumerError,
|
||||||
|
isFetching: consumerLoading,
|
||||||
|
} = useOnboardingStatus(
|
||||||
|
{
|
||||||
|
enabled: !!serviceToInclude.filter(
|
||||||
|
(service) => service === MessagingQueueHealthCheckService.Consumers,
|
||||||
|
).length,
|
||||||
|
},
|
||||||
|
MessagingQueueHealthCheckService.Consumers,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Producer Data
|
||||||
|
const {
|
||||||
|
data: producerData,
|
||||||
|
error: producerError,
|
||||||
|
isFetching: producerLoading,
|
||||||
|
} = useOnboardingStatus(
|
||||||
|
{
|
||||||
|
enabled: !!serviceToInclude.filter(
|
||||||
|
(service) => service === MessagingQueueHealthCheckService.Producers,
|
||||||
|
).length,
|
||||||
|
},
|
||||||
|
MessagingQueueHealthCheckService.Producers,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Kafka Data
|
||||||
|
const {
|
||||||
|
data: kafkaData,
|
||||||
|
error: kafkaError,
|
||||||
|
isFetching: kafkaLoading,
|
||||||
|
} = useOnboardingStatus(
|
||||||
|
{
|
||||||
|
enabled: !!serviceToInclude.filter(
|
||||||
|
(service) => service === MessagingQueueHealthCheckService.Kafka,
|
||||||
|
).length,
|
||||||
|
},
|
||||||
|
MessagingQueueHealthCheckService.Kafka,
|
||||||
|
);
|
||||||
|
|
||||||
|
// combined loading and update state
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(consumerLoading || producerLoading || kafkaLoading);
|
||||||
|
}, [consumerLoading, producerLoading, kafkaLoading]);
|
||||||
|
|
||||||
|
const missingConfiguration = useMemo(() => {
|
||||||
|
const consumerMissing =
|
||||||
|
(serviceToInclude.includes(MessagingQueueHealthCheckService.Consumers) &&
|
||||||
|
consumerData?.payload?.data?.filter((item) => item.status === '0')
|
||||||
|
.length) ||
|
||||||
|
0;
|
||||||
|
const producerMissing =
|
||||||
|
(serviceToInclude.includes(MessagingQueueHealthCheckService.Producers) &&
|
||||||
|
producerData?.payload?.data?.filter((item) => item.status === '0')
|
||||||
|
.length) ||
|
||||||
|
0;
|
||||||
|
const kafkaMissing =
|
||||||
|
(serviceToInclude.includes(MessagingQueueHealthCheckService.Kafka) &&
|
||||||
|
kafkaData?.payload?.data?.filter((item) => item.status === '0').length) ||
|
||||||
|
0;
|
||||||
|
|
||||||
|
return consumerMissing + producerMissing + kafkaMissing;
|
||||||
|
}, [consumerData, producerData, kafkaData, serviceToInclude]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={(): void => setCheckListOpen(true)}
|
||||||
|
loading={loading}
|
||||||
|
className={cx(
|
||||||
|
'config-btn',
|
||||||
|
missingConfiguration ? 'missing-config-btn' : '',
|
||||||
|
)}
|
||||||
|
icon={<Bolt size={12} />}
|
||||||
|
>
|
||||||
|
<div className="config-btn-content">
|
||||||
|
{missingConfiguration
|
||||||
|
? `Missing Configuration (${missingConfiguration})`
|
||||||
|
: 'Configuration'}
|
||||||
|
</div>
|
||||||
|
<FolderTree size={14} />
|
||||||
|
</Button>
|
||||||
|
<AttributeCheckList
|
||||||
|
visible={checkListOpen}
|
||||||
|
onClose={(): void => setCheckListOpen(false)}
|
||||||
|
onboardingStatusResponses={[
|
||||||
|
{
|
||||||
|
title: 'Consumers',
|
||||||
|
data: consumerData?.payload?.data || [],
|
||||||
|
errorMsg: (consumerError || consumerData?.error) as string,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Producers',
|
||||||
|
data: producerData?.payload?.data || [],
|
||||||
|
errorMsg: (producerError || producerData?.error) as string,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Kafka',
|
||||||
|
data: kafkaData?.payload?.data || [],
|
||||||
|
errorMsg: (kafkaError || kafkaData?.error) as string,
|
||||||
|
},
|
||||||
|
].filter((item) => serviceToInclude.includes(item.title.toLowerCase()))}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessagingQueueHealthCheck;
|
@ -45,22 +45,28 @@
|
|||||||
|
|
||||||
border-bottom: 1px solid var(--bg-slate-500);
|
border-bottom: 1px solid var(--bg-slate-500);
|
||||||
|
|
||||||
.header-config {
|
.header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.messaging-queue-options {
|
.header-config {
|
||||||
.ant-select-selector {
|
display: flex;
|
||||||
display: flex;
|
gap: 10px;
|
||||||
height: 32px;
|
align-items: center;
|
||||||
padding: 6px 6px 6px 8px;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
border-radius: 2px;
|
.messaging-queue-options {
|
||||||
border: 1px solid var(--bg-slate-400);
|
.ant-select-selector {
|
||||||
background: var(--bg-ink-300);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { isCloudUser } from 'utils/app';
|
import { isCloudUser } from 'utils/app';
|
||||||
|
|
||||||
|
import MessagingQueueHealthCheck from './MessagingQueueHealthCheck/MessagingQueueHealthCheck';
|
||||||
import {
|
import {
|
||||||
KAFKA_SETUP_DOC_LINK,
|
KAFKA_SETUP_DOC_LINK,
|
||||||
|
MessagingQueueHealthCheckService,
|
||||||
MessagingQueuesViewType,
|
MessagingQueuesViewType,
|
||||||
} from './MessagingQueuesUtils';
|
} from './MessagingQueuesUtils';
|
||||||
import { ComingSoon } from './MQCommon/MQCommon';
|
import { ComingSoon } from './MQCommon/MQCommon';
|
||||||
@ -70,7 +72,16 @@ function MessagingQueues(): JSX.Element {
|
|||||||
{t('breadcrumb')}
|
{t('breadcrumb')}
|
||||||
</div>
|
</div>
|
||||||
<div className="messaging-header">
|
<div className="messaging-header">
|
||||||
<div className="header-config">{t('header')}</div>
|
<div className="header-content">
|
||||||
|
<div className="header-config">{t('header')}</div>
|
||||||
|
<MessagingQueueHealthCheck
|
||||||
|
serviceToInclude={[
|
||||||
|
MessagingQueueHealthCheckService.Consumers,
|
||||||
|
MessagingQueueHealthCheckService.Producers,
|
||||||
|
MessagingQueueHealthCheckService.Kafka,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
|
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
|
||||||
</div>
|
</div>
|
||||||
<div className="messaging-overview">
|
<div className="messaging-overview">
|
||||||
@ -87,7 +98,7 @@ function MessagingQueues(): JSX.Element {
|
|||||||
type="default"
|
type="default"
|
||||||
onClick={(): void =>
|
onClick={(): void =>
|
||||||
getStartedRedirect(
|
getStartedRedirect(
|
||||||
`${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=consumer`,
|
`${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=${MessagingQueueHealthCheckService.Consumers}`,
|
||||||
'Configure Consumer',
|
'Configure Consumer',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -106,7 +117,7 @@ function MessagingQueues(): JSX.Element {
|
|||||||
type="default"
|
type="default"
|
||||||
onClick={(): void =>
|
onClick={(): void =>
|
||||||
getStartedRedirect(
|
getStartedRedirect(
|
||||||
`${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=producer`,
|
`${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=${MessagingQueueHealthCheckService.Producers}`,
|
||||||
'Configure Producer',
|
'Configure Producer',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -125,7 +136,7 @@ function MessagingQueues(): JSX.Element {
|
|||||||
type="default"
|
type="default"
|
||||||
onClick={(): void =>
|
onClick={(): void =>
|
||||||
getStartedRedirect(
|
getStartedRedirect(
|
||||||
`${ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=kafka`,
|
`${ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=${MessagingQueueHealthCheckService.Kafka}`,
|
||||||
'Monitor kafka',
|
'Monitor kafka',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -381,3 +381,9 @@ export const getAttributeDataFromOnboardingStatus = (
|
|||||||
attributeDataWithError,
|
attributeDataWithError,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum MessagingQueueHealthCheckService {
|
||||||
|
Consumers = 'consumers',
|
||||||
|
Producers = 'producers',
|
||||||
|
Kafka = 'kafka',
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user