diff --git a/README.md b/README.md index f060cc60e1..324b8e2d69 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,8 @@ Moreover, SigNoz has few more advanced features wrt Jaeger: - SigNoz Logs management are based on ClickHouse, a columnar OLAP datastore which makes aggregate log analytics queries much more efficient - 50% lower resource requirement compared to Elastic during ingestion +We have published benchmarks comparing Elastic with SigNoz. Check it out [here](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark) +

 

### SigNoz vs Loki @@ -152,6 +154,8 @@ Moreover, SigNoz has few more advanced features wrt Jaeger: - SigNoz supports indexes over high cardinality data and has no limitations on the number of indexes, while Loki reaches max streams with a few indexes added to it. - Searching over a huge volume of data is difficult and slow in Loki compared to SigNoz +We have published benchmarks comparing Loki with SigNoz. Check it out [here](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark) +

diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 1ad46cf057..6792b3acce 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -137,7 +137,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.16.0 + image: signoz/query-service:0.16.1 command: ["-config=/root/config/prometheus.yml"] # ports: # - "6060:6060" # pprof port @@ -166,7 +166,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:0.16.0 + image: signoz/frontend:0.16.1 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index e507dd1645..586b77c1f6 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -153,7 +153,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.16.0} + image: signoz/query-service:${DOCKER_TAG:-0.16.1} container_name: query-service command: ["-config=/root/config/prometheus.yml"] # ports: @@ -181,7 +181,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.16.0} + image: signoz/frontend:${DOCKER_TAG:-0.16.1} container_name: frontend restart: on-failure depends_on: diff --git a/deploy/install.sh b/deploy/install.sh index ef4e75171a..4eeee3ee67 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -228,7 +228,7 @@ wait_for_containers_start() { # The while loop is important because for-loops don't work for dynamic values while [[ $timeout -gt 0 ]]; do - status_code="$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3301/api/v1/services/list || true)" + status_code="$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:3301/api/v1/health?live=1" || true)" if [[ status_code -eq 200 ]]; then break else diff --git a/frontend/package.json b/frontend/package.json index b0aec96f2b..0486067372 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -174,6 +174,7 @@ "lint-staged": "^12.3.7", "portfinder-sync": "^0.0.2", "prettier": "2.2.1", + "react-hooks-testing-library": "0.6.0", "react-hot-loader": "^4.13.0", "react-resizable": "3.0.4", "ts-jest": "^27.1.4", diff --git a/frontend/src/AppRoutes/Private.tsx b/frontend/src/AppRoutes/Private.tsx index 754b6cbf40..82d6be2620 100644 --- a/frontend/src/AppRoutes/Private.tsx +++ b/frontend/src/AppRoutes/Private.tsx @@ -1,11 +1,11 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { notification } from 'antd'; import getLocalStorageApi from 'api/browser/localstorage/get'; import loginApi from 'api/user/login'; import { Logout } from 'api/utils'; import Spinner from 'components/Spinner'; import { LOCALSTORAGE } from 'constants/localStorage'; import ROUTES from 'constants/routes'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -47,7 +47,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { const dispatch = useDispatch>(); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const currentRoute = mapRoutes.get('current'); @@ -157,12 +157,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { // NOTE: disabling this rule as there is no need to have div // eslint-disable-next-line react/jsx-no-useless-fragment - return ( - <> - {NotificationElement} - {children} - - ); + return <>{children}; } interface PrivateRouteProps { diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index 33c8980155..a5641c35da 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -3,6 +3,7 @@ import NotFound from 'components/NotFound'; import Spinner from 'components/Spinner'; import AppLayout from 'container/AppLayout'; import { useThemeConfig } from 'hooks/useDarkMode'; +import { NotificationProvider } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { Suspense } from 'react'; import { Route, Router, Switch } from 'react-router-dom'; @@ -15,26 +16,28 @@ function App(): JSX.Element { return ( - - - - }> - - {routes.map(({ path, component, exact }) => ( - - ))} + + + + + }> + + {routes.map(({ path, component, exact }) => ( + + ))} - - - - - - + + + + + + + ); } diff --git a/frontend/src/api/trace/getSpans.ts b/frontend/src/api/trace/getSpans.ts index 55fa677e38..06555d20fe 100644 --- a/frontend/src/api/trace/getSpans.ts +++ b/frontend/src/api/trace/getSpans.ts @@ -10,7 +10,7 @@ const getSpans = async ( ): Promise | ErrorResponse> => { try { const updatedSelectedTags = props.selectedTags.map((e) => ({ - Key: e.Key[0], + Key: e.Key, Operator: e.Operator, StringValues: e.StringValues, NumberValues: e.NumberValues, diff --git a/frontend/src/api/trace/getSpansAggregate.ts b/frontend/src/api/trace/getSpansAggregate.ts index d0cc04c2b4..373e677ab1 100644 --- a/frontend/src/api/trace/getSpansAggregate.ts +++ b/frontend/src/api/trace/getSpansAggregate.ts @@ -28,7 +28,7 @@ const getSpanAggregate = async ( }); const updatedSelectedTags = props.selectedTags.map((e) => ({ - Key: e.Key[0], + Key: e.Key, Operator: e.Operator, StringValues: e.StringValues, NumberValues: e.NumberValues, diff --git a/frontend/src/components/Logs/CopyClipboardHOC.tsx b/frontend/src/components/Logs/CopyClipboardHOC.tsx index f7aa461d88..8296b8a05c 100644 --- a/frontend/src/components/Logs/CopyClipboardHOC.tsx +++ b/frontend/src/components/Logs/CopyClipboardHOC.tsx @@ -1,4 +1,5 @@ -import { notification, Popover } from 'antd'; +import { Popover } from 'antd'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useCallback, useEffect } from 'react'; import { useCopyToClipboard } from 'react-use'; @@ -7,7 +8,7 @@ function CopyClipboardHOC({ children, }: CopyClipboardHOCProps): JSX.Element { const [value, setCopy] = useCopyToClipboard(); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (value.value) { notifications.success({ @@ -22,7 +23,6 @@ function CopyClipboardHOC({ return ( - {NotificationElement} Copy to clipboard} diff --git a/frontend/src/components/Logs/LogItem/index.tsx b/frontend/src/components/Logs/LogItem/index.tsx index bc061651e7..e09f6c4ba7 100644 --- a/frontend/src/components/Logs/LogItem/index.tsx +++ b/frontend/src/components/Logs/LogItem/index.tsx @@ -1,8 +1,9 @@ import { blue, grey, orange } from '@ant-design/colors'; import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons'; -import { Button, Divider, notification, Row, Typography } from 'antd'; +import { Button, Divider, Row, Typography } from 'antd'; import { map } from 'd3'; import dayjs from 'dayjs'; +import { useNotifications } from 'hooks/useNotifications'; import { FlatLogData } from 'lib/logs/flatLogData'; import React, { useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -79,7 +80,7 @@ function LogItem({ logData }: LogItemProps): JSX.Element { const dispatch = useDispatch(); const flattenLogData = useMemo(() => FlatLogData(logData), [logData]); const [, setCopy] = useCopyToClipboard(); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const handleDetailedView = useCallback(() => { dispatch({ @@ -97,7 +98,6 @@ function LogItem({ logData }: LogItemProps): JSX.Element { return ( - {NotificationElement}
{'{'} diff --git a/frontend/src/container/AllAlertChannels/AlertChannels.tsx b/frontend/src/container/AllAlertChannels/AlertChannels.tsx index 03a9e8a40e..72d5a54b85 100644 --- a/frontend/src/container/AllAlertChannels/AlertChannels.tsx +++ b/frontend/src/container/AllAlertChannels/AlertChannels.tsx @@ -1,9 +1,10 @@ /* eslint-disable react/display-name */ -import { Button, notification } from 'antd'; +import { Button } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { ResizeTable } from 'components/ResizeTable'; import ROUTES from 'constants/routes'; import useComponentPermission from 'hooks/useComponentPermission'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -17,7 +18,7 @@ import Delete from './Delete'; function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element { const { t } = useTranslation(['channels']); - const [notifications, Element] = notification.useNotification(); + const { notifications } = useNotifications(); const [channels, setChannels] = useState(allChannels); const { role } = useSelector((state) => state.app); const [action] = useComponentPermission(['new_alert_action'], role); @@ -63,12 +64,7 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element { }); } - return ( - <> - {Element} - - - ); + return ; } interface AlertChannelsProps { diff --git a/frontend/src/container/AllError/index.tsx b/frontend/src/container/AllError/index.tsx index 4ab3e7c100..64d83e70ec 100644 --- a/frontend/src/container/AllError/index.tsx +++ b/frontend/src/container/AllError/index.tsx @@ -3,7 +3,6 @@ import { Button, Card, Input, - notification, Space, TableProps, Tooltip, @@ -18,6 +17,7 @@ import getErrorCounts from 'api/errors/getErrorCounts'; import { ResizeTable } from 'components/ResizeTable'; import ROUTES from 'constants/routes'; import dayjs from 'dayjs'; +import { useNotifications } from 'hooks/useNotifications'; import useUrlQuery from 'hooks/useUrlQuery'; import createQueryParams from 'lib/createQueryParams'; import history from 'lib/history'; @@ -127,7 +127,7 @@ function AllErrors(): JSX.Element { enabled: !loading, }, ]); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (data?.error) { @@ -386,24 +386,21 @@ function AllErrors(): JSX.Element { ); return ( - <> - {NotificationElement} - - + ); } diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 189aac2e33..640a7340a5 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,4 +1,3 @@ -import { notification } from 'antd'; import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs'; import getFeaturesFlags from 'api/features/getFeatureFlags'; import getUserLatestVersion from 'api/user/getLatestVersion'; @@ -6,6 +5,7 @@ import getUserVersion from 'api/user/getVersion'; import Header from 'container/Header'; import SideNav from 'container/SideNav'; import TopNav from 'container/TopNav'; +import { useNotifications } from 'hooks/useNotifications'; import React, { ReactNode, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useQueries } from 'react-query'; @@ -91,7 +91,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element { const latestVersionCounter = useRef(0); const latestConfigCounter = useRef(0); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if ( @@ -228,7 +228,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element { return ( - {NotificationElement} {isToDisplayLayout &&
} {isToDisplayLayout && } diff --git a/frontend/src/container/CreateAlertChannels/index.tsx b/frontend/src/container/CreateAlertChannels/index.tsx index 7600f00de3..a5db46da07 100644 --- a/frontend/src/container/CreateAlertChannels/index.tsx +++ b/frontend/src/container/CreateAlertChannels/index.tsx @@ -1,4 +1,4 @@ -import { Form, notification } from 'antd'; +import { Form } from 'antd'; import createPagerApi from 'api/channels/createPager'; import createSlackApi from 'api/channels/createSlack'; import createWebhookApi from 'api/channels/createWebhook'; @@ -7,6 +7,7 @@ import testSlackApi from 'api/channels/testSlack'; import testWebhookApi from 'api/channels/testWebhook'; import ROUTES from 'constants/routes'; import FormAlertChannels from 'container/FormAlertChannels'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -58,7 +59,7 @@ function CreateAlertChannels({ }); const [savingState, setSavingState] = useState(false); const [testingState, setTestingState] = useState(false); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const [type, setType] = useState(preType); const onTypeChangeHandler = useCallback( @@ -336,7 +337,6 @@ function CreateAlertChannels({ onSaveHandler, savingState, testingState, - NotificationElement, title: t('page_title_create'), initialValue: { type, diff --git a/frontend/src/container/EditAlertChannels/index.tsx b/frontend/src/container/EditAlertChannels/index.tsx index 0ced770e24..5711635167 100644 --- a/frontend/src/container/EditAlertChannels/index.tsx +++ b/frontend/src/container/EditAlertChannels/index.tsx @@ -1,4 +1,4 @@ -import { Form, notification } from 'antd'; +import { Form } from 'antd'; import editPagerApi from 'api/channels/editPager'; import editSlackApi from 'api/channels/editSlack'; import editWebhookApi from 'api/channels/editWebhook'; @@ -17,6 +17,7 @@ import { WebhookType, } from 'container/CreateAlertChannels/config'; import FormAlertChannels from 'container/FormAlertChannels'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -36,7 +37,7 @@ function EditAlertChannels({ }); const [savingState, setSavingState] = useState(false); const [testingState, setTestingState] = useState(false); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const { id } = useParams<{ id: string }>(); const [type, setType] = useState( @@ -281,7 +282,6 @@ function EditAlertChannels({ onSaveHandler, testingState, savingState, - NotificationElement, title: t('page_title_edit'), initialValue, editing: true, diff --git a/frontend/src/container/ErrorDetails/index.tsx b/frontend/src/container/ErrorDetails/index.tsx index 57a8c92906..999e8013d9 100644 --- a/frontend/src/container/ErrorDetails/index.tsx +++ b/frontend/src/container/ErrorDetails/index.tsx @@ -1,9 +1,10 @@ -import { Button, Divider, notification, Space, Typography } from 'antd'; +import { Button, Divider, Space, Typography } from 'antd'; import getNextPrevId from 'api/errors/getNextPrevId'; import Editor from 'components/Editor'; import { ResizeTable } from 'components/ResizeTable'; import { getNanoSeconds } from 'container/AllError/utils'; import dayjs from 'dayjs'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import { urlKey } from 'pages/ErrorDetails/utils'; import React, { useMemo, useState } from 'react'; @@ -80,7 +81,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element { [], ); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onClickErrorIdHandler = async ( id: string, @@ -121,7 +122,6 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element { return ( <> - {NotificationElement} {errorDetail.exceptionType} {errorDetail.exceptionMessage} diff --git a/frontend/src/container/FormAlertChannels/index.tsx b/frontend/src/container/FormAlertChannels/index.tsx index 41be8382ac..91de745b3b 100644 --- a/frontend/src/container/FormAlertChannels/index.tsx +++ b/frontend/src/container/FormAlertChannels/index.tsx @@ -31,7 +31,6 @@ function FormAlertChannels({ onSaveHandler, savingState, testingState, - NotificationElement, title, initialValue, editing = false, @@ -53,8 +52,6 @@ function FormAlertChannels({ }; return ( <> - {NotificationElement} - {title}
@@ -126,10 +123,6 @@ interface FormAlertChannelsProps { onTestHandler: (props: ChannelType) => void; testingState: boolean; savingState: boolean; - NotificationElement: React.ReactElement< - unknown, - string | React.JSXElementConstructor - >; title: string; initialValue: Store; // editing indicates if the form is opened in edit mode diff --git a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx index 84ce85b786..662a14c1e6 100644 --- a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx +++ b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx @@ -1,6 +1,7 @@ -import { notification, Select } from 'antd'; +import { Select } from 'antd'; import getChannels from 'api/channels/getAll'; import useFetch from 'hooks/useFetch'; +import { useNotifications } from 'hooks/useNotifications'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,7 +21,7 @@ function ChannelSelect({ const { loading, payload, error, errorMessage } = useFetch(getChannels); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const handleChange = (value: string[]): void => { onSelectChannels(value); @@ -50,22 +51,19 @@ function ChannelSelect({ return children; }; return ( - <> - {NotificationElement} - { - handleChange(value as string[]); - }} - optionLabelProp="label" - > - {renderOptions()} - - + { + handleChange(value as string[]); + }} + optionLabelProp="label" + > + {renderOptions()} + ); } diff --git a/frontend/src/container/FormAlertRules/QuerySection.tsx b/frontend/src/container/FormAlertRules/QuerySection.tsx index cbb1db83e8..f02299dbc8 100644 --- a/frontend/src/container/FormAlertRules/QuerySection.tsx +++ b/frontend/src/container/FormAlertRules/QuerySection.tsx @@ -1,11 +1,12 @@ import { PlusOutlined } from '@ant-design/icons'; -import { Button, notification, Tabs } from 'antd'; +import { Button, Tabs } from 'antd'; import MetricsBuilderFormula from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula'; import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query'; import { IQueryBuilderFormulaHandleChange, IQueryBuilderQueryHandleChange, } from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertTypes } from 'types/api/alerts/alertTypes'; @@ -163,7 +164,7 @@ function QuerySection({ ...allQueries, }); }; - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const addMetricQuery = useCallback(() => { if (Object.keys(metricQueries).length > 5) { @@ -351,7 +352,6 @@ function QuerySection({ }; return ( <> - {NotificationElement} {t('alert_form_step1')}
{renderTabs(alertType)}
diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index efd2179f94..9a3ea93466 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -1,10 +1,11 @@ import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons'; -import { Col, FormInstance, Modal, notification, Typography } from 'antd'; +import { Col, FormInstance, Modal, Typography } from 'antd'; import saveAlertApi from 'api/alerts/save'; import testAlertApi from 'api/alerts/testAlert'; import ROUTES from 'constants/routes'; import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag'; import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -190,7 +191,7 @@ function FormAlertRules({ }); } }; - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const validatePromParams = useCallback((): boolean => { let retval = true; @@ -483,7 +484,6 @@ function FormAlertRules({ ); return ( <> - {NotificationElement} {Element} diff --git a/frontend/src/container/GeneralSettings/GeneralSettings.tsx b/frontend/src/container/GeneralSettings/GeneralSettings.tsx index 85a4d30ae9..7530364d65 100644 --- a/frontend/src/container/GeneralSettings/GeneralSettings.tsx +++ b/frontend/src/container/GeneralSettings/GeneralSettings.tsx @@ -1,19 +1,10 @@ /* eslint-disable sonarjs/no-duplicate-string */ import { LoadingOutlined } from '@ant-design/icons'; -import { - Button, - Card, - Col, - Divider, - Modal, - notification, - Row, - Spin, - Typography, -} from 'antd'; +import { Button, Card, Col, Divider, Modal, Row, Spin, Typography } from 'antd'; import setRetentionApi from 'api/settings/setRetention'; import TextToolTip from 'components/TextToolTip'; import useComponentPermission from 'hooks/useComponentPermission'; +import { useNotifications } from 'hooks/useNotifications'; import find from 'lodash-es/find'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -172,7 +163,7 @@ function GeneralSettings({ logsTtlValuesPayload.status === 'pending' ? 1000 : null, ); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onModalToggleHandler = (type: TTTLType): void => { if (type === 'metrics') setModalMetrics((modal) => !modal); @@ -593,7 +584,6 @@ function GeneralSettings({ return ( <> - {NotificationElement} {Element} diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx index 158fb927de..8b85557bfe 100644 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ b/frontend/src/container/GridGraphLayout/index.tsx @@ -1,7 +1,8 @@ /* eslint-disable react/no-unstable-nested-components */ -import { notification } from 'antd'; + import updateDashboardApi from 'api/dashboard/update'; import useComponentPermission from 'hooks/useComponentPermission'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useCallback, useEffect, useState } from 'react'; import { Layout } from 'react-grid-layout'; import { useTranslation } from 'react-i18next'; @@ -204,7 +205,7 @@ function GridGraph(props: Props): JSX.Element { [widgets, onDragSelect], ); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onEmptyWidgetHandler = useCallback(async () => { try { @@ -276,21 +277,18 @@ function GridGraph(props: Props): JSX.Element { }, [layouts, onEmptyWidgetHandler, t, toggleAddWidget, notifications]); return ( - <> - {NotificationElement} - - + ); } diff --git a/frontend/src/container/Licenses/ApplyLicenseForm.tsx b/frontend/src/container/Licenses/ApplyLicenseForm.tsx index 065622e77c..b729a310a7 100644 --- a/frontend/src/container/Licenses/ApplyLicenseForm.tsx +++ b/frontend/src/container/Licenses/ApplyLicenseForm.tsx @@ -1,6 +1,7 @@ -import { Button, Form, Input, notification } from 'antd'; +import { Button, Form, Input } from 'antd'; import getFeaturesFlags from 'api/features/getFeatureFlags'; import apply from 'api/licenses/apply'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { QueryObserverResult, RefetchOptions, useQuery } from 'react-query'; @@ -27,7 +28,7 @@ function ApplyLicenseForm({ enabled: false, }); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onFinish = async (values: unknown | { key: string }): Promise => { const params = values as { key: string }; @@ -77,7 +78,6 @@ function ApplyLicenseForm({ return ( - {NotificationElement} { (async (): Promise => { @@ -51,8 +52,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { history.push(ROUTES.ALERTS_NEW); }, []); - const [notifications, Element] = notification.useNotification(); - const onEditHandler = (id: string): void => { history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`); }; @@ -144,7 +143,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { Edit - + ), }); @@ -152,9 +151,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { return ( <> - {NotificationElement} - {Element} - - {NotificationElement} - => onToggleHandler(id, !disabled)} - type="link" - > - {disabled ? 'Enable' : 'Disable'} - - + => onToggleHandler(id, !disabled)} + type="link" + > + {disabled ? 'Enable' : 'Disable'} + ); } diff --git a/frontend/src/container/ListAlertRules/index.tsx b/frontend/src/container/ListAlertRules/index.tsx index e985a270d2..078769141e 100644 --- a/frontend/src/container/ListAlertRules/index.tsx +++ b/frontend/src/container/ListAlertRules/index.tsx @@ -1,7 +1,8 @@ -import { notification, Space } from 'antd'; +import { Space } from 'antd'; import getAll from 'api/alerts/getAll'; import ReleaseNote from 'components/ReleaseNote'; import Spinner from 'components/Spinner'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; @@ -17,7 +18,7 @@ function ListAlertRules(): JSX.Element { cacheTime: 0, }); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (status === 'error' || (status === 'success' && data.statusCode >= 400)) { @@ -29,26 +30,18 @@ function ListAlertRules(): JSX.Element { // api failed to load the data if (isError) { - return ( -
- {NotificationElement} - {data?.error || t('something_went_wrong')} -
- ); + return
{data?.error || t('something_went_wrong')}
; } // api is successful but error is present if (status === 'success' && data.statusCode >= 400) { return ( - <> - {NotificationElement} - - + ); } @@ -59,7 +52,6 @@ function ListAlertRules(): JSX.Element { return ( - {NotificationElement} (''); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onChangeHandler: UploadProps['onChange'] = (info) => { const { fileList } = info; @@ -132,61 +125,58 @@ function ImportJSON({ ); return ( - <> - {NotificationElement} - - {t('import_json')} - {t('import_dashboard_by_pasting')} - - } - footer={ - - - {isCreateDashboardError && getErrorNode(t('error_loading_json'))} - - } - > -
- - false} - action="none" - data={jsonData} - > - - - {isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}} - + + {t('import_json')} + {t('import_dashboard_by_pasting')} + + } + footer={ + + + {isCreateDashboardError && getErrorNode(t('error_loading_json'))} + + } + > +
+ + false} + action="none" + data={jsonData} + > + + + {isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}} + - - {t('paste_json_below')} - setEditorValue(newValue)} - value={editorValue} - language="json" - /> - -
-
- + + {t('paste_json_below')} + setEditorValue(newValue)} + value={editorValue} + language="json" + /> + +
+
); } diff --git a/frontend/src/container/Login/index.tsx b/frontend/src/container/Login/index.tsx index bb7f7302df..e8c25f79f0 100644 --- a/frontend/src/container/Login/index.tsx +++ b/frontend/src/container/Login/index.tsx @@ -1,8 +1,9 @@ -import { Button, Input, notification, Space, Tooltip, Typography } from 'antd'; +import { Button, Input, Space, Tooltip, Typography } from 'antd'; import loginApi from 'api/user/login'; import loginPrecheckApi from 'api/user/loginPrecheck'; import afterLogin from 'AppRoutes/utils'; import ROUTES from 'constants/routes'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -42,7 +43,7 @@ function Login({ const [precheckInProcess, setPrecheckInProcess] = useState(false); const [precheckComplete, setPrecheckComplete] = useState(false); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (withPassword === 'Y') { @@ -185,7 +186,6 @@ function Login({ return ( - {NotificationElement} {t('login_page_title')} diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx b/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx index 551c47bc41..3fd74e9190 100644 --- a/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx +++ b/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx @@ -1,4 +1,4 @@ -import { notification } from 'antd'; +import { useNotifications } from 'hooks/useNotifications'; import { flatten } from 'lodash-es'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; @@ -36,7 +36,7 @@ function SearchFields({ const keyPrefixRef = useRef(hashCode(JSON.stringify(fieldsQuery))); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { const updatedFieldsQuery = createParsedQueryStructure([ @@ -102,7 +102,6 @@ function SearchFields({ return ( <> - {NotificationElement} { if (currentPassword && !isPasswordValid(currentPassword)) { @@ -82,7 +83,6 @@ function PasswordContainer(): JSX.Element { return ( - {NotificationElement} {t('change_password', { ns: 'settings', diff --git a/frontend/src/container/MySettings/UpdateName/index.tsx b/frontend/src/container/MySettings/UpdateName/index.tsx index 2a9dbcc576..df20b7141d 100644 --- a/frontend/src/container/MySettings/UpdateName/index.tsx +++ b/frontend/src/container/MySettings/UpdateName/index.tsx @@ -1,5 +1,6 @@ -import { Button, notification, Space, Typography } from 'antd'; +import { Button, Space, Typography } from 'antd'; import editUser from 'api/user/editUser'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; @@ -21,7 +22,7 @@ function UpdateName(): JSX.Element { const [changedName, setChangedName] = useState(user?.name || ''); const [loading, setLoading] = useState(false); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); if (!user || !org) { return
; @@ -72,7 +73,6 @@ function UpdateName(): JSX.Element { return (
- {NotificationElement} Name state.dashboards, ); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const [selectedDashboard] = dashboards; const { data } = selectedDashboard; @@ -57,7 +58,6 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { return ( - {NotificationElement} {menuItems.map(({ name, Icon, display }) => ( { diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx index 666ccac4cf..9ca7c6fb83 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx @@ -1,8 +1,9 @@ import { blue, red } from '@ant-design/colors'; import { PlusOutlined } from '@ant-design/icons'; -import { Button, Modal, notification, Row, Space, Tag } from 'antd'; +import { Button, Modal, Row, Space, Tag } from 'antd'; import { NotificationInstance } from 'antd/es/notification/interface'; import { ResizeTable } from 'components/ResizeTable'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useRef, useState } from 'react'; import { connect, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; @@ -26,7 +27,7 @@ function VariablesSetting({ (state) => state.dashboards, ); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const [selectedDashboard] = dashboards; @@ -143,7 +144,6 @@ function VariablesSetting({ return ( <> - {NotificationElement} {variableViewMode ? ( (false); const [lastUpdatedVar, setLastUpdatedVar] = useState(''); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onVarChanged = (name: string): void => { setLastUpdatedVar(name); @@ -62,7 +63,6 @@ function DashboardVariableSelection({ return ( - {NotificationElement} {map(sortBy(Object.keys(variables)), (variableName) => ( (false); const { t } = useTranslation(['dashboard', 'common']); const [state, setCopy] = useCopyToClipboard(); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (state.error) { @@ -84,34 +85,28 @@ function ShareModal({ }, [isViewJSON, jsonValue, selectedData, selectedDataCleaned, setCopy, t]); return ( - <> - {NotificationElement} - { - onToggleHandler(); - setIsViewJSON(false); - }} - width="70vw" - centered - title={t('share', { - ns: 'common', - })} - okText={t('download_json')} - cancelText={t('cancel')} - destroyOnClose - footer={GetFooterComponent} - > - {!isViewJSON ? ( - {t('export_dashboard')} - ) : ( - setJSONValue(value)} - value={jsonValue} - /> - )} - - + { + onToggleHandler(); + setIsViewJSON(false); + }} + width="70vw" + centered + title={t('share', { + ns: 'common', + })} + okText={t('download_json')} + cancelText={t('cancel')} + destroyOnClose + footer={GetFooterComponent} + > + {!isViewJSON ? ( + {t('export_dashboard')} + ) : ( + setJSONValue(value)} value={jsonValue} /> + )} + ); } diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/index.tsx index 589ff88147..9b7762fcbf 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/index.tsx @@ -1,10 +1,10 @@ import { PlusOutlined } from '@ant-design/icons'; -import { notification } from 'antd'; import { QueryBuilderFormulaTemplate, QueryBuilderQueryTemplate, } from 'constants/dashboard'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { useNotifications } from 'hooks/useNotifications'; import GetFormulaName from 'lib/query/GetFormulaName'; import GetQueryName from 'lib/query/GetQueryName'; import React from 'react'; @@ -37,7 +37,7 @@ function QueryBuilderQueryContainer({ metricsBuilderQueries, selectedGraph, }: IQueryBuilderQueryContainerProps): JSX.Element | null { - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const handleQueryBuilderQueryChange = ({ queryIndex, aggregateFunction, @@ -156,7 +156,6 @@ function QueryBuilderQueryContainer({ } return ( <> - {NotificationElement} {metricsBuilderQueries.queryBuilder.map((q, idx) => ( ((state) => state.app); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onCreateHandler = async (): Promise => { try { @@ -51,7 +52,6 @@ function AddDomain({ refetch }: Props): JSX.Element { return ( <> - {NotificationElement} {t('authenticated_domains', { diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx index 7adefc6b2e..b93fecd114 100644 --- a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx +++ b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx @@ -1,5 +1,6 @@ -import { Button, Form, notification, Space } from 'antd'; +import { Button, Form, Space } from 'antd'; import { useForm } from 'antd/lib/form/Form'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { AuthDomain, GOOGLE_AUTH, SAML } from 'types/api/SAML/listDomain'; @@ -31,7 +32,7 @@ function EditSSO({ const { t } = useTranslation(['common']); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onFinishHandler = useCallback(() => { form @@ -75,7 +76,6 @@ function EditSSO({ autoComplete="off" form={form} > - {NotificationElement} {renderFormInputs(record)} { @@ -254,7 +255,6 @@ function AuthDomains(): JSX.Element { if (!isLoading && data?.payload?.length === 0) { return ( - {NotificationElement} - {NotificationElement} (false); const dispatch = useDispatch>(); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onSubmit = async ({ name: orgName }: OnSubmitProps): Promise => { try { @@ -76,7 +77,6 @@ function DisplayName({ onFinish={onSubmit} autoComplete="off" > - {NotificationElement} diff --git a/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx b/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx index 10f589e7cc..a6e9254771 100644 --- a/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx +++ b/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx @@ -1,7 +1,8 @@ import { CopyOutlined } from '@ant-design/icons'; -import { Button, Input, notification, Select, Space, Tooltip } from 'antd'; +import { Button, Input, Select, Space, Tooltip } from 'antd'; import getResetPasswordToken from 'api/user/getResetPasswordToken'; import ROUTES from 'constants/routes'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useCopyToClipboard } from 'react-use'; @@ -36,7 +37,7 @@ function EditMembersDetails({ [], ); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (state.error) { @@ -91,7 +92,6 @@ function EditMembersDetails({ return ( - {NotificationElement} Email address (false); const [isUpdateLoading, setIsUpdateLoading] = useState(false); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const onUpdateDetailsHandler = (): void => { setDataSource((data) => { @@ -164,7 +165,6 @@ function UserFunction({ return ( <> - {NotificationElement} onModalToggleHandler(setIsModalVisible, true)} diff --git a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx index 2052366f73..5c1518eee2 100644 --- a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx +++ b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx @@ -1,5 +1,5 @@ import { PlusOutlined } from '@ant-design/icons'; -import { Button, Modal, notification, Space, Typography } from 'antd'; +import { Button, Modal, Space, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import deleteInvite from 'api/user/deleteInvite'; import getPendingInvites from 'api/user/getPendingInvites'; @@ -7,6 +7,7 @@ import sendInvite from 'api/user/sendInvite'; import { ResizeTable } from 'components/ResizeTable'; import { INVITE_MEMBERS_HASH } from 'constants/app'; import ROUTES from 'constants/routes'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; @@ -26,7 +27,7 @@ function PendingInvitesContainer(): JSX.Element { const [isInvitingMembers, setIsInvitingMembers] = useState(false); const { t } = useTranslation(['organizationsettings', 'common']); const [state, setText] = useCopyToClipboard(); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (state.error) { @@ -229,7 +230,6 @@ function PendingInvitesContainer(): JSX.Element { return (
- {NotificationElement} { if (!token) { @@ -83,73 +84,70 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element { return ( - <> - {NotificationElement} - - - Reset Your Password + + + Reset Your Password -
- - { - setState(e.target.value, setPassword); - }} - required - id="currentPassword" - /> -
-
- - { - const updateValue = e.target.value; - setState(updateValue, setConfirmPassword); - if (password !== updateValue) { - setConfirmPasswordError(true); - } else { - setConfirmPasswordError(false); - } - }} - required - id="UpdatePassword" - /> - - {confirmPasswordError && ( - - Passwords don’t match. Please try again - - )} -
- - -
+
+ + { + const updateValue = e.target.value; + setState(updateValue, setConfirmPassword); + if (password !== updateValue) { + setConfirmPasswordError(true); + } else { + setConfirmPasswordError(false); } + }} + required + id="UpdatePassword" + /> + + {confirmPasswordError && ( + - Get Started - - - - - + Passwords don’t match. Please try again + + )} +
+ + + + + + ); } diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx index 3baf556d39..a413671d18 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx @@ -1,6 +1,7 @@ -import { Checkbox, notification, Tooltip, Typography } from 'antd'; +import { Checkbox, Tooltip, Typography } from 'antd'; import getFilters from 'api/trace/getFilters'; import { AxiosError } from 'axios'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Dispatch } from 'redux'; @@ -38,7 +39,7 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { (userSelectedFilter.get(name) || []).find((e) => e === keyValue) !== undefined; - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); // eslint-disable-next-line sonarjs/cognitive-complexity const onCheckHandler = async (): Promise => { @@ -163,7 +164,6 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { return ( - {NotificationElement} (''); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { setUserEnteredValue(selectedFilter.get('traceID')?.[0] || ''); }, [selectedFilter]); @@ -109,7 +110,6 @@ function TraceID(): JSX.Element { }; return (
- {NotificationElement} = async (e) => { @@ -297,7 +298,6 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { return ( <> - {NotificationElement} {PanelName !== 'duration' && } diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx index 22c5788469..5d241efec0 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx @@ -16,7 +16,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element { const { index, setLocalSelectedTags, tag } = props; - const [selectedKey, setSelectedKey] = useState(tag.Key[0] || ''); + const [selectedKey, setSelectedKey] = useState(tag.Key || ''); const traces = useSelector((state) => state.traces); diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx index f95afab615..90003b0142 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx @@ -150,12 +150,12 @@ function SingleTags(props: AllTagsProps): JSX.Element { } - {selectedKey[0] ? ( + {selectedKey ? ( ) : ( diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts b/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts index 9faefef523..0ac195c3d1 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts +++ b/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts @@ -72,7 +72,7 @@ export function onTagValueChange( export function disableTagValue( selectedOperator: OperatorValues, setLocalValue: React.Dispatch>, - selectedKeys: string[], + selectedKeys: string, setLocalSelectedTags: React.Dispatch>, index: number, ): boolean { @@ -169,9 +169,9 @@ export function selectOptions( return []; } -export function mapOperators(selectedKey: string[]): AllMenuProps[] { +export function mapOperators(selectedKey: string): AllMenuProps[] { return AllMenu.filter((e) => - e?.supportedTypes?.includes(extractTagType(selectedKey[0])), + e?.supportedTypes?.includes(extractTagType(selectedKey)), ); } @@ -192,7 +192,7 @@ export function onTagKeySelect( setLocalSelectedTags((tags) => [ ...tags.slice(0, index), { - Key: [value], + Key: value, Operator: tag.Operator, StringValues: tag.StringValues, NumberValues: tag.NumberValues, diff --git a/frontend/src/container/Trace/Search/AllTags/index.tsx b/frontend/src/container/Trace/Search/AllTags/index.tsx index f37209eccb..effb0c87b9 100644 --- a/frontend/src/container/Trace/Search/AllTags/index.tsx +++ b/frontend/src/container/Trace/Search/AllTags/index.tsx @@ -39,7 +39,7 @@ function AllTags({ setLocalSelectedTags((tags) => [ ...tags, { - Key: [], + Key: '', Operator: 'Equals', StringValues: [], NumberValues: [], @@ -94,7 +94,7 @@ function AllTags({ {localSelectedTags.map((tags, index) => ( onCloseHandler(index)} diff --git a/frontend/src/container/Trace/Search/util.ts b/frontend/src/container/Trace/Search/util.ts index e03a19e5bb..347d1df54f 100644 --- a/frontend/src/container/Trace/Search/util.ts +++ b/frontend/src/container/Trace/Search/util.ts @@ -59,7 +59,7 @@ export const parseQueryToTags = (query: string): PayloadProps => { // If the operator is Exists or NotExists, then return the tag object without values if (operator === 'Exists' || operator === 'NotExists') { return { - Key: [tagName], + Key: tagName, StringValues: [], NumberValues: [], BoolValues: [], @@ -97,7 +97,7 @@ export const parseQueryToTags = (query: string): PayloadProps => { // Return the tag object return { - Key: [tagName], + Key: tagName, StringValues, NumberValues, BoolValues, @@ -120,31 +120,31 @@ export const parseTagsToQuery = (tags: Tags): PayloadProps => { const payload = tags .map(({ StringValues, NumberValues, BoolValues, Key, Operator }) => { // Check if the key of the tag is undefined - if (!Key[0]) { + if (!Key) { isError = true; } if (Operator === 'Exists' || Operator === 'NotExists') { - return `${Key[0]} ${Operator}`; + return `${Key} ${Operator}`; } // Check if the tag has string values if (StringValues.length > 0) { // Format the string values and join them with a ',' const formattedStringValues = formatValues(StringValues); - return `${Key[0]} ${Operator} (${formattedStringValues})`; + return `${Key} ${Operator} (${formattedStringValues})`; } // Check if the tag has number values if (NumberValues.length > 0) { // Format the number values and join them with a ',' const formattedNumberValues = formatValues(NumberValues); - return `${Key[0]} ${Operator} (${formattedNumberValues})`; + return `${Key} ${Operator} (${formattedNumberValues})`; } // Check if the tag has boolean values if (BoolValues.length > 0) { // Format the boolean values and join them with a ',' const formattedBoolValues = formatValues(BoolValues); - return `${Key[0]} ${Operator} (${formattedBoolValues})`; + return `${Key} ${Operator} (${formattedBoolValues})`; } return ''; diff --git a/frontend/src/container/TriggeredAlerts/index.tsx b/frontend/src/container/TriggeredAlerts/index.tsx index b877728e19..e9d024c81a 100644 --- a/frontend/src/container/TriggeredAlerts/index.tsx +++ b/frontend/src/container/TriggeredAlerts/index.tsx @@ -1,7 +1,7 @@ -import { notification } from 'antd'; import getTriggeredApi from 'api/alerts/getTriggered'; import Spinner from 'components/Spinner'; import { State } from 'hooks/useFetch'; +import { useNotifications } from 'hooks/useNotifications'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PayloadProps } from 'types/api/alerts/getTriggered'; @@ -17,7 +17,7 @@ function TriggeredAlerts(): JSX.Element { payload: [], }); const { t } = useTranslation(['common']); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); const fetchData = useCallback(async () => { try { @@ -69,21 +69,11 @@ function TriggeredAlerts(): JSX.Element { }, [groupState.error, groupState.errorMessage, t, notifications]); if (groupState.error) { - return ( - <> - {NotificationElement} - - - ); + return ; } if (groupState.loading || groupState.payload === undefined) { - return ( - <> - {NotificationElement} - - - ); + return ; } // commented the reduce() call as we no longer use /alerts/groups @@ -95,12 +85,7 @@ function TriggeredAlerts(): JSX.Element { // return [...acc, ...curr.alerts]; // }, initialAlerts); - return ( - <> - {NotificationElement} - - - ); + return ; } export default TriggeredAlerts; diff --git a/frontend/src/hooks/useNotifications.tsx b/frontend/src/hooks/useNotifications.tsx new file mode 100644 index 0000000000..8ba37c8d5c --- /dev/null +++ b/frontend/src/hooks/useNotifications.tsx @@ -0,0 +1,43 @@ +import { notification } from 'antd'; +import { NotificationInstance } from 'antd/es/notification/interface'; +import React, { createContext, useContext, useMemo } from 'react'; + +type Notification = { + notifications: NotificationInstance; +}; + +const defaultNotification: Notification = { + notifications: { + success: (): void => {}, + error: (): void => {}, + info: (): void => {}, + warning: (): void => {}, + open: (): void => {}, + destroy: (): void => {}, + }, +}; + +export const NotificationContext = createContext( + defaultNotification, +); + +export function NotificationProvider({ + children, +}: { + children: JSX.Element; +}): JSX.Element { + const [notificationApi, NotificationElement] = notification.useNotification(); + const notifications = useMemo(() => ({ notifications: notificationApi }), [ + notificationApi, + ]); + + return ( + + {NotificationElement} + {children} + + ); +} + +export const useNotifications = (): Notification => + useContext(NotificationContext); diff --git a/frontend/src/lib/resourceAttributes.ts b/frontend/src/lib/resourceAttributes.ts index 1d21799ad9..3c3fdd99e1 100644 --- a/frontend/src/lib/resourceAttributes.ts +++ b/frontend/src/lib/resourceAttributes.ts @@ -37,7 +37,7 @@ export const convertRawQueriesToTraceSelectedTags = ( queries: IResourceAttributeQuery[], ): Tags[] => queries.map((query) => ({ - Key: [convertMetricKeyToTrace(query.tagKey)], + Key: convertMetricKeyToTrace(query.tagKey), Operator: convertOperatorLabelToTraceOperator(query.operator), StringValues: query.tagValue, NumberValues: [], diff --git a/frontend/src/pages/EditRules/index.tsx b/frontend/src/pages/EditRules/index.tsx index 2c1b512977..7cdbbf9a1d 100644 --- a/frontend/src/pages/EditRules/index.tsx +++ b/frontend/src/pages/EditRules/index.tsx @@ -1,8 +1,8 @@ -import { notification } from 'antd'; import get from 'api/alerts/get'; import Spinner from 'components/Spinner'; import ROUTES from 'constants/routes'; import EditRulesContainer from 'container/EditRules'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; @@ -26,7 +26,7 @@ function EditRules(): JSX.Element { enabled: isValidRuleId, }); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (!isValidRuleId) { @@ -42,12 +42,7 @@ function EditRules(): JSX.Element { ruleId == null || (data?.payload?.data === undefined && !isLoading) ) { - return ( -
- {NotificationElement} - {data?.error || t('something_went_wrong')} -
- ); + return
{data?.error || t('something_went_wrong')}
; } if (isLoading || !data?.payload) { @@ -55,13 +50,10 @@ function EditRules(): JSX.Element { } return ( - <> - {NotificationElement} - - + ); } diff --git a/frontend/src/pages/Metrics/index.tsx b/frontend/src/pages/Metrics/index.tsx index a00cb2b84a..5af71f6bef 100644 --- a/frontend/src/pages/Metrics/index.tsx +++ b/frontend/src/pages/Metrics/index.tsx @@ -1,10 +1,11 @@ -import { notification, Space } from 'antd'; +import { Space } from 'antd'; import getLocalStorageKey from 'api/browser/localstorage/get'; import ReleaseNote from 'components/ReleaseNote'; import Spinner from 'components/Spinner'; import { SKIP_ONBOARDING } from 'constants/onboarding'; import ResourceAttributesFilter from 'container/MetricsApplication/ResourceAttributesFilter'; import MetricTable from 'container/MetricsTable'; +import { useNotifications } from 'hooks/useNotifications'; import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes'; import React, { useEffect, useMemo } from 'react'; import { connect, useSelector } from 'react-redux'; @@ -30,7 +31,7 @@ function Metrics({ getService }: MetricsProps): JSX.Element { error, errorMessage, } = useSelector((state) => state.metrics); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if (error) { @@ -91,7 +92,6 @@ function Metrics({ getService }: MetricsProps): JSX.Element { return ( - {NotificationElement} diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx index 4d632ea0d4..53174a3610 100644 --- a/frontend/src/pages/SignUp/SignUp.tsx +++ b/frontend/src/pages/SignUp/SignUp.tsx @@ -1,4 +1,4 @@ -import { Button, Input, notification, Space, Switch, Typography } from 'antd'; +import { Button, Input, Space, Switch, Typography } from 'antd'; import editOrg from 'api/user/editOrg'; import getInviteDetails from 'api/user/getInviteDetails'; import loginApi from 'api/user/login'; @@ -6,6 +6,7 @@ import signUpApi from 'api/user/signup'; import afterLogin from 'AppRoutes/utils'; import WelcomeLeftContainer from 'components/WelcomeLeftContainer'; import ROUTES from 'constants/routes'; +import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -56,7 +57,7 @@ function SignUp({ version }: SignUpProps): JSX.Element { enabled: token !== null, }); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { if ( @@ -266,169 +267,164 @@ function SignUp({ version }: SignUpProps): JSX.Element { return ( - <> - {NotificationElement} - -
- Create your account + + + Create your account +
+ + { + setState(e.target.value, setEmail); + }} + required + id="signupEmail" + disabled={isDetailsDisable} + /> +
+ + {isNameVisible && (
- + { - setState(e.target.value, setEmail); + setState(e.target.value, setFirstName); }} required - id="signupEmail" + id="signupFirstName" disabled={isDetailsDisable} />
+ )} - {isNameVisible && ( -
- - { - setState(e.target.value, setFirstName); - }} - required - id="signupFirstName" - disabled={isDetailsDisable} - /> -
- )} - +
+ + { + setState(e.target.value, setOrganizationName); + }} + required + id="organizationName" + disabled={isDetailsDisable} + /> +
+ {!precheck.sso && (
- - {t('label_password')} + { - setState(e.target.value, setOrganizationName); + setState(e.target.value, setPassword); }} required - id="organizationName" - disabled={isDetailsDisable} + id="currentPassword" />
- {!precheck.sso && ( -
- - { - setState(e.target.value, setPassword); - }} - required - id="currentPassword" - /> -
- )} - {!precheck.sso && ( -
- - { - const updateValue = e.target.value; - setState(updateValue, setConfirmPassword); - }} - required - id="confirmPassword" - /> - - {confirmPasswordError && ( - - {t('failed_confirm_password')} - - )} - {isPasswordPolicyError && ( - - {isPasswordNotValidMessage} - - )} -
- )} - - {isPreferenceVisible && ( - <> - - - - onSwitchHandler(value, setHasOptedUpdates) - } - checked={hasOptedUpdates} - /> - {t('prompt_keepme_posted')} - - - - - - onSwitchHandler(value, setIsAnonymous)} - checked={isAnonymous} - /> - {t('prompt_anonymise')} - - - - )} - - {isPreferenceVisible && ( - + + { + const updateValue = e.target.value; + setState(updateValue, setConfirmPassword); }} - > - This will create an admin account. If you are not an admin, please ask - your admin for an invite link - - )} + required + id="confirmPassword" + /> - - - - -
- + {confirmPasswordError && ( + + {t('failed_confirm_password')} + + )} + {isPasswordPolicyError && ( + + {isPasswordNotValidMessage} + + )} +
+ )} + + {isPreferenceVisible && ( + <> + + + onSwitchHandler(value, setHasOptedUpdates)} + checked={hasOptedUpdates} + /> + {t('prompt_keepme_posted')} + + + + + + onSwitchHandler(value, setIsAnonymous)} + checked={isAnonymous} + /> + {t('prompt_anonymise')} + + + + )} + + {isPreferenceVisible && ( + + This will create an admin account. If you are not an admin, please ask + your admin for an invite link + + )} + + + + + + ); } diff --git a/frontend/src/pages/Trace/index.tsx b/frontend/src/pages/Trace/index.tsx index 437d65da5c..2de31ecd53 100644 --- a/frontend/src/pages/Trace/index.tsx +++ b/frontend/src/pages/Trace/index.tsx @@ -1,4 +1,4 @@ -import { Card, notification } from 'antd'; +import { Card } from 'antd'; import { NotificationInstance } from 'antd/es/notification/interface'; import ROUTES from 'constants/routes'; import Filters from 'container/Trace/Filters'; @@ -6,6 +6,7 @@ import TraceGraph from 'container/Trace/Graph'; import Search from 'container/Trace/Search'; import TraceGraphFilter from 'container/Trace/TraceGraphFilter'; import TraceTable from 'container/Trace/TraceTable'; +import { useNotifications } from 'hooks/useNotifications'; import getStep from 'lib/getStep'; import history from 'lib/history'; import React, { useCallback, useEffect, useState } from 'react'; @@ -53,7 +54,7 @@ function Trace({ isFilterExclude, } = useSelector((state) => state.traces); - const [notifications, NotificationElement] = notification.useNotification(); + const { notifications } = useNotifications(); useEffect(() => { getInitialFilter(minTime, maxTime, notifications); @@ -139,7 +140,6 @@ function Trace({ return ( <> - {NotificationElement}
diff --git a/frontend/src/types/reducer/trace.ts b/frontend/src/types/reducer/trace.ts index fda615160c..701f27efa6 100644 --- a/frontend/src/types/reducer/trace.ts +++ b/frontend/src/types/reducer/trace.ts @@ -47,7 +47,7 @@ interface SpansAggregateData { } export interface Tags { - Key: string[]; + Key: string; Operator: OperatorValues; StringValues: string[]; NumberValues: number[]; diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index b0fbef4d0f..a1b2ceabab 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -3640,3 +3640,13 @@ func (r *ClickHouseReader) QueryDashboardVars(ctx context.Context, query string) } return &result, nil } + +func (r *ClickHouseReader) CheckClickHouse(ctx context.Context) error { + rows, err := r.db.Query(ctx, "SELECT 1") + if err != nil { + return err + } + defer rows.Close() + + return nil +} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 58a060649a..8d49dbb0ad 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -351,7 +351,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/feedback", OpenAccess(aH.submitFeedback)).Methods(http.MethodPost) // router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet) router.HandleFunc("/api/v1/services", ViewAccess(aH.getServices)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/services/list", aH.getServicesList).Methods(http.MethodGet) + router.HandleFunc("/api/v1/services/list", ViewAccess(aH.getServicesList)).Methods(http.MethodGet) router.HandleFunc("/api/v1/service/overview", ViewAccess(aH.getServiceOverview)).Methods(http.MethodPost) router.HandleFunc("/api/v1/service/top_operations", ViewAccess(aH.getTopOperations)).Methods(http.MethodPost) router.HandleFunc("/api/v1/service/top_level_operations", ViewAccess(aH.getServicesTopLevelOps)).Methods(http.MethodPost) @@ -364,6 +364,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet) router.HandleFunc("/api/v1/featureFlags", OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet) router.HandleFunc("/api/v1/configs", OpenAccess(aH.getConfigs)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/health", OpenAccess(aH.getHealth)).Methods(http.MethodGet) router.HandleFunc("/api/v1/getSpanFilters", ViewAccess(aH.getSpanFilters)).Methods(http.MethodPost) router.HandleFunc("/api/v1/getTagFilters", ViewAccess(aH.getTagFilters)).Methods(http.MethodPost) @@ -1671,6 +1672,22 @@ func (aH *APIHandler) getConfigs(w http.ResponseWriter, r *http.Request) { aH.Respond(w, configs) } +// getHealth is used to check the health of the service. +// 'live' query param can be used to check liveliness of +// the service by checking the database connection. +func (aH *APIHandler) getHealth(w http.ResponseWriter, r *http.Request) { + _, ok := r.URL.Query()["live"] + if ok { + err := aH.reader.CheckClickHouse(r.Context()) + if err != nil { + aH.HandleError(w, err, http.StatusServiceUnavailable) + return + } + } + + aH.WriteJSON(w, r, map[string]string{"status": "ok"}) +} + // inviteUser is used to invite a user. It is used by an admin api. func (aH *APIHandler) inviteUser(w http.ResponseWriter, r *http.Request) { req, err := parseInviteRequest(r) diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go index a551e4b7f8..1a98e5d1d7 100644 --- a/pkg/query-service/interfaces/interface.go +++ b/pkg/query-service/interfaces/interface.go @@ -77,4 +77,5 @@ type Reader interface { GetFanoutStorage() *storage.Storage QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error) + CheckClickHouse(ctx context.Context) error }