mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-14 22:20:49 +01:00
Compare commits
2 Commits
feat/scatt
...
test/explo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee0924164 | ||
|
|
65298c25b0 |
@@ -104,6 +104,7 @@ function ListViewOrderBy({
|
||||
|
||||
return (
|
||||
<Select
|
||||
data-testid="list-view-order-by"
|
||||
showSearch
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -8,7 +9,13 @@ import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import { QueryBuilderContext } from 'providers/QueryBuilder';
|
||||
import { fireEvent, render, RenderResult, waitFor } from 'tests/test-utils';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
RenderResult,
|
||||
screen,
|
||||
waitFor,
|
||||
} from 'tests/test-utils';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { LogsAggregatorOperator } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -396,5 +403,95 @@ describe('LogsExplorerViews -', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('rebuilds the List View query when the order changes', async () => {
|
||||
(useCopyLogLink as jest.Mock).mockReturnValue({ activeLogId: undefined });
|
||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
||||
data: { payload: logsQueryRangeSuccessNewFormatResponse },
|
||||
});
|
||||
|
||||
const stagedQueryOrderedBy = (
|
||||
columnName: string,
|
||||
id: string,
|
||||
): Record<string, unknown> => ({
|
||||
...mockQueryBuilderContextValue.stagedQuery,
|
||||
id,
|
||||
builder: {
|
||||
...mockQueryBuilderContextValue.stagedQuery.builder,
|
||||
queryData: mockQueryBuilderContextValue.stagedQuery.builder.queryData.map(
|
||||
(item: Record<string, unknown>) => ({
|
||||
...item,
|
||||
orderBy: [{ columnName, order: 'desc' }],
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
// `customRender` wraps a captured element, so `rerender` does not reach the
|
||||
// tree. The order change is driven from state inside it instead.
|
||||
function Harness(): JSX.Element {
|
||||
const [ordered, setOrdered] = useState(false);
|
||||
const stagedQuery = ordered
|
||||
? stagedQueryOrderedBy('severity_text', 'query-2')
|
||||
: stagedQueryOrderedBy('timestamp', 'query-1');
|
||||
|
||||
return (
|
||||
<VirtuosoMockContext.Provider
|
||||
value={{ viewportHeight: 300, itemHeight: 100 }}
|
||||
>
|
||||
<PreferenceContextProvider>
|
||||
<QueryBuilderContext.Provider
|
||||
value={
|
||||
{
|
||||
...mockQueryBuilderContextValue,
|
||||
panelType: PANEL_TYPES.LIST,
|
||||
stagedQuery,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="change-order"
|
||||
onClick={(): void => setOrdered(true)}
|
||||
/>
|
||||
<LogsExplorerViews
|
||||
setIsLoadingQueries={(): void => {}}
|
||||
listQueryKeyRef={{ current: {} }}
|
||||
chartQueryKeyRef={{ current: {} }}
|
||||
setWarning={(): void => {}}
|
||||
showLiveLogs={false}
|
||||
handleChangeSelectedView={(): void => {}}
|
||||
/>
|
||||
</QueryBuilderContext.Provider>
|
||||
</PreferenceContextProvider>
|
||||
</VirtuosoMockContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const latestListOrderBy = (): unknown => {
|
||||
const calls = (useGetExplorerQueryRange as jest.Mock).mock.calls.filter(
|
||||
(call) => call[1] === PANEL_TYPES.LIST && call[0],
|
||||
);
|
||||
return calls[calls.length - 1]?.[0]?.builder?.queryData?.[0]?.orderBy;
|
||||
};
|
||||
|
||||
render(<Harness />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(latestListOrderBy()).toStrictEqual([
|
||||
{ columnName: 'timestamp', order: 'desc' },
|
||||
{ columnName: 'id', order: 'desc' },
|
||||
]);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId('change-order'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(latestListOrderBy()).toStrictEqual([
|
||||
{ columnName: 'severity_text', order: 'desc' },
|
||||
{ columnName: 'id', order: 'desc' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
113
tests/e2e/helpers/explorer-order-by.ts
Normal file
113
tests/e2e/helpers/explorer-order-by.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dismissQuickFiltersAnnouncement,
|
||||
LOGS_EXPLORER_PATH,
|
||||
} from './quick-filters';
|
||||
|
||||
export { LOGS_EXPLORER_PATH };
|
||||
|
||||
export const TRACES_EXPLORER_PATH = '/traces-explorer';
|
||||
export const LLM_EXPLORER_PATH = '/ai-observability/explorer';
|
||||
|
||||
const RELATIVE_TIME = '6h';
|
||||
|
||||
const ORDER_BY_TEST_ID = 'list-view-order-by';
|
||||
|
||||
export type ExplorerView = 'list' | 'trace' | 'timeseries' | 'table';
|
||||
|
||||
const VIEW_TAB_CLASS: Record<ExplorerView, string> = {
|
||||
list: 'list-view-tab',
|
||||
trace: 'trace-view-tab',
|
||||
timeseries: 'timeseries-view-tab',
|
||||
table: 'table-view-tab',
|
||||
};
|
||||
|
||||
export async function gotoExplorer(page: Page, path: string): Promise<void> {
|
||||
await dismissQuickFiltersAnnouncement(page);
|
||||
await page.goto(`${path}?relativeTime=${RELATIVE_TIME}`);
|
||||
await expect(orderByControl(page)).toBeVisible();
|
||||
}
|
||||
|
||||
export function orderByControl(page: Page): Locator {
|
||||
return page.getByTestId(ORDER_BY_TEST_ID);
|
||||
}
|
||||
|
||||
export async function expectOrderBy(page: Page, value: string): Promise<void> {
|
||||
await expect(
|
||||
orderByControl(page).locator('.ant-select-selection-item'),
|
||||
).toHaveText(value);
|
||||
}
|
||||
|
||||
export async function setOrderBy(page: Page, value: string): Promise<void> {
|
||||
await orderByControl(page).click();
|
||||
await page
|
||||
.locator('.ant-select-item-option')
|
||||
.filter({ hasText: value })
|
||||
.first()
|
||||
.click();
|
||||
await expectOrderBy(page, value);
|
||||
}
|
||||
|
||||
export function viewTab(page: Page, view: ExplorerView): Locator {
|
||||
return page.locator(`.${VIEW_TAB_CLASS[view]}`);
|
||||
}
|
||||
|
||||
export async function switchView(
|
||||
page: Page,
|
||||
view: ExplorerView,
|
||||
): Promise<void> {
|
||||
await viewTab(page, view).click();
|
||||
await expect(viewTab(page, view)).toHaveClass(/active-tab/);
|
||||
}
|
||||
|
||||
export function orderByFromUrl(
|
||||
page: Page,
|
||||
): { columnName: string; order: string }[] {
|
||||
const raw = new URL(page.url()).searchParams.get('compositeQuery');
|
||||
if (!raw) {
|
||||
return [];
|
||||
}
|
||||
const query = JSON.parse(decodeURIComponent(raw));
|
||||
return query?.builder?.queryData?.[0]?.orderBy ?? [];
|
||||
}
|
||||
|
||||
export function withOrderByInUrl(
|
||||
path: string,
|
||||
orderBy: { columnName: string; order: string },
|
||||
dataSource: 'logs' | 'traces',
|
||||
): string {
|
||||
const compositeQuery = {
|
||||
queryType: 'builder',
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
dataSource,
|
||||
queryName: 'A',
|
||||
aggregateOperator: 'noop',
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
filters: { items: [], op: 'AND' },
|
||||
groupBy: [],
|
||||
orderBy: [orderBy],
|
||||
limit: null,
|
||||
having: [],
|
||||
legend: '',
|
||||
reduceTo: 'avg',
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
id: 'e2e-order-by',
|
||||
};
|
||||
|
||||
const params = new URLSearchParams({
|
||||
relativeTime: RELATIVE_TIME,
|
||||
panelTypes: JSON.stringify('list'),
|
||||
compositeQuery: encodeURIComponent(JSON.stringify(compositeQuery)),
|
||||
});
|
||||
|
||||
return `${path}?${params.toString()}`;
|
||||
}
|
||||
37
tests/e2e/tests/llm-o11y/order-by.spec.ts
Normal file
37
tests/e2e/tests/llm-o11y/order-by.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { expect, test } from '../../fixtures/auth';
|
||||
import {
|
||||
expectOrderBy,
|
||||
gotoExplorer,
|
||||
LLM_EXPLORER_PATH,
|
||||
orderByFromUrl,
|
||||
setOrderBy,
|
||||
switchView,
|
||||
} from '../../helpers/explorer-order-by';
|
||||
|
||||
test.describe('LLM Observability Explorer — order by', () => {
|
||||
test('TC-01 does not carry a trace field into the list view', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await gotoExplorer(page, LLM_EXPLORER_PATH);
|
||||
|
||||
await switchView(page, 'trace');
|
||||
await expectOrderBy(page, 'last_activity_time (desc)');
|
||||
|
||||
await switchView(page, 'list');
|
||||
|
||||
await expectOrderBy(page, 'timestamp (desc)');
|
||||
expect(orderByFromUrl(page)).toStrictEqual([]);
|
||||
});
|
||||
|
||||
test('TC-02 does not carry a span field into the trace view', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await gotoExplorer(page, LLM_EXPLORER_PATH);
|
||||
await setOrderBy(page, 'timestamp (asc)');
|
||||
|
||||
await switchView(page, 'trace');
|
||||
|
||||
await expectOrderBy(page, 'last_activity_time (desc)');
|
||||
expect(orderByFromUrl(page)).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
38
tests/e2e/tests/logs-explorer/order-by.spec.ts
Normal file
38
tests/e2e/tests/logs-explorer/order-by.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { expect, test } from '../../fixtures/auth';
|
||||
import {
|
||||
expectOrderBy,
|
||||
gotoExplorer,
|
||||
LOGS_EXPLORER_PATH,
|
||||
orderByFromUrl,
|
||||
withOrderByInUrl,
|
||||
} from '../../helpers/explorer-order-by';
|
||||
import { dismissQuickFiltersAnnouncement } from '../../helpers/quick-filters';
|
||||
|
||||
test.describe('Logs Explorer — order by', () => {
|
||||
test('TC-01 applies the order a deep link carries', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await dismissQuickFiltersAnnouncement(page);
|
||||
await page.goto(
|
||||
withOrderByInUrl(
|
||||
LOGS_EXPLORER_PATH,
|
||||
{ columnName: 'timestamp', order: 'asc' },
|
||||
'logs',
|
||||
),
|
||||
);
|
||||
|
||||
await expectOrderBy(page, 'timestamp (asc)');
|
||||
expect(orderByFromUrl(page)).toStrictEqual([
|
||||
{ columnName: 'timestamp', order: 'asc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('TC-02 writes nothing to the URL until the order is changed', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await gotoExplorer(page, LOGS_EXPLORER_PATH);
|
||||
|
||||
await expectOrderBy(page, 'timestamp (desc)');
|
||||
expect(orderByFromUrl(page)).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
57
tests/e2e/tests/traces-explorer/order-by.spec.ts
Normal file
57
tests/e2e/tests/traces-explorer/order-by.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { expect, test } from '../../fixtures/auth';
|
||||
import {
|
||||
expectOrderBy,
|
||||
gotoExplorer,
|
||||
orderByFromUrl,
|
||||
setOrderBy,
|
||||
switchView,
|
||||
TRACES_EXPLORER_PATH,
|
||||
} from '../../helpers/explorer-order-by';
|
||||
|
||||
test.describe('Traces Explorer — order by', () => {
|
||||
test('TC-01 keeps the order after opening a trace and going back', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await gotoExplorer(page, TRACES_EXPLORER_PATH);
|
||||
await setOrderBy(page, 'duration_nano (desc)');
|
||||
|
||||
await page.locator('.ant-table-row').first().click();
|
||||
await expect(page).toHaveURL(/\/trace\//);
|
||||
|
||||
await page.goBack();
|
||||
|
||||
await expectOrderBy(page, 'duration_nano (desc)');
|
||||
expect(orderByFromUrl(page)).toStrictEqual([
|
||||
{ columnName: 'duration_nano', order: 'desc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('TC-02 writes the order to the URL and keeps it across a reload', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await gotoExplorer(page, TRACES_EXPLORER_PATH);
|
||||
expect(orderByFromUrl(page)).toStrictEqual([]);
|
||||
|
||||
await setOrderBy(page, 'duration_nano (asc)');
|
||||
expect(orderByFromUrl(page)).toStrictEqual([
|
||||
{ columnName: 'duration_nano', order: 'asc' },
|
||||
]);
|
||||
|
||||
await page.reload();
|
||||
|
||||
await expectOrderBy(page, 'duration_nano (asc)');
|
||||
});
|
||||
|
||||
test('TC-03 clears the order when the Time Series tab hands over to List', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await gotoExplorer(page, TRACES_EXPLORER_PATH);
|
||||
await setOrderBy(page, 'duration_nano (desc)');
|
||||
|
||||
await switchView(page, 'timeseries');
|
||||
await switchView(page, 'list');
|
||||
|
||||
await expectOrderBy(page, 'timestamp (desc)');
|
||||
expect(orderByFromUrl(page)).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user