Compare commits

..

17 Commits

Author SHA1 Message Date
primus-bot[bot]
131759ec96 chore(release): bump to v0.80.0 (#7703)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-04-23 12:20:51 +05:30
Shivanshu Raj Shrivastava
365a3e250f chore: fix error rate (#7701)
Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
2025-04-22 20:26:37 +00:00
Nityananda Gohain
f3a1f3cc20 fix: handle rate operators for table panel (#7695)
* fix: handle rate operators for table panel
2025-04-22 19:09:08 +00:00
Aditya Singh
ae509b4ae9 Resource attr filter: style fix and quick filter changes (#7691)
* chore: resource attr filter init

* chore: resource attr filter api integration

* chore: operator config updated

* chore: fliter show hide logic and styles

* chore: add support for custom operator list to qb

* chore: minor refactor

* chore: minor code refactor

* test: quick filters test suite added

* test: quick filters test suite added

* test: all errors test suite added

* chore: style fix

* test: all errors mock fix

* chore: test case fix and mixpanel update

* chore: color update

* chore: minor refactor

* chore: style fix

* chore: set default query in exceptions tab

* chore: style fix

* chore: minor refactor

* chore: minor refactor

* chore: minor refactor

* chore: test update

* chore: fix filter header with no query name

* fix: scroll fix

* chore: add data source traces to quick filters

* chore: replace div with fragment

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
2025-04-22 15:45:49 +00:00
Shaheer Kochai
43e2be0333 feat: use search v2 component for traces (#7537)
* Revert "fix: display same key with multiple data types in filter suggestions by enhancing the deduping logic (#7255)"

This reverts commit 1e85981a17.

* fix: use query search v2 for traces data source to handle multiple data types for the same key

* fix(QueryBuilderSearchV2): add user typed option if it doesn't exist in the payload

* fix(QueryBuilderSearchV2): increase the height of search dropdown for non-logs data sources

* fix: display span scope selector for trace data source

* chore: remove the span scope selector from qb search v1 and move the component to search v2

* fix: write test to ensure that we display span scope selector for traces data source

* fix: limit converting  ->   only to log data source

* fix: don't display empty suggestion if only spaces are typed

* chore: tests for span scope selector

* chore: qb search flow (key, operator, value) test cases

* refactor: fix the Maximum update depth reached issue while running tests

* chore: overall improvements to span scope selector tests
2025-04-22 15:24:03 +00:00
Yunus M
20a40b33ce chore: update copy webpack plugin (#7687)
* chore: update copy webpack plugin
2025-04-22 20:36:57 +05:30
sawhil
a9b07c4b47 feat: added test case for checking copying functionality 2025-04-22 15:18:38 +05:30
sawhil
2a5c7cc0ab feat: added test cases for copy span link functionality 2025-04-22 15:18:38 +05:30
sawhil
afb18b8142 fix: pr comments - used useSafeNavigate hook 2025-04-22 15:18:38 +05:30
sawhil
9a580915e6 fix: removed not required prop 2025-04-22 15:18:38 +05:30
sawhil
0944af3d31 feat: added copy span link support alongside span click expand in waterfall graph 2025-04-22 15:18:38 +05:30
SagarRajput-7
9338efcefc fix: boolean values are not shown in the list panel's column (#7668)
* fix: boolean values are not shown in the list panel's column

* fix: moved logic to component level

* fix: added type

* fix: added test cases

* fix: added test cases
2025-04-22 12:03:28 +05:30
sawhil
6b9e0ce799 fix: minor comment update 2025-04-21 12:45:04 +05:30
sawhil
d4c3c24849 feat: added test cases 2025-04-21 12:45:04 +05:30
sawhil
30d935a768 fix: used existing constant 2025-04-21 12:45:04 +05:30
sawhil
073d42c416 fix: removed timestamp and id from being passed to query from log details drawer 2025-04-21 12:45:04 +05:30
Aditya Singh
f11b9644cf Introduce new Resource Attribute FIlter in exceptions tab (#7589)
* chore: resource attr filter init

* chore: resource attr filter api integration

* chore: operator config updated

* chore: fliter show hide logic and styles

* chore: add support for custom operator list to qb

* chore: minor refactor

* chore: minor code refactor

* test: quick filters test suite added

* test: quick filters test suite added

* test: all errors test suite added

* chore: style fix

* test: all errors mock fix

* chore: test case fix and mixpanel update

* chore: color update

* chore: minor refactor

* chore: style fix

* chore: set default query in exceptions tab

* chore: style fix

* chore: minor refactor

* chore: minor refactor

* chore: minor refactor

* chore: test update

* chore: fix filter header with no query name

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
2025-04-21 05:41:05 +00:00
249 changed files with 3527 additions and 1324 deletions

View File

@@ -174,7 +174,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.79.1
image: signoz/signoz:v0.80.0
command:
- --config=/root/config/prometheus.yml
- --use-logs-new-schema=true

View File

@@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.79.1
image: signoz/signoz:v0.80.0
command:
- --config=/root/config/prometheus.yml
- --use-logs-new-schema=true

View File

@@ -177,7 +177,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.79.1}
image: signoz/signoz:${VERSION:-v0.80.0}
container_name: signoz
command:
- --config=/root/config/prometheus.yml

View File

@@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.79.1}
image: signoz/signoz:${VERSION:-v0.80.0}
container_name: signoz
command:
- --config=/root/config/prometheus.yml

View File

@@ -79,6 +79,7 @@
"eventemitter3": "5.0.1",
"file-loader": "6.1.1",
"fontfaceobserver": "2.3.0",
"history": "4.10.1",
"html-webpack-plugin": "5.5.0",
"http-proxy-middleware": "3.0.3",
"i18next": "^21.6.12",
@@ -114,7 +115,8 @@
"react-markdown": "8.0.7",
"react-query": "3.39.3",
"react-redux": "^7.2.2",
"react-router": "7.5.1",
"react-router-dom": "^5.2.0",
"react-router-dom-v5-compat": "6.27.0",
"react-syntax-highlighter": "15.5.0",
"react-use": "^17.3.2",
"react-virtuoso": "4.0.3",
@@ -184,7 +186,7 @@
"@types/react-lottie": "1.2.10",
"@types/react-redux": "^7.1.11",
"@types/react-resizable": "3.0.3",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.1.6",
"@types/react-syntax-highlighter": "15.5.7",
"@types/redux-mock-store": "1.0.4",
"@types/styled-components": "^5.1.4",
@@ -196,7 +198,7 @@
"autoprefixer": "10.4.19",
"babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "9.0.0",
"copy-webpack-plugin": "^8.1.0",
"copy-webpack-plugin": "^11.0.0",
"critters-webpack-plugin": "^3.0.1",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^19.0.4",
@@ -253,6 +255,7 @@
"body-parser": "1.20.3",
"http-proxy-middleware": "3.0.3",
"cross-spawn": "7.0.5",
"cookie": "^0.7.1"
"cookie": "^0.7.1",
"serialize-javascript": "6.0.2"
}
}

View File

@@ -5,17 +5,15 @@ import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useGlobalEventListener } from 'hooks/useGlobalEventListener';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { isEmpty } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { ReactChild, useCallback, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { matchPath, useLocation } from 'react-router';
import { matchPath, useLocation } from 'react-router-dom';
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import { USER_ROLES } from 'types/roles';
import { safeNavigateNoSameURLMemo } from 'utils/navigate';
import { routePermission } from 'utils/permission';
import routes, {
@@ -28,10 +26,6 @@ import routes, {
function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const location = useLocation();
const { safeNavigate } = useSafeNavigate();
const { safeNavigate: unsafeNavigate } = useSafeNavigate({
preventSameUrlNavigation: false,
});
const { pathname } = location;
const {
org,
@@ -50,13 +44,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
() =>
new Map(
[...routes, LIST_LICENSES, SUPPORT_ROUTE].map((e) => {
const currentPath = matchPath(
{
// Temp: Hard type cast
path: e.path as string,
},
pathname,
);
const currentPath = matchPath(pathname, {
path: e.path,
});
return [currentPath === null ? null : 'current', e];
}),
),
@@ -111,7 +101,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
// if the current route is allowed to be overriden by org onboarding then only do the same
!ROUTES_NOT_TO_BE_OVERRIDEN.includes(pathname)
) {
safeNavigateNoSameURLMemo(ROUTES.ONBOARDING);
history.push(ROUTES.ONBOARDING);
}
}
}, [
@@ -124,37 +114,31 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
pathname,
]);
const safeNavigateToWorkSpaceBlocked = useCallback(
(route: any): void => {
const { path } = route;
const navigateToWorkSpaceBlocked = (route: any): void => {
const { path } = route;
const isRouteEnabledForWorkspaceBlockedState =
isAdmin &&
(path === ROUTES.ORG_SETTINGS ||
path === ROUTES.BILLING ||
path === ROUTES.MY_SETTINGS);
const isRouteEnabledForWorkspaceBlockedState =
isAdmin &&
(path === ROUTES.ORG_SETTINGS ||
path === ROUTES.BILLING ||
path === ROUTES.MY_SETTINGS);
if (
path &&
path !== ROUTES.WORKSPACE_LOCKED &&
!isRouteEnabledForWorkspaceBlockedState
) {
safeNavigateNoSameURLMemo(ROUTES.WORKSPACE_LOCKED);
}
},
[isAdmin],
);
if (
path &&
path !== ROUTES.WORKSPACE_LOCKED &&
!isRouteEnabledForWorkspaceBlockedState
) {
history.push(ROUTES.WORKSPACE_LOCKED);
}
};
const safeNavigateToWorkSpaceAccessRestricted = useCallback(
(route: any): void => {
const { path } = route;
const navigateToWorkSpaceAccessRestricted = (route: any): void => {
const { path } = route;
if (path && path !== ROUTES.WORKSPACE_ACCESS_RESTRICTED) {
safeNavigateNoSameURLMemo(ROUTES.WORKSPACE_ACCESS_RESTRICTED);
}
},
[],
);
if (path && path !== ROUTES.WORKSPACE_ACCESS_RESTRICTED) {
history.push(ROUTES.WORKSPACE_ACCESS_RESTRICTED);
}
};
useEffect(() => {
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
@@ -173,16 +157,10 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
platform === LicensePlatform.CLOUD &&
currentRoute
) {
safeNavigateToWorkSpaceAccessRestricted(currentRoute);
navigateToWorkSpaceAccessRestricted(currentRoute);
}
}
}, [
isFetchingActiveLicenseV3,
activeLicenseV3,
mapRoutes,
pathname,
safeNavigateToWorkSpaceAccessRestricted,
]);
}, [isFetchingActiveLicenseV3, activeLicenseV3, mapRoutes, pathname]);
useEffect(() => {
if (!isFetchingActiveLicenseV3) {
@@ -194,7 +172,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
currentRoute &&
activeLicenseV3?.platform === LicensePlatform.CLOUD
) {
safeNavigateToWorkSpaceBlocked(currentRoute);
navigateToWorkSpaceBlocked(currentRoute);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -204,16 +182,15 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
activeLicenseV3?.platform,
mapRoutes,
pathname,
safeNavigateToWorkSpaceBlocked,
]);
const safeNavigateToWorkSpaceSuspended = useCallback((route: any): void => {
const navigateToWorkSpaceSuspended = (route: any): void => {
const { path } = route;
if (path && path !== ROUTES.WORKSPACE_SUSPENDED) {
safeNavigateNoSameURLMemo(ROUTES.WORKSPACE_SUSPENDED);
history.push(ROUTES.WORKSPACE_SUSPENDED);
}
}, []);
};
useEffect(() => {
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
@@ -226,16 +203,10 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
currentRoute &&
activeLicenseV3.platform === LicensePlatform.CLOUD
) {
safeNavigateToWorkSpaceSuspended(currentRoute);
navigateToWorkSpaceSuspended(currentRoute);
}
}
}, [
isFetchingActiveLicenseV3,
activeLicenseV3,
mapRoutes,
pathname,
safeNavigateToWorkSpaceSuspended,
]);
}, [isFetchingActiveLicenseV3, activeLicenseV3, mapRoutes, pathname]);
useEffect(() => {
if (org && org.length > 0 && org[0].id !== undefined) {
@@ -249,13 +220,13 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
currentRoute?.path === ROUTES.GET_STARTED &&
featureFlags?.find((e) => e.name === FeatureKeys.ONBOARDING_V3)?.active
) {
safeNavigateNoSameURLMemo(ROUTES.GET_STARTED_WITH_CLOUD);
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
}
}, [currentRoute, featureFlags]);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
// if it is an old route safeNavigate to the new route
// if it is an old route navigate to the new route
if (isOldRoute) {
const redirectUrl = oldNewRoutesMapping[pathname];
@@ -263,8 +234,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
...location,
pathname: redirectUrl,
};
safeNavigateNoSameURLMemo(newLocation, { replace: true });
history.replace(newLocation);
return;
}
// if the current route
@@ -274,21 +244,21 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
if (isLoggedInState) {
const route = routePermission[key];
if (route && route.find((e) => e === user.role) === undefined) {
safeNavigateNoSameURLMemo(ROUTES.UN_AUTHORIZED);
history.push(ROUTES.UN_AUTHORIZED);
}
} else {
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, pathname);
safeNavigateNoSameURLMemo(ROUTES.LOGIN);
history.push(ROUTES.LOGIN);
}
} else if (isLoggedInState) {
const fromPathname = getLocalStorageApi(
LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT,
);
if (fromPathname) {
safeNavigateNoSameURLMemo(fromPathname);
history.push(fromPathname);
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, '');
} else if (pathname !== ROUTES.SOMETHING_WENT_WRONG) {
safeNavigateNoSameURLMemo(ROUTES.HOME);
history.push(ROUTES.HOME);
}
} else {
// do nothing as the unauthenticated routes are LOGIN and SIGNUP and the LOGIN container takes care of routing to signup if
@@ -299,45 +269,17 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT,
);
if (fromPathname) {
safeNavigateNoSameURLMemo(fromPathname);
history.push(fromPathname);
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, '');
} else {
safeNavigateNoSameURLMemo(ROUTES.HOME);
history.push(ROUTES.HOME);
}
} else {
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, pathname);
safeNavigateNoSameURLMemo(ROUTES.LOGIN);
history.push(ROUTES.LOGIN);
}
}, [isLoggedInState, pathname, user, isOldRoute, currentRoute, location]);
// global event listener for NAVIGATE event
// This will provide access to useNavigation hook from outside of components
useGlobalEventListener(
'SAFE_NAVIGATE',
(event: CustomEvent) => {
const { to, options } = event.detail;
if (to) {
safeNavigate(to, options);
}
},
{
passive: true,
},
);
useGlobalEventListener(
'UNSAFE_NAVIGATE',
(event: CustomEvent) => {
const { to, options } = event.detail;
if (to) {
unsafeNavigate(to, options);
}
},
{
passive: true,
},
);
// NOTE: disabling this rule as there is no need to have div
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;

View File

@@ -15,6 +15,7 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { LICENSE_PLAN_KEY } from 'hooks/useLicense';
import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import posthog from 'posthog-js';
import AlertRuleProvider from 'providers/Alert';
@@ -23,9 +24,9 @@ import { IUser } from 'providers/App/types';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useCallback, useEffect, useState } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router';
import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat';
import { extractDomain } from 'utils/app';
import { safeNavigateNoSameURLMemo } from 'utils/navigate';
import { Home } from './pageComponents';
import PrivateRoute from './Private';
@@ -315,7 +316,7 @@ function App(): JSX.Element {
// this needs to be on top of data missing error because if there is an error, data will never be loaded and it will
// move to indefinitive loading
if (userFetchError && pathname !== ROUTES.SOMETHING_WENT_WRONG) {
safeNavigateNoSameURLMemo(ROUTES.SOMETHING_WENT_WRONG);
history.replace(ROUTES.SOMETHING_WENT_WRONG);
}
// if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting
@@ -327,33 +328,40 @@ function App(): JSX.Element {
return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<ConfigProvider theme={themeConfig}>
<BrowserRouter>
<NotificationProvider>
<PrivateRoute>
<ResourceProvider>
<QueryBuilderProvider>
<DashboardProvider>
<KeyboardHotkeysProvider>
<AlertRuleProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Routes>
{routes.map(({ path, element }) => (
<Route key={`${path}`} path={path} element={element} />
))}
<Route path="/" element={<Home />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</AppLayout>
</AlertRuleProvider>
</KeyboardHotkeysProvider>
</DashboardProvider>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>
</NotificationProvider>
</BrowserRouter>
<Router history={history}>
<CompatRouter>
<NotificationProvider>
<PrivateRoute>
<ResourceProvider>
<QueryBuilderProvider>
<DashboardProvider>
<KeyboardHotkeysProvider>
<AlertRuleProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<Route exact path="/" component={Home} />
<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</AlertRuleProvider>
</KeyboardHotkeysProvider>
</DashboardProvider>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>
</NotificationProvider>
</CompatRouter>
</Router>
</ConfigProvider>
</Sentry.ErrorBoundary>
);

View File

@@ -1,6 +1,6 @@
import ROUTES from 'constants/routes';
import MessagingQueues from 'pages/MessagingQueues';
import { RouteProps } from 'react-router';
import { RouteProps } from 'react-router-dom';
import {
AlertHistory,
@@ -65,383 +65,443 @@ import {
const routes: AppRoutes[] = [
{
component: SignupPage,
path: ROUTES.SIGN_UP,
element: <SignupPage />,
exact: true,
isPrivate: false,
key: 'SIGN_UP',
},
{
path: ROUTES.GET_STARTED,
// exact: false
element: <Onboarding />,
exact: false,
component: Onboarding,
isPrivate: true,
key: 'GET_STARTED',
},
{
path: ROUTES.GET_STARTED_WITH_CLOUD,
// exact: false
element: <OnboardingV2 />,
exact: false,
component: OnboardingV2,
isPrivate: true,
key: 'GET_STARTED_WITH_CLOUD',
},
{
path: ROUTES.HOME,
element: <Home />,
exact: true,
component: Home,
isPrivate: true,
key: 'HOME',
},
{
path: ROUTES.ONBOARDING,
// exact: false
element: <OrgOnboarding />,
exact: false,
component: OrgOnboarding,
isPrivate: true,
key: 'ONBOARDING',
},
{
component: LogsIndexToFields,
path: ROUTES.LOGS_INDEX_FIELDS,
element: <LogsIndexToFields />,
exact: true,
isPrivate: true,
key: 'LOGS_INDEX_FIELDS',
},
{
component: ServicesTablePage,
path: ROUTES.APPLICATION,
element: <ServicesTablePage />,
exact: true,
isPrivate: true,
key: 'APPLICATION',
},
{
path: ROUTES.SERVICE_METRICS,
element: <ServiceMetricsPage />,
exact: true,
component: ServiceMetricsPage,
isPrivate: true,
key: 'SERVICE_METRICS',
},
{
path: ROUTES.SERVICE_TOP_LEVEL_OPERATIONS,
element: <ServiceTopLevelOperationsPage />,
exact: true,
component: ServiceTopLevelOperationsPage,
isPrivate: true,
key: 'SERVICE_TOP_LEVEL_OPERATIONS',
},
{
path: ROUTES.SERVICE_MAP,
element: <ServiceMapPage />,
component: ServiceMapPage,
isPrivate: true,
exact: true,
key: 'SERVICE_MAP',
},
{
path: ROUTES.LOGS_SAVE_VIEWS,
element: <LogsSaveViews />,
component: LogsSaveViews,
isPrivate: true,
exact: true,
key: 'LOGS_SAVE_VIEWS',
},
{
path: ROUTES.TRACE_DETAIL,
element: <TraceDetail />,
exact: true,
component: TraceDetail,
isPrivate: true,
key: 'TRACE_DETAIL',
},
{
path: ROUTES.SETTINGS,
element: <SettingsPage />,
exact: true,
component: SettingsPage,
isPrivate: true,
key: 'SETTINGS',
},
{
path: ROUTES.USAGE_EXPLORER,
element: <UsageExplorerPage />,
exact: true,
component: UsageExplorerPage,
isPrivate: true,
key: 'USAGE_EXPLORER',
},
{
path: ROUTES.ALL_DASHBOARD,
element: <DashboardPage />,
exact: true,
component: DashboardPage,
isPrivate: true,
key: 'ALL_DASHBOARD',
},
{
path: ROUTES.DASHBOARD,
element: <NewDashboardPage />,
exact: true,
component: NewDashboardPage,
isPrivate: true,
key: 'DASHBOARD',
},
{
path: ROUTES.DASHBOARD_WIDGET,
element: <DashboardWidget />,
exact: true,
component: DashboardWidget,
isPrivate: true,
key: 'DASHBOARD_WIDGET',
},
{
path: ROUTES.EDIT_ALERTS,
element: <EditRulesPage />,
exact: true,
component: EditRulesPage,
isPrivate: true,
key: 'EDIT_ALERTS',
},
{
path: ROUTES.LIST_ALL_ALERT,
element: <ListAllALertsPage />,
exact: true,
component: ListAllALertsPage,
isPrivate: true,
key: 'LIST_ALL_ALERT',
},
{
path: ROUTES.ALERTS_NEW,
element: <CreateNewAlerts />,
exact: true,
component: CreateNewAlerts,
isPrivate: true,
key: 'ALERTS_NEW',
},
{
path: ROUTES.ALERT_HISTORY,
element: <AlertHistory />,
exact: true,
component: AlertHistory,
isPrivate: true,
key: 'ALERT_HISTORY',
},
{
path: ROUTES.ALERT_OVERVIEW,
element: <AlertOverview />,
exact: true,
component: AlertOverview,
isPrivate: true,
key: 'ALERT_OVERVIEW',
},
{
path: ROUTES.TRACE,
element: <TraceFilter />,
exact: true,
component: TraceFilter,
isPrivate: true,
key: 'TRACE',
},
{
path: ROUTES.TRACES_EXPLORER,
element: <TracesExplorer />,
exact: true,
component: TracesExplorer,
isPrivate: true,
key: 'TRACES_EXPLORER',
},
{
path: ROUTES.TRACES_SAVE_VIEWS,
element: <TracesSaveViews />,
exact: true,
component: TracesSaveViews,
isPrivate: true,
key: 'TRACES_SAVE_VIEWS',
},
{
path: ROUTES.TRACES_FUNNELS,
element: <TracesFunnels />,
exact: true,
component: TracesFunnels,
isPrivate: true,
key: 'TRACES_FUNNELS',
},
{
path: ROUTES.TRACES_FUNNELS_DETAIL,
element: <TracesFunnelDetails />,
exact: true,
component: TracesFunnelDetails,
isPrivate: true,
key: 'TRACES_FUNNELS_DETAIL',
},
{
path: ROUTES.CHANNELS_NEW,
element: <CreateAlertChannelAlerts />,
exact: true,
component: CreateAlertChannelAlerts,
isPrivate: true,
key: 'CHANNELS_NEW',
},
{
path: ROUTES.CHANNELS_EDIT,
element: <EditAlertChannelsAlerts />,
exact: true,
component: EditAlertChannelsAlerts,
isPrivate: true,
key: 'CHANNELS_EDIT',
},
{
path: ROUTES.ALL_CHANNELS,
element: <AllAlertChannels />,
exact: true,
component: AllAlertChannels,
isPrivate: true,
key: 'ALL_CHANNELS',
},
{
path: ROUTES.ALL_ERROR,
element: <AllErrors />,
exact: true,
isPrivate: true,
component: AllErrors,
key: 'ALL_ERROR',
},
{
path: ROUTES.ERROR_DETAIL,
element: <ErrorDetails />,
exact: true,
component: ErrorDetails,
isPrivate: true,
key: 'ERROR_DETAIL',
},
{
path: ROUTES.VERSION,
element: <StatusPage />,
exact: true,
component: StatusPage,
isPrivate: true,
key: 'VERSION',
},
{
path: ROUTES.ORG_SETTINGS,
element: <OrganizationSettings />,
exact: true,
component: OrganizationSettings,
isPrivate: true,
key: 'ORG_SETTINGS',
},
{
path: ROUTES.INGESTION_SETTINGS,
element: <IngestionSettings />,
exact: true,
component: IngestionSettings,
isPrivate: true,
key: 'INGESTION_SETTINGS',
},
{
path: ROUTES.API_KEYS,
element: <APIKeys />,
exact: true,
component: APIKeys,
isPrivate: true,
key: 'API_KEYS',
},
{
path: ROUTES.MY_SETTINGS,
element: <MySettings />,
exact: true,
component: MySettings,
isPrivate: true,
key: 'MY_SETTINGS',
},
{
path: ROUTES.CUSTOM_DOMAIN_SETTINGS,
element: <CustomDomainSettings />,
exact: true,
component: CustomDomainSettings,
isPrivate: true,
key: 'CUSTOM_DOMAIN_SETTINGS',
},
{
path: ROUTES.LOGS,
element: <Logs />,
exact: true,
component: Logs,
key: 'LOGS',
isPrivate: true,
},
{
path: ROUTES.LOGS_EXPLORER,
element: <LogsExplorer />,
exact: true,
component: LogsExplorer,
key: 'LOGS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.OLD_LOGS_EXPLORER,
element: <OldLogsExplorer />,
exact: true,
component: OldLogsExplorer,
key: 'OLD_LOGS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.LIVE_LOGS,
element: <LiveLogs />,
exact: true,
component: LiveLogs,
key: 'LIVE_LOGS',
isPrivate: true,
},
{
path: ROUTES.LOGS_PIPELINES,
element: <PipelinePage />,
exact: true,
component: PipelinePage,
key: 'LOGS_PIPELINES',
isPrivate: true,
},
{
path: ROUTES.LOGIN,
element: <Login />,
exact: true,
component: Login,
isPrivate: false,
key: 'LOGIN',
},
{
path: ROUTES.UN_AUTHORIZED,
element: <UnAuthorized />,
exact: true,
component: UnAuthorized,
key: 'UN_AUTHORIZED',
isPrivate: true,
},
{
path: ROUTES.PASSWORD_RESET,
element: <PasswordReset />,
exact: true,
component: PasswordReset,
key: 'PASSWORD_RESET',
isPrivate: false,
},
{
path: ROUTES.SOMETHING_WENT_WRONG,
element: <SomethingWentWrong />,
exact: true,
component: SomethingWentWrong,
key: 'SOMETHING_WENT_WRONG',
isPrivate: false,
},
{
path: ROUTES.BILLING,
element: <BillingPage />,
exact: true,
component: BillingPage,
key: 'BILLING',
isPrivate: true,
},
{
path: ROUTES.WORKSPACE_LOCKED,
element: <WorkspaceBlocked />,
exact: true,
component: WorkspaceBlocked,
isPrivate: true,
key: 'WORKSPACE_LOCKED',
},
{
path: ROUTES.WORKSPACE_SUSPENDED,
element: <WorkspaceSuspended />,
exact: true,
component: WorkspaceSuspended,
isPrivate: true,
key: 'WORKSPACE_SUSPENDED',
},
{
path: ROUTES.WORKSPACE_ACCESS_RESTRICTED,
element: <WorkspaceAccessRestricted />,
exact: true,
component: WorkspaceAccessRestricted,
isPrivate: true,
key: 'WORKSPACE_ACCESS_RESTRICTED',
},
{
path: ROUTES.SHORTCUTS,
element: <ShortcutsPage />,
exact: true,
component: ShortcutsPage,
isPrivate: true,
key: 'SHORTCUTS',
},
{
path: ROUTES.INTEGRATIONS,
element: <InstalledIntegrations />,
exact: true,
component: InstalledIntegrations,
isPrivate: true,
key: 'INTEGRATIONS',
},
{
path: ROUTES.MESSAGING_QUEUES_KAFKA,
element: <MessagingQueues />,
exact: true,
component: MessagingQueues,
key: 'MESSAGING_QUEUES_KAFKA',
isPrivate: true,
},
{
path: ROUTES.MESSAGING_QUEUES_CELERY_TASK,
element: <MessagingQueues />,
exact: true,
component: MessagingQueues,
key: 'MESSAGING_QUEUES_CELERY_TASK',
isPrivate: true,
},
{
path: ROUTES.MESSAGING_QUEUES_OVERVIEW,
element: <MessagingQueues />,
exact: true,
component: MessagingQueues,
key: 'MESSAGING_QUEUES_OVERVIEW',
isPrivate: true,
},
{
path: ROUTES.MESSAGING_QUEUES_KAFKA_DETAIL,
element: <MessagingQueues />,
exact: true,
component: MessagingQueues,
key: 'MESSAGING_QUEUES_KAFKA_DETAIL',
isPrivate: true,
},
{
path: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
element: <InfrastructureMonitoring />,
exact: true,
component: InfrastructureMonitoring,
key: 'INFRASTRUCTURE_MONITORING_HOSTS',
isPrivate: true,
},
{
path: ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
element: <InfrastructureMonitoring />,
exact: true,
component: InfrastructureMonitoring,
key: 'INFRASTRUCTURE_MONITORING_KUBERNETES',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER,
element: <MetricsExplorer />,
exact: true,
component: MetricsExplorer,
key: 'METRICS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER_EXPLORER,
element: <MetricsExplorer />,
exact: true,
component: MetricsExplorer,
key: 'METRICS_EXPLORER_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.METRICS_EXPLORER_VIEWS,
element: <MetricsExplorer />,
exact: true,
component: MetricsExplorer,
key: 'METRICS_EXPLORER_VIEWS',
isPrivate: true,
},
{
path: ROUTES.API_MONITORING,
element: <ApiMonitoring />,
exact: true,
component: ApiMonitoring,
key: 'API_MONITORING',
isPrivate: true,
},
@@ -449,14 +509,16 @@ const routes: AppRoutes[] = [
export const SUPPORT_ROUTE: AppRoutes = {
path: ROUTES.SUPPORT,
element: <SupportPage />,
exact: true,
component: SupportPage,
key: 'SUPPORT',
isPrivate: true,
};
export const LIST_LICENSES: AppRoutes = {
path: ROUTES.LIST_LICENSES,
element: <LicensePage />,
exact: true,
component: LicensePage,
isPrivate: true,
key: 'LIST_LICENSES',
};
@@ -487,8 +549,9 @@ export const ROUTES_NOT_TO_BE_OVERRIDEN: string[] = [
];
export interface AppRoutes {
element: RouteProps['element'];
component: RouteProps['component'];
path: RouteProps['path'];
exact: RouteProps['exact'];
isPrivate: boolean;
key: keyof typeof ROUTES;
}

View File

@@ -1,7 +1,7 @@
import deleteLocalStorageKey from 'api/browser/localstorage/remove';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { safeNavigateNoSameURLMemo } from 'utils/navigate';
import history from 'lib/history';
export const Logout = (): void => {
deleteLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN);
@@ -23,5 +23,5 @@ export const Logout = (): void => {
window.Intercom('shutdown');
}
safeNavigateNoSameURLMemo(ROUTES.LOGIN);
history.push(ROUTES.LOGIN);
};

View File

@@ -8,9 +8,8 @@ import {
import { useCeleryFilterOptions } from 'components/CeleryTask/useCeleryFilterOptions';
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { useLocation } from 'react-router';
import { useHistory, useLocation } from 'react-router-dom';
interface SelectOptionConfig {
placeholder: string;
@@ -28,7 +27,7 @@ function FilterSelect({
);
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const location = useLocation();
return (
@@ -56,13 +55,7 @@ function FilterSelect({
}
onChange={(value): void => {
handleSearch('');
setQueryParamsFromOptions(
value,
urlQuery,
safeNavigate,
location,
queryParam,
);
setQueryParamsFromOptions(value, urlQuery, history, location, queryParam);
}}
/>
);

View File

@@ -3,9 +3,8 @@ import './CeleryTaskConfigOptions.styles.scss';
import { Select, Spin, Typography } from 'antd';
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { useLocation } from 'react-router';
import { useHistory, useLocation } from 'react-router-dom';
import {
getValuesFromQueryParams,
@@ -17,7 +16,7 @@ function CeleryTaskConfigOptions(): JSX.Element {
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
'celery.task_name',
);
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const location = useLocation();
const urlQuery = useUrlQuery();
@@ -53,7 +52,7 @@ function CeleryTaskConfigOptions(): JSX.Element {
setQueryParamsFromOptions(
value,
urlQuery,
safeNavigate,
history,
location,
QueryParams.taskName,
);

View File

@@ -7,13 +7,12 @@ import { ViewMenuAction } from 'container/GridCardLayout/config';
import GridCard from 'container/GridCardLayout/GridCard';
import { Card } from 'container/GridCardLayout/styles';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { isEmpty } from 'lodash-es';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import { useHistory, useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
@@ -50,7 +49,7 @@ function CeleryTaskBar({
queryEnabled: boolean;
checkIfDataExists?: (isDataAvailable: boolean) => void;
}): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const { pathname } = useLocation();
const dispatch = useDispatch();
const urlQuery = useUrlQuery();
@@ -68,13 +67,13 @@ function CeleryTaskBar({
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, urlQuery, safeNavigate],
[dispatch, history, pathname, urlQuery],
);
const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All);

View File

@@ -5,13 +5,12 @@ import { ViewMenuAction } from 'container/GridCardLayout/config';
import GridCard from 'container/GridCardLayout/GridCard';
import { Card } from 'container/GridCardLayout/styles';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router';
import { useHistory, useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
@@ -54,7 +53,7 @@ function CeleryTaskGraph({
checkIfDataExists?: (isDataAvailable: boolean) => void;
analyticsEvent?: string;
}): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const { pathname } = useLocation();
const dispatch = useDispatch();
const urlQuery = useUrlQuery();
@@ -83,13 +82,13 @@ function CeleryTaskGraph({
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, urlQuery, safeNavigate],
[dispatch, history, pathname, urlQuery],
);
return (

View File

@@ -10,13 +10,12 @@ import { Card } from 'container/GridCardLayout/styles';
import { Button } from 'container/MetricsApplication/Tabs/styles';
import { useGraphClickHandler } from 'container/MetricsApplication/Tabs/util';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import { useHistory, useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { DataSource } from 'types/common/queryBuilder';
@@ -48,7 +47,7 @@ function CeleryTaskLatencyGraph({
queryEnabled: boolean;
checkIfDataExists?: (isDataAvailable: boolean) => void;
}): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const { pathname } = useLocation();
const dispatch = useDispatch();
const urlQuery = useUrlQuery();
@@ -80,13 +79,13 @@ function CeleryTaskLatencyGraph({
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, urlQuery, safeNavigate],
[dispatch, history, pathname, urlQuery],
);
const selectedFilters = useMemo(

View File

@@ -1,6 +1,6 @@
import { QueryParams } from 'constants/query';
import { History, Location } from 'history';
import getRenderer from 'lib/uPlotLib/utils/getRenderer';
import type { Location, NavigateFunction } from 'react-router';
import { Widgets } from 'types/api/dashboard/getAll';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
@@ -17,13 +17,13 @@ export function getValuesFromQueryParams(
export function setQueryParamsFromOptions(
value: string[],
urlQuery: URLSearchParams,
safeNavigate: NavigateFunction,
history: History<unknown>,
location: Location<unknown>,
queryParams: QueryParams,
): void {
urlQuery.set(queryParams, value.join(','));
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl, { replace: true });
history.replace(generatedUrl);
}
export function getFiltersFromQueryParams(

View File

@@ -6,7 +6,7 @@ import { useNotifications } from 'hooks/useNotifications';
import { CreditCard, X } from 'lucide-react';
import { useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';

View File

@@ -26,7 +26,7 @@ import {
useMemo,
useState,
} from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { popupContainer } from 'utils/selectPopupContainer';
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';

View File

@@ -14,7 +14,7 @@ import {
import { Clock, PenLine } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction, useMemo } from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import RangePickerModal from './RangePickerModal';
import TimezonePicker from './TimezonePicker';

View File

@@ -194,7 +194,7 @@ function ExplorerCard({
showSearch
placeholder="Select a view"
dropdownStyle={DropDownOverlay}
popupMatchSelectWidth={false}
dropdownMatchSelectWidth={false}
optionLabelProp="value"
value={viewName || undefined}
>

View File

@@ -13,7 +13,7 @@ import { CreditCard, HelpCircle, X } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';

View File

@@ -1,16 +1,16 @@
import { ComponentType, lazy, LazyExoticComponent } from 'react';
import { lazyRetry } from 'utils/lazyWithRetries';
type LazyComponent = ComponentType<Record<string, unknown>>;
type LoadableProps = Promise<{
default: LazyComponent;
}>;
function Loadable(importPath: {
(): LoadableProps;
}): LazyExoticComponent<LazyComponent> {
return lazy(() => lazyRetry(() => importPath()));
}
type LazyComponent = ComponentType<Record<string, unknown>>;
type LoadableProps = Promise<{
default: LazyComponent;
}>;
export default Loadable;

View File

@@ -15,15 +15,15 @@ import {
import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { History } from 'history';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { Bolt, Check, OctagonAlert, X } from 'lucide-react';
import {
KAFKA_SETUP_DOC_LINK,
MessagingQueueHealthCheckService,
} from 'pages/MessagingQueues/MessagingQueuesUtils';
import { ReactNode, useEffect, useState } from 'react';
import type { NavigateFunction } from 'react-router';
import { useHistory } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
interface AttributeCheckListProps {
@@ -46,7 +46,7 @@ export enum AttributesFilters {
function ErrorTitleAndKey({
title,
parentTitle,
safeNavigate,
history,
isCloudUserVal,
errorMsg,
isLeaf,
@@ -54,7 +54,7 @@ function ErrorTitleAndKey({
title: string;
parentTitle: string;
isCloudUserVal: boolean;
safeNavigate: NavigateFunction;
history: History<unknown>;
errorMsg?: string;
isLeaf?: boolean;
}): TreeDataNode {
@@ -76,7 +76,7 @@ function ErrorTitleAndKey({
}
if (isCloudUserVal && !!link) {
safeNavigate(link);
history.push(link);
} else {
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
}
@@ -146,7 +146,7 @@ function generateTreeDataNodes(
response: OnboardingStatusResponse['data'],
parentTitle: string,
isCloudUserVal: boolean,
safeNavigate: NavigateFunction,
history: History<unknown>,
): TreeDataNode[] {
return response
.map((item) => {
@@ -159,7 +159,7 @@ function generateTreeDataNodes(
title: item.attribute,
errorMsg: item.error_message || '',
parentTitle,
safeNavigate,
history,
isCloudUserVal,
});
}
@@ -182,7 +182,7 @@ function AttributeCheckList({
setFilter(value);
};
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
useEffect(() => {
const filteredData = onboardingStatusResponses.map((response) => {
@@ -192,7 +192,7 @@ function AttributeCheckList({
errorMsg: response.errorMsg,
isLeaf: true,
parentTitle: response.title,
safeNavigate,
history,
isCloudUserVal,
});
}
@@ -210,7 +210,7 @@ function AttributeCheckList({
filteredData,
response.title,
isCloudUserVal,
safeNavigate,
history,
),
};
});

View File

@@ -1,4 +1,4 @@
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
export const Button = styled(Link)`

View File

@@ -6,6 +6,7 @@ import {
VerticalAlignTopOutlined,
} from '@ant-design/icons';
import { Tooltip, Typography } from 'antd';
import TypicalOverlayScrollbar from 'components/TypicalOverlayScrollbar/TypicalOverlayScrollbar';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { cloneDeep, isFunction } from 'lodash-es';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -68,10 +69,14 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
<section className="header">
<section className="left-actions">
<FilterOutlined />
<Typography.Text className="text">Filters for</Typography.Text>
<Tooltip title={`Filter currently in sync with query ${lastQueryName}`}>
<Typography.Text className="sync-tag">{lastQueryName}</Typography.Text>
</Tooltip>
<Typography.Text className="text">
{lastQueryName ? 'Filters for' : 'Filters'}
</Typography.Text>
{lastQueryName && (
<Tooltip title={`Filter currently in sync with query ${lastQueryName}`}>
<Typography.Text className="sync-tag">{lastQueryName}</Typography.Text>
</Tooltip>
)}
</section>
<section className="right-actions">
@@ -89,31 +94,33 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
</section>
)}
<section className="filters">
{config.map((filter) => {
switch (filter.type) {
case FiltersType.CHECKBOX:
return (
<Checkbox
source={source}
filter={filter}
onFilterChange={onFilterChange}
/>
);
case FiltersType.SLIDER:
return <Slider filter={filter} />;
// eslint-disable-next-line sonarjs/no-duplicated-branches
default:
return (
<Checkbox
source={source}
filter={filter}
onFilterChange={onFilterChange}
/>
);
}
})}
</section>
<TypicalOverlayScrollbar>
<section className="filters">
{config.map((filter) => {
switch (filter.type) {
case FiltersType.CHECKBOX:
return (
<Checkbox
source={source}
filter={filter}
onFilterChange={onFilterChange}
/>
);
case FiltersType.SLIDER:
return <Slider filter={filter} />;
// eslint-disable-next-line sonarjs/no-duplicated-branches
default:
return (
<Checkbox
source={source}
filter={filter}
onFilterChange={onFilterChange}
/>
);
}
})}
</section>
</TypicalOverlayScrollbar>
</div>
);
}

View File

@@ -0,0 +1,111 @@
import '@testing-library/jest-dom';
import { fireEvent, render, screen } from '@testing-library/react';
import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import QuickFilters from '../QuickFilters';
import { QuickFiltersSource } from '../types';
import { QuickFiltersConfig } from './constants';
// Mock the useQueryBuilder hook
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
useQueryBuilder: jest.fn(),
}));
// Mock the useGetAggregateValues hook
jest.mock('hooks/queryBuilder/useGetAggregateValues', () => ({
useGetAggregateValues: jest.fn(),
}));
const handleFilterVisibilityChange = jest.fn();
const redirectWithQueryBuilderData = jest.fn();
function TestQuickFilters(): JSX.Element {
return (
<MockQueryClientProvider>
<QuickFilters
source={QuickFiltersSource.EXCEPTIONS}
config={QuickFiltersConfig}
handleFilterVisibilityChange={handleFilterVisibilityChange}
/>
</MockQueryClientProvider>
);
}
describe('Quick Filters', () => {
beforeEach(() => {
// Provide a mock implementation for useQueryBuilder
(useQueryBuilder as jest.Mock).mockReturnValue({
currentQuery: {
builder: {
queryData: [
{
queryName: 'Test Query',
filters: { items: [{ key: 'test', value: 'value' }] },
},
],
},
},
lastUsedQuery: 0,
redirectWithQueryBuilderData,
});
// Provide a mock implementation for useGetAggregateValues
(useGetAggregateValues as jest.Mock).mockReturnValue({
data: {
statusCode: 200,
error: null,
message: 'success',
payload: {
stringAttributeValues: [
'mq-kafka',
'otel-demo',
'otlp-python',
'sample-flask',
],
numberAttributeValues: null,
boolAttributeValues: null,
},
}, // Mocked API response
isLoading: false,
});
});
it('renders correctly with default props', () => {
const { container } = render(<TestQuickFilters />);
expect(container).toMatchSnapshot();
});
it('displays the correct query name in the header', () => {
render(<TestQuickFilters />);
expect(screen.getByText('Filters for')).toBeInTheDocument();
expect(screen.getByText('Test Query')).toBeInTheDocument();
});
it('should add filter data to query when checkbox is clicked', () => {
render(<TestQuickFilters />);
const checkbox = screen.getByText('mq-kafka');
fireEvent.click(checkbox);
expect(redirectWithQueryBuilderData).toHaveBeenCalledWith(
expect.objectContaining({
builder: {
queryData: expect.arrayContaining([
expect.objectContaining({
filters: expect.objectContaining({
items: expect.arrayContaining([
expect.objectContaining({
key: expect.objectContaining({
key: 'deployment.environment',
}),
value: 'mq-kafka',
}),
]),
}),
}),
]),
},
}),
); // sets composite query param
});
});

View File

@@ -0,0 +1,382 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Quick Filters renders correctly with default props 1`] = `
<div>
<div
class="quick-filters"
>
<section
class="header"
>
<section
class="left-actions"
>
<span
aria-label="filter"
class="anticon anticon-filter"
role="img"
>
<svg
aria-hidden="true"
data-icon="filter"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V597.4L907.7 202c12.2-21.3-3.1-48-27.6-48zM603.4 798H420.6V642h182.9v156zm9.6-236.6l-9.5 16.6h-183l-9.5-16.6L212.7 226h598.6L613 561.4z"
/>
</svg>
</span>
<span
class="ant-typography text css-dev-only-do-not-override-2i2tap"
>
Filters for
</span>
<span
class="ant-typography sync-tag css-dev-only-do-not-override-2i2tap"
>
Test Query
</span>
</section>
<section
class="right-actions"
>
<span
aria-label="sync"
class="anticon anticon-sync sync-icon"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="sync"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M168 504.2c1-43.7 10-86.1 26.9-126 17.3-41 42.1-77.7 73.7-109.4S337 212.3 378 195c42.4-17.9 87.4-27 133.9-27s91.5 9.1 133.8 27A341.5 341.5 0 01755 268.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.7 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c0-6.7-7.7-10.5-12.9-6.3l-56.4 44.1C765.8 155.1 646.2 92 511.8 92 282.7 92 96.3 275.6 92 503.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8zm756 7.8h-60c-4.4 0-7.9 3.5-8 7.8-1 43.7-10 86.1-26.9 126-17.3 41-42.1 77.8-73.7 109.4A342.45 342.45 0 01512.1 856a342.24 342.24 0 01-243.2-100.8c-9.9-9.9-19.2-20.4-27.8-31.4l60.2-47a8 8 0 00-3-14.1l-175.7-43c-5-1.2-9.9 2.6-9.9 7.7l-.7 181c0 6.7 7.7 10.5 12.9 6.3l56.4-44.1C258.2 868.9 377.8 932 512.2 932c229.2 0 415.5-183.7 419.8-411.8a8 8 0 00-8-8.2z"
/>
</svg>
</span>
<div
class="divider-filter"
/>
<span
aria-label="vertical-align-top"
class="anticon anticon-vertical-align-top"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="vertical-align-top"
fill="currentColor"
focusable="false"
height="1em"
style="transform: rotate(270deg);"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z"
/>
</svg>
</span>
</section>
</section>
<div
class="overlay-scrollbar"
data-overlayscrollbars-initialize="true"
>
<div
data-overlayscrollbars-contents=""
>
<section
class="filters"
>
<div
class="checkbox-filter"
>
<section
class="filter-header-checkbox"
>
<section
class="left-action"
>
<svg
class="lucide lucide-chevron-down"
cursor="pointer"
fill="none"
height="13"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="13"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m6 9 6 6 6-6"
/>
</svg>
<span
class="ant-typography title css-dev-only-do-not-override-2i2tap"
>
Environment
</span>
</section>
<section
class="right-action"
>
<span
class="ant-typography clear-all css-dev-only-do-not-override-2i2tap"
>
Clear All
</span>
</section>
</section>
<section
class="search"
>
<input
class="ant-input css-dev-only-do-not-override-2i2tap"
placeholder="Filter values"
type="text"
value=""
/>
</section>
<section
class="values"
>
<div
class="value"
>
<label
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked check-box css-dev-only-do-not-override-2i2tap"
>
<span
class="ant-checkbox ant-wave-target css-dev-only-do-not-override-2i2tap ant-checkbox-checked"
>
<input
checked=""
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<div
class="checkbox-value-section"
>
<span
class="ant-typography ant-typography-ellipsis ant-typography-single-line ant-typography-ellipsis-single-line value-string css-dev-only-do-not-override-2i2tap"
>
mq-kafka
</span>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text only-btn"
type="button"
>
<span>
Only
</span>
</button>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text toggle-btn"
type="button"
>
<span>
Toggle
</span>
</button>
</div>
</div>
<div
class="value"
>
<label
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked check-box css-dev-only-do-not-override-2i2tap"
>
<span
class="ant-checkbox ant-wave-target css-dev-only-do-not-override-2i2tap ant-checkbox-checked"
>
<input
checked=""
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<div
class="checkbox-value-section"
>
<span
class="ant-typography ant-typography-ellipsis ant-typography-single-line ant-typography-ellipsis-single-line value-string css-dev-only-do-not-override-2i2tap"
>
otel-demo
</span>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text only-btn"
type="button"
>
<span>
Only
</span>
</button>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text toggle-btn"
type="button"
>
<span>
Toggle
</span>
</button>
</div>
</div>
<div
class="value"
>
<label
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked check-box css-dev-only-do-not-override-2i2tap"
>
<span
class="ant-checkbox ant-wave-target css-dev-only-do-not-override-2i2tap ant-checkbox-checked"
>
<input
checked=""
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<div
class="checkbox-value-section"
>
<span
class="ant-typography ant-typography-ellipsis ant-typography-single-line ant-typography-ellipsis-single-line value-string css-dev-only-do-not-override-2i2tap"
>
otlp-python
</span>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text only-btn"
type="button"
>
<span>
Only
</span>
</button>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text toggle-btn"
type="button"
>
<span>
Toggle
</span>
</button>
</div>
</div>
<div
class="value"
>
<label
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked check-box css-dev-only-do-not-override-2i2tap"
>
<span
class="ant-checkbox ant-wave-target css-dev-only-do-not-override-2i2tap ant-checkbox-checked"
>
<input
checked=""
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
<div
class="checkbox-value-section"
>
<span
class="ant-typography ant-typography-ellipsis ant-typography-single-line ant-typography-ellipsis-single-line value-string css-dev-only-do-not-override-2i2tap"
>
sample-flask
</span>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text only-btn"
type="button"
>
<span>
Only
</span>
</button>
<button
class="ant-btn css-dev-only-do-not-override-2i2tap ant-btn-text toggle-btn"
type="button"
>
<span>
Toggle
</span>
</button>
</div>
</div>
</section>
</div>
<div
class="checkbox-filter"
>
<section
class="filter-header-checkbox"
>
<section
class="left-action"
>
<svg
class="lucide lucide-chevron-right"
cursor="pointer"
fill="none"
height="13"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="13"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m9 18 6-6-6-6"
/>
</svg>
<span
class="ant-typography title css-dev-only-do-not-override-2i2tap"
>
Service Name
</span>
</section>
<section
class="right-action"
/>
</section>
</div>
</section>
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,30 @@
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { FiltersType } from '../types';
export const QuickFiltersConfig = [
{
type: FiltersType.CHECKBOX,
title: 'Environment',
attributeKey: {
key: 'deployment.environment',
dataType: DataTypes.String,
type: 'resource',
isColumn: false,
isJSON: false,
},
defaultOpen: true,
},
{
type: FiltersType.CHECKBOX,
title: 'Service Name',
attributeKey: {
key: 'service.name',
dataType: DataTypes.String,
type: 'resource',
isColumn: false,
isJSON: false,
},
defaultOpen: false,
},
];

View File

@@ -40,4 +40,5 @@ export enum QuickFiltersSource {
INFRA_MONITORING = 'infra-monitoring',
TRACES_EXPLORER = 'traces-explorer',
API_MONITORING = 'api-monitoring',
EXCEPTIONS = 'exceptions',
}

View File

@@ -1,5 +1,4 @@
import { Tabs, TabsProps } from 'antd';
import { safeNavigateNoSameURLMemo } from 'utils/navigate';
import { RouteTabProps } from './types';
@@ -7,6 +6,7 @@ function RouteTab({
routes,
activeKey,
onChangeHandler,
history,
...rest
}: RouteTabProps & TabsProps): JSX.Element {
const onChange = (activeRoute: string): void => {
@@ -17,7 +17,7 @@ function RouteTab({
const selectedRoute = routes.find((e) => e.key === activeRoute);
if (selectedRoute) {
safeNavigateNoSameURLMemo(selectedRoute.route);
history.push(selectedRoute.route);
}
};

View File

@@ -1,5 +1,5 @@
import { TabsProps } from 'antd';
// import type { NavigateFunction } from 'react-router';
import { History } from 'history';
export type TabRoutes = {
name: React.ReactNode;
@@ -12,5 +12,5 @@ export interface RouteTabProps {
routes: TabRoutes[];
activeKey: TabsProps['activeKey'];
onChangeHandler?: (key: string) => void;
// safeNavigate: NavigateFunction;
history: History<unknown>;
}

View File

@@ -27,4 +27,5 @@ export enum LOCALSTORAGE {
CELERY_OVERVIEW_COLUMNS = 'CELERY_OVERVIEW_COLUMNS',
DONT_SHOW_SLOW_API_WARNING = 'DONT_SHOW_SLOW_API_WARNING',
METRICS_LIST_OPTIONS = 'METRICS_LIST_OPTIONS',
SHOW_EXCEPTIONS_QUICK_FILTERS = 'SHOW_EXCEPTIONS_QUICK_FILTERS',
}

View File

@@ -398,6 +398,23 @@ export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
],
};
export enum OperatorConfigKeys {
'EXCEPTIONS' = 'EXCEPTIONS',
}
export const OPERATORS_CONFIG = {
[OperatorConfigKeys.EXCEPTIONS]: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.IN,
OPERATORS.NIN,
OPERATORS.EXISTS,
OPERATORS.NOT_EXISTS,
OPERATORS.CONTAINS,
OPERATORS.NOT_CONTAINS,
],
};
export const HAVING_OPERATORS: string[] = [
OPERATORS['='],
OPERATORS['!='],

View File

@@ -16,3 +16,51 @@ export const OperatorConversions: Array<{
traceValue: 'NotIn',
},
];
// mapping from qb to exceptions
export const CompositeQueryOperatorsConfig: Array<{
label: string;
metricValue: string;
traceValue: OperatorValues;
}> = [
{
label: 'in',
metricValue: '=~',
traceValue: 'In',
},
{
label: 'nin',
metricValue: '!~',
traceValue: 'NotIn',
},
{
label: '=',
metricValue: '=',
traceValue: 'Equals',
},
{
label: '!=',
metricValue: '!=',
traceValue: 'NotEquals',
},
{
label: 'exists',
metricValue: '=~',
traceValue: 'Exists',
},
{
label: 'nexists',
metricValue: '!~',
traceValue: 'NotExists',
},
{
label: 'contains',
metricValue: '=~',
traceValue: 'Contains',
},
{
label: 'ncontains',
metricValue: '!~',
traceValue: 'NotContains',
},
];

View File

@@ -7,7 +7,7 @@ import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { DraftingCompass } from 'lucide-react';
import React from 'react';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
type Props = {
children: React.ReactNode;

View File

@@ -3,10 +3,10 @@ import './TopContributorsCard.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { ArrowRight } from 'lucide-react';
import { useMemo, useState } from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import TopContributorsContent from './TopContributorsContent';
import { TopContributorsCardProps } from './types';
@@ -17,7 +17,6 @@ function TopContributorsCard({
totalCurrentTriggers,
}: TopContributorsCardProps): JSX.Element {
const { search } = useLocation();
const { safeNavigate } = useSafeNavigate();
const searchParams = useMemo(() => new URLSearchParams(search), [search]);
const viewAllTopContributorsParam = searchParams.get('viewAllTopContributors');
@@ -44,7 +43,7 @@ function TopContributorsCard({
return newState;
});
safeNavigate({ search: searchParams.toString() });
history.push({ search: searchParams.toString() });
};
return (

View File

@@ -3,8 +3,8 @@ import Uplot from 'components/Uplot';
import { QueryParams } from 'constants/query';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
import { uPlotXAxisValuesFormat } from 'lib/uPlotLib/utils/constants';
@@ -28,8 +28,6 @@ function HorizontalTimelineGraph({
isDarkMode: boolean;
data: AlertRuleTimelineGraphResponse[];
}): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const transformedData: AlignedData = useMemo(() => {
if (!data?.length) {
return [[], []];
@@ -104,7 +102,7 @@ function HorizontalTimelineGraph({
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
safeNavigate({
history.push({
search: urlQuery.toString(),
});
}
@@ -133,7 +131,6 @@ function HorizontalTimelineGraph({
urlQuery,
dispatch,
timezone.value,
safeNavigate,
],
);
return <Uplot data={transformedData} options={options} />;

View File

@@ -2,11 +2,11 @@ import './TabsAndFilters.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { TimelineFilter, TimelineTab } from 'container/AlertHistory/types';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { Info } from 'lucide-react';
import Tabs2 from 'periscope/components/Tabs2';
import { useMemo } from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
function ComingSoon(): JSX.Element {
return (
@@ -41,8 +41,6 @@ function TimelineTabs(): JSX.Element {
function TimelineFilters(): JSX.Element {
const { search } = useLocation();
const { safeNavigate } = useSafeNavigate();
const searchParams = useMemo(() => new URLSearchParams(search), [search]);
const initialSelectedTab = useMemo(
@@ -52,7 +50,7 @@ function TimelineFilters(): JSX.Element {
const handleFilter = (value: TimelineFilter): void => {
searchParams.set('timelineFilter', value);
safeNavigate({ search: searchParams.toString() });
history.push({ search: searchParams.toString() });
};
const tabs = [

View File

@@ -5,11 +5,11 @@ import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router';
import { generatePath } from 'react-router-dom';
import { Channels, PayloadProps } from 'types/api/channels/getAll';
import Delete from './Delete';
@@ -17,22 +17,17 @@ import Delete from './Delete';
function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
const { t } = useTranslation(['channels']);
const { notifications } = useNotifications();
const { safeNavigate } = useSafeNavigate();
const [channels, setChannels] = useState<Channels[]>(allChannels);
const { user } = useAppContext();
const [action] = useComponentPermission(['new_alert_action'], user.role);
const onClickEditHandler = useCallback(
(id: string) => {
safeNavigate(
generatePath(ROUTES.CHANNELS_EDIT, {
id,
}),
{ replace: true },
);
},
[safeNavigate],
);
const onClickEditHandler = useCallback((id: string) => {
history.replace(
generatePath(ROUTES.CHANNELS_EDIT, {
id,
}),
);
}, []);
const columns: ColumnsType<Channels> = [
{

View File

@@ -7,7 +7,7 @@ import TextToolTip from 'components/TextToolTip';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect } from 'react';
@@ -21,14 +21,13 @@ const { Paragraph } = Typography;
function AlertChannels(): JSX.Element {
const { t } = useTranslation(['channels']);
const { user } = useAppContext();
const { safeNavigate } = useSafeNavigate();
const [addNewChannelPermission] = useComponentPermission(
['add_new_channel'],
user.role,
);
const onToggleHandler = useCallback(() => {
safeNavigate(ROUTES.CHANNELS_NEW);
}, [safeNavigate]);
history.push(ROUTES.CHANNELS_NEW);
}, []);
const { loading, payload, error, errorMessage } = useFetch(getAll);

View File

@@ -18,20 +18,21 @@ import getErrorCounts from 'api/errors/getErrorCounts';
import { ResizeTable } from 'components/ResizeTable';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import ROUTES from 'constants/routes';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useNotifications } from 'hooks/useNotifications';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { convertCompositeQueryToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
import { Link, useLocation } from 'react-router';
import { Link, useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Exception, PayloadProps } from 'types/api/errors/getAll';
@@ -66,7 +67,6 @@ function AllErrors(): JSX.Element {
(state) => state.globalTime,
);
const { pathname } = useLocation();
const { safeNavigate } = useSafeNavigate();
const params = useUrlQuery();
const { t } = useTranslation(['common']);
const {
@@ -110,10 +110,11 @@ function AllErrors(): JSX.Element {
);
const { queries } = useResourceAttribute();
const compositeData = useGetCompositeQueryParam();
const [{ isLoading, data }, errorCountResponse] = useQueries([
{
queryKey: ['getAllErrors', updatedPath, maxTime, minTime, queries],
queryKey: ['getAllErrors', updatedPath, maxTime, minTime, compositeData],
queryFn: (): Promise<SuccessResponse<PayloadProps> | ErrorResponse> =>
getAll({
end: maxTime,
@@ -124,7 +125,9 @@ function AllErrors(): JSX.Element {
orderParam: getUpdatedParams,
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
tags: convertRawQueriesToTraceSelectedTags(queries),
tags: convertCompositeQueryToTraceSelectedTags(
compositeData?.builder.queryData?.[0]?.filters.items,
),
}),
enabled: !loading,
},
@@ -135,7 +138,7 @@ function AllErrors(): JSX.Element {
minTime,
getUpdatedExceptionType,
getUpdatedServiceName,
queries,
compositeData,
],
queryFn: (): Promise<ErrorResponse | SuccessResponse<number>> =>
getErrorCounts({
@@ -143,7 +146,9 @@ function AllErrors(): JSX.Element {
start: minTime,
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
tags: convertRawQueriesToTraceSelectedTags(queries),
tags: convertCompositeQueryToTraceSelectedTags(
compositeData?.builder.queryData?.[0]?.filters.items,
),
}),
enabled: !loading,
},
@@ -203,9 +208,7 @@ function AllErrors(): JSX.Element {
queryParams.serviceName = serviceFilterValue;
}
safeNavigate(`${pathname}?${createQueryParams(queryParams)}`, {
replace: true,
});
history.replace(`${pathname}?${createQueryParams(queryParams)}`);
confirm();
},
[
@@ -216,7 +219,6 @@ function AllErrors(): JSX.Element {
getUpdatedServiceName,
pathname,
updatedOrder,
safeNavigate,
],
);
@@ -418,7 +420,7 @@ function AllErrors(): JSX.Element {
serviceName: getFilterString(params.get(urlKey.serviceName)),
exceptionType: getFilterString(params.get(urlKey.exceptionType)),
});
safeNavigate(
history.replace(
`${pathname}?${createQueryParams({
order: updatedOrder,
offset: (current - 1) * pageSize,
@@ -427,19 +429,14 @@ function AllErrors(): JSX.Element {
exceptionType,
serviceName,
})}`,
{ replace: true },
);
}
},
[pathname, safeNavigate],
[pathname],
);
const logEventCalledRef = useRef(false);
useEffect(() => {
if (
!logEventCalledRef.current &&
!isUndefined(errorCountResponse.data?.payload)
) {
if (!isUndefined(errorCountResponse.data?.payload)) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === 'resource_deployment_environment',
)?.tagValue;
@@ -447,9 +444,12 @@ function AllErrors(): JSX.Element {
logEvent('Exception: List page visited', {
numberOfExceptions: errorCountResponse?.data?.payload,
selectedEnvironments,
resourceAttributeUsed: !!queries?.length,
resourceAttributeUsed: !!compositeData?.builder.queryData?.[0]?.filters
.items?.length,
tags: convertCompositeQueryToTraceSelectedTags(
compositeData?.builder.queryData?.[0]?.filters.items,
),
});
logEventCalledRef.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [errorCountResponse.data?.payload]);

View File

@@ -0,0 +1,114 @@
import '@testing-library/jest-dom';
import { fireEvent, render, screen } from '@testing-library/react';
import { ENVIRONMENT } from 'constants/env';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import TimezoneProvider from 'providers/Timezone';
import { Provider, useSelector } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import store from 'store';
import AllErrors from '../index';
import {
INIT_URL_WITH_COMMON_QUERY,
MOCK_ERROR_LIST,
TAG_FROM_QUERY,
} from './constants';
jest.mock('hooks/useResourceAttribute', () =>
jest.fn(() => ({
queries: [],
})),
);
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
}));
function Exceptions({ initUrl }: { initUrl?: string[] }): JSX.Element {
return (
<MemoryRouter initialEntries={initUrl ?? ['/exceptions']}>
<TimezoneProvider>
<Provider store={store}>
<MockQueryClientProvider>
<AllErrors />
</MockQueryClientProvider>
</Provider>
</TimezoneProvider>
</MemoryRouter>
);
}
Exceptions.defaultProps = {
initUrl: ['/exceptions'],
};
const BASE_URL = ENVIRONMENT.baseURL;
const listErrorsURL = `${BASE_URL}/api/v1/listErrors`;
const countErrorsURL = `${BASE_URL}/api/v1/countErrors`;
const postListErrorsSpy = jest.fn();
describe('Exceptions - All Errors', () => {
beforeEach(() => {
(useSelector as jest.Mock).mockReturnValue({
maxTime: 1000,
minTime: 0,
loading: false,
});
server.use(
rest.post(listErrorsURL, async (req, res, ctx) => {
const body = await req.json();
postListErrorsSpy(body);
return res(ctx.status(200), ctx.json(MOCK_ERROR_LIST));
}),
);
server.use(
rest.post(countErrorsURL, (req, res, ctx) =>
res(ctx.status(200), ctx.json(540)),
),
);
});
it('renders correctly with default props', async () => {
render(<Exceptions />);
const item = await screen.findByText(/redis timeout/i);
expect(item).toBeInTheDocument();
});
it('should sort Error Message appropriately', async () => {
render(<Exceptions />);
await screen.findByText(/redis timeout/i);
const caretIconUp = screen.getAllByLabelText('caret-up')[0];
const caretIconDown = screen.getAllByLabelText('caret-down')[0];
// sort by ascending
expect(caretIconUp.className).not.toContain('active');
fireEvent.click(caretIconUp);
expect(caretIconUp.className).toContain('active');
let queryParams = new URLSearchParams(window.location.search);
expect(queryParams.get('order')).toBe('ascending');
expect(queryParams.get('orderParam')).toBe('exceptionType');
// sort by descending
expect(caretIconDown.className).not.toContain('active');
fireEvent.click(caretIconDown);
expect(caretIconDown.className).toContain('active');
queryParams = new URLSearchParams(window.location.search);
expect(queryParams.get('order')).toBe('descending');
});
it('should call useQueries with exact composite query object', async () => {
render(<Exceptions initUrl={[INIT_URL_WITH_COMMON_QUERY]} />);
await screen.findByText(/redis timeout/i);
expect(postListErrorsSpy).toHaveBeenCalledWith(
expect.objectContaining({
tags: TAG_FROM_QUERY,
}),
);
});
});

View File

@@ -0,0 +1,94 @@
export const MOCK_USE_QUERIES_DATA = [
{
isLoading: false,
isError: false,
error: null,
data: {
statusCode: 200,
payload: [
{
exceptionType: '*errors.errorString',
exceptionMessage: 'redis timeout',
exceptionCount: 2510,
lastSeen: '2025-04-14T18:27:57.797616374Z',
firstSeen: '2025-04-14T17:58:00.262775497Z',
serviceName: 'redis-manual',
groupID: '511b9c91a92b9c5166ecb77235f5743b',
},
],
},
},
{
status: 'success',
isLoading: false,
isSuccess: true,
isError: false,
isIdle: false,
data: {
statusCode: 200,
error: null,
payload: 525,
},
dataUpdatedAt: 1744661020341,
error: null,
errorUpdatedAt: 0,
failureCount: 0,
errorUpdateCount: 0,
isFetched: true,
isFetchedAfterMount: true,
isFetching: false,
isRefetching: false,
isLoadingError: false,
isPlaceholderData: false,
isPreviousData: false,
isRefetchError: false,
isStale: true,
},
];
export const INIT_URL_WITH_COMMON_QUERY =
'/exceptions?compositeQuery=%257B%2522queryType%2522%253A%2522builder%2522%252C%2522builder%2522%253A%257B%2522queryData%2522%253A%255B%257B%2522dataSource%2522%253A%2522traces%2522%252C%2522queryName%2522%253A%2522A%2522%252C%2522aggregateOperator%2522%253A%2522noop%2522%252C%2522aggregateAttribute%2522%253A%257B%2522id%2522%253A%2522----resource--false%2522%252C%2522dataType%2522%253A%2522%2522%252C%2522key%2522%253A%2522%2522%252C%2522isColumn%2522%253Afalse%252C%2522type%2522%253A%2522resource%2522%252C%2522isJSON%2522%253Afalse%257D%252C%2522timeAggregation%2522%253A%2522rate%2522%252C%2522spaceAggregation%2522%253A%2522sum%2522%252C%2522functions%2522%253A%255B%255D%252C%2522filters%2522%253A%257B%2522items%2522%253A%255B%257B%2522id%2522%253A%2522db118ac7-9313-4adb-963f-f31b5b32c496%2522%252C%2522op%2522%253A%2522in%2522%252C%2522key%2522%253A%257B%2522key%2522%253A%2522deployment.environment%2522%252C%2522dataType%2522%253A%2522string%2522%252C%2522type%2522%253A%2522resource%2522%252C%2522isColumn%2522%253Afalse%252C%2522isJSON%2522%253Afalse%257D%252C%2522value%2522%253A%2522mq-kafka%2522%257D%255D%252C%2522op%2522%253A%2522AND%2522%257D%252C%2522expression%2522%253A%2522A%2522%252C%2522disabled%2522%253Afalse%252C%2522stepInterval%2522%253A60%252C%2522having%2522%253A%255B%255D%252C%2522limit%2522%253Anull%252C%2522orderBy%2522%253A%255B%255D%252C%2522groupBy%2522%253A%255B%255D%252C%2522legend%2522%253A%2522%2522%252C%2522reduceTo%2522%253A%2522avg%2522%257D%255D%252C%2522queryFormulas%2522%253A%255B%255D%257D%252C%2522promql%2522%253A%255B%257B%2522name%2522%253A%2522A%2522%252C%2522query%2522%253A%2522%2522%252C%2522legend%2522%253A%2522%2522%252C%2522disabled%2522%253Afalse%257D%255D%252C%2522clickhouse_sql%2522%253A%255B%257B%2522name%2522%253A%2522A%2522%252C%2522legend%2522%253A%2522%2522%252C%2522disabled%2522%253Afalse%252C%2522query%2522%253A%2522%2522%257D%255D%252C%2522id%2522%253A%2522dd576d04-0822-476d-b0c2-807a7af2e5e7%2522%257D';
export const extractCompositeQueryObject = (
url: string,
): Record<string, unknown> | null => {
try {
const urlObj = new URL(`http://dummy-base${url}`); // Add dummy base to parse relative URL
const encodedParam = urlObj.searchParams.get('compositeQuery');
if (!encodedParam) return null;
// Decode twice
const firstDecode = decodeURIComponent(encodedParam);
const secondDecode = decodeURIComponent(firstDecode);
// Parse JSON
return JSON.parse(secondDecode);
} catch (err) {
console.error('Failed to extract compositeQuery:', err);
return null;
}
};
export const TAG_FROM_QUERY = [
{
BoolValues: [],
Key: 'deployment.environment',
NumberValues: [],
Operator: 'In',
StringValues: ['mq-kafka'],
TagType: 'ResourceAttribute',
},
];
export const MOCK_ERROR_LIST = [
{
exceptionType: '*errors.errorString',
exceptionMessage: 'redis timeout',
exceptionCount: 2510,
lastSeen: '2025-04-14T18:27:57.797616374Z',
firstSeen: '2025-04-14T17:58:00.262775497Z',
serviceName: 'redis-manual',
groupID: '511b9c91a92b9c5166ecb77235f5743b',
},
];

View File

@@ -25,7 +25,7 @@ import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { INTEGRATION_TYPES } from 'pages/Integrations/utils';
@@ -42,7 +42,7 @@ import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueries } from 'react-query';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
import {
@@ -121,7 +121,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
});
const isDarkMode = useIsDarkMode();
const { safeNavigate } = useSafeNavigate();
const { pathname } = useLocation();
const { t } = useTranslation(['titles']);
@@ -320,9 +319,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const handleUpgrade = useCallback((): void => {
if (user.role === USER_ROLES.ADMIN) {
safeNavigate(ROUTES.BILLING);
history.push(ROUTES.BILLING);
}
}, [user.role, safeNavigate]);
}, [user.role]);
const handleFailedPayment = useCallback((): void => {
manageCreditCard({
@@ -340,6 +339,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const isApiMonitoringView = (): boolean => routeKey === 'API_MONITORING';
const isExceptionsView = (): boolean => routeKey === 'ALL_ERROR';
const isTracesView = (): boolean =>
routeKey === 'TRACES_EXPLORER' || routeKey === 'TRACES_SAVE_VIEWS';
@@ -662,7 +663,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
isMessagingQueues() ||
isCloudIntegrationPage() ||
isInfraMonitoring() ||
isApiMonitoringView()
isApiMonitoringView() ||
isExceptionsView()
? 0
: '0 1rem',

View File

@@ -3,7 +3,7 @@ import './Header.styles.scss';
import Breadcrumb from 'antd/es/breadcrumb';
import ROUTES from 'constants/routes';
import { Blocks, LifeBuoy } from 'lucide-react';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
function Header(): JSX.Element {
return (

View File

@@ -5,10 +5,10 @@ import { Button, Select, Skeleton } from 'antd';
import { SelectProps } from 'antd/lib';
import logEvent from 'api/common/logEvent';
import { useAwsAccounts } from 'hooks/integration/aws/useAwsAccounts';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { Check, ChevronDown } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { CloudAccount } from '../../ServicesSection/types';
import AccountSettingsModal from './AccountSettingsModal';
@@ -140,7 +140,7 @@ function AccountActionsRenderer({
function AccountActions(): JSX.Element {
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const navigate = useNavigate();
const { data: accounts, isLoading } = useAwsAccounts();
const initialAccount = useMemo(
@@ -162,7 +162,7 @@ function AccountActions(): JSX.Element {
setActiveAccount(initialAccount);
const latestUrlQuery = new URLSearchParams(window.location.search);
latestUrlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
safeNavigate({ search: latestUrlQuery.toString() });
navigate({ search: latestUrlQuery.toString() });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialAccount]);
@@ -216,7 +216,7 @@ function AccountActions(): JSX.Element {
if (accounts) {
setActiveAccount(getAccountById(accounts, value));
urlQuery.set('cloudAccountId', value);
safeNavigate({ search: urlQuery.toString() });
navigate({ search: urlQuery.toString() });
}
}}
onIntegrationModalOpen={startAccountConnectionAttempt}

View File

@@ -7,8 +7,8 @@ import {
getRegionPreviewText,
useAccountSettingsModal,
} from 'hooks/integration/aws/useAccountSettingsModal';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { Dispatch, SetStateAction, useCallback } from 'react';
import { useQueryClient } from 'react-query';
@@ -45,13 +45,12 @@ function AccountSettingsModal({
const queryClient = useQueryClient();
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const handleRemoveIntegrationAccountSuccess = (): void => {
queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]);
urlQuery.delete('cloudAccountId');
handleClose();
safeNavigate({ search: urlQuery.toString() });
history.replace({ search: urlQuery.toString() });
logEvent('AWS Integration: Account removed', {
id: account?.id,

View File

@@ -1,4 +1,4 @@
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import { ServiceData } from './types';

View File

@@ -1,8 +1,8 @@
import Spinner from 'components/Spinner';
import { useGetAccountServices } from 'hooks/integration/aws/useGetAccountServices';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { useCallback, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import ServiceItem from './ServiceItem';
@@ -16,7 +16,7 @@ function ServicesList({
filter,
}: ServicesListProps): JSX.Element {
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const navigate = useNavigate();
const { data: services = [], isLoading } = useGetAccountServices(
cloudAccountId,
);
@@ -26,9 +26,9 @@ function ServicesList({
(serviceId: string): void => {
const latestUrlQuery = new URLSearchParams(window.location.search);
latestUrlQuery.set('service', serviceId);
safeNavigate({ search: latestUrlQuery.toString() });
navigate({ search: latestUrlQuery.toString() });
},
[safeNavigate],
[navigate],
);
const filteredServices = useMemo(() => {

View File

@@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
const isInternalLink = href.startsWith('/');

View File

@@ -15,7 +15,7 @@ import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -86,7 +86,6 @@ function CreateAlertChannels({
const [savingState, setSavingState] = useState<boolean>(false);
const [testingState, setTestingState] = useState<boolean>(false);
const { notifications } = useNotifications();
const { safeNavigate } = useSafeNavigate();
const [type, setType] = useState<ChannelType>(preType);
const onTypeChangeHandler = useCallback(
@@ -144,7 +143,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
@@ -164,7 +163,7 @@ function CreateAlertChannels({
} finally {
setSavingState(false);
}
}, [prepareSlackRequest, t, notifications, safeNavigate]);
}, [prepareSlackRequest, t, notifications]);
const prepareWebhookRequest = useCallback(() => {
// initial api request without auth params
@@ -211,7 +210,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
@@ -231,7 +230,7 @@ function CreateAlertChannels({
} finally {
setSavingState(false);
}
}, [prepareWebhookRequest, t, notifications, safeNavigate]);
}, [prepareWebhookRequest, t, notifications]);
const preparePagerRequest = useCallback(() => {
const validationError = ValidatePagerChannel(selectedConfig as PagerChannel);
@@ -272,7 +271,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
@@ -298,7 +297,7 @@ function CreateAlertChannels({
} finally {
setSavingState(false);
}
}, [t, notifications, preparePagerRequest, safeNavigate]);
}, [t, notifications, preparePagerRequest]);
const prepareOpsgenieRequest = useCallback(
() => ({
@@ -323,7 +322,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
@@ -343,7 +342,7 @@ function CreateAlertChannels({
} finally {
setSavingState(false);
}
}, [prepareOpsgenieRequest, t, notifications, safeNavigate]);
}, [prepareOpsgenieRequest, t, notifications]);
const prepareEmailRequest = useCallback(
() => ({
@@ -366,7 +365,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
@@ -386,7 +385,7 @@ function CreateAlertChannels({
} finally {
setSavingState(false);
}
}, [prepareEmailRequest, t, notifications, safeNavigate]);
}, [prepareEmailRequest, t, notifications]);
const prepareMsTeamsRequest = useCallback(
() => ({
@@ -410,7 +409,7 @@ function CreateAlertChannels({
message: 'Success',
description: t('channel_creation_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
@@ -430,7 +429,7 @@ function CreateAlertChannels({
} finally {
setSavingState(false);
}
}, [prepareMsTeamsRequest, t, notifications, safeNavigate]);
}, [prepareMsTeamsRequest, t, notifications]);
const onSaveHandler = useCallback(
async (value: ChannelType) => {

View File

@@ -4,9 +4,9 @@ import { ENTITY_VERSION_V4 } from 'constants/app';
import { QueryParams } from 'constants/query';
import FormAlertRules, { AlertDetectionTypes } from 'container/FormAlertRules';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
@@ -24,7 +24,6 @@ function CreateRules(): JSX.Element {
const [initValues, setInitValues] = useState<AlertDef | null>(null);
const location = useLocation();
const { safeNavigate } = useSafeNavigate();
const queryParams = new URLSearchParams(location.search);
const alertTypeFromURL = queryParams.get(QueryParams.ruleType);
const version = queryParams.get('version');
@@ -97,7 +96,7 @@ function CreateRules(): JSX.Element {
}
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
safeNavigate(generatedUrl, { replace: true });
history.replace(generatedUrl);
};
useEffect(() => {

View File

@@ -25,10 +25,10 @@ import {
} from 'container/CreateAlertChannels/config';
import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
function EditAlertChannels({
initialValue,
@@ -52,9 +52,7 @@ function EditAlertChannels({
const [savingState, setSavingState] = useState<boolean>(false);
const [testingState, setTestingState] = useState<boolean>(false);
const { notifications } = useNotifications();
const { safeNavigate } = useSafeNavigate();
// Temp: Hard type casting for string | undefined
const { id } = useParams() as { id: string };
const { id } = useParams<{ id: string }>();
const [type, setType] = useState<ChannelType>(
initialValue?.type ? (initialValue.type as ChannelType) : ChannelType.Slack,
@@ -103,7 +101,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
@@ -115,7 +113,7 @@ function EditAlertChannels({
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareSlackRequest, t, notifications, selectedConfig, safeNavigate]);
}, [prepareSlackRequest, t, notifications, selectedConfig]);
const prepareWebhookRequest = useCallback(() => {
const { name, username, password } = selectedConfig;
@@ -160,7 +158,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
}
showError(response.error || t('channel_edit_failed'));
@@ -170,7 +168,7 @@ function EditAlertChannels({
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareWebhookRequest, t, notifications, selectedConfig, safeNavigate]);
}, [prepareWebhookRequest, t, notifications, selectedConfig]);
const prepareEmailRequest = useCallback(
() => ({
@@ -193,7 +191,7 @@ function EditAlertChannels({
message: 'Success',
description: t('channel_edit_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
@@ -206,7 +204,7 @@ function EditAlertChannels({
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareEmailRequest, t, notifications, safeNavigate]);
}, [prepareEmailRequest, t, notifications]);
const preparePagerRequest = useCallback(
() => ({
@@ -247,7 +245,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
@@ -260,7 +258,7 @@ function EditAlertChannels({
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [preparePagerRequest, notifications, selectedConfig, t, safeNavigate]);
}, [preparePagerRequest, notifications, selectedConfig, t]);
const prepareOpsgenieRequest = useCallback(
() => ({
@@ -295,7 +293,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
@@ -308,7 +306,7 @@ function EditAlertChannels({
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareOpsgenieRequest, t, notifications, selectedConfig, safeNavigate]);
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
const prepareMsTeamsRequest = useCallback(
() => ({
@@ -342,7 +340,7 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
safeNavigate(ROUTES.ALL_CHANNELS, { replace: true });
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
@@ -355,7 +353,7 @@ function EditAlertChannels({
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareMsTeamsRequest, t, notifications, selectedConfig, safeNavigate]);
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
const onSaveHandler = useCallback(
async (value: ChannelType) => {

View File

@@ -8,15 +8,15 @@ import { ResizeTable } from 'components/ResizeTable';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { getNanoSeconds } from 'container/AllError/utils';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { urlKey } from 'pages/ErrorDetails/utils';
import { useTimezone } from 'providers/Timezone';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { PayloadProps as GetByErrorTypeAndServicePayload } from 'types/api/errors/getByErrorTypeAndService';
import { keyToExclude } from './config';
@@ -77,7 +77,6 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
);
const { notifications } = useNotifications();
const { safeNavigate } = useSafeNavigate();
const onClickErrorIdHandler = async (
id: string,
@@ -97,9 +96,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
errorId: id,
};
safeNavigate(`${pathname}?${createQueryParams(queryParams)}`, {
replace: true,
});
history.replace(`${pathname}?${createQueryParams(queryParams)}`);
} catch (error) {
notifications.error({
message: t('something_went_wrong'),
@@ -121,7 +118,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
traceId: errorDetail.traceID,
exceptionId: errorDetail?.errorId,
});
safeNavigate(`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`);
history.push(`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`);
};
const logEventCalledRef = useRef(false);

View File

@@ -39,7 +39,6 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import useErrorNotification from 'hooks/useErrorNotification';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
import { cloneDeep, isEqual, omit } from 'lodash-es';
import {
@@ -61,6 +60,7 @@ import {
useRef,
useState,
} from 'react';
import { useHistory } from 'react-router-dom';
import { Dashboard } from 'types/api/dashboard/getAll';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -96,7 +96,7 @@ function ExplorerOptions({
const [newViewName, setNewViewName] = useState<string>('');
const [color, setColor] = useState(Color.BG_SIENNA_500);
const { notifications } = useNotifications();
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const ref = useRef<RefSelectProps>(null);
const isDarkMode = useIsDarkMode();
const isLogsExplorer = sourcepage === DataSource.LOGS;
@@ -181,13 +181,13 @@ function ExplorerOptions({
const stringifiedQuery = handleConditionalQueryModification();
safeNavigate(
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
stringifiedQuery,
)}`,
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleConditionalQueryModification, safeNavigate]);
}, [handleConditionalQueryModification, history]);
const onCancel = (value: boolean) => (): void => {
onModalToggle(value);
@@ -461,7 +461,7 @@ function ExplorerOptions({
: defaultLogsSelectedColumns,
});
safeNavigate(DATASOURCE_VS_ROUTES[sourcepage], { replace: true });
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
};
const isQueryUpdated = isStagedQueryUpdated(

View File

@@ -9,7 +9,7 @@ import {
SlackChannel,
WebhookChannel,
} from 'container/CreateAlertChannels/config';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
@@ -35,7 +35,6 @@ function FormAlertChannels({
editing = false,
}: FormAlertChannelsProps): JSX.Element {
const { t } = useTranslation('channels');
const { safeNavigate } = useSafeNavigate();
const renderSettings = (): ReactElement | null => {
switch (type) {
@@ -148,7 +147,7 @@ function FormAlertChannels({
</Button>
<Button
onClick={(): void => {
safeNavigate(ROUTES.SETTINGS);
history.replace(ROUTES.SETTINGS);
}}
>
{t('button_return')}

View File

@@ -18,10 +18,10 @@ import {
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useAppContext } from 'providers/App/App';
@@ -29,7 +29,7 @@ import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { AlertDef } from 'types/api/alerts/def';
@@ -85,7 +85,6 @@ function ChartPreview({
>((state) => state.globalTime);
const { featureFlags } = useAppContext();
const { safeNavigate } = useSafeNavigate();
const handleBackNavigation = (): void => {
const searchParams = new URLSearchParams(window.location.search);
@@ -201,9 +200,9 @@ function ChartPreview({
urlQuery.set(QueryParams.startTime, minTime.toString());
urlQuery.set(QueryParams.endTime, maxTime.toString());
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
},
[dispatch, location.pathname, urlQuery, safeNavigate],
[dispatch, location.pathname, urlQuery],
);
const { timezone } = useTimezone();

View File

@@ -29,7 +29,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {

View File

@@ -2,16 +2,15 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import './FullScreenHeader.styles.scss';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
export default function FullScreenHeader({
overrideRoute,
}: {
overrideRoute?: string;
}): React.ReactElement {
const { safeNavigate } = useSafeNavigate();
const handleLogoClick = (): void => {
safeNavigate(overrideRoute || '/');
history.push(overrideRoute || '/');
};
return (
<div className="full-screen-header-container">

View File

@@ -28,7 +28,7 @@ import GetMinMax from 'lib/getMinMax';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';

View File

@@ -25,7 +25,7 @@ import {
useRef,
useState,
} from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { Dashboard } from 'types/api/dashboard/getAll';
import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid';

View File

@@ -34,7 +34,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FullScreen, FullScreenHandle } from 'react-full-screen';
import { ItemCallback, Layout } from 'react-grid-layout';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import { ROLES, USER_ROLES } from 'types/roles';

View File

@@ -3,7 +3,7 @@ import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import ValueGraph from 'components/ValueGraph';
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
import { memo, useMemo } from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { TitleContainer, ValueContainer } from './styles';
import { GridValueComponentProps } from './types';

View File

@@ -3,14 +3,14 @@ import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { ArrowRight, ArrowUpRight, Plus } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { Link, useLocation } from 'react-router';
import { Link, useLocation } from 'react-router-dom';
import { GettableAlert } from 'types/api/alerts/get';
import { USER_ROLES } from 'types/roles';
@@ -28,7 +28,6 @@ export default function AlertRules({
const location = useLocation();
const params = new URLSearchParams(location.search);
const { safeNavigate } = useSafeNavigate();
// Fetch Alerts
const { data: alerts, isError, isLoading } = useQuery('allAlerts', {
@@ -132,7 +131,7 @@ export default function AlertRules({
params.set(QueryParams.ruleId, record.id.toString());
safeNavigate(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
};
const renderAlertRules = (): JSX.Element => (

View File

@@ -7,7 +7,7 @@ import { ArrowRight, ArrowUpRight, Plus } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import { Dashboard } from 'types/api/dashboard/getAll';
import { USER_ROLES } from 'types/roles';

View File

@@ -3,7 +3,7 @@ import { Button, Skeleton, Tag, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useGetDeploymentsData } from 'hooks/CustomDomain/useGetDeploymentsData';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { Globe, Link2 } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
@@ -20,7 +20,6 @@ function DataSourceInfo({
isLoading: boolean;
}): JSX.Element {
const { activeLicenseV3 } = useAppContext();
const { safeNavigate } = useSafeNavigate();
const notSendingData = !dataSentToSigNoz;
@@ -92,7 +91,7 @@ function DataSourceInfo({
activeLicenseV3 &&
activeLicenseV3.platform === LicensePlatform.CLOUD
) {
safeNavigate(ROUTES.GET_STARTED_WITH_CLOUD);
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
@@ -109,7 +108,7 @@ function DataSourceInfo({
activeLicenseV3 &&
activeLicenseV3.platform === LicensePlatform.CLOUD
) {
safeNavigate(ROUTES.GET_STARTED_WITH_CLOUD);
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,

View File

@@ -17,7 +17,7 @@ import { getHostListsQuery } from 'container/InfraMonitoringHosts/utils';
import { useGetHostList } from 'hooks/infraMonitoring/useGetHostList';
import { useGetK8sPodsList } from 'hooks/infraMonitoring/useGetK8sPodsList';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import cloneDeep from 'lodash-es/cloneDeep';
import { CompassIcon, DotIcon, HomeIcon, Plus, Wrench } from 'lucide-react';
import { AnimatePresence } from 'motion/react';
@@ -45,7 +45,6 @@ const homeInterval = 30 * 60 * 1000;
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function Home(): JSX.Element {
const { user } = useAppContext();
const { safeNavigate } = useSafeNavigate();
const [startTime, setStartTime] = useState<number | null>(null);
const [endTime, setEndTime] = useState<number | null>(null);
@@ -383,14 +382,14 @@ export default function Home(): JSX.Element {
logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Logs',
});
safeNavigate(ROUTES.LOGS_EXPLORER);
history.push(ROUTES.LOGS_EXPLORER);
}}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Logs',
});
safeNavigate(ROUTES.LOGS_EXPLORER);
history.push(ROUTES.LOGS_EXPLORER);
}
}}
>
@@ -424,14 +423,14 @@ export default function Home(): JSX.Element {
logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Traces',
});
safeNavigate(ROUTES.TRACES_EXPLORER);
history.push(ROUTES.TRACES_EXPLORER);
}}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Traces',
});
safeNavigate(ROUTES.TRACES_EXPLORER);
history.push(ROUTES.TRACES_EXPLORER);
}
}}
>
@@ -465,14 +464,14 @@ export default function Home(): JSX.Element {
logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Metrics',
});
safeNavigate(ROUTES.INFRASTRUCTURE_MONITORING_HOSTS);
history.push(ROUTES.INFRASTRUCTURE_MONITORING_HOSTS);
}}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Metrics',
});
safeNavigate(ROUTES.INFRASTRUCTURE_MONITORING_HOSTS);
history.push(ROUTES.INFRASTRUCTURE_MONITORING_HOSTS);
}
}}
>
@@ -519,7 +518,7 @@ export default function Home(): JSX.Element {
logEvent('Homepage: Explore clicked', {
source: 'Logs',
});
safeNavigate(ROUTES.LOGS_EXPLORER);
history.push(ROUTES.LOGS_EXPLORER);
}}
>
Open Logs Explorer
@@ -533,7 +532,7 @@ export default function Home(): JSX.Element {
logEvent('Homepage: Explore clicked', {
source: 'Traces',
});
safeNavigate(ROUTES.TRACES_EXPLORER);
history.push(ROUTES.TRACES_EXPLORER);
}}
>
Open Traces Explorer
@@ -574,7 +573,7 @@ export default function Home(): JSX.Element {
logEvent('Homepage: Explore clicked', {
source: 'Dashboards',
});
safeNavigate(ROUTES.ALL_DASHBOARD);
history.push(ROUTES.ALL_DASHBOARD);
}}
>
Create dashboard
@@ -616,7 +615,7 @@ export default function Home(): JSX.Element {
logEvent('Homepage: Explore clicked', {
source: 'Alerts',
});
safeNavigate(ROUTES.ALERTS_NEW);
history.push(ROUTES.ALERTS_NEW);
}}
>
Create an alert

View File

@@ -4,7 +4,7 @@ import './HomeChecklist.styles.scss';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { ArrowRight, ArrowRightToLine, BookOpenText } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
@@ -41,7 +41,6 @@ function HomeChecklist({
const [whatsNextChecklistItems, setWhatsNextChecklistItems] = useState<
ChecklistItem[]
>([]);
const { safeNavigate } = useSafeNavigate();
useEffect(() => {
setCompletedChecklistItems(checklistItems.filter((item) => item.completed));
@@ -93,12 +92,12 @@ function HomeChecklist({
});
if (item.toRoute !== ROUTES.GET_STARTED_WITH_CLOUD) {
safeNavigate(item.toRoute || '');
history.push(item.toRoute || '');
} else if (
activeLicenseV3 &&
activeLicenseV3.platform === LicensePlatform.CLOUD
) {
safeNavigate(item.toRoute || '');
history.push(item.toRoute || '');
} else {
window?.open(
item.docsLink || '',

View File

@@ -14,7 +14,7 @@ import { SOURCEPAGE_VS_ROUTES } from 'pages/SaveView/constants';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import { ViewProps } from 'types/api/saveViews/types';
import { DataSource } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';

View File

@@ -11,6 +11,7 @@ import useGetTopLevelOperations from 'hooks/useGetTopLevelOperations';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { ArrowRight, ArrowUpRight } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
@@ -18,7 +19,7 @@ import { IUser } from 'providers/App/types';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { QueryKey } from 'react-query';
import { useSelector } from 'react-redux';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import { AppState } from 'store/reducers';
import {
LicensePlatform,
@@ -28,7 +29,6 @@ import { ServicesList } from 'types/api/metrics/getService';
import { GlobalReducer } from 'types/reducer/globalTime';
import { Tags } from 'types/reducer/trace';
import { USER_ROLES } from 'types/roles';
import { safeNavigateNoSameURLMemo } from 'utils/navigate';
import { DOCS_LINKS } from '../constants';
import { columns, TIME_PICKER_OPTIONS } from './constants';
@@ -72,7 +72,7 @@ const EmptyState = memo(
activeLicenseV3 &&
activeLicenseV3.platform === LicensePlatform.CLOUD
) {
safeNavigateNoSameURLMemo(ROUTES.GET_STARTED_WITH_CLOUD);
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,

View File

@@ -3,12 +3,13 @@ import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useQueryService } from 'hooks/useQueryService';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { ArrowRight, ArrowUpRight } from 'lucide-react';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { ServicesList } from 'types/api/metrics/getService';
@@ -126,7 +127,7 @@ export default function ServiceTraces({
activeLicenseV3 &&
activeLicenseV3.platform === LicensePlatform.CLOUD
) {
safeNavigate(ROUTES.GET_STARTED_WITH_CLOUD);
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
@@ -159,7 +160,7 @@ export default function ServiceTraces({
</div>
</div>
),
[user?.role, activeLicenseV3, safeNavigate],
[user?.role, activeLicenseV3],
);
const renderDashboardsList = useCallback(

View File

@@ -5,7 +5,7 @@ import { Button, Divider, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useCallback, useState } from 'react';
import { DataSource } from 'types/common/queryBuilder';
@@ -33,14 +33,13 @@ export function AlertsEmptyState(): JSX.Element {
['add_new_alert', 'action'],
user.role,
);
const { safeNavigate } = useSafeNavigate();
const [loading, setLoading] = useState(false);
const onClickNewAlertHandler = useCallback(() => {
setLoading(false);
safeNavigate(ROUTES.ALERTS_NEW);
}, [safeNavigate]);
history.push(ROUTES.ALERTS_NEW);
}, []);
return (
<div className="alert-list-container">

View File

@@ -20,8 +20,8 @@ import useComponentPermission from 'hooks/useComponentPermission';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import useInterval from 'hooks/useInterval';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useAppContext } from 'providers/App/App';
import { useCallback, useState } from 'react';
@@ -61,7 +61,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const filteredData = filterAlerts(allAlertRules, value);
return filteredData || [];
});
const { safeNavigate } = useSafeNavigate();
// Type asuring
const sortingOrder: 'ascend' | 'descend' | null =
@@ -103,7 +102,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
logEvent('Alert: New alert button clicked', {
number: allAlertRules?.length,
});
safeNavigate(ROUTES.ALERTS_NEW);
history.push(ROUTES.ALERTS_NEW);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -119,7 +118,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
params.set(QueryParams.ruleId, record.id.toString());
setEditLoader(false);
safeNavigate(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
};
const onCloneHandler = (
@@ -147,7 +146,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
setTimeout(() => {
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
params.set(QueryParams.ruleId, String(clonedAlert.id));
safeNavigate(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
}, 2000);
}
if (status === 'error') {

View File

@@ -75,7 +75,7 @@ import {
} from 'react';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router';
import { generatePath } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import {
Dashboard,

View File

@@ -25,7 +25,7 @@ import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
// See more: https://github.com/lucide-icons/lucide/issues/94
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router';
import { generatePath } from 'react-router-dom';
import { DashboardData } from 'types/api/dashboard/getAll';
function ImportJSON({

View File

@@ -2,7 +2,7 @@ import { CloseCircleFilled } from '@ant-design/icons';
import { useMachine } from '@xstate/react';
import { Button, Select } from 'antd';
import { RefSelectProps } from 'antd/lib/select';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { filter, map } from 'lodash-es';
import {
MutableRefObject,
@@ -11,7 +11,6 @@ import {
useRef,
useState,
} from 'react';
import { useLocation } from 'react-router';
import { Dashboard } from 'types/api/dashboard/getAll';
import { v4 as uuidv4 } from 'uuid';
@@ -42,15 +41,13 @@ function SearchFilter({
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const [staging, setStaging] = useState<string[] | string[][] | unknown[]>([]);
const [queries, setQueries] = useState<IQueryStructure[]>([]);
const { safeNavigate } = useSafeNavigate();
const { pathname, search } = useLocation();
useEffect(() => {
const searchQueryString = new URLSearchParams(search).get('search');
const searchQueryString = new URLSearchParams(history.location.search).get(
'search',
);
if (searchQueryString)
setQueries(convertURLQueryStringToQuery(searchQueryString) || []);
// TODO: SMIT is this a bug no search in deps?
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
filterDashboards(executeSearchQueries(queries, searchData));
@@ -59,15 +56,15 @@ function SearchFilter({
const updateURLWithQuery = useCallback(
(inputQueries?: IQueryStructure[]): void => {
safeNavigate({
pathname,
history.push({
pathname: history.location.pathname,
search:
inputQueries || queries
? `?search=${convertQueriesToURLQuery(inputQueries || queries)}`
: '',
});
},
[queries, pathname, safeNavigate],
[queries],
);
useEffect(() => {
@@ -152,8 +149,8 @@ function SearchFilter({
const clearQueries = (): void => {
setQueries([]);
safeNavigate({
pathname,
history.push({
pathname: history.location.pathname,
search: ``,
});
};

View File

@@ -6,7 +6,7 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import ROUTES from 'constants/routes';
import { useDeleteDashboard } from 'hooks/dashboard/useDeleteDashboard';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -36,7 +36,6 @@ export function DeleteButton({
const isAuthor = user?.email === createdBy;
const queryClient = useQueryClient();
const { safeNavigate } = useSafeNavigate();
const { notifications } = useNotifications();
@@ -69,7 +68,7 @@ export function DeleteButton({
});
queryClient.invalidateQueries([REACT_QUERY_KEY.GET_ALL_DASHBOARDS]);
if (routeToListPage) {
safeNavigate(ROUTES.ALL_DASHBOARD);
history.replace(ROUTES.ALL_DASHBOARD);
}
destroy();
},
@@ -87,7 +86,6 @@ export function DeleteButton({
t,
queryClient,
routeToListPage,
safeNavigate,
]);
const getDeleteTooltipContent = (): string => {

View File

@@ -1,13 +1,12 @@
import { LockFilled } from '@ant-design/icons';
import ROUTES from 'constants/routes';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { Data } from '../DashboardsList';
import { TableLinkText } from './styles';
function Name(name: Data['name'], data: Data): JSX.Element {
const { id: DashboardId, isLocked } = data;
const { safeNavigate } = useSafeNavigate();
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${DashboardId}`;
@@ -15,7 +14,7 @@ function Name(name: Data['name'], data: Data): JSX.Element {
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
} else {
safeNavigate(getLink());
history.push(getLink());
}
};

View File

@@ -8,14 +8,14 @@ import {
import ROUTES from 'constants/routes';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { DataSource } from 'types/common/queryBuilder';
import { constructCompositeQuery } from '../constants';
function BackButton(): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const { updateAllQueriesOperators } = useQueryBuilder();
@@ -40,8 +40,8 @@ function BackButton(): JSX.Element {
const path = `${ROUTES.LOGS_EXPLORER}?${QueryParams.compositeQuery}=${JSONCompositeQuery}`;
safeNavigate(path);
}, [safeNavigate, compositeQuery, updateAllQueriesOperators]);
history.push(path);
}, [history, compositeQuery, updateAllQueriesOperators]);
return (
<Button icon={<ArrowLeftOutlined />} onClick={handleBack}>

View File

@@ -14,7 +14,7 @@ import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload
import { useEventSource } from 'providers/EventSource';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { ILog } from 'types/api/logs/log';
import { Query } from 'types/api/queryBuilder/queryBuilderData';

View File

@@ -14,7 +14,7 @@ import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/co
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useUrlQuery from 'hooks/useUrlQuery';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { Virtuoso } from 'react-virtuoso';
import { ILog } from 'types/api/logs/log';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';

View File

@@ -13,15 +13,16 @@ import AddToQueryHOC, {
import { ResizeTable } from 'components/ResizeTable';
import { OPERATORS } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
import { FontSize, OptionsQuery } from 'container/OptionsMenu/types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
import { Pin } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { generatePath } from 'react-router';
import { generatePath } from 'react-router-dom';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
@@ -34,9 +35,6 @@ import FieldRenderer from './FieldRenderer';
import { TableViewActions } from './TableView/TableViewActions';
import { filterKeyForField, findKeyPath, flattenObject } from './utils';
// Fields which should be restricted from adding it to query
const RESTRICTED_FIELDS = ['timestamp'];
interface TableViewProps {
logData: ILog;
fieldSearchInput: string;
@@ -68,7 +66,6 @@ function TableView({
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
const [isfilterOutLoading, setIsFilterOutLoading] = useState<boolean>(false);
const isDarkMode = useIsDarkMode();
const { safeNavigate } = useSafeNavigate();
const [pinnedAttributes, setPinnedAttributes] = useState<
Record<string, boolean>
@@ -171,7 +168,7 @@ function TableView({
// open the trace in new tab
window.open(route, '_blank');
} else {
safeNavigate(route);
history.push(route);
}
}
};
@@ -250,7 +247,7 @@ function TableView({
}
const fieldFilterKey = filterKeyForField(field);
if (!RESTRICTED_FIELDS.includes(fieldFilterKey)) {
if (!RESTRICTED_SELECTED_FIELDS.includes(fieldFilterKey)) {
return (
<AddToQueryHOC
fieldKey={fieldFilterKey}

View File

@@ -9,12 +9,13 @@ import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { OPERATORS } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
import dompurify from 'dompurify';
import { isEmpty } from 'lodash-es';
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import React, { useMemo, useState } from 'react';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
@@ -142,7 +143,7 @@ export function TableViewActions(
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
{renderFieldContent()}
</CopyClipboardHOC>
{!isListViewPanel && (
{!isListViewPanel && !RESTRICTED_SELECTED_FIELDS.includes(fieldFilterKey) && (
<span className="action-btn">
<Tooltip title="Filter for value">
<Button

View File

@@ -0,0 +1,130 @@
import { render, screen } from '@testing-library/react';
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
import { TableViewActions } from '../TableViewActions';
// Mock the components and hooks
jest.mock('components/Logs/CopyClipboardHOC', () => ({
__esModule: true,
default: ({ children }: { children: React.ReactNode }): JSX.Element => (
<div className="CopyClipboardHOC">{children}</div>
),
}));
jest.mock('providers/Timezone', () => ({
useTimezone: (): {
formatTimezoneAdjustedTimestamp: (timestamp: string) => string;
} => ({
formatTimezoneAdjustedTimestamp: (timestamp: string): string => timestamp,
}),
}));
jest.mock('react-router-dom', () => ({
useLocation: (): {
pathname: string;
search: string;
hash: string;
state: null;
} => ({
pathname: '/test',
search: '',
hash: '',
state: null,
}),
}));
describe('TableViewActions', () => {
const TEST_VALUE = 'test value';
const ACTION_BUTTON_TEST_ID = '.action-btn';
const defaultProps = {
fieldData: {
field: 'test-field',
value: TEST_VALUE,
},
record: {
key: 'test-key',
field: 'test-field',
value: TEST_VALUE,
},
isListViewPanel: false,
isfilterInLoading: false,
isfilterOutLoading: false,
onClickHandler: jest.fn(),
onGroupByAttribute: jest.fn(),
};
it('should render without crashing', () => {
render(
<TableViewActions
fieldData={defaultProps.fieldData}
record={defaultProps.record}
isListViewPanel={defaultProps.isListViewPanel}
isfilterInLoading={defaultProps.isfilterInLoading}
isfilterOutLoading={defaultProps.isfilterOutLoading}
onClickHandler={defaultProps.onClickHandler}
onGroupByAttribute={defaultProps.onGroupByAttribute}
/>,
);
expect(screen.getByText(TEST_VALUE)).toBeInTheDocument();
});
it('should not render action buttons for restricted fields', () => {
RESTRICTED_SELECTED_FIELDS.forEach((field) => {
const { container } = render(
<TableViewActions
fieldData={{
...defaultProps.fieldData,
field,
}}
record={{
...defaultProps.record,
field,
}}
isListViewPanel={defaultProps.isListViewPanel}
isfilterInLoading={defaultProps.isfilterInLoading}
isfilterOutLoading={defaultProps.isfilterOutLoading}
onClickHandler={defaultProps.onClickHandler}
onGroupByAttribute={defaultProps.onGroupByAttribute}
/>,
);
// Verify that action buttons are not rendered for restricted fields
expect(
container.querySelector(ACTION_BUTTON_TEST_ID),
).not.toBeInTheDocument();
});
});
it('should render action buttons for non-restricted fields', () => {
const { container } = render(
<TableViewActions
fieldData={defaultProps.fieldData}
record={defaultProps.record}
isListViewPanel={defaultProps.isListViewPanel}
isfilterInLoading={defaultProps.isfilterInLoading}
isfilterOutLoading={defaultProps.isfilterOutLoading}
onClickHandler={defaultProps.onClickHandler}
onGroupByAttribute={defaultProps.onGroupByAttribute}
/>,
);
// Verify that action buttons are rendered for non-restricted fields
expect(container.querySelector(ACTION_BUTTON_TEST_ID)).toBeInTheDocument();
});
it('should not render action buttons in list view panel', () => {
const { container } = render(
<TableViewActions
fieldData={defaultProps.fieldData}
record={defaultProps.record}
isListViewPanel
isfilterInLoading={defaultProps.isfilterInLoading}
isfilterOutLoading={defaultProps.isfilterOutLoading}
onClickHandler={defaultProps.onClickHandler}
onGroupByAttribute={defaultProps.onGroupByAttribute}
/>,
);
// Verify that action buttons are not rendered in list view panel
expect(
container.querySelector(ACTION_BUTTON_TEST_ID),
).not.toBeInTheDocument();
});
});

View File

@@ -2,12 +2,12 @@ import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants';
import ROUTES from 'constants/routes';
import { getOldLogsOperatorFromNew } from 'hooks/logs/useActiveLog';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { getGeneratedFilterQueryString } from 'lib/getGeneratedFilterQueryString';
import getStep from 'lib/getStep';
import { getIdConditions } from 'pages/Logs/utils';
import { memo, useCallback } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { getLogs } from 'store/actions/logs/getLogs';
@@ -33,7 +33,7 @@ function LogDetailedView({
getLogs,
getLogsAggregate,
}: LogDetailedViewProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const {
detailedLog,
searchFilter: { queryString },
@@ -66,11 +66,9 @@ function LogDetailedView({
queryString,
);
safeNavigate(`${ROUTES.OLD_LOGS_EXPLORER}?q=${updatedQueryString}`, {
replace: true,
});
history.replace(`${ROUTES.OLD_LOGS_EXPLORER}?q=${updatedQueryString}`);
},
[safeNavigate, queryString],
[history, queryString],
);
const handleClickActionItem = useCallback(

View File

@@ -8,13 +8,12 @@ import afterLogin from 'AppRoutes/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
import { safeNavigateNoSameURLMemo } from 'utils/navigate';
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
@@ -40,7 +39,6 @@ function Login({
const { t } = useTranslation(['login']);
const [isLoading, setIsLoading] = useState<boolean>(false);
const { user } = useAppContext();
const { safeNavigate } = useSafeNavigate();
const [precheckResult, setPrecheckResult] = useState<PrecheckResultType>({
sso: false,
@@ -69,7 +67,7 @@ function Login({
const { setupCompleted } = getUserVersionResponse.data.payload;
if (!setupCompleted) {
// no org account registered yet, re-route user to sign up first
safeNavigateNoSameURLMemo(ROUTES.SIGN_UP);
history.push(ROUTES.SIGN_UP);
}
}
}, [getUserVersionResponse]);
@@ -92,10 +90,10 @@ function Login({
LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT,
);
if (fromPathname) {
safeNavigateNoSameURLMemo(fromPathname);
history.push(fromPathname);
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, '');
} else {
safeNavigateNoSameURLMemo(ROUTES.APPLICATION);
history.push(ROUTES.APPLICATION);
}
}
}
@@ -289,7 +287,7 @@ function Login({
{t('prompt_if_admin')}{' '}
<Typography.Link
onClick={(): void => {
safeNavigate(ROUTES.SIGN_UP);
history.push(ROUTES.SIGN_UP);
}}
style={{ fontWeight: 700 }}
>

View File

@@ -4,15 +4,15 @@ import './LogsError.styles.scss';
import { Typography } from 'antd';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { ArrowRight } from 'lucide-react';
export default function LogsError(): JSX.Element {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const { safeNavigate } = useSafeNavigate();
const handleContactSupport = (): void => {
if (isCloudUserVal) {
safeNavigate('/support');
history.push('/support');
} else {
window.open('https://signoz.io/slack', '_blank');
}

View File

@@ -9,7 +9,7 @@ import GetMinMax from 'lib/getMinMax';
import { colors } from 'lib/getRandomColor';
import { memo, useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { LogsExplorerChartProps } from './LogsExplorerChart.interfaces';

View File

@@ -1,7 +1,7 @@
import { QueryParams } from 'constants/query';
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { parseQuery } from 'lib/logql';
import isEqual from 'lodash-es/isEqual';
import { useCallback, useEffect } from 'react';
@@ -24,7 +24,6 @@ export function useSearchParser(): {
updateQueryString: (arg0: string) => void;
} {
const dispatch = useDispatch<Dispatch<AppActions>>();
const { safeNavigate } = useSafeNavigate();
const {
searchFilter: { parsedQuery, queryString },
order,
@@ -40,16 +39,10 @@ export function useSearchParser(): {
const updateQueryString = useCallback(
(updatedQueryString: string) => {
// Defaults to the current URL
safeNavigate(
{
// No need to prepend '?'
search: `${QueryParams.q}=${updatedQueryString}&${QueryParams.order}=${order}`,
},
{
replace: true,
},
);
history.replace({
pathname: history.location.pathname,
search: `?${QueryParams.q}=${updatedQueryString}&${QueryParams.order}=${order}`,
});
const globalTime = getMinMax(selectedTime, minTime, maxTime);
@@ -71,7 +64,7 @@ export function useSearchParser(): {
},
// need to hide this warning as we don't want to update the query string on every change
// eslint-disable-next-line react-hooks/exhaustive-deps
[dispatch, parsedQuery, selectedTime, queryString, safeNavigate],
[dispatch, parsedQuery, selectedTime, queryString],
);
useEffect(() => {

View File

@@ -13,16 +13,16 @@ import {
import { QueryHistoryState } from 'container/LiveLogs/types';
import LocalTopNav from 'container/LocalTopNav';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { useCallback, useMemo } from 'react';
import { useQueryClient } from 'react-query';
import { useHistory } from 'react-router-dom';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { LiveButtonStyled } from './styles';
function LogsTopNav(): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
const queryClient = useQueryClient();
const { stagedQuery, panelType } = useQueryBuilder();
@@ -65,9 +65,8 @@ function LogsTopNav(): JSX.Element {
const path = `${ROUTES.LIVE_LOGS}?${QueryParams.compositeQuery}=${JSONCompositeQuery}`;
// TODO: SMIT Discuss with reviewer
safeNavigate(path, { state: queryHistoryState });
}, [safeNavigate, panelType, queryClient, stagedQuery]);
history.push(path, queryHistoryState);
}, [history, panelType, queryClient, stagedQuery]);
const liveButton = useMemo(
() => (

View File

@@ -16,9 +16,10 @@ import {
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation, useParams } from 'react-router';
import { useLocation, useParams } from 'react-router-dom';
import store from 'store';
import { UpdateTimeInterval } from 'store/actions';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
@@ -39,17 +40,13 @@ import {
} from './util';
function DBCall(): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { queries } = useResourceAttribute();
const urlQuery = useUrlQuery();
const { pathname } = useLocation();
const { safeNavigate } = useSafeNavigate();
const dispatch = useDispatch();
const onDragSelect = useCallback(
@@ -60,13 +57,13 @@ function DBCall(): JSX.Element {
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, urlQuery, safeNavigate],
[dispatch, pathname, urlQuery],
);
const tagFilterItems: TagFilterItem[] = useMemo(
@@ -161,6 +158,7 @@ function DBCall(): JSX.Element {
servicename,
isDBCall: true,
});
const { safeNavigate } = useSafeNavigate();
const onGraphClickHandler = useGraphClickHandler(setSelectedTimeStamp);

View File

@@ -18,9 +18,10 @@ import {
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation, useParams } from 'react-router';
import { useLocation, useParams } from 'react-router-dom';
import store from 'store';
import { UpdateTimeInterval } from 'store/actions';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
@@ -42,14 +43,10 @@ import {
function External(): JSX.Element {
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { queries } = useResourceAttribute();
const { safeNavigate } = useSafeNavigate();
const urlQuery = useUrlQuery();
const { pathname } = useLocation();
@@ -63,13 +60,13 @@ function External(): JSX.Element {
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, urlQuery, safeNavigate],
[dispatch, pathname, urlQuery],
);
const tagFilterItems = useMemo(
@@ -224,6 +221,8 @@ function External(): JSX.Element {
isExternalCall: true,
});
const { safeNavigate } = useSafeNavigate();
const onGraphClickHandler = useGraphClickHandler(setSelectedTimeStamp);
return (

View File

@@ -16,13 +16,14 @@ import {
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { defaultTo } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useParams } from 'react-router';
import { useLocation, useParams } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
@@ -57,10 +58,7 @@ import {
} from './util';
function Application(): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
@@ -71,7 +69,6 @@ function Application(): JSX.Element {
const { search, pathname } = useLocation();
const { queries } = useResourceAttribute();
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const { featureFlags } = useAppContext();
const isSpanMetricEnabled =
@@ -211,13 +208,13 @@ function Application(): JSX.Element {
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, urlQuery, safeNavigate],
[dispatch, pathname, urlQuery],
);
/**
@@ -256,7 +253,7 @@ function Application(): JSX.Element {
queryString,
);
safeNavigate(newPath);
history.push(newPath);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[stepInterval],
@@ -298,6 +295,7 @@ function Application(): JSX.Element {
},
],
});
const { safeNavigate } = useSafeNavigate();
return (
<>

View File

@@ -16,7 +16,7 @@ import {
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
import { ReactNode, useMemo } from 'react';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
@@ -32,10 +32,7 @@ function ApDexMetrics({
topLevelOperationsRoute,
handleGraphClick,
}: ApDexMetricsProps): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const apDexMetricsWidget = useMemo(

View File

@@ -1,7 +1,7 @@
import Spinner from 'components/Spinner';
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
import useErrorNotification from 'hooks/useErrorNotification';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { IServiceName } from '../../types';
import ApDexMetrics from './ApDexMetrics';
@@ -15,10 +15,7 @@ function ApDexMetricsApplication({
thresholdValue,
topLevelOperationsRoute,
}: ApDexDataSwitcherProps): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { data, isLoading, error } = useGetMetricMeta(metricMeta, servicename);

View File

@@ -7,7 +7,7 @@ import { GraphTitle } from 'container/MetricsApplication/constant';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { apDexTracesQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
import { useMemo } from 'react';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
@@ -21,10 +21,7 @@ function ApDexTraces({
tagFilterItems,
thresholdValue,
}: ApDexDataSwitcherProps): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const apDexTracesWidget = useMemo(

View File

@@ -3,7 +3,7 @@ import { Card, GraphContainer } from 'container/MetricsApplication/styles';
import { useGetApDexSettings } from 'hooks/apDex/useGetApDexSettings';
import useErrorNotification from 'hooks/useErrorNotification';
import { memo } from 'react';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { IServiceName } from '../../types';
import ApDexMetricsApplication from './ApDexMetricsApplication';
@@ -15,10 +15,7 @@ function ApDexApplication({
topLevelOperationsRoute,
tagFilterItems,
}: ApDexApplicationProps): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { data, isLoading, error, isRefetching } = useGetApDexSettings(

View File

@@ -16,7 +16,7 @@ import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { useAppContext } from 'providers/App/App';
import { useMemo } from 'react';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
@@ -38,10 +38,7 @@ function ServiceOverview({
topLevelOperationsIsLoading,
stepInterval,
}: ServiceOverviewProps): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { featureFlags } = useAppContext();

View File

@@ -5,7 +5,7 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute
import { useMemo } from 'react';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { PayloadProps } from 'types/api/metrics/getTopOperations';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -15,10 +15,9 @@ function TopOperation(): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
// Temp: Hard type casting for string | undefined
const { servicename: encodedServiceName } = (useParams() as unknown) as {
const { servicename: encodedServiceName } = useParams<{
servicename?: string;
};
}>();
const servicename = decodeURIComponent(encodedServiceName || '');
const { queries } = useResourceAttribute();

View File

@@ -12,7 +12,7 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { ReactNode, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -24,10 +24,7 @@ import ColumnWithLink from './TableRenderer/ColumnWithLink';
import { getTableColumnRenderer } from './TableRenderer/TableColumnRenderer';
function TopOperationMetrics(): JSX.Element {
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const servicename = decodeURIComponent(encodedServiceName);
const { notifications } = useNotifications();

View File

@@ -11,7 +11,7 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { useRef } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
@@ -31,10 +31,7 @@ function TopOperationsTable({
isLoading,
}: TopOperationsTableProps): JSX.Element {
const searchInput = useRef<InputRef>(null);
// Temp: Hard type casting for string | undefined
const {
servicename: encodedServiceName,
} = (useParams() as unknown) as IServiceName;
const { servicename: encodedServiceName } = useParams<IServiceName>();
const { safeNavigate } = useSafeNavigate();
const servicename = decodeURIComponent(encodedServiceName);
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
@@ -48,8 +45,7 @@ function TopOperationsTable({
const apmToTraceQuery = useGetAPMToTracesQueries({ servicename });
// Temp: Hard type casting for string | undefined
const params = (useParams() as unknown) as { servicename: string };
const params = useParams<{ servicename: string }>();
const handleOnClick = (operation: string): void => {
const { servicename: encodedServiceName } = params;

Some files were not shown because too many files have changed in this diff Show More