Compare commits

...

18 Commits

Author SHA1 Message Date
manika-signoz
bb0801a83f chore: rename isShortcutKey file 2025-12-15 16:40:49 +05:30
manika-signoz
1c2fc57da1 Merge branch 'main' into feat/cmd-click 2025-12-15 16:38:27 +05:30
manika-signoz
ed8fbe2e4a chore: use isCtrlOrMMetaKey in sidenav 2025-12-15 10:55:59 +05:30
manika-signoz
260f5c39f5 chore: rename method isShortcut to isCtrlOrMMetaKey 2025-12-15 10:47:58 +05:30
manika-signoz
1f137c5a9c chore: add noopener and noreferrer to sidenav and tracestable 2025-12-04 17:07:23 +05:30
manika-signoz
17efa7672b fix: add 'noopener,noreferrer' to dashboardslist 2025-12-04 16:52:44 +05:30
manika-signoz
80433b663d fix: failing test case 2025-12-04 16:36:56 +05:30
manika-signoz
9bf68b276f fix: failing tsc 2025-12-04 16:21:59 +05:30
Manika Malhotra
874d67f43a Merge branch 'main' into feat/cmd-click 2025-12-04 16:12:52 +05:30
manika-signoz
2259e8c299 chore: add generic method and use across 2025-12-04 16:10:16 +05:30
manika-signoz
c3aa0e5b3b chore: add noopener, noreferrer and event passing 2025-12-04 15:38:27 +05:30
Manika Malhotra
1f21efd0cf Merge branch 'main' into feat/cmd-click 2025-12-04 09:58:18 +05:30
Srikanth Chekuri
f648e3c684 Merge branch 'main' into feat/cmd-click 2025-12-02 18:13:08 +05:30
Manika Malhotra
db0c55cc53 Merge branch 'main' into feat/cmd-click 2025-12-01 10:47:57 +05:30
Manika Malhotra
9eb2a984f7 Merge branch 'main' into feat/cmd-click 2025-11-24 20:20:03 +05:30
manika-signoz
dd81ba1711 chore: resolve comments 2025-11-24 20:19:01 +05:30
Manika Malhotra
eed73013a4 Merge branch 'main' into feat/cmd-click 2025-11-24 10:41:38 +05:30
manika-signoz
c6fc2be670 chore: add implementation for cmd + click 2025-11-19 03:59:10 +05:30
34 changed files with 509 additions and 331 deletions

View File

@@ -47,6 +47,7 @@ import { AppState } from 'store/reducers';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces';
@@ -133,7 +134,7 @@ function LogDetailInner({
};
// Go to logs explorer page with the log data
const handleOpenInExplorer = (): void => {
const handleOpenInExplorer = (event: React.MouseEvent): void => {
const queryParams = {
[QueryParams.activeLogId]: `"${log?.id}"`,
[QueryParams.startTime]: minTime?.toString() || '',
@@ -146,7 +147,16 @@ function LogDetailInner({
),
),
};
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`);
if (isCtrlOrMMetaKey(event)) {
window.open(
`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`,
'_blank',
'noopener,noreferrer',
);
} else {
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`);
}
};
const handleQueryExpressionChange = useCallback(

View File

@@ -28,6 +28,7 @@ import React, {
useState,
} from 'react';
import { Virtuoso } from 'react-virtuoso';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { popupContainer } from 'utils/selectPopupContainer';
import { CustomMultiSelectProps, CustomTagProps, OptionData } from './types';
@@ -902,7 +903,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
const lastVisibleChipIndex = getLastVisibleChipIndex();
// Handle special keyboard combinations
const isCtrlOrCmd = e.ctrlKey || e.metaKey;
const isCtrlOrCmd = isCtrlOrMMetaKey(e);
// Handle Ctrl+A (select all)
if (isCtrlOrCmd && e.key === 'a') {

View File

@@ -5,12 +5,12 @@ import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { Channels } from 'types/api/channels/getAll';
import { genericNavigate } from 'utils/genericNavigate';
import Delete from './Delete';
@@ -20,13 +20,15 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
const { user } = useAppContext();
const [action] = useComponentPermission(['new_alert_action'], user.role);
const onClickEditHandler = useCallback((id: string) => {
history.push(
generatePath(ROUTES.CHANNELS_EDIT, {
channelId: id,
}),
);
}, []);
const onClickEditHandler = useCallback(
(id: string, event: React.MouseEvent): void => {
genericNavigate(
generatePath(ROUTES.CHANNELS_EDIT, { channelId: id }),
event,
);
},
[],
);
const columns: ColumnsType<Channels> = [
{
@@ -52,7 +54,10 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
width: 80,
render: (id: string): JSX.Element => (
<>
<Button onClick={(): void => onClickEditHandler(id)} type="link">
<Button
onClick={(event: React.MouseEvent): void => onClickEditHandler(id, event)}
type="link"
>
{t('column_channel_edit')}
</Button>
<Delete id={id} notifications={notifications} />

View File

@@ -8,7 +8,6 @@ import Spinner from 'components/Spinner';
import TextToolTip from 'components/TextToolTip';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect } from 'react';
@@ -17,6 +16,7 @@ import { useQuery } from 'react-query';
import { SuccessResponseV2 } from 'types/api';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { genericNavigate } from 'utils/genericNavigate';
import AlertChannelsComponent from './AlertChannels';
import { Button, ButtonContainer, RightActionContainer } from './styles';
@@ -30,8 +30,8 @@ function AlertChannels(): JSX.Element {
['add_new_channel'],
user.role,
);
const onToggleHandler = useCallback(() => {
history.push(ROUTES.CHANNELS_NEW);
const onToggleHandler = useCallback((event: React.MouseEvent) => {
genericNavigate(ROUTES.CHANNELS_NEW, event);
}, []);
const { isLoading, data, error } = useQuery<
@@ -78,7 +78,7 @@ function AlertChannels(): JSX.Element {
}
>
<Button
onClick={onToggleHandler}
onClick={(event: React.MouseEvent): void => onToggleHandler(event)}
icon={<PlusOutlined />}
disabled={!addNewChannelPermission}
>

View File

@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { PayloadProps as GetByErrorTypeAndServicePayload } from 'types/api/errors/getByErrorTypeAndService';
import { genericNavigate } from 'utils/genericNavigate';
import { keyToExclude } from './config';
import { DashedContainer, EditorContainer, EventContainer } from './styles';
@@ -111,14 +112,18 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
value: errorDetail[key as keyof GetByErrorTypeAndServicePayload],
}));
const onClickTraceHandler = (): void => {
const onClickTraceHandler = (event: React.MouseEvent): void => {
logEvent('Exception: Navigate to trace detail page', {
groupId: errorDetail?.groupID,
spanId: errorDetail.spanID,
traceId: errorDetail.traceID,
exceptionId: errorDetail?.errorId,
});
history.push(`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`);
genericNavigate(
`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`,
event,
);
};
const logEventCalledRef = useRef(false);
@@ -185,7 +190,10 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
<DashedContainer>
<Typography>{t('see_trace_graph')}</Typography>
<Button onClick={onClickTraceHandler} type="primary">
<Button
onClick={(event: React.MouseEvent): void => onClickTraceHandler(event)}
type="primary"
>
{t('see_error_in_trace_graph')}
</Button>
</DashedContainer>

View File

@@ -73,6 +73,7 @@ import { ViewProps } from 'types/api/saveViews/types';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';
import { panelTypeToExplorerView } from 'utils/explorerUtils';
import { genericNavigate } from 'utils/genericNavigate';
import { PreservedViewsTypes } from './constants';
import ExplorerOptionsHideArea from './ExplorerOptionsHideArea';
@@ -192,7 +193,7 @@ function ExplorerOptions({
);
const onCreateAlertsHandler = useCallback(
(defaultQuery: Query | null) => {
(defaultQuery: Query | null, event?: React.MouseEvent) => {
if (sourcepage === DataSource.TRACES) {
logEvent('Traces Explorer: Create alert', {
panelType,
@@ -211,10 +212,11 @@ function ExplorerOptions({
const stringifiedQuery = handleConditionalQueryModification(defaultQuery);
history.push(
genericNavigate(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
stringifiedQuery,
)}`,
event,
);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -741,7 +743,9 @@ function ExplorerOptions({
<Button
disabled={disabled}
shape="round"
onClick={(): void => onCreateAlertsHandler(query)}
onClick={(event: React.MouseEvent): void =>
onCreateAlertsHandler(query, event)
}
icon={<ConciergeBell size={16} />}
>
Create an Alert

View File

@@ -3,7 +3,6 @@ import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
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';
@@ -13,6 +12,7 @@ import { useQuery } from 'react-query';
import { Link, useLocation } from 'react-router-dom';
import { GettableAlert } from 'types/api/alerts/get';
import { USER_ROLES } from 'types/roles';
import { genericNavigate } from 'utils/genericNavigate';
export default function AlertRules({
onUpdateChecklistDoneItem,
@@ -114,7 +114,10 @@ export default function AlertRules({
</div>
);
const onEditHandler = (record: GettableAlert) => (): void => {
const onEditHandler = (
record: GettableAlert,
event?: React.MouseEvent | React.KeyboardEvent,
): void => {
logEvent('Homepage: Alert clicked', {
ruleId: record.id,
ruleName: record.alert,
@@ -131,7 +134,7 @@ export default function AlertRules({
params.set(QueryParams.ruleId, record.id.toString());
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
genericNavigate(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`, event);
};
const renderAlertRules = (): JSX.Element => (
@@ -143,10 +146,10 @@ export default function AlertRules({
tabIndex={0}
className="alert-rule-item home-data-item"
key={rule.id}
onClick={onEditHandler(rule)}
onClick={(event: React.MouseEvent): void => onEditHandler(rule, event)}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
onEditHandler(rule);
onEditHandler(rule, e);
}
}}
>

View File

@@ -1,4 +1,5 @@
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable sonarjs/cognitive-complexity */
import { Button, Skeleton, Tag, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
@@ -9,6 +10,7 @@ import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { genericNavigate } from 'utils/genericNavigate';
import { DOCS_LINKS } from '../constants';
@@ -84,16 +86,16 @@ function DataSourceInfo({
icon={<img src="/Icons/container-plus.svg" alt="plus" />}
role="button"
tabIndex={0}
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Connect dataSource clicked', {});
if (
activeLicense &&
activeLicense.platform === LicensePlatform.CLOUD
) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
genericNavigate(ROUTES.GET_STARTED_WITH_CLOUD, event);
} else {
window?.open(
window.open(
DOCS_LINKS.ADD_DATA_SOURCE,
'_blank',
'noopener noreferrer',

View File

@@ -30,6 +30,7 @@ import { UserPreference } from 'types/api/preferences/preference';
import { DataSource } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';
import { isIngestionActive } from 'utils/app';
import { genericNavigate } from 'utils/genericNavigate';
import { popupContainer } from 'utils/selectPopupContainer';
import AlertRules from './AlertRules/AlertRules';
@@ -550,11 +551,11 @@ export default function Home(): JSX.Element {
type="default"
className="periscope-btn secondary"
icon={<Wrench size={14} />}
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
source: 'Logs',
});
history.push(ROUTES.LOGS_EXPLORER);
genericNavigate(ROUTES.LOGS_EXPLORER, event);
}}
>
Open Logs Explorer
@@ -564,11 +565,11 @@ export default function Home(): JSX.Element {
type="default"
className="periscope-btn secondary"
icon={<Wrench size={14} />}
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
source: 'Traces',
});
history.push(ROUTES.TRACES_EXPLORER);
genericNavigate(ROUTES.TRACES_EXPLORER, event);
}}
>
Open Traces Explorer
@@ -578,11 +579,11 @@ export default function Home(): JSX.Element {
type="default"
className="periscope-btn secondary"
icon={<Wrench size={14} />}
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
source: 'Metrics',
});
history.push(ROUTES.METRICS_EXPLORER_EXPLORER);
genericNavigate(ROUTES.METRICS_EXPLORER_EXPLORER, event);
}}
>
Open Metrics Explorer
@@ -619,11 +620,11 @@ export default function Home(): JSX.Element {
type="default"
className="periscope-btn secondary"
icon={<Plus size={14} />}
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
source: 'Dashboards',
});
history.push(ROUTES.ALL_DASHBOARD);
genericNavigate(ROUTES.ALL_DASHBOARD, event);
}}
>
Create dashboard
@@ -661,11 +662,11 @@ export default function Home(): JSX.Element {
type="default"
className="periscope-btn secondary"
icon={<Plus size={14} />}
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
source: 'Alerts',
});
history.push(ROUTES.ALERTS_NEW);
genericNavigate(ROUTES.ALERTS_NEW, event);
}}
>
Create an alert

View File

@@ -4,12 +4,12 @@ import './HomeChecklist.styles.scss';
import { Button } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { ArrowRight, ArrowRightToLine, BookOpenText } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { USER_ROLES } from 'types/roles';
import { genericNavigate } from 'utils/genericNavigate';
export type ChecklistItem = {
id: string;
@@ -86,18 +86,22 @@ function HomeChecklist({
<Button
type="default"
className="periscope-btn secondary"
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Welcome Checklist: Get started clicked', {
step: item.id,
});
const checkForNewTabAndNavigate = (): void => {
genericNavigate(item.toRoute || '', event);
};
if (item.toRoute !== ROUTES.GET_STARTED_WITH_CLOUD) {
history.push(item.toRoute || '');
checkForNewTabAndNavigate();
} else if (
activeLicense &&
activeLicense.platform === LicensePlatform.CLOUD
) {
history.push(item.toRoute || '');
checkForNewTabAndNavigate();
} else {
window?.open(
item.docsLink || '',

View File

@@ -11,7 +11,6 @@ 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';
@@ -29,6 +28,8 @@ 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 { genericNavigate } from 'utils/genericNavigate';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { FeatureKeys } from '../../../constants/features';
import { DOCS_LINKS } from '../constants';
@@ -64,7 +65,7 @@ const EmptyState = memo(
<Button
type="default"
className="periscope-btn secondary"
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Get Started clicked', {
source: 'Service Metrics',
});
@@ -73,7 +74,7 @@ const EmptyState = memo(
activeLicenseV3 &&
activeLicenseV3.platform === LicensePlatform.CLOUD
) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
genericNavigate(ROUTES.GET_STARTED_WITH_CLOUD, event);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
@@ -116,7 +117,7 @@ const ServicesListTable = memo(
onRowClick,
}: {
services: ServicesList[];
onRowClick: (record: ServicesList) => void;
onRowClick: (record: ServicesList, event: React.MouseEvent) => void;
}): JSX.Element => (
<div className="services-list-container home-data-item-container metrics-services-list">
<div className="services-list">
@@ -125,8 +126,8 @@ const ServicesListTable = memo(
dataSource={services}
pagination={false}
className="services-table"
onRow={(record): { onClick: () => void } => ({
onClick: (): void => onRowClick(record),
onRow={(record): { onClick: (event: React.MouseEvent) => void } => ({
onClick: (event: React.MouseEvent): void => onRowClick(record, event),
})}
/>
</div>
@@ -284,11 +285,19 @@ function ServiceMetrics({
}, [onUpdateChecklistDoneItem, loadingUserPreferences, servicesExist]);
const handleRowClick = useCallback(
(record: ServicesList) => {
(record: ServicesList, event: React.MouseEvent) => {
logEvent('Homepage: Service clicked', {
serviceName: record.serviceName,
});
safeNavigate(`${ROUTES.APPLICATION}/${record.serviceName}`);
if (event && isCtrlOrMMetaKey(event)) {
window.open(
`${ROUTES.APPLICATION}/${record.serviceName}`,
'_blank',
'noopener,noreferrer',
);
} else {
safeNavigate(`${ROUTES.APPLICATION}/${record.serviceName}`);
}
},
[safeNavigate],
);

View File

@@ -3,7 +3,6 @@ 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';
@@ -15,6 +14,8 @@ import { LicensePlatform } from 'types/api/licensesV3/getActive';
import { ServicesList } from 'types/api/metrics/getService';
import { GlobalReducer } from 'types/reducer/globalTime';
import { USER_ROLES } from 'types/roles';
import { genericNavigate } from 'utils/genericNavigate';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { DOCS_LINKS } from '../constants';
import { columns, TIME_PICKER_OPTIONS } from './constants';
@@ -118,7 +119,7 @@ export default function ServiceTraces({
<Button
type="default"
className="periscope-btn secondary"
onClick={(): void => {
onClick={(event: React.MouseEvent): void => {
logEvent('Homepage: Get Started clicked', {
source: 'Service Traces',
});
@@ -127,7 +128,7 @@ export default function ServiceTraces({
activeLicense &&
activeLicense.platform === LicensePlatform.CLOUD
) {
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
genericNavigate(ROUTES.GET_STARTED_WITH_CLOUD, event);
} else {
window?.open(
DOCS_LINKS.ADD_DATA_SOURCE,
@@ -172,13 +173,21 @@ export default function ServiceTraces({
dataSource={top5Services}
pagination={false}
className="services-table"
onRow={(record): { onClick: () => void } => ({
onClick: (): void => {
onRow={(record): { onClick: (event: React.MouseEvent) => void } => ({
onClick: (event: React.MouseEvent): void => {
logEvent('Homepage: Service clicked', {
serviceName: record.serviceName,
});
safeNavigate(`${ROUTES.APPLICATION}/${record.serviceName}`);
if (event && isCtrlOrMMetaKey(event)) {
window.open(
`${ROUTES.APPLICATION}/${record.serviceName}`,
'_blank',
'noopener,noreferrer',
);
} else {
safeNavigate(`${ROUTES.APPLICATION}/${record.serviceName}`);
}
},
})}
/>

View File

@@ -5,10 +5,10 @@ import { Button, Divider, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useCallback, useState } from 'react';
import { DataSource } from 'types/common/queryBuilder';
import { genericNavigate } from 'utils/genericNavigate';
import AlertInfoCard from './AlertInfoCard';
import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks';
@@ -36,9 +36,9 @@ export function AlertsEmptyState(): JSX.Element {
const [loading, setLoading] = useState(false);
const onClickNewAlertHandler = useCallback(() => {
const onClickNewAlertHandler = useCallback((event: React.MouseEvent): void => {
setLoading(false);
history.push(ROUTES.ALERTS_NEW);
genericNavigate(ROUTES.ALERTS_NEW, event);
}, []);
return (
@@ -70,7 +70,9 @@ export function AlertsEmptyState(): JSX.Element {
<div className="action-container">
<Button
className="add-alert-btn"
onClick={onClickNewAlertHandler}
onClick={(event: React.MouseEvent): void =>
onClickNewAlertHandler(event)
}
icon={<PlusOutlined />}
disabled={!addNewAlert}
loading={loading}

View File

@@ -39,6 +39,7 @@ import { UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { GettableAlert } from 'types/api/alerts/get';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import DeleteAlert from './DeleteAlert';
import { ColumnButton, SearchContainer } from './styles';
@@ -299,7 +300,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const onClickHandler = (e: React.MouseEvent<HTMLElement>): void => {
e.stopPropagation();
e.preventDefault();
onEditHandler(record, e.metaKey || e.ctrlKey);
onEditHandler(record, isCtrlOrMMetaKey(e));
};
return <Typography.Link onClick={onClickHandler}>{value}</Typography.Link>;

View File

@@ -118,7 +118,7 @@ const templatesList: DashboardTemplate[] = [
interface DashboardTemplatesModalProps {
showNewDashboardTemplatesModal: boolean;
onCreateNewDashboard: () => void;
onCreateNewDashboard: (event: React.MouseEvent) => void;
onCancel: () => void;
}
@@ -204,7 +204,9 @@ export default function DashboardTemplatesModal({
type="primary"
className="periscope-btn primary"
icon={<Plus size={14} />}
onClick={onCreateNewDashboard}
onClick={(event: React.MouseEvent): void =>
onCreateNewDashboard(event)
}
>
New dashboard
</Button>

View File

@@ -86,6 +86,7 @@ import {
Widgets,
} from 'types/api/dashboard/getAll';
import APIError from 'types/api/error';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
import ImportJSON from './ImportJSON';
@@ -282,35 +283,48 @@ function DashboardsList(): JSX.Element {
refetchDashboardList,
})) || [];
const onNewDashboardHandler = useCallback(async () => {
try {
logEvent('Dashboard List: Create dashboard clicked', {});
setNewDashboardState({
...newDashboardState,
loading: true,
});
const response = await createDashboard({
title: t('new_dashboard_title', {
ns: 'dashboard',
}),
uploadedGrafana: false,
version: ENTITY_VERSION_V5,
});
const onNewDashboardHandler = useCallback(
async (event: React.MouseEvent): Promise<void> => {
try {
logEvent('Dashboard List: Create dashboard clicked', {});
setNewDashboardState({
...newDashboardState,
loading: true,
});
const response = await createDashboard({
title: t('new_dashboard_title', {
ns: 'dashboard',
}),
uploadedGrafana: false,
version: ENTITY_VERSION_V5,
});
safeNavigate(
generatePath(ROUTES.DASHBOARD, {
dashboardId: response.data.id,
}),
);
} catch (error) {
showErrorModal(error as APIError);
setNewDashboardState({
...newDashboardState,
error: true,
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
});
}
}, [newDashboardState, safeNavigate, showErrorModal, t]);
if (event && isCtrlOrMMetaKey(event)) {
window.open(
generatePath(ROUTES.DASHBOARD, {
dashboardId: response.data.id,
}),
'_blank',
'noopener,noreferrer',
);
} else {
safeNavigate(
generatePath(ROUTES.DASHBOARD, {
dashboardId: response.data.id,
}),
);
}
} catch (error) {
showErrorModal(error as APIError);
setNewDashboardState({
...newDashboardState,
error: true,
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
});
}
},
[newDashboardState, safeNavigate, showErrorModal, t],
);
const onModalHandler = (uploadedGrafana: boolean): void => {
logEvent('Dashboard List: Import JSON clicked', {});
@@ -412,8 +426,8 @@ function DashboardsList(): JSX.Element {
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
if (isCtrlOrMMetaKey(event)) {
window.open(getLink(), '_blank', 'noopener,noreferrer');
} else {
safeNavigate(getLink());
}
@@ -639,8 +653,8 @@ function DashboardsList(): JSX.Element {
label: (
<div
className="create-dashboard-menu-item"
onClick={(): void => {
onNewDashboardHandler();
onClick={(event: React.MouseEvent): void => {
onNewDashboardHandler(event);
}}
>
<LayoutGrid size={14} /> Create dashboard
@@ -927,7 +941,9 @@ function DashboardsList(): JSX.Element {
<DashboardTemplatesModal
showNewDashboardTemplatesModal={showNewDashboardTemplatesModal}
onCreateNewDashboard={onNewDashboardHandler}
onCreateNewDashboard={(event: React.MouseEvent): Promise<void> =>
onNewDashboardHandler(event)
}
onCancel={(): void => {
setShowNewDashboardTemplatesModal(false);
}}

View File

@@ -1,6 +1,6 @@
import { LockFilled } from '@ant-design/icons';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { genericNavigate } from 'utils/genericNavigate';
import { Data } from '../DashboardsList';
import { TableLinkText } from './styles';
@@ -11,11 +11,7 @@ function Name(name: Data['name'], data: Data): JSX.Element {
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${DashboardId}`;
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
} else {
history.push(getLink());
}
genericNavigate(getLink(), event);
};
return (

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Col } from 'antd';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app';
@@ -179,14 +180,17 @@ function DBCall(): JSX.Element {
type="default"
size="small"
id="database_call_rps_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onClick={(event): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
>
View Traces
</Button>
@@ -215,14 +219,17 @@ function DBCall(): JSX.Element {
type="default"
size="small"
id="database_call_avg_duration_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onClick={(event): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
>
View Traces
</Button>

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Col } from 'antd';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app';
@@ -244,22 +245,28 @@ function External(): JSX.Element {
<Col span={12}>
<GraphControlsPanel
id="external_call_error_percentage_button"
onViewTracesClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery: errorApmToTraceQuery,
stepInterval,
safeNavigate,
})}
onViewAPIMonitoringClick={onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address || '',
isError: true,
stepInterval: 300,
safeNavigate,
})}
onViewTracesClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery: errorApmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
onViewAPIMonitoringClick={(event: React.MouseEvent): void =>
onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address || '',
isError: true,
stepInterval: 300,
safeNavigate,
event,
})
}
/>
<Card data-testid="external_call_error_percentage">
<GraphContainer>
@@ -286,22 +293,28 @@ function External(): JSX.Element {
<Col span={12}>
<GraphControlsPanel
id="external_call_duration_button"
onViewTracesClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onViewAPIMonitoringClick={onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
safeNavigate,
})}
onViewTracesClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
onViewAPIMonitoringClick={(event: React.MouseEvent): void =>
onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
safeNavigate,
event,
})
}
/>
<Card data-testid="external_call_duration">
@@ -331,22 +344,28 @@ function External(): JSX.Element {
<Col span={12}>
<GraphControlsPanel
id="external_call_rps_by_address_button"
onViewTracesClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onViewAPIMonitoringClick={onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
safeNavigate,
})}
onViewTracesClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
onViewAPIMonitoringClick={(event: React.MouseEvent): void =>
onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
safeNavigate,
event,
})
}
/>
<Card data-testid="external_call_rps_by_address">
<GraphContainer>
@@ -373,22 +392,28 @@ function External(): JSX.Element {
<Col span={12}>
<GraphControlsPanel
id="external_call_duration_by_address_button"
onViewTracesClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onViewAPIMonitoringClick={onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
safeNavigate,
})}
onViewTracesClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
onViewAPIMonitoringClick={(event: React.MouseEvent): void =>
onViewAPIMonitoringPopupClick({
servicename,
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
safeNavigate,
event,
})
}
/>
<Card data-testid="external_call_duration_by_address">

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-identical-functions */
import logEvent from 'api/common/logEvent';
import getTopLevelOperations, {
ServiceDataProps,
@@ -31,6 +32,7 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
import { genericNavigate } from 'utils/genericNavigate';
import { secondsToMilliseconds } from 'utils/timeUtils';
import { v4 as uuid } from 'uuid';
@@ -228,14 +230,16 @@ function Application(): JSX.Element {
* @param timestamp - The timestamp in seconds
* @param apmToTraceQuery - query object
* @param isViewLogsClicked - Whether this is for viewing logs vs traces
* @param event - Click event to handle opening in new tab
* @returns A callback function that handles the navigation when executed
*/
const onErrorTrackHandler = useCallback(
(
timestamp: number,
apmToTraceQuery: Query,
event: React.MouseEvent,
isViewLogsClicked?: boolean,
): (() => void) => (): void => {
): void => {
const endTime = secondsToMilliseconds(timestamp);
const startTime = secondsToMilliseconds(timestamp - stepInterval);
@@ -259,7 +263,7 @@ function Application(): JSX.Element {
queryString,
);
history.push(newPath);
genericNavigate(newPath, event);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[stepInterval],
@@ -319,14 +323,17 @@ function Application(): JSX.Element {
type="default"
size="small"
id="Rate_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
>
View Traces
</Button>
@@ -349,14 +356,17 @@ function Application(): JSX.Element {
type="default"
size="small"
id="ApDex_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
>
View Traces
</Button>
@@ -370,15 +380,12 @@ function Application(): JSX.Element {
<ColErrorContainer>
<GraphControlsPanel
id="Error_button"
onViewLogsClick={onErrorTrackHandler(
selectedTimeStamp,
logErrorQuery,
true,
)}
onViewTracesClick={onErrorTrackHandler(
selectedTimeStamp,
errorTrackQuery,
)}
onViewLogsClick={(event: React.MouseEvent): void =>
onErrorTrackHandler(selectedTimeStamp, logErrorQuery, event, true)
}
onViewTracesClick={(event: React.MouseEvent): void =>
onErrorTrackHandler(selectedTimeStamp, errorTrackQuery, event)
}
/>
<TopLevelOperation

View File

@@ -6,9 +6,9 @@ import { Binoculars, DraftingCompass, ScrollText } from 'lucide-react';
interface GraphControlsPanelProps {
id: string;
onViewLogsClick?: () => void;
onViewTracesClick: () => void;
onViewAPIMonitoringClick?: () => void;
onViewLogsClick?: (event: React.MouseEvent) => void;
onViewTracesClick: (event: React.MouseEvent) => void;
onViewAPIMonitoringClick?: (event: React.MouseEvent) => void;
}
function GraphControlsPanel({
@@ -23,7 +23,7 @@ function GraphControlsPanel({
type="link"
icon={<DraftingCompass size={14} />}
size="small"
onClick={onViewTracesClick}
onClick={(event: React.MouseEvent): void => onViewTracesClick(event)}
style={{ color: Color.BG_VANILLA_100 }}
>
View traces
@@ -33,7 +33,7 @@ function GraphControlsPanel({
type="link"
icon={<ScrollText size={14} />}
size="small"
onClick={onViewLogsClick}
onClick={(event: React.MouseEvent): void => onViewLogsClick(event)}
style={{ color: Color.BG_VANILLA_100 }}
>
View logs
@@ -44,7 +44,9 @@ function GraphControlsPanel({
type="link"
icon={<Binoculars size={14} />}
size="small"
onClick={onViewAPIMonitoringClick}
onClick={(event: React.MouseEvent): void =>
onViewAPIMonitoringClick(event)
}
style={{ color: Color.BG_VANILLA_100 }}
>
View External APIs

View File

@@ -103,23 +103,29 @@ function ServiceOverview({
<>
<GraphControlsPanel
id="Service_button"
onViewLogsClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery: apmToLogQuery,
isViewLogsClicked: true,
stepInterval,
safeNavigate,
})}
onViewTracesClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
onViewLogsClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery: apmToLogQuery,
isViewLogsClicked: true,
stepInterval,
safeNavigate,
event,
})
}
onViewTracesClick={(event: React.MouseEvent): void =>
onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
event,
})
}
/>
<Card data-testid="service_latency">
<GraphContainer>

View File

@@ -3,6 +3,7 @@ import { navigateToTrace } from 'container/MetricsApplication/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { v4 as uuid } from 'uuid';
import { useGetAPMToTracesQueries } from '../../util';
@@ -50,7 +51,7 @@ function ColumnWithLink({
return (
<Tooltip placement="topLeft" title={text}>
<Typography.Link
onClick={(e): void => handleOnClick(text, e.metaKey || e.ctrlKey)}
onClick={(e): void => handleOnClick(text, isCtrlOrMMetaKey(e))}
>
{text}
</Typography.Link>

View File

@@ -22,6 +22,7 @@ import {
} from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { Tags } from 'types/reducer/trace';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { secondsToMilliseconds } from 'utils/timeUtils';
import { v4 as uuid } from 'uuid';
@@ -43,6 +44,7 @@ interface OnViewTracePopupClickProps {
isViewLogsClicked?: boolean;
stepInterval?: number;
safeNavigate: (url: string) => void;
event: React.MouseEvent;
}
interface OnViewAPIMonitoringPopupClickProps {
@@ -53,6 +55,7 @@ interface OnViewAPIMonitoringPopupClickProps {
isError: boolean;
safeNavigate: (url: string) => void;
event: React.MouseEvent;
}
export function generateExplorerPath(
@@ -83,7 +86,7 @@ export function generateExplorerPath(
* @param isViewLogsClicked - Whether this is for viewing logs vs traces
* @param stepInterval - Time interval in seconds
* @param safeNavigate - Navigation function
* @param event - Click event to handle opening in new tab
*/
export function onViewTracePopupClick({
selectedTraceTags,
@@ -93,33 +96,34 @@ export function onViewTracePopupClick({
isViewLogsClicked,
stepInterval,
safeNavigate,
}: OnViewTracePopupClickProps): VoidFunction {
return (): void => {
const endTime = secondsToMilliseconds(timestamp);
const startTime = secondsToMilliseconds(timestamp - (stepInterval || 60));
event,
}: OnViewTracePopupClickProps): void {
const endTime = secondsToMilliseconds(timestamp);
const startTime = secondsToMilliseconds(timestamp - (stepInterval || 60));
const urlParams = new URLSearchParams(window.location.search);
urlParams.set(QueryParams.startTime, startTime.toString());
urlParams.set(QueryParams.endTime, endTime.toString());
urlParams.delete(QueryParams.relativeTime);
const avialableParams = routeConfig[ROUTES.TRACE];
const queryString = getQueryString(avialableParams, urlParams);
const urlParams = new URLSearchParams(window.location.search);
urlParams.set(QueryParams.startTime, startTime.toString());
urlParams.set(QueryParams.endTime, endTime.toString());
urlParams.delete(QueryParams.relativeTime);
const avialableParams = routeConfig[ROUTES.TRACE];
const queryString = getQueryString(avialableParams, urlParams);
const JSONCompositeQuery = encodeURIComponent(
JSON.stringify(apmToTraceQuery),
);
const JSONCompositeQuery = encodeURIComponent(JSON.stringify(apmToTraceQuery));
const newPath = generateExplorerPath(
isViewLogsClicked,
urlParams,
servicename,
selectedTraceTags,
JSONCompositeQuery,
queryString,
);
const newPath = generateExplorerPath(
isViewLogsClicked,
urlParams,
servicename,
selectedTraceTags,
JSONCompositeQuery,
queryString,
);
if (event && isCtrlOrMMetaKey(event)) {
window.open(newPath, '_blank', 'noopener,noreferrer');
} else {
safeNavigate(newPath);
};
}
}
const generateAPIMonitoringPath = (
@@ -149,49 +153,52 @@ export function onViewAPIMonitoringPopupClick({
isError,
stepInterval,
safeNavigate,
}: OnViewAPIMonitoringPopupClickProps): VoidFunction {
return (): void => {
const endTime = timestamp + (stepInterval || 60);
const startTime = timestamp - (stepInterval || 60);
const filters = {
items: [
...(isError
? [
{
id: uuid().slice(0, 8),
key: {
key: 'hasError',
dataType: DataTypes.bool,
type: 'tag',
id: 'hasError--bool--tag--true',
},
op: 'in',
value: ['true'],
event,
}: OnViewAPIMonitoringPopupClickProps): void {
const endTime = timestamp + (stepInterval || 60);
const startTime = timestamp - (stepInterval || 60);
const filters = {
items: [
...(isError
? [
{
id: uuid().slice(0, 8),
key: {
key: 'hasError',
dataType: DataTypes.bool,
type: 'tag',
id: 'hasError--bool--tag--true',
},
]
: []),
{
id: uuid().slice(0, 8),
key: {
key: 'service.name',
dataType: DataTypes.String,
type: 'resource',
},
op: '=',
value: servicename,
op: 'in',
value: ['true'],
},
]
: []),
{
id: uuid().slice(0, 8),
key: {
key: 'service.name',
dataType: DataTypes.String,
type: 'resource',
},
],
op: 'AND',
};
const newPath = generateAPIMonitoringPath(
domainName,
startTime,
endTime,
filters,
);
safeNavigate(newPath);
op: '=',
value: servicename,
},
],
op: 'AND',
};
const newPath = generateAPIMonitoringPath(
domainName,
startTime,
endTime,
filters,
);
if (event && isCtrlOrMMetaKey(event)) {
window.open(newPath, '_blank', 'noopener,noreferrer');
} else {
safeNavigate(newPath);
}
}
export function useGraphClickHandler(

View File

@@ -17,6 +17,7 @@ import { AppState } from 'store/reducers';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { v4 as uuid } from 'uuid';
import { IServiceName } from './Tabs/types';
@@ -115,7 +116,7 @@ function TopOperationsTable({
e.stopPropagation();
e.preventDefault();
if (e.metaKey || e.ctrlKey) {
if (isCtrlOrMMetaKey(e)) {
handleOnClick(text, true); // open in new tab
} else {
handleOnClick(text, false); // open in current tab

View File

@@ -1,13 +1,14 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './NoLogs.styles.scss';
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
import { ArrowUpRight } from 'lucide-react';
import { DataSource } from 'types/common/queryBuilder';
import DOCLINKS from 'utils/docLinks';
import { genericNavigate } from 'utils/genericNavigate';
export default function NoLogs({
dataSource,
@@ -16,6 +17,8 @@ export default function NoLogs({
}): JSX.Element {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const REL_NOOPENER_NOREFERRER = 'noopener,noreferrer';
const handleLinkClick = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
): void => {
@@ -38,13 +41,25 @@ export default function NoLogs({
} else {
link = ROUTES.GET_STARTED_LOGS_MANAGEMENT;
}
history.push(link);
genericNavigate(link, e);
} else if (dataSource === 'traces') {
window.open(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE, '_blank');
window.open(
DOCLINKS.TRACES_EXPLORER_EMPTY_STATE,
'_blank',
REL_NOOPENER_NOREFERRER,
);
} else if (dataSource === DataSource.METRICS) {
window.open(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE, '_blank');
window.open(
DOCLINKS.METRICS_EXPLORER_EMPTY_STATE,
'_blank',
REL_NOOPENER_NOREFERRER,
);
} else {
window.open(`${DOCLINKS.USER_GUIDE}${dataSource}/`, '_blank');
window.open(
`${DOCLINKS.USER_GUIDE}${dataSource}/`,
'_blank',
REL_NOOPENER_NOREFERRER,
);
}
};
return (
@@ -59,7 +74,12 @@ export default function NoLogs({
</span>
</Typography>
<Typography.Link className="send-logs-link" onClick={handleLinkClick}>
<Typography.Link
className="send-logs-link"
onClick={(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void =>
handleLinkClick(e)
}
>
Sending {dataSource} to SigNoz <ArrowUpRight size={16} />
</Typography.Link>
</div>

View File

@@ -48,6 +48,7 @@ import {
} from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
@@ -217,7 +218,7 @@ function QueryBuilderSearch({
if (
!disableNavigationShortcuts &&
(event.ctrlKey || event.metaKey) &&
isCtrlOrMMetaKey(event) &&
event.key === 'Enter'
) {
event.preventDefault();
@@ -228,7 +229,7 @@ function QueryBuilderSearch({
if (
!disableNavigationShortcuts &&
(event.ctrlKey || event.metaKey) &&
isCtrlOrMMetaKey(event) &&
event.key === '/'
) {
event.preventDefault();

View File

@@ -52,6 +52,7 @@ import {
TagFilter,
} from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
@@ -456,12 +457,12 @@ function QueryBuilderSearchV2(
setTags((prev) => prev.slice(0, -1));
}
if ((event.ctrlKey || event.metaKey) && event.key === '/') {
if (isCtrlOrMMetaKey(event) && event.key === '/') {
event.preventDefault();
event.stopPropagation();
setShowAllFilters((prev) => !prev);
}
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
if (isCtrlOrMMetaKey(event) && event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
handleRunQuery();

View File

@@ -67,6 +67,8 @@ import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { checkVersionState } from 'utils/app';
import { showErrorNotification } from 'utils/error';
import { genericNavigate } from 'utils/genericNavigate';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
import { useCmdK } from '../../providers/cmdKProvider';
import { routeConfig } from './config';
@@ -292,8 +294,6 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
icon: <Cog size={16} />,
};
const isCtrlMetaKey = (e: MouseEvent): boolean => e.ctrlKey || e.metaKey;
const isLatestVersion = checkVersionState(currentVersion, latestVersion);
const [
@@ -411,7 +411,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
const openInNewTab = (path: string): void => {
window.open(path, '_blank');
window.open(path, '_blank', 'noopener,noreferrer');
};
const onClickGetStarted = (event: MouseEvent): void => {
@@ -424,7 +424,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
? ROUTES.GET_STARTED_WITH_CLOUD
: ROUTES.GET_STARTED;
if (isCtrlMetaKey(event)) {
if (isCtrlOrMMetaKey(event)) {
openInNewTab(onboaringRoute);
} else {
history.push(onboaringRoute);
@@ -439,7 +439,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const queryString = getQueryString(availableParams || [], params);
if (pathname !== key) {
if (event && isCtrlMetaKey(event)) {
if (event && isCtrlOrMMetaKey(event)) {
openInNewTab(`${key}?${queryString.join('&')}`);
} else {
history.push(`${key}?${queryString.join('&')}`, {
@@ -634,11 +634,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const handleMenuItemClick = (event: MouseEvent, item: SidebarItem): void => {
if (item.key === ROUTES.SETTINGS) {
if (isCtrlMetaKey(event)) {
openInNewTab(settingsRoute);
} else {
history.push(settingsRoute);
}
genericNavigate(settingsRoute, event);
} else if (item.key === 'quick-search') {
openCmdK();
} else if (item) {
@@ -762,7 +758,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
);
if (item && !('type' in item) && item.isExternal && item.url) {
window.open(item.url, '_blank');
window.open(item.url, '_blank', 'noopener,noreferrer');
}
if (item && !('type' in item)) {

View File

@@ -10,9 +10,7 @@ import {
import { formUrlParams } from 'container/TraceDetail/utils';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import history from 'lib/history';
import omit from 'lodash-es/omit';
import { HTMLAttributes } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { updateURL } from 'store/actions/trace/util';
@@ -25,6 +23,7 @@ import {
UPDATE_SPANS_AGGREGATE_PAGE_SIZE,
} from 'types/actions/trace';
import { TraceReducer } from 'types/reducer/trace';
import { genericNavigate } from 'utils/genericNavigate';
import { v4 } from 'uuid';
dayjs.extend(duration);
@@ -203,15 +202,13 @@ function TraceTable(): JSX.Element {
style={{
cursor: 'pointer',
}}
onRow={(record: TableType): HTMLAttributes<TableType> => ({
onClick: (event): void => {
onRow={(
record: TableType,
): { onClick: (event: React.MouseEvent) => void } => ({
onClick: (event: React.MouseEvent): void => {
event.preventDefault();
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getLink(record), '_blank');
} else {
history.push(getLink(record));
}
genericNavigate(getLink(record), event);
},
})}
pagination={{

View File

@@ -84,8 +84,8 @@ function TracesTableComponent({
onClick: (event): void => {
event.preventDefault();
event.stopPropagation();
if (event.metaKey || event.ctrlKey) {
window.open(getTraceLink(record), '_blank');
if (event.ctrlKey || event.metaKey) {
window.open(getTraceLink(record), '_blank', 'noopener,noreferrer');
} else {
history.push(getTraceLink(record));
}

View File

@@ -27,6 +27,7 @@ import {
within,
} from 'tests/test-utils';
import { QueryRangePayloadV5 } from 'types/api/v5/queryRange';
import * as genericNavigate from 'utils/genericNavigate';
import TracesExplorer from '..';
import { Filter } from '../Filter/Filter';
@@ -772,6 +773,12 @@ describe('TracesExplorer - ', () => {
});
it('create an alert btn assert', async () => {
const historyPush = jest.fn();
jest.spyOn(genericNavigate, 'genericNavigate').mockImplementation((link) => {
historyPush(link);
});
const { getByText } = renderWithTracesExplorerRouter(<TracesExplorer />, [
'/traces-explorer/?panelType=list&selectedExplorerView=list',
]);

View File

@@ -0,0 +1,18 @@
import history from 'lib/history';
import { KeyboardEvent, MouseEvent } from 'react';
import { isCtrlOrMMetaKey } from 'utils/isCtrlOrMMetaKey';
export const genericNavigate = (
link: string,
event?:
| MouseEvent
| KeyboardEvent
| globalThis.MouseEvent
| globalThis.KeyboardEvent,
): void => {
if (event && isCtrlOrMMetaKey(event)) {
window.open(link, '_blank', 'noopener,noreferrer');
} else {
history.push(link);
}
};

View File

@@ -0,0 +1,9 @@
import { KeyboardEvent, MouseEvent } from 'react';
export const isCtrlOrMMetaKey = (
event:
| MouseEvent
| KeyboardEvent
| globalThis.MouseEvent
| globalThis.KeyboardEvent,
): boolean => event.metaKey || event.ctrlKey;