mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-03 08:33:26 +00:00
fix: remove isRoot and isEntrypoint from the list of selectable columns in the columns menu in traces explorer and traces list panel in dashboard (#9629)
* fix: hide isRoot and isEntryPoint options from columns options * test: add tests to ensure isRoot and isEntryPoint are hidden in column options * refactor: improve the columns exclusion logic + update test
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
import { Checkbox, Empty } from 'antd';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { EXCLUDED_COLUMNS } from 'container/OptionsMenu/constants';
|
||||
import { QueryKeySuggestionsResponseProps } from 'types/api/querySuggestions/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
type ExplorerAttributeColumnsProps = {
|
||||
isLoading: boolean;
|
||||
data: any;
|
||||
data: AxiosResponse<QueryKeySuggestionsResponseProps> | undefined;
|
||||
searchText: string;
|
||||
isAttributeKeySelected: (key: string) => boolean;
|
||||
handleCheckboxChange: (key: string) => void;
|
||||
dataSource: DataSource;
|
||||
};
|
||||
|
||||
function ExplorerAttributeColumns({
|
||||
@@ -15,6 +20,7 @@ function ExplorerAttributeColumns({
|
||||
searchText,
|
||||
isAttributeKeySelected,
|
||||
handleCheckboxChange,
|
||||
dataSource,
|
||||
}: ExplorerAttributeColumnsProps): JSX.Element {
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -27,8 +33,10 @@ function ExplorerAttributeColumns({
|
||||
const filteredAttributeKeys =
|
||||
Object.values(data?.data?.data?.keys || {})
|
||||
?.flat()
|
||||
?.filter((attributeKey: any) =>
|
||||
attributeKey.name.toLowerCase().includes(searchText.toLowerCase()),
|
||||
?.filter(
|
||||
(attributeKey) =>
|
||||
attributeKey.name.toLowerCase().includes(searchText.toLowerCase()) &&
|
||||
!EXCLUDED_COLUMNS[dataSource].includes(attributeKey.name),
|
||||
) || [];
|
||||
if (filteredAttributeKeys.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -183,6 +183,7 @@ function ExplorerColumnsRenderer({
|
||||
searchText={searchText}
|
||||
isAttributeKeySelected={isAttributeKeySelected}
|
||||
handleCheckboxChange={handleCheckboxChange}
|
||||
dataSource={initialDataSource}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -450,4 +450,58 @@ describe('ExplorerColumnsRenderer', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show isRoot or isEntryPoint in add column dropdown (traces, dashboard table panel)', async () => {
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
currentQuery: {
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateOperator: 'count',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
(useGetQueryKeySuggestions as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
data: {
|
||||
keys: {
|
||||
attributeKeys: [
|
||||
{ name: 'isRoot', dataType: 'bool', type: '' },
|
||||
{ name: 'isEntryPoint', dataType: 'bool', type: '' },
|
||||
{ name: 'duration', dataType: 'number', type: '' },
|
||||
{ name: 'serviceName', dataType: 'string', type: '' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
render(
|
||||
<Wrapper>
|
||||
<ExplorerColumnsRenderer
|
||||
selectedLogFields={[]}
|
||||
setSelectedLogFields={mockSetSelectedLogFields}
|
||||
selectedTracesFields={[]}
|
||||
setSelectedTracesFields={mockSetSelectedTracesFields}
|
||||
/>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('add-columns-button'));
|
||||
|
||||
// Visible columns should appear
|
||||
expect(screen.getByText('duration')).toBeInTheDocument();
|
||||
expect(screen.getByText('serviceName')).toBeInTheDocument();
|
||||
|
||||
// Hidden columns should NOT appear
|
||||
expect(screen.queryByText('isRoot')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('isEntryPoint')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import { usePreferenceContext } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import { useQueries } from 'react-query';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import useOptionsMenu from '../useOptionsMenu';
|
||||
|
||||
// Mock all dependencies
|
||||
jest.mock('hooks/useNotifications');
|
||||
jest.mock('providers/preferences/context/PreferenceContextProvider');
|
||||
jest.mock('hooks/useUrlQueryData');
|
||||
jest.mock('hooks/querySuggestions/useGetQueryKeySuggestions');
|
||||
jest.mock('react-query', () => ({
|
||||
...jest.requireActual('react-query'),
|
||||
useQueries: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('useOptionsMenu', () => {
|
||||
const mockNotifications = { error: jest.fn(), success: jest.fn() };
|
||||
const mockUpdateColumns = jest.fn();
|
||||
const mockUpdateFormatting = jest.fn();
|
||||
const mockRedirectWithQuery = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
(useNotifications as jest.Mock).mockReturnValue({
|
||||
notifications: mockNotifications,
|
||||
});
|
||||
|
||||
(usePreferenceContext as jest.Mock).mockReturnValue({
|
||||
traces: {
|
||||
preferences: {
|
||||
columns: [],
|
||||
formatting: {
|
||||
format: 'raw',
|
||||
maxLines: 2,
|
||||
fontSize: 'small',
|
||||
},
|
||||
},
|
||||
updateColumns: mockUpdateColumns,
|
||||
updateFormatting: mockUpdateFormatting,
|
||||
},
|
||||
logs: {
|
||||
preferences: {
|
||||
columns: [],
|
||||
formatting: {
|
||||
format: 'raw',
|
||||
maxLines: 2,
|
||||
fontSize: 'small',
|
||||
},
|
||||
},
|
||||
updateColumns: mockUpdateColumns,
|
||||
updateFormatting: mockUpdateFormatting,
|
||||
},
|
||||
});
|
||||
|
||||
(useUrlQueryData as jest.Mock).mockReturnValue({
|
||||
query: null,
|
||||
redirectWithQuery: mockRedirectWithQuery,
|
||||
});
|
||||
|
||||
(useQueries as jest.Mock).mockReturnValue([]);
|
||||
});
|
||||
|
||||
it('does not show isRoot or isEntryPoint in column options when dataSource is TRACES', () => {
|
||||
// Mock the query key suggestions to return data including isRoot and isEntryPoint
|
||||
(useGetQueryKeySuggestions as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
data: {
|
||||
keys: {
|
||||
attributeKeys: [
|
||||
{
|
||||
name: 'isRoot',
|
||||
signal: 'traces',
|
||||
fieldDataType: 'bool',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
name: 'isEntryPoint',
|
||||
signal: 'traces',
|
||||
fieldDataType: 'bool',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
signal: 'traces',
|
||||
fieldDataType: 'float64',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
name: 'serviceName',
|
||||
signal: 'traces',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
isFetching: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useOptionsMenu({
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateOperator: 'count',
|
||||
}),
|
||||
);
|
||||
|
||||
// Get the column options from the config
|
||||
const columnOptions = result.current.config.addColumn?.options ?? [];
|
||||
const optionNames = columnOptions.map((option) => option.label);
|
||||
|
||||
// isRoot and isEntryPoint should NOT be in the options
|
||||
expect(optionNames).not.toContain('isRoot');
|
||||
expect(optionNames).not.toContain('body');
|
||||
expect(optionNames).not.toContain('isEntryPoint');
|
||||
|
||||
// Other attributes should be present
|
||||
expect(optionNames).toContain('duration');
|
||||
expect(optionNames).toContain('serviceName');
|
||||
});
|
||||
|
||||
it('does not show body in column options when dataSource is METRICS', () => {
|
||||
// Mock the query key suggestions to return data including body
|
||||
(useGetQueryKeySuggestions as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
data: {
|
||||
keys: {
|
||||
attributeKeys: [
|
||||
{
|
||||
name: 'body',
|
||||
signal: 'logs',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
signal: 'metrics',
|
||||
fieldDataType: 'int64',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
signal: 'metrics',
|
||||
fieldDataType: 'float64',
|
||||
fieldContext: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
isFetching: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useOptionsMenu({
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateOperator: 'count',
|
||||
}),
|
||||
);
|
||||
|
||||
// Get the column options from the config
|
||||
const columnOptions = result.current.config.addColumn?.options ?? [];
|
||||
const optionNames = columnOptions.map((option) => option.label);
|
||||
|
||||
// body should NOT be in the options
|
||||
expect(optionNames).not.toContain('body');
|
||||
|
||||
// Other attributes should be present
|
||||
expect(optionNames).toContain('status');
|
||||
expect(optionNames).toContain('value');
|
||||
});
|
||||
|
||||
it('does not show body in column options when dataSource is LOGS', () => {
|
||||
// Mock the query key suggestions to return data including body
|
||||
(useGetQueryKeySuggestions as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
data: {
|
||||
keys: {
|
||||
attributeKeys: [
|
||||
{
|
||||
name: 'body',
|
||||
signal: 'logs',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
name: 'level',
|
||||
signal: 'logs',
|
||||
fieldDataType: 'string',
|
||||
fieldContext: '',
|
||||
},
|
||||
{
|
||||
name: 'timestamp',
|
||||
signal: 'logs',
|
||||
fieldDataType: 'int64',
|
||||
fieldContext: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
isFetching: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useOptionsMenu({
|
||||
dataSource: DataSource.LOGS,
|
||||
aggregateOperator: 'count',
|
||||
}),
|
||||
);
|
||||
|
||||
// Get the column options from the config
|
||||
const columnOptions = result.current.config.addColumn?.options ?? [];
|
||||
const optionNames = columnOptions.map((option) => option.label);
|
||||
|
||||
// body should be in the options
|
||||
expect(optionNames).toContain('body');
|
||||
|
||||
// Other attributes should be present
|
||||
expect(optionNames).toContain('level');
|
||||
expect(optionNames).toContain('timestamp');
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { FontSize, OptionsQuery } from './types';
|
||||
|
||||
@@ -11,6 +12,12 @@ export const defaultOptionsQuery: OptionsQuery = {
|
||||
fontSize: FontSize.SMALL,
|
||||
};
|
||||
|
||||
export const EXCLUDED_COLUMNS: Record<DataSource, string[]> = {
|
||||
[DataSource.TRACES]: ['body', 'isRoot', 'isEntryPoint'],
|
||||
[DataSource.METRICS]: ['body'],
|
||||
[DataSource.LOGS]: [],
|
||||
};
|
||||
|
||||
export const defaultLogsSelectedColumns: TelemetryFieldKey[] = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
defaultLogsSelectedColumns,
|
||||
defaultOptionsQuery,
|
||||
defaultTraceSelectedColumns,
|
||||
EXCLUDED_COLUMNS,
|
||||
URL_OPTIONS,
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -267,8 +268,9 @@ const useOptionsMenu = ({
|
||||
|
||||
const optionsFromAttributeKeys = useMemo(() => {
|
||||
const filteredAttributeKeys = searchedAttributeKeys.filter((item) => {
|
||||
if (dataSource !== DataSource.LOGS) {
|
||||
return item.name !== 'body';
|
||||
const exclusions = EXCLUDED_COLUMNS[dataSource];
|
||||
if (exclusions) {
|
||||
return !exclusions.includes(item.name);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user