mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
chore: automatically show query addon when the value is present even after refresh (#9024)
* chore: automatically show query addon when the value is present even after refresh * chore: minor cleanup * test: added tests for queryAddon * test: removed inputwithlabel mock
This commit is contained in:
parent
31e042adf7
commit
b1ea7eab70
@ -49,6 +49,7 @@ function InputWithLabel({
|
|||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
name={label.toLowerCase()}
|
name={label.toLowerCase()}
|
||||||
|
data-testid={`input-${label}`}
|
||||||
/>
|
/>
|
||||||
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
|
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
|
||||||
{onClose && (
|
{onClose && (
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { OrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter/Orde
|
|||||||
import { ReduceToFilter } from 'container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter';
|
import { ReduceToFilter } from 'container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { get, isEmpty } from 'lodash-es';
|
||||||
import { BarChart2, ChevronUp, ExternalLink, ScrollText } from 'lucide-react';
|
import { BarChart2, ChevronUp, ExternalLink, ScrollText } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -34,6 +34,14 @@ const ADD_ONS_KEYS = {
|
|||||||
LEGEND_FORMAT: 'legend_format',
|
LEGEND_FORMAT: 'legend_format',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ADD_ONS_KEYS_TO_QUERY_PATH = {
|
||||||
|
[ADD_ONS_KEYS.GROUP_BY]: 'groupBy',
|
||||||
|
[ADD_ONS_KEYS.HAVING]: 'having.expression',
|
||||||
|
[ADD_ONS_KEYS.ORDER_BY]: 'orderBy',
|
||||||
|
[ADD_ONS_KEYS.LIMIT]: 'limit',
|
||||||
|
[ADD_ONS_KEYS.LEGEND_FORMAT]: 'legend',
|
||||||
|
};
|
||||||
|
|
||||||
const ADD_ONS = [
|
const ADD_ONS = [
|
||||||
{
|
{
|
||||||
icon: <BarChart2 size={14} />,
|
icon: <BarChart2 size={14} />,
|
||||||
@ -91,6 +99,9 @@ const REDUCE_TO = {
|
|||||||
'https://signoz.io/docs/userguide/query-builder-v5/#reduce-operations',
|
'https://signoz.io/docs/userguide/query-builder-v5/#reduce-operations',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasValue = (value: unknown): boolean =>
|
||||||
|
value != null && value !== '' && !(Array.isArray(value) && value.length === 0);
|
||||||
|
|
||||||
// Custom tooltip content component
|
// Custom tooltip content component
|
||||||
function TooltipContent({
|
function TooltipContent({
|
||||||
label,
|
label,
|
||||||
@ -195,21 +206,29 @@ function QueryAddOns({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add reduce to if showReduceTo is true
|
|
||||||
if (showReduceTo) {
|
if (showReduceTo) {
|
||||||
filteredAddOns = [...filteredAddOns, REDUCE_TO];
|
filteredAddOns = [...filteredAddOns, REDUCE_TO];
|
||||||
}
|
}
|
||||||
|
|
||||||
setAddOns(filteredAddOns);
|
setAddOns(filteredAddOns);
|
||||||
|
|
||||||
// Filter selectedViews to only include add-ons present in filteredAddOns
|
const activeAddOnKeys = new Set(
|
||||||
setSelectedViews((prevSelectedViews) =>
|
Object.entries(ADD_ONS_KEYS_TO_QUERY_PATH)
|
||||||
prevSelectedViews.filter((view) =>
|
.filter(([, path]) => hasValue(get(query, path)))
|
||||||
filteredAddOns.some((addOn) => addOn.key === view.key),
|
.map(([key]) => key),
|
||||||
|
);
|
||||||
|
|
||||||
|
const availableAddOnKeys = new Set(filteredAddOns.map((addOn) => addOn.key));
|
||||||
|
|
||||||
|
// Filter and set selected views: add-ons that are both active and available
|
||||||
|
setSelectedViews(
|
||||||
|
ADD_ONS.filter(
|
||||||
|
(addOn) =>
|
||||||
|
activeAddOnKeys.has(addOn.key) && availableAddOnKeys.has(addOn.key),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [panelType, isListViewPanel, query.dataSource]);
|
}, [panelType, isListViewPanel, query]);
|
||||||
|
|
||||||
const handleOptionClick = (e: RadioChangeEvent): void => {
|
const handleOptionClick = (e: RadioChangeEvent): void => {
|
||||||
if (selectedViews.find((view) => view.key === e.target.value.key)) {
|
if (selectedViews.find((view) => view.key === e.target.value.key)) {
|
||||||
@ -285,7 +304,7 @@ function QueryAddOns({
|
|||||||
{selectedViews.length > 0 && (
|
{selectedViews.length > 0 && (
|
||||||
<div className="selected-add-ons-content">
|
<div className="selected-add-ons-content">
|
||||||
{selectedViews.find((view) => view.key === 'group_by') && (
|
{selectedViews.find((view) => view.key === 'group_by') && (
|
||||||
<div className="add-on-content">
|
<div className="add-on-content" data-testid="group-by-content">
|
||||||
<div className="periscope-input-with-label">
|
<div className="periscope-input-with-label">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@ -321,7 +340,7 @@ function QueryAddOns({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedViews.find((view) => view.key === 'having') && (
|
{selectedViews.find((view) => view.key === 'having') && (
|
||||||
<div className="add-on-content">
|
<div className="add-on-content" data-testid="having-content">
|
||||||
<div className="periscope-input-with-label">
|
<div className="periscope-input-with-label">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@ -353,7 +372,7 @@ function QueryAddOns({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedViews.find((view) => view.key === 'limit') && (
|
{selectedViews.find((view) => view.key === 'limit') && (
|
||||||
<div className="add-on-content">
|
<div className="add-on-content" data-testid="limit-content">
|
||||||
<InputWithLabel
|
<InputWithLabel
|
||||||
label="Limit"
|
label="Limit"
|
||||||
onChange={handleChangeLimit}
|
onChange={handleChangeLimit}
|
||||||
@ -367,7 +386,7 @@ function QueryAddOns({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedViews.find((view) => view.key === 'order_by') && (
|
{selectedViews.find((view) => view.key === 'order_by') && (
|
||||||
<div className="add-on-content">
|
<div className="add-on-content" data-testid="order-by-content">
|
||||||
<div className="periscope-input-with-label">
|
<div className="periscope-input-with-label">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@ -405,7 +424,7 @@ function QueryAddOns({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedViews.find((view) => view.key === 'reduce_to') && showReduceTo && (
|
{selectedViews.find((view) => view.key === 'reduce_to') && showReduceTo && (
|
||||||
<div className="add-on-content">
|
<div className="add-on-content" data-testid="reduce-to-content">
|
||||||
<div className="periscope-input-with-label">
|
<div className="periscope-input-with-label">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@ -436,7 +455,7 @@ function QueryAddOns({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedViews.find((view) => view.key === 'legend_format') && (
|
{selectedViews.find((view) => view.key === 'legend_format') && (
|
||||||
<div className="add-on-content">
|
<div className="add-on-content" data-testid="legend-format-content">
|
||||||
<InputWithLabel
|
<InputWithLabel
|
||||||
label="Legend format"
|
label="Legend format"
|
||||||
placeholder="Write legend format"
|
placeholder="Write legend format"
|
||||||
|
|||||||
@ -0,0 +1,186 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import QueryAddOns from '../QueryV2/QueryAddOns/QueryAddOns';
|
||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
// Mocks: only what is required for this component to render and for us to assert handler calls
|
||||||
|
const mockHandleChangeQueryData = jest.fn();
|
||||||
|
const mockHandleSetQueryData = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('hooks/queryBuilder/useQueryBuilderOperations', () => ({
|
||||||
|
useQueryOperations: () => ({
|
||||||
|
handleChangeQueryData: mockHandleChangeQueryData,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||||
|
useQueryBuilder: () => ({
|
||||||
|
handleSetQueryData: mockHandleSetQueryData,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('container/QueryBuilder/filters/GroupByFilter/GroupByFilter', () => ({
|
||||||
|
GroupByFilter: ({ onChange }: any) => (
|
||||||
|
<button data-testid="groupby" onClick={() => onChange(['service.name'])}>
|
||||||
|
GroupByFilter
|
||||||
|
</button>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('container/QueryBuilder/filters/OrderByFilter/OrderByFilter', () => ({
|
||||||
|
OrderByFilter: ({ onChange }: any) => (
|
||||||
|
<button
|
||||||
|
data-testid="orderby"
|
||||||
|
onClick={() => onChange([{ columnName: 'duration', order: 'desc' }])}
|
||||||
|
>
|
||||||
|
OrderByFilter
|
||||||
|
</button>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../QueryV2/QueryAddOns/HavingFilter/HavingFilter', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: ({ onChange, onClose }: any) => (
|
||||||
|
<div>
|
||||||
|
<button data-testid="having-change" onClick={() => onChange('p99 > 500')}>
|
||||||
|
HavingFilter
|
||||||
|
</button>
|
||||||
|
<button data-testid="having-close" onClick={onClose}>
|
||||||
|
close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter',
|
||||||
|
() => ({
|
||||||
|
ReduceToFilter: ({ onChange }: any) => (
|
||||||
|
<button data-testid="reduce-to" onClick={() => onChange('sum')}>
|
||||||
|
ReduceToFilter
|
||||||
|
</button>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
function baseQuery(overrides: Partial<any> = {}): any {
|
||||||
|
return {
|
||||||
|
dataSource: DataSource.TRACES,
|
||||||
|
aggregations: [{ id: 'a', operator: 'count' }],
|
||||||
|
groupBy: [],
|
||||||
|
orderBy: [],
|
||||||
|
legend: '',
|
||||||
|
limit: null,
|
||||||
|
having: { expression: '' },
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('QueryAddOns', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('VALUE panel: no sections auto-open when query has no active add-ons', () => {
|
||||||
|
render(
|
||||||
|
<QueryAddOns
|
||||||
|
query={baseQuery()}
|
||||||
|
version="v5"
|
||||||
|
isListViewPanel={false}
|
||||||
|
showReduceTo
|
||||||
|
panelType={PANEL_TYPES.VALUE}
|
||||||
|
index={0}
|
||||||
|
isForTraceOperator={false}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('legend-format-content')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('reduce-to-content')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('order-by-content')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('limit-content')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('group-by-content')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('having-content')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides group-by section for METRICS even if groupBy is set in query', () => {
|
||||||
|
render(
|
||||||
|
<QueryAddOns
|
||||||
|
query={baseQuery({
|
||||||
|
dataSource: DataSource.METRICS,
|
||||||
|
groupBy: ['service.name'],
|
||||||
|
})}
|
||||||
|
version="v5"
|
||||||
|
isListViewPanel={false}
|
||||||
|
showReduceTo={false}
|
||||||
|
panelType={PANEL_TYPES.TIME_SERIES}
|
||||||
|
index={0}
|
||||||
|
isForTraceOperator={false}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('group-by-content')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to Order By open in list view panel', () => {
|
||||||
|
render(
|
||||||
|
<QueryAddOns
|
||||||
|
query={baseQuery()}
|
||||||
|
version="v5"
|
||||||
|
isListViewPanel
|
||||||
|
showReduceTo={false}
|
||||||
|
panelType={PANEL_TYPES.LIST}
|
||||||
|
index={0}
|
||||||
|
isForTraceOperator={false}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('order-by-content')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('limit input auto-opens when limit is set and changing it calls handler', () => {
|
||||||
|
render(
|
||||||
|
<QueryAddOns
|
||||||
|
query={baseQuery({ limit: 5 })}
|
||||||
|
version="v5"
|
||||||
|
isListViewPanel={false}
|
||||||
|
showReduceTo={false}
|
||||||
|
panelType={PANEL_TYPES.TIME_SERIES}
|
||||||
|
index={0}
|
||||||
|
isForTraceOperator={false}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByTestId('input-Limit') as HTMLInputElement;
|
||||||
|
expect(screen.getByTestId('limit-content')).toBeInTheDocument();
|
||||||
|
expect(input.value).toBe('5');
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: '10' } });
|
||||||
|
expect(mockHandleChangeQueryData).toHaveBeenCalledWith('limit', 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('auto-opens Order By and Limit when present in query', () => {
|
||||||
|
const query = baseQuery({
|
||||||
|
orderBy: [{ columnName: 'duration', order: 'desc' }],
|
||||||
|
limit: 7,
|
||||||
|
});
|
||||||
|
render(
|
||||||
|
<QueryAddOns
|
||||||
|
query={query}
|
||||||
|
version="v5"
|
||||||
|
isListViewPanel={false}
|
||||||
|
showReduceTo={false}
|
||||||
|
panelType={PANEL_TYPES.TIME_SERIES}
|
||||||
|
index={0}
|
||||||
|
isForTraceOperator={false}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('order-by-content')).toBeInTheDocument();
|
||||||
|
const limitInput = screen.getByTestId('input-Limit') as HTMLInputElement;
|
||||||
|
expect(screen.getByTestId('limit-content')).toBeInTheDocument();
|
||||||
|
expect(limitInput.value).toBe('7');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user