mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-20 03:30:43 +01:00
Compare commits
3 Commits
feat/chart
...
fix/panel-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1face0e22 | ||
|
|
19c722044a | ||
|
|
1aa6346a4c |
@@ -18,10 +18,9 @@ jest.mock('periscope/components/DataViewer', () => ({
|
||||
DataViewer: (): JSX.Element => <div data-testid="overview-data-viewer" />,
|
||||
}));
|
||||
|
||||
// The flag to be removed later
|
||||
jest.mock('../constants', () => ({
|
||||
...jest.requireActual('../constants'),
|
||||
isLogDetailsV2: true,
|
||||
// Force v2 for these tests regardless of route.
|
||||
jest.mock('../useIsLogDetailsV2', () => ({
|
||||
useIsLogDetailsV2: (): boolean => true,
|
||||
}));
|
||||
|
||||
const mockLog: ILog = {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// temporary flag to be removed with old log details code.
|
||||
export const isLogDetailsV2 = true;
|
||||
|
||||
export const VIEW_TYPES = {
|
||||
OVERVIEW: 'OVERVIEW',
|
||||
JSON: 'JSON',
|
||||
|
||||
@@ -51,11 +51,12 @@ import { ILogBody } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
|
||||
import { isLogDetailsV2, RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
|
||||
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
|
||||
import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces';
|
||||
import LogDetailsHeader from './LogDetailsHeader/LogDetailsHeader';
|
||||
import { useLogNavigation } from './LogDetailsHeader/useLogNavigation';
|
||||
import LogHighlights from './LogHighlights/LogHighlights';
|
||||
import { useIsLogDetailsV2 } from './useIsLogDetailsV2';
|
||||
|
||||
import './LogDetails.styles.scss';
|
||||
|
||||
@@ -92,6 +93,8 @@ function LogDetailInner({
|
||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||
const { stagedQuery } = useQueryBuilder();
|
||||
|
||||
const isLogDetailsV2 = useIsLogDetailsV2();
|
||||
|
||||
// Handle clicks outside to close drawer, except on explicitly ignored regions
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent): void => {
|
||||
|
||||
9
frontend/src/components/LogDetail/useIsLogDetailsV2.ts
Normal file
9
frontend/src/components/LogDetail/useIsLogDetailsV2.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
// v2 is rolled out only on the logs explorer route for now; every other surface
|
||||
// (dashboards, infra monitoring, etc.) keeps the v1 log details view.
|
||||
export function useIsLogDetailsV2(): boolean {
|
||||
const { pathname } = useLocation();
|
||||
return pathname === ROUTES.LOGS_EXPLORER;
|
||||
}
|
||||
@@ -139,11 +139,6 @@ export default function AlertRules({
|
||||
encodeURIComponent(JSON.stringify(compositeQuery)),
|
||||
);
|
||||
|
||||
const panelType = record.condition.compositeQuery.panelType;
|
||||
if (panelType) {
|
||||
params.set(QueryParams.panelTypes, panelType);
|
||||
}
|
||||
|
||||
params.set(QueryParams.ruleId, record.id);
|
||||
|
||||
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('ListAlertRules — row click navigation', () => {
|
||||
const [url] = safeNavigateMock.mock.calls[0];
|
||||
expect(url).toContain('/alerts/overview?');
|
||||
expect(url).toContain('ruleId=rule-1');
|
||||
expect(url).toContain('panelTypes=graph');
|
||||
expect(url).not.toContain('panelTypes');
|
||||
expect(url).toContain('compositeQuery=');
|
||||
});
|
||||
|
||||
|
||||
@@ -36,11 +36,6 @@ export function useAlertRulesHandlers(
|
||||
encodeURIComponent(JSON.stringify(compositeQuery)),
|
||||
);
|
||||
|
||||
const panelType = rule.condition.compositeQuery.panelType;
|
||||
if (panelType) {
|
||||
params.set(QueryParams.panelTypes, panelType);
|
||||
}
|
||||
|
||||
params.set(QueryParams.ruleId, rule.id);
|
||||
|
||||
return `${ROUTES.ALERT_OVERVIEW}?${params.toString()}`;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { OptionsQuery } from 'container/OptionsMenu/types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { ChevronDown, ChevronRight, Search } from '@signozhq/icons';
|
||||
import { isLogDetailsV2 } from 'components/LogDetail/constants';
|
||||
import { useIsLogDetailsV2 } from 'components/LogDetail/useIsLogDetailsV2';
|
||||
import { DataViewer } from 'periscope/components/DataViewer';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
@@ -69,6 +69,8 @@ function Overview({
|
||||
isListViewPanel,
|
||||
});
|
||||
|
||||
const isLogDetailsV2 = useIsLogDetailsV2();
|
||||
|
||||
if (isLogDetailsV2) {
|
||||
const raw = aggregateAttributesResourcesToObject(logData);
|
||||
const prettyData = buildPrettyViewData(raw);
|
||||
|
||||
@@ -95,7 +95,6 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(updatedQuery)),
|
||||
);
|
||||
params.set(QueryParams.panelTypes, widget.panelTypes);
|
||||
params.set(QueryParams.version, ENTITY_VERSION_V5);
|
||||
params.set(QueryParams.source, YAxisSource.DASHBOARDS);
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Router } from 'react-router-dom';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
||||
import { useGetPanelTypesQueryParam } from './useGetPanelTypesQueryParam';
|
||||
|
||||
const renderWithSearch = (
|
||||
search: string,
|
||||
defaultPanelType?: PANEL_TYPES,
|
||||
): PANEL_TYPES | null => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [`/logs/logs-explorer${search}`],
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useGetPanelTypesQueryParam(defaultPanelType),
|
||||
{
|
||||
wrapper: ({ children }) => <Router history={history}>{children}</Router>,
|
||||
},
|
||||
);
|
||||
|
||||
return result.current;
|
||||
};
|
||||
|
||||
describe('useGetPanelTypesQueryParam', () => {
|
||||
it('reads a JSON encoded panel type, as written by the explorers', () => {
|
||||
expect(renderWithSearch('?panelTypes=%22table%22', PANEL_TYPES.LIST)).toBe(
|
||||
PANEL_TYPES.TABLE,
|
||||
);
|
||||
});
|
||||
|
||||
it('reads a plain string panel type, as written by the alerts flow', () => {
|
||||
expect(renderWithSearch('?panelTypes=graph', PANEL_TYPES.LIST)).toBe(
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to the default for an unparseable panel type', () => {
|
||||
expect(renderWithSearch('?panelTypes=%7Bfoo', PANEL_TYPES.LIST)).toBe(
|
||||
PANEL_TYPES.LIST,
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to the default for a value that is not a panel type', () => {
|
||||
expect(renderWithSearch('?panelTypes=%22nope%22', PANEL_TYPES.LIST)).toBe(
|
||||
PANEL_TYPES.LIST,
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to the default when the param is absent', () => {
|
||||
expect(renderWithSearch('', PANEL_TYPES.LIST)).toBe(PANEL_TYPES.LIST);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,24 @@ import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
|
||||
const PANEL_TYPE_VALUES = new Set<string>(Object.values(PANEL_TYPES));
|
||||
|
||||
// The param is JSON encoded by the explorers and written as a plain string by the
|
||||
// alerts flow, so accept both and treat anything unrecognised as absent.
|
||||
const parsePanelType = (value: string): PANEL_TYPES | null => {
|
||||
let parsed: unknown = value;
|
||||
|
||||
try {
|
||||
parsed = JSON.parse(value);
|
||||
} catch {
|
||||
parsed = value;
|
||||
}
|
||||
|
||||
return typeof parsed === 'string' && PANEL_TYPE_VALUES.has(parsed)
|
||||
? (parsed as PANEL_TYPES)
|
||||
: null;
|
||||
};
|
||||
|
||||
export const useGetPanelTypesQueryParam = <T extends PANEL_TYPES | undefined>(
|
||||
defaultPanelType?: T,
|
||||
): T extends undefined ? PANEL_TYPES | null : PANEL_TYPES => {
|
||||
@@ -11,6 +29,10 @@ export const useGetPanelTypesQueryParam = <T extends PANEL_TYPES | undefined>(
|
||||
return useMemo(() => {
|
||||
const panelTypeQuery = urlQuery.get(QueryParams.panelTypes);
|
||||
|
||||
return panelTypeQuery ? JSON.parse(panelTypeQuery) : defaultPanelType;
|
||||
}, [urlQuery, defaultPanelType]);
|
||||
return (
|
||||
(panelTypeQuery ? parsePanelType(panelTypeQuery) : null) ?? defaultPanelType
|
||||
);
|
||||
}, [urlQuery, defaultPanelType]) as T extends undefined
|
||||
? PANEL_TYPES | null
|
||||
: PANEL_TYPES;
|
||||
};
|
||||
|
||||
@@ -168,7 +168,6 @@ describe('useCreateAlertFromPanel', () => {
|
||||
// The resolved query is seeded with the panel-derived alert prefill.
|
||||
expect(mockBuildAlertUrl).toHaveBeenCalledWith(
|
||||
{ resolved: 'query' },
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
undefined,
|
||||
mockPrefill,
|
||||
);
|
||||
|
||||
@@ -79,7 +79,6 @@ export function useCreateAlertFromPanel(): (
|
||||
const unit = readPanelUnit(panel.spec.plugin);
|
||||
const url = buildAlertUrl(
|
||||
query,
|
||||
panelType,
|
||||
unit,
|
||||
deriveAlertPrefill(panel, query, unit),
|
||||
);
|
||||
|
||||
@@ -65,14 +65,19 @@ describe('buildCreateAlertUrl', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('tags the URL with panel type, v5 version, and the dashboards source', () => {
|
||||
it('tags the URL with the v5 version and the dashboards source', () => {
|
||||
const params = parse(buildCreateAlertUrl(makePanel()));
|
||||
|
||||
expect(params.get(QueryParams.panelTypes)).toBe(PANEL_TYPES.TIME_SERIES);
|
||||
expect(params.get(QueryParams.version)).toBe(ENTITY_VERSION_V5);
|
||||
expect(params.get(QueryParams.source)).toBe('dashboards');
|
||||
});
|
||||
|
||||
it('does not tag the URL with a panel type, which the alert page ignores', () => {
|
||||
const params = parse(buildCreateAlertUrl(makePanel()));
|
||||
|
||||
expect(params.get(QueryParams.panelTypes)).toBeNull();
|
||||
});
|
||||
|
||||
it('encodes the translated query as the compositeQuery param', () => {
|
||||
const params = parse(buildCreateAlertUrl(makePanel()));
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
import { YAxisSource } from 'components/YAxisUnitSelector/types';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { PANEL_KIND_TO_PANEL_TYPE } from 'pages/DashboardPageV2/DashboardContainer/Panels/types/panelKind';
|
||||
import { fromPerses } from 'pages/DashboardPageV2/DashboardContainer/queryV5/persesQueryAdapters';
|
||||
@@ -34,7 +33,6 @@ export function readPanelUnit(
|
||||
*/
|
||||
export function buildAlertUrl(
|
||||
query: Query,
|
||||
panelType: PANEL_TYPES,
|
||||
unit?: string,
|
||||
prefill?: PanelAlertPrefill,
|
||||
): string {
|
||||
@@ -48,7 +46,6 @@ export function buildAlertUrl(
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(query)),
|
||||
);
|
||||
params.set(QueryParams.panelTypes, panelType);
|
||||
params.set(QueryParams.version, ENTITY_VERSION_V5);
|
||||
params.set(QueryParams.source, YAxisSource.DASHBOARDS);
|
||||
|
||||
@@ -76,10 +73,5 @@ export function buildCreateAlertUrl(panel: DashboardtypesPanelDTO): string {
|
||||
const panelType = PANEL_KIND_TO_PANEL_TYPE[panel.spec.plugin.kind];
|
||||
const query = fromPerses(panel.spec.queries, panelType);
|
||||
const unit = readPanelUnit(panel.spec.plugin);
|
||||
return buildAlertUrl(
|
||||
query,
|
||||
panelType,
|
||||
unit,
|
||||
deriveAlertPrefill(panel, query, unit),
|
||||
);
|
||||
return buildAlertUrl(query, unit, deriveAlertPrefill(panel, query, unit));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user