diff --git a/frontend/public/locales/en-GB/infraMonitoring.json b/frontend/public/locales/en-GB/infraMonitoring.json index edf1a5f8c8..304053b993 100644 --- a/frontend/public/locales/en-GB/infraMonitoring.json +++ b/frontend/public/locales/en-GB/infraMonitoring.json @@ -2,6 +2,7 @@ "containers_visualization_message": "The ability to visualise containers is in active development and should be available to you soon.", "processes_visualization_message": "The ability to visualise processes is in active development and should be available to you soon.", "working_message": "We're working to extend infrastructure monitoring to take care of a bunch of different cases. Thank you for your patience.", - "waitlist_message": "Join the waitlist for early access or contact support.", + "waitlist_message": "Join the waitlist for early access.", + "waitlist_success_message": "We have received your request for early access. We will get back to you as soon as we launch the feature.", "contact_support": "Contact Support" } diff --git a/frontend/public/locales/en/infraMonitoring.json b/frontend/public/locales/en/infraMonitoring.json index edf1a5f8c8..304053b993 100644 --- a/frontend/public/locales/en/infraMonitoring.json +++ b/frontend/public/locales/en/infraMonitoring.json @@ -2,6 +2,7 @@ "containers_visualization_message": "The ability to visualise containers is in active development and should be available to you soon.", "processes_visualization_message": "The ability to visualise processes is in active development and should be available to you soon.", "working_message": "We're working to extend infrastructure monitoring to take care of a bunch of different cases. Thank you for your patience.", - "waitlist_message": "Join the waitlist for early access or contact support.", + "waitlist_message": "Join the waitlist for early access.", + "waitlist_success_message": "We have received your request for early access. We will get back to you as soon as we launch the feature.", "contact_support": "Contact Support" } diff --git a/frontend/src/api/infraMonitoring/getHostLists.ts b/frontend/src/api/infraMonitoring/getHostLists.ts index ce2ef9b544..870f87f9d6 100644 --- a/frontend/src/api/infraMonitoring/getHostLists.ts +++ b/frontend/src/api/infraMonitoring/getHostLists.ts @@ -48,6 +48,8 @@ export interface HostListResponse { records: HostData[]; groups: null; total: number; + sentAnyHostMetricsData: boolean; + isSendingK8SAgentMetrics: boolean; }; } diff --git a/frontend/src/components/HostMetricsDetail/Containers/Containers.styles.scss b/frontend/src/components/HostMetricsDetail/Containers/Containers.styles.scss index 6542d748ea..925ec30f2a 100644 --- a/frontend/src/components/HostMetricsDetail/Containers/Containers.styles.scss +++ b/frontend/src/components/HostMetricsDetail/Containers/Containers.styles.scss @@ -1,7 +1,24 @@ .host-containers { - max-width: 600px; - margin: 150px auto; - padding: 0 16px; + gap: 24px; + height: 60vh; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + margin: 0 auto; + box-sizing: border-box; + + .infra-container-card-container { + display: flex; + flex-direction: column; + gap: 24px; + } + + .dev-status-container { + display: flex; + flex-direction: column; + gap: 12px; + } .infra-container-card { display: flex; @@ -17,6 +34,7 @@ width: 400px; font-family: 'Inter'; margin-top: 12px; + font-weight: 300; } .infra-container-working-msg { diff --git a/frontend/src/components/HostMetricsDetail/Containers/Containers.tsx b/frontend/src/components/HostMetricsDetail/Containers/Containers.tsx index e838b4aa42..4dc01ff5e8 100644 --- a/frontend/src/components/HostMetricsDetail/Containers/Containers.tsx +++ b/frontend/src/components/HostMetricsDetail/Containers/Containers.tsx @@ -3,6 +3,8 @@ import './Containers.styles.scss'; import { Space, Typography } from 'antd'; import { useTranslation } from 'react-i18next'; +import WaitlistFragment from '../WaitlistFragment/WaitlistFragment'; + const { Text } = Typography; function Containers(): JSX.Element { @@ -10,24 +12,30 @@ function Containers(): JSX.Element { return ( -
- infra-container - - - {t('containers_visualization_message')} - -
+
+
+
+ infra-container + + + {t('containers_visualization_message')} + +
+ +
+ + broom + {t('working_message')} + +
+
-
- - broom - {t('working_message')} - +
); diff --git a/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx b/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx index 7a3ab8c0ab..ee61e687cb 100644 --- a/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx +++ b/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx @@ -11,6 +11,7 @@ import { Typography, } from 'antd'; import { RadioChangeEvent } from 'antd/lib'; +import logEvent from 'api/common/logEvent'; import { QueryParams } from 'constants/query'; import { initialQueryBuilderFormValuesMap, @@ -118,6 +119,13 @@ function HostMetricsDetails({ initialFilters, ); + useEffect(() => { + logEvent('Infra Monitoring: Hosts list details page visited', { + host: host?.hostName, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { setLogFilters(initialFilters); setTracesFilters(initialFilters); @@ -143,6 +151,7 @@ function HostMetricsDetails({ const handleTimeChange = useCallback( (interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => { setSelectedInterval(interval as Time); + if (interval === 'custom' && dateTimeRange) { setModalTimeRange({ startTime: Math.floor(dateTimeRange[0] / 1000), @@ -156,7 +165,13 @@ function HostMetricsDetails({ endTime: Math.floor(maxTime / 1000000000), }); } + + logEvent('Infra Monitoring: Hosts list details time updated', { + host: host?.hostName, + interval, + }); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [], ); @@ -171,6 +186,10 @@ function HostMetricsDetails({ (item) => item.key?.key !== 'id' && item.key?.key !== 'host.name', ); + logEvent('Infra Monitoring: Hosts list details logs filters applied', { + host: host?.hostName, + }); + return { op: 'AND', items: [ @@ -181,6 +200,7 @@ function HostMetricsDetails({ }; }); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [], ); @@ -190,6 +210,11 @@ function HostMetricsDetails({ const hostNameFilter = prevFilters.items.find( (item) => item.key?.key === 'host.name', ); + + logEvent('Infra Monitoring: Hosts list details traces filters applied', { + host: host?.hostName, + }); + return { op: 'AND', items: [ @@ -199,6 +224,7 @@ function HostMetricsDetails({ }; }); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [], ); @@ -211,6 +237,11 @@ function HostMetricsDetails({ urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString()); } + logEvent('Infra Monitoring: Hosts list details explore clicked', { + host: host?.hostName, + view: selectedView, + }); + if (selectedView === VIEW_TYPES.LOGS) { const filtersWithoutPagination = { ...logFilters, diff --git a/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss b/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss index e2f182d04d..b1e4fda451 100644 --- a/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss +++ b/frontend/src/components/HostMetricsDetail/Processes/Processes.styles.scss @@ -1,7 +1,24 @@ .host-processes { - max-width: 600px; - margin: 150px auto; - padding: 0 16px; + gap: 24px; + height: 60vh; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + margin: 0 auto; + box-sizing: border-box; + + .infra-container-card-container { + display: flex; + flex-direction: column; + gap: 24px; + } + + .dev-status-container { + display: flex; + flex-direction: column; + gap: 12px; + } .infra-container-card { display: flex; @@ -17,6 +34,7 @@ width: 400px; font-family: 'Inter'; margin-top: 12px; + font-weight: 300; } .infra-container-working-msg { diff --git a/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx b/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx index cd5baf14ca..19da46a0e2 100644 --- a/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx +++ b/frontend/src/components/HostMetricsDetail/Processes/Processes.tsx @@ -3,6 +3,8 @@ import './Processes.styles.scss'; import { Space, Typography } from 'antd'; import { useTranslation } from 'react-i18next'; +import WaitlistFragment from '../WaitlistFragment/WaitlistFragment'; + const { Text } = Typography; function Processes(): JSX.Element { @@ -10,23 +12,29 @@ function Processes(): JSX.Element { return ( -
- infra-container - - {t('processes_visualization_message')} - -
+
+
+
+ infra-container + + {t('processes_visualization_message')} + +
+ +
+ + broom + {t('working_message')} + +
+
-
- - broom - {t('working_message')} - +
); diff --git a/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitListFragment.styles.scss b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitListFragment.styles.scss new file mode 100644 index 0000000000..3cad900e6b --- /dev/null +++ b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitListFragment.styles.scss @@ -0,0 +1,15 @@ +.wait-list-container { + display: flex; + flex-direction: column; + gap: 8px; + + .wait-list-text { + font-weight: 300; + } + + .join-waitlist-btn { + width: 160px; + border-radius: 2px; + background: var(--slate-500); + } +} diff --git a/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitlistFragment.tsx b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitlistFragment.tsx new file mode 100644 index 0000000000..08c15db2b7 --- /dev/null +++ b/frontend/src/components/HostMetricsDetail/WaitlistFragment/WaitlistFragment.tsx @@ -0,0 +1,75 @@ +import './WaitListFragment.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { Button, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; +import { useNotifications } from 'hooks/useNotifications'; +import { CheckCircle2, HandPlatter } from 'lucide-react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +export default function WaitlistFragment({ + entityType, +}: { + entityType: string; +}): JSX.Element { + const { user } = useSelector((state) => state.app); + const { t } = useTranslation(['infraMonitoring']); + const { notifications } = useNotifications(); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + + const handleJoinWaitlist = (): void => { + if (!user || !user.email) return; + + setIsSubmitting(true); + + logEvent('Infra Monitoring: Get Early Access Clicked', { + entity_type: entityType, + userEmail: user.email, + }) + .then(() => { + notifications.success({ + message: t('waitlist_success_message'), + }); + + setIsSubmitting(false); + setIsSuccess(true); + + setTimeout(() => { + setIsSuccess(false); + }, 4000); + }) + .catch((error) => { + console.error('Error logging event:', error); + }); + }; + + return ( +
+ + {t('waitlist_message')} + + + +
+ ); +} diff --git a/frontend/src/container/InfraMonitoringHosts/HostsEmptyOrIncorrectMetrics.tsx b/frontend/src/container/InfraMonitoringHosts/HostsEmptyOrIncorrectMetrics.tsx new file mode 100644 index 0000000000..64a0550126 --- /dev/null +++ b/frontend/src/container/InfraMonitoringHosts/HostsEmptyOrIncorrectMetrics.tsx @@ -0,0 +1,52 @@ +import { Typography } from 'antd'; + +export default function HostsEmptyOrIncorrectMetrics({ + noData, + incorrectData, +}: { + noData: boolean; + incorrectData: boolean; +}): JSX.Element { + return ( +
+
+ eyes emoji + + {noData && ( +
+ + No host metrics data received yet. + + + + Infrastructure monitoring requires the{' '} + + OpenTelemetry system metrics + + . Please refer to{' '} + + this + {' '} + to learn how to send host metrics to SigNoz. + +
+ )} + + {incorrectData && ( + + To see host metrics, upgrade to the latest version of SigNoz k8s-infra + chart. Please contact support if you need help. + + )} +
+
+ ); +} diff --git a/frontend/src/container/InfraMonitoringHosts/HostsList.tsx b/frontend/src/container/InfraMonitoringHosts/HostsList.tsx index e7ae5912b2..2dc9b26662 100644 --- a/frontend/src/container/InfraMonitoringHosts/HostsList.tsx +++ b/frontend/src/container/InfraMonitoringHosts/HostsList.tsx @@ -2,6 +2,7 @@ import './InfraMonitoring.styles.scss'; import { LoadingOutlined } from '@ant-design/icons'; import { + Skeleton, Spin, Table, TablePaginationConfig, @@ -9,17 +10,17 @@ import { Typography, } from 'antd'; import { SorterResult } from 'antd/es/table/interface'; +import logEvent from 'api/common/logEvent'; import { HostListPayload } from 'api/infraMonitoring/getHostLists'; import HostMetricDetail from 'components/HostMetricsDetail'; -import NoLogs from 'container/NoLogs/NoLogs'; import { useGetHostList } from 'hooks/infraMonitoring/useGetHostList'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource } from 'types/common/queryBuilder'; import { GlobalReducer } from 'types/reducer/globalTime'; +import HostsEmptyOrIncorrectMetrics from './HostsEmptyOrIncorrectMetrics'; import HostsListControls from './HostsListControls'; import { formatDataForTable, @@ -28,6 +29,7 @@ import { HostRowData, } from './utils'; +// eslint-disable-next-line sonarjs/cognitive-complexity function HostsList(): JSX.Element { const { maxTime, minTime } = useSelector( (state) => state.globalTime, @@ -69,6 +71,16 @@ function HostsList(): JSX.Element { }, ); + const sentAnyHostMetricsData = useMemo( + () => data?.payload?.data?.sentAnyHostMetricsData || false, + [data], + ); + + const isSendingIncorrectK8SAgentMetrics = useMemo( + () => data?.payload?.data?.isSendingK8SAgentMetrics || false, + [data], + ); + const hostMetricsData = useMemo(() => data?.payload?.data?.records || [], [ data, ]); @@ -81,9 +93,6 @@ function HostsList(): JSX.Element { const columns = useMemo(() => getHostsListColumns(), []); - const isDataPresent = - !isLoading && !isFetching && !isError && hostMetricsData.length === 0; - const handleTableChange: TableProps['onChange'] = useCallback( ( pagination: TablePaginationConfig, @@ -112,11 +121,19 @@ function HostsList(): JSX.Element { if (isNewFilterAdded) { setFilters(value); setCurrentPage(1); + + logEvent('Infra Monitoring: Hosts list filters applied', { + filters: value, + }); } }, [filters], ); + useEffect(() => { + logEvent('Infra Monitoring: Hosts list page visited', {}); + }, []); + const selectedHostData = useMemo(() => { if (!selectedHostName) return null; return ( @@ -126,29 +143,85 @@ function HostsList(): JSX.Element { const handleRowClick = (record: HostRowData): void => { setSelectedHostName(record.hostName); + + logEvent('Infra Monitoring: Hosts list item clicked', { + host: record.hostName, + }); }; const handleCloseHostDetail = (): void => { setSelectedHostName(null); }; + const showHostsTable = + !isError && + sentAnyHostMetricsData && + !isSendingIncorrectK8SAgentMetrics && + !(formattedHostMetricsData.length === 0 && filters.items.length > 0); + + const showNoFilteredHostsMessage = + !isFetching && + !isLoading && + formattedHostMetricsData.length === 0 && + filters.items.length > 0; + + const showHostsEmptyState = + !isFetching && + !isLoading && + (!sentAnyHostMetricsData || isSendingIncorrectK8SAgentMetrics); + return (
{isError && {data?.error || 'Something went wrong'}} - {isDataPresent && filters.items.length === 0 && ( - + {showHostsEmptyState && ( + + )} + + {showNoFilteredHostsMessage && ( +
+
+ thinking-emoji + + + This query had no results. Edit your query and try again! + +
+
)} - {!isFetching && - !isLoading && - formattedHostMetricsData.length === 0 && - filters.items.length > 0 && ( -
No hosts match the applied filters.
- )} + {(isFetching || isLoading) && ( +
+ + + +
+ )} - {!isError && ( + {showHostsTable && ( tr > th { diff --git a/frontend/src/container/SideNav/NavItem/NavItem.tsx b/frontend/src/container/SideNav/NavItem/NavItem.tsx index 97a8cdb0ed..012708cc30 100644 --- a/frontend/src/container/SideNav/NavItem/NavItem.tsx +++ b/frontend/src/container/SideNav/NavItem/NavItem.tsx @@ -16,7 +16,7 @@ export default function NavItem({ isActive: boolean; onClick: (event: React.MouseEvent) => void; }): JSX.Element { - const { label, icon, isBeta } = item; + const { label, icon, isBeta, isNew } = item; return (
)} + + {isNew && ( +
+ + New + +
+ )} ); diff --git a/frontend/src/container/SideNav/SideNav.styles.scss b/frontend/src/container/SideNav/SideNav.styles.scss index 1a148e2469..21263ed063 100644 --- a/frontend/src/container/SideNav/SideNav.styles.scss +++ b/frontend/src/container/SideNav/SideNav.styles.scss @@ -184,10 +184,16 @@ display: none; } - .nav-item-beta { + .nav-item-beta, + .nav-item-new { display: none; } + .sidenav-new-tag { + background-color: rgba(37, 225, 146, 0.1); + color: var(--text-forest-500); + } + &:hover { flex: 0 0 240px; max-width: 240px; @@ -221,7 +227,8 @@ display: block; } - .nav-item-beta { + .nav-item-beta, + .nav-item-new { display: block; } } diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index 8b3706e563..0c7230cb50 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -3,6 +3,7 @@ import ROUTES from 'constants/routes'; import { BarChart2, BellDot, + Boxes, BugIcon, Cloudy, DraftingCompass, @@ -11,7 +12,6 @@ import { LayoutGrid, ListMinus, MessageSquare, - PackagePlus, Receipt, Route, ScrollText, @@ -82,6 +82,12 @@ const menuItems: SidebarItem[] = [ label: 'Logs', icon: , }, + { + key: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS, + label: 'Infra Monitoring', + icon: , + isNew: true, + }, { key: ROUTES.ALL_DASHBOARD, label: 'Dashboards', @@ -91,7 +97,6 @@ const menuItems: SidebarItem[] = [ key: ROUTES.MESSAGING_QUEUES, label: 'Messaging Queues', icon: , - isBeta: true, }, { key: ROUTES.LIST_ALL_ALERT, @@ -119,12 +124,6 @@ const menuItems: SidebarItem[] = [ label: 'Billing', icon: , }, - { - key: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS, - label: 'Infra Monitoring', - icon: , - isBeta: true, - }, { key: ROUTES.SETTINGS, label: 'Settings', diff --git a/frontend/src/container/SideNav/sideNav.types.ts b/frontend/src/container/SideNav/sideNav.types.ts index ab9e991702..af2877171f 100644 --- a/frontend/src/container/SideNav/sideNav.types.ts +++ b/frontend/src/container/SideNav/sideNav.types.ts @@ -13,6 +13,7 @@ export interface SidebarItem { key: string | number; label?: ReactNode; isBeta?: boolean; + isNew?: boolean; } export enum SecondaryMenuItemKey {