Compare commits

..

9 Commits

Author SHA1 Message Date
Nityananda Gohain
f43292c10b Merge branch 'develop' into issue_2705 2024-07-21 21:16:25 +05:30
Nityananda Gohain
ea51320e4f Merge branch 'develop' into issue_2705 2024-07-08 10:37:45 +05:30
Nityananda Gohain
436009ec20 Merge branch 'develop' into issue_2705 2024-07-04 14:54:01 +05:30
nityanandagohain
785a872ee0 fix: correct tag type for scope 2024-07-04 14:51:32 +05:30
nityanandagohain
9ab3a03bfe Merge remote-tracking branch 'origin/develop' into issue_2705 2024-07-03 21:41:11 +05:30
nityanandagohain
8a3688b74d feat: rename instrumentation_scope to scope 2024-07-03 21:40:44 +05:30
Nityananda Gohain
5a686f3be1 Merge branch 'develop' into issue_2705 2024-07-01 12:38:38 +05:30
nityanandagohain
da31b2f7df feat: support for attribute suggestion and querying 2024-06-19 16:33:25 +05:30
nityanandagohain
a25189eca7 feat: qb changes to support instrumentation scope 2024-06-19 14:22:11 +05:30
26 changed files with 177 additions and 775 deletions

View File

@@ -5,6 +5,7 @@ import { Button } from 'antd';
import { Tag } from 'antd/lib';
import Input from 'components/Input';
import { Check, X } from 'lucide-react';
import { TweenOneGroup } from 'rc-tween-one';
import React, { Dispatch, SetStateAction, useState } from 'react';
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
@@ -45,19 +46,41 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
func(value);
};
const forMap = (tag: string): React.ReactElement => (
<span key={tag} style={{ display: 'inline-block' }}>
<Tag
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
>
{tag}
</Tag>
</span>
);
const tagChild = tags.map(forMap);
const renderTagsAnimated = (): React.ReactElement => (
<TweenOneGroup
appear={false}
className="tags"
enter={{ scale: 0.8, opacity: 0, type: 'from', duration: 100 }}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
onEnd={(e): void => {
if (e.type === 'appear' || e.type === 'enter') {
(e.target as any).style = 'display: inline-block';
}
}}
>
{tagChild}
</TweenOneGroup>
);
return (
<div className="tags-container">
{tags.map<React.ReactNode>((tag) => (
<Tag
key={tag}
closable
style={{ userSelect: 'none' }}
onClose={(): void => handleClose(tag)}
>
<span>{tag}</span>
</Tag>
))}
{renderTagsAnimated()}
{inputVisible && (
<div className="add-tag-container">
<Input

View File

@@ -125,9 +125,10 @@ function GridCardGraph({
offset: 0,
limit: updatedQuery.builder.queryData[0].limit || 0,
},
// we do not need select columns in case of logs
selectColumns:
initialDataSource === DataSource.TRACES && widget.selectedTracesFields,
initialDataSource === DataSource.LOGS
? widget.selectedLogFields
: widget.selectedTracesFields,
},
fillGaps: widget.fillSpans,
};

View File

@@ -940,50 +940,3 @@
border-color: var(--bg-vanilla-300) !important;
}
}
.mt-8 {
margin-top: 8px;
}
.mt-12 {
margin-top: 12px;
}
.mt-24 {
margin-top: 24px;
}
.mb-24 {
margin-bottom: 24px;
}
.ingestion-setup-details-links {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 24px;
padding: 12px;
border-radius: 4px;
background: rgba(113, 144, 249, 0.1);
color: var(--bg-robin-300, #95acfb);
.learn-more {
display: inline-flex;
justify-content: center;
align-items: center;
text-decoration: underline;
color: var(--bg-robin-300, #95acfb);
}
}
.lightMode {
.ingestion-setup-details-links {
background: rgba(113, 144, 249, 0.1);
color: var(--bg-robin-500);
.learn-more {
color: var(--bg-robin-500);
}
}
}

View File

@@ -35,12 +35,10 @@ import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionK
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useNotifications } from 'hooks/useNotifications';
import {
ArrowUpRight,
CalendarClock,
Check,
Copy,
Infinity,
Info,
Minus,
PenLine,
Plus,
@@ -877,35 +875,10 @@ function MultiIngestionSettings(): JSX.Element {
return (
<div className="ingestion-key-container">
<div className="ingestion-key-content">
<div className="ingestion-setup-details-links">
<Info size={14} />
<span>
Find your ingestion URL and learn more about sending data to SigNoz{' '}
<a
href="https://signoz.io/docs/ingestion/signoz-cloud/overview/"
target="_blank"
className="learn-more"
rel="noreferrer"
>
here <ArrowUpRight size={14} />
</a>
</span>
</div>
<header>
<Typography.Title className="title"> Ingestion Keys </Typography.Title>
<Typography.Text className="subtitle">
Create and manage ingestion keys for the SigNoz Cloud{' '}
<a
href="https://signoz.io/docs/ingestion/signoz-cloud/keys/"
target="_blank"
className="learn-more"
rel="noreferrer"
>
{' '}
Learn more <ArrowUpRight size={14} />
</a>
Create and manage ingestion keys for the SigNoz Cloud
</Typography.Text>
</header>

View File

@@ -1,45 +0,0 @@
import { render, screen } from 'tests/test-utils';
import MultiIngestionSettings from '../MultiIngestionSettings';
describe('MultiIngestionSettings Page', () => {
beforeEach(() => {
render(<MultiIngestionSettings />);
});
afterEach(() => {
jest.clearAllMocks();
});
it('renders MultiIngestionSettings page without crashing', () => {
expect(
screen.getByText(
'Find your ingestion URL and learn more about sending data to SigNoz',
),
).toBeInTheDocument();
expect(screen.getByText('Ingestion Keys')).toBeInTheDocument();
expect(
screen.getByText('Create and manage ingestion keys for the SigNoz Cloud'),
).toBeInTheDocument();
const overviewLink = screen.getByRole('link', { name: /here/i });
expect(overviewLink).toHaveAttribute(
'href',
'https://signoz.io/docs/ingestion/signoz-cloud/overview/',
);
expect(overviewLink).toHaveAttribute('target', '_blank');
expect(overviewLink).toHaveClass('learn-more');
expect(overviewLink).toHaveAttribute('rel', 'noreferrer');
const aboutKeyslink = screen.getByRole('link', { name: /Learn more/i });
expect(aboutKeyslink).toHaveAttribute(
'href',
'https://signoz.io/docs/ingestion/signoz-cloud/keys/',
);
expect(aboutKeyslink).toHaveAttribute('target', '_blank');
expect(aboutKeyslink).toHaveClass('learn-more');
expect(aboutKeyslink).toHaveAttribute('rel', 'noreferrer');
});
});

View File

@@ -309,7 +309,6 @@ function ExplorerColumnsRenderer({
>
<Button
className="action-btn"
data-testid="add-columns-button"
icon={
<PlusCircle
size={16}

View File

@@ -563,11 +563,11 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
if (selectedGraph === PANEL_TYPES.LIST) {
const initialDataSource = currentQuery.builder.queryData[0].dataSource;
if (initialDataSource === DataSource.LOGS) {
// we do not need selected log columns in the request data as the entire response contains all the necessary data
setRequestData((prev) => ({
...prev,
tableParams: {
...prev.tableParams,
selectColumns: selectedLogFields,
},
}));
} else if (initialDataSource === DataSource.TRACES) {

View File

@@ -1,4 +1,5 @@
import { Card, Typography } from 'antd';
import { Typography } from 'antd';
import Card from 'antd/es/card/Card';
import styled from 'styled-components';
export const Container = styled(Card)`

View File

@@ -1,7 +1,8 @@
import './TraceDetails.styles.scss';
import { FilterOutlined } from '@ant-design/icons';
import { Button, Col, Layout, Typography } from 'antd';
import { Button, Col, Typography } from 'antd';
import Sider from 'antd/es/layout/Sider';
import cx from 'classnames';
import {
StyledCol,
@@ -41,8 +42,6 @@ import {
INTERVAL_UNITS,
} from './utils';
const { Sider } = Layout;
function TraceDetail({ response }: TraceDetailProps): JSX.Element {
const spanServiceColors = useMemo(
() => spanServiceNameToColorMapping(response[0].events),

View File

@@ -1,4 +1,5 @@
import { Card, Col } from 'antd';
import { Col } from 'antd';
import Card from 'antd/es/card/Card';
import styled from 'styled-components';
export const Container = styled(Card)`

View File

@@ -1,81 +0,0 @@
export const explorerView = {
status: 'success',
data: [
{
uuid: 'test-uuid-1',
name: 'Table View',
category: '',
createdAt: '2023-08-29T18:04:10.906310033Z',
createdBy: 'test-user-1',
updatedAt: '2024-01-29T10:42:47.346331133Z',
updatedBy: 'test-user-1',
sourcePage: 'traces',
tags: [''],
compositeQuery: {
builderQueries: {
A: {
queryName: 'A',
stepInterval: 60,
dataSource: 'traces',
aggregateOperator: 'count',
aggregateAttribute: {
key: 'component',
dataType: 'string',
type: 'tag',
isColumn: true,
isJSON: false,
},
filters: {
op: 'AND',
items: [
{
key: {
key: 'component',
dataType: 'string',
type: 'tag',
isColumn: true,
isJSON: false,
},
value: 'test-component',
op: '!=',
},
],
},
groupBy: [
{
key: 'component',
dataType: 'string',
type: 'tag',
isColumn: true,
isJSON: false,
},
{
key: 'client-uuid',
dataType: 'string',
type: 'resource',
isColumn: false,
isJSON: false,
},
],
expression: 'A',
disabled: false,
limit: 0,
offset: 0,
pageSize: 0,
orderBy: [
{
columnName: 'timestamp',
order: 'desc',
},
],
reduceTo: 'sum',
ShiftBy: 0,
},
},
panelType: 'table',
queryType: 'builder',
},
extraData: '{"color":"#00ffd0"}',
},
],
};

View File

@@ -2,7 +2,6 @@ import { rest } from 'msw';
import { billingSuccessResponse } from './__mockdata__/billing';
import { dashboardSuccessResponse } from './__mockdata__/dashboards';
import { explorerView } from './__mockdata__/explorer_views';
import { inviteUser } from './__mockdata__/invite_user';
import { licensesSuccessResponse } from './__mockdata__/licenses';
import { membersResponse } from './__mockdata__/members';
@@ -56,51 +55,6 @@ export const handlers = [
const metricName = req.url.searchParams.get('metricName');
const tagKey = req.url.searchParams.get('tagKey');
const attributeKey = req.url.searchParams.get('attributeKey');
if (attributeKey === 'serviceName') {
return res(
ctx.status(200),
ctx.json({
status: 'success',
data: {
stringAttributeValues: [
'customer',
'demo-app',
'driver',
'frontend',
'mysql',
'redis',
'route',
'go-grpc-otel-server',
'test',
],
numberAttributeValues: null,
boolAttributeValues: null,
},
}),
);
}
if (attributeKey === 'name') {
return res(
ctx.status(200),
ctx.json({
status: 'success',
data: {
stringAttributeValues: [
'HTTP GET',
'HTTP GET /customer',
'HTTP GET /dispatch',
'HTTP GET /route',
],
numberAttributeValues: null,
boolAttributeValues: null,
},
}),
);
}
if (
metricName === 'signoz_calls_total' &&
tagKey === 'resource_signoz_collector_id'
@@ -148,31 +102,4 @@ export const handlers = [
rest.post('http://localhost/api/v1/invite', (_, res, ctx) =>
res(ctx.status(200), ctx.json(inviteUser)),
),
rest.get(
'http://localhost/api/v3/autocomplete/aggregate_attributes',
(req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
status: 'success',
data: { attributeKeys: null },
}),
),
),
rest.get('http://localhost/api/v1/explorer/views', (req, res, ctx) =>
res(ctx.status(200), ctx.json(explorerView)),
),
rest.post('http://localhost/api/v1/event', (req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
statusCode: 200,
error: null,
payload: 'Event Processed Successfully',
}),
),
),
];

View File

@@ -26,10 +26,7 @@ export const getRoutes = (
settings.push(...organizationSettings(t));
}
if (
isGatewayEnabled &&
(userRole === USER_ROLES.ADMIN || userRole === USER_ROLES.EDITOR)
) {
if (isGatewayEnabled && userRole === USER_ROLES.ADMIN) {
settings.push(...multiIngestionSettings(t));
}

View File

@@ -109,7 +109,6 @@ export function DurationSection(props: DurationProps): JSX.Element {
className="min-max-input"
onChange={onChangeMinHandler}
value={preMin}
data-testid="min-input"
addonAfter="ms"
/>
<Input
@@ -119,7 +118,6 @@ export function DurationSection(props: DurationProps): JSX.Element {
className="min-max-input"
onChange={onChangeMaxHandler}
value={preMax}
data-testid="max-input"
addonAfter="ms"
/>
</div>

View File

@@ -224,18 +224,13 @@ export function Filter(props: FilterProps): JSX.Element {
<Button
onClick={(): void => handleRun({ resetAll: true })}
className="sync-icon"
data-testid="reset-filters"
>
<SyncOutlined />
</Button>
</Tooltip>
</Flex>
<Tooltip title="Collapse" placement="right">
<Button
onClick={(): void => setOpen(false)}
className="arrow-icon"
data-testid="toggle-filter-panel"
>
<Button onClick={(): void => setOpen(false)} className="arrow-icon">
<VerticalAlignTopOutlined rotate={270} />
</Button>
</Tooltip>

View File

@@ -64,7 +64,7 @@ export function Section(props: SectionProps): JSX.Element {
return (
<div>
<Divider plain className="divider" />
<div className="section-body-header" data-testid={`collapse-${panelName}`}>
<div className="section-body-header">
<Collapse
bordered={false}
className="collapseContainer"
@@ -96,11 +96,7 @@ export function Section(props: SectionProps): JSX.Element {
},
]}
/>
<Button
type="link"
onClick={onClearHandler}
data-testid={`collapse-${panelName}-clearBtn`}
>
<Button type="link" onClick={onClearHandler}>
Clear All
</Button>
</div>

View File

@@ -145,7 +145,6 @@ export function SectionBody(props: SectionBodyProps): JSX.Element {
key={`${type}-${item}`}
onChange={(e): void => onCheckHandler(e, item)}
checked={checkboxMatcher(item)}
data-testid={`${type}-${item}`}
>
<div className="checkbox-label">
<div className={labelClassname(item)} />

View File

@@ -1,19 +1,16 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-await-in-loop */
import userEvent from '@testing-library/user-event';
import {
initialQueriesMap,
initialQueryBuilderFormValues,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import * as compositeQueryHook from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { QueryBuilderContext } from 'providers/QueryBuilder';
import { fireEvent, render, screen, waitFor, within } from 'tests/test-utils';
import { render } from 'tests/test-utils';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import TracesExplorer from '..';
import { Filter } from '../Filter/Filter';
import { AllTraceFilterKeyValue } from '../Filter/filterUtils';
@@ -40,48 +37,6 @@ jest.mock('uplot', () => {
};
});
jest.mock(
'container/TopNav/DateTimeSelectionV2/index.tsx',
() =>
function MockDateTimeSelection(): JSX.Element {
return <div>MockDateTimeSelection</div>;
},
);
function checkIfSectionIsOpen(
getByTestId: (testId: string) => HTMLElement,
panelName: string,
): void {
const section = getByTestId(`collapse-${panelName}`);
expect(section.querySelector('.ant-collapse-item-active')).not.toBeNull();
}
function checkIfSectionIsNotOpen(
getByTestId: (testId: string) => HTMLElement,
panelName: string,
): void {
const section = getByTestId(`collapse-${panelName}`);
expect(section.querySelector('.ant-collapse-item-active')).toBeNull();
}
const defaultOpenSections = ['hasError', 'durationNano', 'serviceName'];
const defaultClosedSections = Object.keys(AllTraceFilterKeyValue).filter(
(section) =>
![...defaultOpenSections, 'durationNanoMin', 'durationNanoMax'].includes(
section,
),
);
async function checkForSectionContent(values: string[]): Promise<void> {
for (const val of values) {
const sectionContent = await screen.findByText(val);
await waitFor(() => expect(sectionContent).toBeInTheDocument());
}
}
const redirectWithQueryBuilderData = jest.fn();
const compositeQuery: Query = {
...initialQueriesMap.traces,
builder: {
@@ -126,157 +81,6 @@ const compositeQuery: Query = {
};
describe('TracesExplorer - ', () => {
// Initial filter panel rendering
// Test the initial state like which filters section are opened, default state of duration slider, etc.
it('should render the Trace filter', async () => {
const { getByText, getByTestId } = render(<Filter setOpen={jest.fn()} />);
Object.values(AllTraceFilterKeyValue).forEach((filter) => {
expect(getByText(filter)).toBeInTheDocument();
});
// Check default state of duration slider
const minDuration = getByTestId('min-input') as HTMLInputElement;
const maxDuration = getByTestId('max-input') as HTMLInputElement;
expect(minDuration).toHaveValue(null);
expect(minDuration).toHaveProperty('placeholder', '0');
expect(maxDuration).toHaveValue(null);
expect(maxDuration).toHaveProperty('placeholder', '100000000');
// Check which all filter section are opened by default
defaultOpenSections.forEach((section) =>
checkIfSectionIsOpen(getByTestId, section),
);
// Check which all filter section are closed by default
defaultClosedSections.forEach((section) =>
checkIfSectionIsNotOpen(getByTestId, section),
);
// check for the status section content
await checkForSectionContent(['Ok', 'Error']);
// check for the service name section content from API response
await checkForSectionContent([
'customer',
'demo-app',
'driver',
'frontend',
'mysql',
'redis',
'route',
'go-grpc-otel-server',
'test',
]);
});
// test the filter panel actions like opening and closing the sections, etc.
it('filter panel actions', async () => {
const { getByTestId } = render(<Filter setOpen={jest.fn()} />);
// Check if the section is closed
checkIfSectionIsNotOpen(getByTestId, 'name');
// Open the section
const name = getByTestId('collapse-name');
expect(name).toBeInTheDocument();
userEvent.click(within(name).getByText(AllTraceFilterKeyValue.name));
await waitFor(() => checkIfSectionIsOpen(getByTestId, 'name'));
await checkForSectionContent([
'HTTP GET',
'HTTP GET /customer',
'HTTP GET /dispatch',
'HTTP GET /route',
]);
// Close the section
userEvent.click(within(name).getByText(AllTraceFilterKeyValue.name));
await waitFor(() => checkIfSectionIsNotOpen(getByTestId, 'name'));
});
it('checking filters should update the query', async () => {
const { getByText } = render(
<QueryBuilderContext.Provider
value={
{
currentQuery: {
...initialQueriesMap.traces,
builder: {
...initialQueriesMap.traces.builder,
queryData: [initialQueryBuilderFormValues],
},
},
redirectWithQueryBuilderData,
} as any
}
>
<Filter setOpen={jest.fn()} />
</QueryBuilderContext.Provider>,
);
const okCheckbox = getByText('Ok');
fireEvent.click(okCheckbox);
expect(
redirectWithQueryBuilderData.mock.calls[
redirectWithQueryBuilderData.mock.calls.length - 1
][0].builder.queryData[0].filters.items,
).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: {
id: expect.any(String),
key: 'hasError',
type: 'tag',
dataType: 'bool',
isColumn: true,
isJSON: false,
},
op: 'in',
value: ['false'],
}),
]),
);
// Check if the query is updated when the error checkbox is clicked
const errorCheckbox = getByText('Error');
fireEvent.click(errorCheckbox);
expect(
redirectWithQueryBuilderData.mock.calls[
redirectWithQueryBuilderData.mock.calls.length - 1
][0].builder.queryData[0].filters.items,
).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: {
id: expect.any(String),
key: 'hasError',
type: 'tag',
dataType: 'bool',
isColumn: true,
isJSON: false,
},
op: 'in',
value: ['false', 'true'],
}),
]),
);
});
it('should render the trace filter with the given query', async () => {
jest
.spyOn(compositeQueryHook, 'useGetCompositeQueryParam')
.mockReturnValue(compositeQuery);
const { findByText, getByTestId } = render(<Filter setOpen={jest.fn()} />);
// check if the default query is applied - composite query has filters - serviceName : demo-app and name : HTTP GET /customer
expect(await findByText('demo-app')).toBeInTheDocument();
expect(getByTestId('serviceName-demo-app')).toBeChecked();
expect(await findByText('HTTP GET /customer')).toBeInTheDocument();
expect(getByTestId('name-HTTP GET /customer')).toBeChecked();
});
it('test edge cases of undefined filters', async () => {
jest.spyOn(compositeQueryHook, 'useGetCompositeQueryParam').mockReturnValue({
...compositeQuery,
@@ -294,6 +98,7 @@ describe('TracesExplorer - ', () => {
const { getByText } = render(<Filter setOpen={jest.fn()} />);
// we should have all the filters
Object.values(AllTraceFilterKeyValue).forEach((filter) => {
expect(getByText(filter)).toBeInTheDocument();
});
@@ -319,141 +124,9 @@ describe('TracesExplorer - ', () => {
const { getByText } = render(<Filter setOpen={jest.fn()} />);
// we should have all the filters
Object.values(AllTraceFilterKeyValue).forEach((filter) => {
expect(getByText(filter)).toBeInTheDocument();
});
});
it('should clear filter on clear & reset button click', async () => {
const { getByText, getByTestId } = render(
<QueryBuilderContext.Provider
value={
{
currentQuery: {
...initialQueriesMap.traces,
builder: {
...initialQueriesMap.traces.builder,
queryData: [initialQueryBuilderFormValues],
},
},
redirectWithQueryBuilderData,
} as any
}
>
<Filter setOpen={jest.fn()} />
</QueryBuilderContext.Provider>,
);
// check for the status section content
await checkForSectionContent(['Ok', 'Error']);
// check for the service name section content from API response
await checkForSectionContent([
'customer',
'demo-app',
'driver',
'frontend',
'mysql',
'redis',
'route',
'go-grpc-otel-server',
'test',
]);
const okCheckbox = getByText('Ok');
fireEvent.click(okCheckbox);
const frontendCheckbox = getByText('frontend');
fireEvent.click(frontendCheckbox);
// check if checked and present in query
expect(
redirectWithQueryBuilderData.mock.calls[
redirectWithQueryBuilderData.mock.calls.length - 1
][0].builder.queryData[0].filters.items,
).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: {
id: expect.any(String),
key: 'hasError',
type: 'tag',
dataType: 'bool',
isColumn: true,
isJSON: false,
},
op: 'in',
value: ['false'],
}),
expect.objectContaining({
key: {
key: 'serviceName',
dataType: 'string',
type: 'tag',
isColumn: true,
isJSON: false,
id: expect.any(String),
},
op: 'in',
value: ['frontend'],
}),
]),
);
const clearButton = getByTestId('collapse-serviceName-clearBtn');
expect(clearButton).toBeInTheDocument();
fireEvent.click(clearButton);
// check if cleared and not present in query
expect(
redirectWithQueryBuilderData.mock.calls[
redirectWithQueryBuilderData.mock.calls.length - 1
][0].builder.queryData[0].filters.items,
).not.toEqual(
expect.arrayContaining([
expect.objectContaining({
key: {
key: 'serviceName',
dataType: 'string',
type: 'tag',
isColumn: true,
isJSON: false,
id: expect.any(String),
},
op: 'in',
value: ['frontend'],
}),
]),
);
// check if reset button is present
const resetButton = getByTestId('reset-filters');
expect(resetButton).toBeInTheDocument();
fireEvent.click(resetButton);
// check if reset id done
expect(
redirectWithQueryBuilderData.mock.calls[
redirectWithQueryBuilderData.mock.calls.length - 1
][0].builder.queryData[0].filters.items,
).toEqual([]);
});
it('filter panel should collapse & uncollapsed', async () => {
const { getByText, getByTestId } = render(<TracesExplorer />);
Object.values(AllTraceFilterKeyValue).forEach((filter) => {
expect(getByText(filter)).toBeInTheDocument();
});
// Filter panel should collapse
const collapseButton = getByTestId('toggle-filter-panel');
expect(collapseButton).toBeInTheDocument();
fireEvent.click(collapseButton);
// uncollapse btn should be present
expect(
await screen.findByTestId('filter-uncollapse-btn'),
).toBeInTheDocument();
});
});

View File

@@ -251,7 +251,6 @@ function TracesExplorer(): JSX.Element {
<Button
onClick={(): void => setOpen(!isOpen)}
className="filter-outlined-btn"
data-testid="filter-uncollapse-btn"
>
<FilterOutlined />
</Button>

View File

@@ -63,7 +63,7 @@ func EnrichmentRequired(params *v3.QueryRangeParamsV3) bool {
func isEnriched(field v3.AttributeKey) bool {
// if it is timestamp/id dont check
if field.Key == "timestamp" || field.Key == "id" || field.Key == constants.SigNozOrderByValue {
if field.Key == "timestamp" || field.Key == "id" || field.Key == constants.SigNozOrderByValue || field.Type == v3.AttributeKeyTypeInstrumentationScope {
return true
}

View File

@@ -17,7 +17,6 @@ const (
ARRAY_INT64 = "Array(Int64)"
ARRAY_FLOAT64 = "Array(Float64)"
ARRAY_BOOL = "Array(Bool)"
NGRAM_SIZE = 4
)
var dataTypeMapping = map[string]string{
@@ -73,7 +72,6 @@ func getPath(keyArr []string) string {
func getJSONFilterKey(key v3.AttributeKey, op v3.FilterOperator, isArray bool) (string, error) {
keyArr := strings.Split(key.Key, ".")
// i.e it should be at least body.name, and not something like body
if len(keyArr) < 2 {
return "", fmt.Errorf("incorrect key, should contain at least 2 parts")
}
@@ -108,29 +106,6 @@ func getJSONFilterKey(key v3.AttributeKey, op v3.FilterOperator, isArray bool) (
return keyname, nil
}
// takes the path and the values and generates where clauses for better usage of index
func getPathIndexFilter(path string) string {
filters := []string{}
keyArr := strings.Split(path, ".")
if len(keyArr) < 2 {
return ""
}
for i, key := range keyArr {
if i == 0 {
continue
}
key = strings.TrimSuffix(key, "[*]")
if len(key) >= NGRAM_SIZE {
filters = append(filters, strings.ToLower(key))
}
}
if len(filters) > 0 {
return fmt.Sprintf("lower(body) like lower('%%%s%%')", strings.Join(filters, "%"))
}
return ""
}
func GetJSONFilter(item v3.FilterItem) (string, error) {
dataType := item.Key.DataType
@@ -179,28 +154,11 @@ func GetJSONFilter(item v3.FilterItem) (string, error) {
return "", fmt.Errorf("unsupported operator: %s", op)
}
filters := []string{}
pathFilter := getPathIndexFilter(item.Key.Key)
if pathFilter != "" {
filters = append(filters, pathFilter)
}
if op == v3.FilterOperatorContains ||
op == v3.FilterOperatorEqual ||
op == v3.FilterOperatorHas {
val, ok := item.Value.(string)
if ok && len(val) >= NGRAM_SIZE {
filters = append(filters, fmt.Sprintf("lower(body) like lower('%%%s%%')", utils.QuoteEscapedString(strings.ToLower(val))))
}
}
// add exists check for non array items as default values of int/float/bool will corrupt the results
if !isArray && !(item.Operator == v3.FilterOperatorExists || item.Operator == v3.FilterOperatorNotExists) {
existsFilter := fmt.Sprintf("JSON_EXISTS(body, '$.%s')", getPath(strings.Split(item.Key.Key, ".")[1:]))
filter = fmt.Sprintf("%s AND %s", existsFilter, filter)
}
filters = append(filters, filter)
return strings.Join(filters, " AND "), nil
return filter, nil
}

View File

@@ -168,7 +168,7 @@ var testGetJSONFilterData = []struct {
Operator: "has",
Value: "index_service",
},
Filter: "lower(body) like lower('%requestor_list%') AND lower(body) like lower('%index_service%') AND has(JSONExtract(JSON_QUERY(body, '$.\"requestor_list\"[*]'), 'Array(String)'), 'index_service')",
Filter: "has(JSONExtract(JSON_QUERY(body, '$.\"requestor_list\"[*]'), 'Array(String)'), 'index_service')",
},
{
Name: "Array membership int64",
@@ -181,7 +181,7 @@ var testGetJSONFilterData = []struct {
Operator: "has",
Value: 2,
},
Filter: "lower(body) like lower('%int_numbers%') AND has(JSONExtract(JSON_QUERY(body, '$.\"int_numbers\"[*]'), '" + ARRAY_INT64 + "'), 2)",
Filter: "has(JSONExtract(JSON_QUERY(body, '$.\"int_numbers\"[*]'), '" + ARRAY_INT64 + "'), 2)",
},
{
Name: "Array membership float64",
@@ -194,7 +194,7 @@ var testGetJSONFilterData = []struct {
Operator: "nhas",
Value: 2.2,
},
Filter: "lower(body) like lower('%nested_num%float_nums%') AND NOT has(JSONExtract(JSON_QUERY(body, '$.\"nested_num\"[*].\"float_nums\"[*]'), '" + ARRAY_FLOAT64 + "'), 2.200000)",
Filter: "NOT has(JSONExtract(JSON_QUERY(body, '$.\"nested_num\"[*].\"float_nums\"[*]'), '" + ARRAY_FLOAT64 + "'), 2.200000)",
},
{
Name: "Array membership bool",
@@ -207,7 +207,7 @@ var testGetJSONFilterData = []struct {
Operator: "has",
Value: true,
},
Filter: "lower(body) like lower('%bool%') AND has(JSONExtract(JSON_QUERY(body, '$.\"bool\"[*]'), '" + ARRAY_BOOL + "'), true)",
Filter: "has(JSONExtract(JSON_QUERY(body, '$.\"bool\"[*]'), '" + ARRAY_BOOL + "'), true)",
},
{
Name: "eq operator",
@@ -220,7 +220,7 @@ var testGetJSONFilterData = []struct {
Operator: "=",
Value: "hello",
},
Filter: "lower(body) like lower('%message%') AND lower(body) like lower('%hello%') AND JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') = 'hello'",
Filter: "JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') = 'hello'",
},
{
Name: "eq operator number",
@@ -233,7 +233,7 @@ var testGetJSONFilterData = []struct {
Operator: "=",
Value: 1,
},
Filter: "lower(body) like lower('%status%') AND JSON_EXISTS(body, '$.\"status\"') AND JSONExtract(JSON_VALUE(body, '$.\"status\"'), '" + INT64 + "') = 1",
Filter: "JSON_EXISTS(body, '$.\"status\"') AND JSONExtract(JSON_VALUE(body, '$.\"status\"'), '" + INT64 + "') = 1",
},
{
Name: "neq operator number",
@@ -246,7 +246,7 @@ var testGetJSONFilterData = []struct {
Operator: "=",
Value: 1.1,
},
Filter: "lower(body) like lower('%status%') AND JSON_EXISTS(body, '$.\"status\"') AND JSONExtract(JSON_VALUE(body, '$.\"status\"'), '" + FLOAT64 + "') = 1.100000",
Filter: "JSON_EXISTS(body, '$.\"status\"') AND JSONExtract(JSON_VALUE(body, '$.\"status\"'), '" + FLOAT64 + "') = 1.100000",
},
{
Name: "eq operator bool",
@@ -259,7 +259,7 @@ var testGetJSONFilterData = []struct {
Operator: "=",
Value: true,
},
Filter: "lower(body) like lower('%boolkey%') AND JSON_EXISTS(body, '$.\"boolkey\"') AND JSONExtract(JSON_VALUE(body, '$.\"boolkey\"'), '" + BOOL + "') = true",
Filter: "JSON_EXISTS(body, '$.\"boolkey\"') AND JSONExtract(JSON_VALUE(body, '$.\"boolkey\"'), '" + BOOL + "') = true",
},
{
Name: "greater than operator",
@@ -272,7 +272,7 @@ var testGetJSONFilterData = []struct {
Operator: ">",
Value: 1,
},
Filter: "lower(body) like lower('%status%') AND JSON_EXISTS(body, '$.\"status\"') AND JSONExtract(JSON_VALUE(body, '$.\"status\"'), '" + INT64 + "') > 1",
Filter: "JSON_EXISTS(body, '$.\"status\"') AND JSONExtract(JSON_VALUE(body, '$.\"status\"'), '" + INT64 + "') > 1",
},
{
Name: "regex operator",
@@ -285,7 +285,7 @@ var testGetJSONFilterData = []struct {
Operator: "regex",
Value: "a*",
},
Filter: "lower(body) like lower('%message%') AND JSON_EXISTS(body, '$.\"message\"') AND match(JSON_VALUE(body, '$.\"message\"'), 'a*')",
Filter: "JSON_EXISTS(body, '$.\"message\"') AND match(JSON_VALUE(body, '$.\"message\"'), 'a*')",
},
{
Name: "contains operator",
@@ -298,7 +298,7 @@ var testGetJSONFilterData = []struct {
Operator: "contains",
Value: "a",
},
Filter: "lower(body) like lower('%message%') AND JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%a%'",
Filter: "JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%a%'",
},
{
Name: "contains operator with quotes",
@@ -311,7 +311,7 @@ var testGetJSONFilterData = []struct {
Operator: "contains",
Value: "hello 'world'",
},
Filter: "lower(body) like lower('%message%') AND lower(body) like lower('%hello \\'world\\'%') AND JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%hello \\'world\\'%'",
Filter: "JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%hello \\'world\\'%'",
},
{
Name: "exists",
@@ -324,7 +324,7 @@ var testGetJSONFilterData = []struct {
Operator: "exists",
Value: "",
},
Filter: "lower(body) like lower('%message%') AND JSON_EXISTS(body, '$.\"message\"')",
Filter: "JSON_EXISTS(body, '$.\"message\"')",
},
}

View File

@@ -51,12 +51,13 @@ var logOperators = map[v3.FilterOperator]string{
v3.FilterOperatorNotExists: "not has(%s_%s_key, '%s')",
}
const BODY = "body"
func getClickhouseLogsColumnType(columnType v3.AttributeKeyType) string {
if columnType == v3.AttributeKeyTypeTag {
return "attributes"
}
if columnType == v3.AttributeKeyTypeInstrumentationScope {
return "scope"
}
return "resources"
}
@@ -195,24 +196,10 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
columnName := getClickhouseColumnName(item.Key)
val := utils.QuoteEscapedString(fmt.Sprintf("%v", item.Value))
if columnName == BODY {
logsOp = strings.Replace(logsOp, "ILIKE", "LIKE", 1) // removing i from ilike and not ilike
conditions = append(conditions, fmt.Sprintf("lower(%s) %s lower('%%%s%%')", columnName, logsOp, val))
} else {
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, logsOp, val))
}
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, logsOp, val))
default:
columnName := getClickhouseColumnName(item.Key)
fmtVal := utils.ClickHouseFormattedValue(value)
// for use lower for like and ilike
if op == v3.FilterOperatorLike || op == v3.FilterOperatorNotLike {
if columnName == BODY {
logsOp = strings.Replace(logsOp, "ILIKE", "LIKE", 1) // removing i from ilike and not ilike
columnName = fmt.Sprintf("lower(%s)", columnName)
fmtVal = fmt.Sprintf("lower(%s)", fmtVal)
}
}
conditions = append(conditions, fmt.Sprintf("%s %s %s", columnName, logsOp, fmtVal))
}
} else {

View File

@@ -53,6 +53,11 @@ var testGetClickhouseColumnNameData = []struct {
AttributeKey: v3.AttributeKey{Key: "test-attr", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
ExpectedColumnName: "`attribute_string_test-attr`",
},
{
Name: "instrumentation scope attribute",
AttributeKey: v3.AttributeKey{Key: "version", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeInstrumentationScope, IsColumn: false},
ExpectedColumnName: "scope_string_value[indexOf(scope_string_key, 'version')]",
},
}
func TestGetClickhouseColumnName(t *testing.T) {
@@ -131,12 +136,11 @@ var timeSeriesFilterQueryData = []struct {
ExpectedFilter: "attributes_string_value[indexOf(attributes_string_key, 'user_name')] = 'john' AND resources_string_value[indexOf(resources_string_key, 'k8s_namespace')] != 'my_service'",
},
{
Name: "Test attribute and resource attribute with different case",
Name: "Test instrumentation scope attribute",
FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "user_name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "%JoHn%", Operator: "like"},
{Key: v3.AttributeKey{Key: "k8s_namespace", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: "%MyService%", Operator: "nlike"},
{Key: v3.AttributeKey{Key: "version", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeInstrumentationScope}, Value: "v1", Operator: "="},
}},
ExpectedFilter: "attributes_string_value[indexOf(attributes_string_key, 'user_name')] ILIKE '%JoHn%' AND resources_string_value[indexOf(resources_string_key, 'k8s_namespace')] NOT ILIKE '%MyService%'",
ExpectedFilter: "scope_string_value[indexOf(scope_string_key, 'version')] = 'v1'",
},
{
Name: "Test materialized column",
@@ -295,22 +299,6 @@ var timeSeriesFilterQueryData = []struct {
}},
ExpectedFilter: "`attribute_int64_status_exists`=false",
},
{
Name: "Test for body contains and ncontains",
FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "body", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Operator: "contains", Value: "test"},
{Key: v3.AttributeKey{Key: "body", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Operator: "ncontains", Value: "test1"},
}},
ExpectedFilter: "lower(body) LIKE lower('%test%') AND lower(body) NOT LIKE lower('%test1%')",
},
{
Name: "Test for body like and nlike",
FilterSet: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "body", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Operator: "like", Value: "test"},
{Key: v3.AttributeKey{Key: "body", DataType: v3.AttributeKeyDataTypeString, IsColumn: true}, Operator: "nlike", Value: "test1"},
}},
ExpectedFilter: "lower(body) LIKE lower('test') AND lower(body) NOT LIKE lower('test1')",
},
}
func TestBuildLogsTimeSeriesFilterQuery(t *testing.T) {
@@ -758,10 +746,11 @@ var testBuildLogsQueryData = []struct {
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}},
},
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
"CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," +
"CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool," +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string " +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, " +
"CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by timestamp DESC",
},
{
@@ -777,10 +766,11 @@ var testBuildLogsQueryData = []struct {
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}},
OrderBy: []v3.OrderBy{{ColumnName: "method", DataType: v3.AttributeKeyDataTypeString, Order: "ASC", IsColumn: true}},
},
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
"CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," +
"CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool," +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string " +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, " +
"CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by `method` ASC",
},
{
@@ -797,12 +787,35 @@ var testBuildLogsQueryData = []struct {
{Key: v3.AttributeKey{Key: "severity_number", DataType: v3.AttributeKeyDataTypeInt64, IsColumn: true}, Operator: "!=", Value: 0},
}},
},
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
"CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," +
"CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool," +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string " +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, " +
"CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND severity_number != 0 order by timestamp DESC",
},
{
Name: "Test Noop with scope filter",
PanelType: v3.PanelTypeList,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
SelectColumns: []v3.AttributeKey{},
QueryName: "A",
AggregateOperator: v3.AggregateOperatorNoOp,
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "scope_name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeUnspecified, IsColumn: true}, Operator: "=", Value: "app"},
{Key: v3.AttributeKey{Key: "scope_version", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeUnspecified, IsColumn: true}, Operator: "=", Value: "version"},
}},
},
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
"CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," +
"CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool," +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, " +
"CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND scope_name = 'app' AND scope_version = 'version' order by timestamp DESC",
},
{
Name: "Test aggregate with having clause",
PanelType: v3.PanelTypeGraph,
@@ -875,7 +888,7 @@ var testBuildLogsQueryData = []struct {
},
},
TableName: "logs",
ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND lower(body) LIKE lower('%test%') AND has(attributes_string_key, 'name') group by ts having value > 10 order by value DESC",
ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND body ILIKE '%test%' AND has(attributes_string_key, 'name') group by ts having value > 10 order by value DESC",
},
{
Name: "Test attribute with same name as top level key",
@@ -1005,7 +1018,7 @@ var testBuildLogsQueryData = []struct {
},
},
TableName: "logs",
ExpectedQuery: "SELECT now() as ts, attributes_string_value[indexOf(attributes_string_key, 'name')] as `name`, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND lower(body) like lower('%message%') AND JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%a%' AND has(attributes_string_key, 'name') group by `name` order by `name` DESC",
ExpectedQuery: "SELECT now() as ts, attributes_string_value[indexOf(attributes_string_key, 'name')] as `name`, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND JSON_EXISTS(body, '$.\"message\"') AND JSON_VALUE(body, '$.\"message\"') ILIKE '%a%' AND has(attributes_string_key, 'name') group by `name` order by `name` DESC",
},
{
Name: "TABLE: Test count with JSON Filter Array, groupBy, orderBy",
@@ -1039,7 +1052,7 @@ var testBuildLogsQueryData = []struct {
},
},
TableName: "logs",
ExpectedQuery: "SELECT now() as ts, attributes_string_value[indexOf(attributes_string_key, 'name')] as `name`, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND lower(body) like lower('%requestor_list%') AND lower(body) like lower('%index_service%') AND has(JSONExtract(JSON_QUERY(body, '$.\"requestor_list\"[*]'), 'Array(String)'), 'index_service') AND has(attributes_string_key, 'name') group by `name` order by `name` DESC",
ExpectedQuery: "SELECT now() as ts, attributes_string_value[indexOf(attributes_string_key, 'name')] as `name`, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND has(JSONExtract(JSON_QUERY(body, '$.\"requestor_list\"[*]'), 'Array(String)'), 'index_service') AND has(attributes_string_key, 'name') group by `name` order by `name` DESC",
},
}
@@ -1332,9 +1345,13 @@ var testPrepLogsQueryData = []struct {
},
},
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND ",
Options: Options{IsLivetailQuery: true},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body," +
"CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') " +
"as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') " +
"as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND ",
Options: Options{IsLivetailQuery: true},
},
{
Name: "Live Tail Query with contains",
@@ -1351,9 +1368,12 @@ var testPrepLogsQueryData = []struct {
},
},
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where attributes_string_value[indexOf(attributes_string_key, 'method')] ILIKE '%GET%' AND ",
Options: Options{IsLivetailQuery: true},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') " +
"as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') " +
"as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where attributes_string_value[indexOf(attributes_string_key, 'method')] ILIKE '%GET%' AND ",
Options: Options{IsLivetailQuery: true},
},
{
Name: "Live Tail Query W/O filter",
@@ -1367,9 +1387,12 @@ var testPrepLogsQueryData = []struct {
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}},
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where ",
Options: Options{IsLivetailQuery: true},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') " +
"as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') " +
"as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where ",
Options: Options{IsLivetailQuery: true},
},
{
Name: "Table query w/o limit",
@@ -1506,8 +1529,11 @@ var testPrepLogsQueryLimitOffsetData = []struct {
Offset: 0,
PageSize: 5,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by `timestamp` desc LIMIT 1",
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') " +
"as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') " +
"as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by `timestamp` desc LIMIT 1",
},
{
Name: "Test limit greater than pageSize - order by ts",
@@ -1527,8 +1553,11 @@ var testPrepLogsQueryLimitOffsetData = []struct {
Offset: 10,
PageSize: 10,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by `timestamp` desc LIMIT 10",
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') " +
"as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') " +
"as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by `timestamp` desc LIMIT 10",
},
{
Name: "Test limit less than pageSize - order by custom",
@@ -1546,8 +1575,11 @@ var testPrepLogsQueryLimitOffsetData = []struct {
Offset: 0,
PageSize: 5,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 1 OFFSET 0",
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') " +
"as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') " +
"as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 1 OFFSET 0",
},
{
Name: "Test limit greater than pageSize - order by custom",
@@ -1567,8 +1599,11 @@ var testPrepLogsQueryLimitOffsetData = []struct {
Offset: 50,
PageSize: 50,
},
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 50 OFFSET 50",
TableName: "logs",
ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') " +
"as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') " +
"as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by attributes_string_value[indexOf(attributes_string_key, 'method')] desc LIMIT 50 OFFSET 50",
},
}

View File

@@ -296,12 +296,13 @@ var StaticSelectedLogFields = []model.LogField{
const (
LogsSQLSelect = "SELECT " +
"timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body," +
"timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body," +
"CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," +
"CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64," +
"CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," +
"CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool," +
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string "
"CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string, " +
"CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope "
TracesExplorerViewSQLSelectWithSubQuery = "WITH subQuery AS (SELECT distinct on (traceID) traceID, durationNano, " +
"serviceName, name FROM %s.%s WHERE parentSpanID = '' AND %s %s ORDER BY durationNano DESC "
TracesExplorerViewSQLSelectQuery = "SELECT subQuery.serviceName, subQuery.name, count() AS " +
@@ -366,6 +367,18 @@ var StaticFieldsLogsV3 = map[string]v3.AttributeKey{
Type: v3.AttributeKeyTypeUnspecified,
IsColumn: true,
},
"scope_name": {
Key: "scope_name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeUnspecified,
IsColumn: true,
},
"scope_version": {
Key: "scope_version",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeUnspecified,
IsColumn: true,
},
}
const SigNozOrderByValue = "#SIGNOZ_VALUE"

View File

@@ -229,13 +229,14 @@ type AggregateAttributeRequest struct {
type TagType string
const (
TagTypeTag TagType = "tag"
TagTypeResource TagType = "resource"
TagTypeTag TagType = "tag"
TagTypeResource TagType = "resource"
TagTypeInstrumentationScope TagType = "scope"
)
func (q TagType) Validate() error {
switch q {
case TagTypeTag, TagTypeResource:
case TagTypeTag, TagTypeResource, TagTypeInstrumentationScope:
return nil
default:
return fmt.Errorf("invalid tag type: %s", q)
@@ -300,9 +301,10 @@ type FilterAttributeKeyResponse struct {
type AttributeKeyType string
const (
AttributeKeyTypeUnspecified AttributeKeyType = ""
AttributeKeyTypeTag AttributeKeyType = "tag"
AttributeKeyTypeResource AttributeKeyType = "resource"
AttributeKeyTypeUnspecified AttributeKeyType = ""
AttributeKeyTypeTag AttributeKeyType = "tag"
AttributeKeyTypeResource AttributeKeyType = "resource"
AttributeKeyTypeInstrumentationScope AttributeKeyType = "scope"
)
type AttributeKey struct {
@@ -327,7 +329,7 @@ func (a AttributeKey) Validate() error {
if a.IsColumn {
switch a.Type {
case AttributeKeyTypeResource, AttributeKeyTypeTag, AttributeKeyTypeUnspecified:
case AttributeKeyTypeResource, AttributeKeyTypeTag, AttributeKeyTypeUnspecified, AttributeKeyTypeInstrumentationScope:
break
default:
return fmt.Errorf("invalid attribute type: %s", a.Type)
@@ -907,8 +909,7 @@ const (
FilterOperatorNotContains FilterOperator = "ncontains"
FilterOperatorRegex FilterOperator = "regex"
FilterOperatorNotRegex FilterOperator = "nregex"
// (I)LIKE is faster than REGEX
// ilike doesn't support index so internally we use lower(body) like for query
// (I)LIKE is faster than REGEX and supports index
FilterOperatorLike FilterOperator = "like"
FilterOperatorNotLike FilterOperator = "nlike"