mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-18 07:56:56 +00:00
feat: add support for entrypoint spans toggle in top operations table (#8175)
* feat: add support for entrypoint spans toggle in top operations table * fix: write tests for entry point toggle * chore: entry point -> entrypoint * fix: add info icon and tooltip for entrypoint spans toggle * fix: fix the copy and link for entrypoint toggle in top operations * chore: update the tooltip text * Update frontend/src/container/MetricsApplication/TopOperationsTable.tsx Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * chore: fix the failing build * chore: update the entry point spans docs link --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
parent
507dc86af2
commit
41661a5e28
@ -2,13 +2,20 @@ import axios from 'api';
|
|||||||
import { PayloadProps, Props } from 'types/api/metrics/getTopOperations';
|
import { PayloadProps, Props } from 'types/api/metrics/getTopOperations';
|
||||||
|
|
||||||
const getTopOperations = async (props: Props): Promise<PayloadProps> => {
|
const getTopOperations = async (props: Props): Promise<PayloadProps> => {
|
||||||
const response = await axios.post(`/service/top_operations`, {
|
const endpoint = props.isEntryPoint
|
||||||
|
? '/service/entry_point_operations'
|
||||||
|
: '/service/top_operations';
|
||||||
|
|
||||||
|
const response = await axios.post(endpoint, {
|
||||||
start: `${props.start}`,
|
start: `${props.start}`,
|
||||||
end: `${props.end}`,
|
end: `${props.end}`,
|
||||||
service: props.service,
|
service: props.service,
|
||||||
tags: props.selectedTags,
|
tags: props.selectedTags,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (props.isEntryPoint) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import getTopOperations from 'api/metrics/getTopOperations';
|
|||||||
import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable';
|
import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@ -20,6 +20,8 @@ function TopOperation(): JSX.Element {
|
|||||||
}>();
|
}>();
|
||||||
const servicename = decodeURIComponent(encodedServiceName || '');
|
const servicename = decodeURIComponent(encodedServiceName || '');
|
||||||
|
|
||||||
|
const [isEntryPoint, setIsEntryPoint] = useState<boolean>(false);
|
||||||
|
|
||||||
const { queries } = useResourceAttribute();
|
const { queries } = useResourceAttribute();
|
||||||
const selectedTags = useMemo(
|
const selectedTags = useMemo(
|
||||||
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
||||||
@ -27,19 +29,27 @@ function TopOperation(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading } = useQuery<PayloadProps>({
|
const { data, isLoading } = useQuery<PayloadProps>({
|
||||||
queryKey: [minTime, maxTime, servicename, selectedTags],
|
queryKey: [minTime, maxTime, servicename, selectedTags, isEntryPoint],
|
||||||
queryFn: (): Promise<PayloadProps> =>
|
queryFn: (): Promise<PayloadProps> =>
|
||||||
getTopOperations({
|
getTopOperations({
|
||||||
service: servicename || '',
|
service: servicename || '',
|
||||||
start: minTime,
|
start: minTime,
|
||||||
end: maxTime,
|
end: maxTime,
|
||||||
selectedTags,
|
selectedTags,
|
||||||
|
isEntryPoint,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const topOperationData = data || [];
|
const topOperationData = data || [];
|
||||||
|
|
||||||
return <TopOperationsTable data={topOperationData} isLoading={isLoading} />;
|
return (
|
||||||
|
<TopOperationsTable
|
||||||
|
data={topOperationData}
|
||||||
|
isLoading={isLoading}
|
||||||
|
isEntryPoint={isEntryPoint}
|
||||||
|
onEntryPointToggle={setIsEntryPoint}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopOperation;
|
export default TopOperation;
|
||||||
|
|||||||
@ -1,9 +1,24 @@
|
|||||||
.top-operation {
|
.top-operation {
|
||||||
position: relative;
|
position: relative;
|
||||||
.top-operation--download {
|
|
||||||
|
&__controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
&__entry-point {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
&__download {
|
||||||
|
.ant-btn-icon {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import './TopOperationsTable.styles.scss';
|
import './TopOperationsTable.styles.scss';
|
||||||
|
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { InputRef, Tooltip, Typography } from 'antd';
|
import { InputRef, Switch, Tooltip, Typography } from 'antd';
|
||||||
import { ColumnsType, ColumnType } from 'antd/lib/table';
|
import { ColumnsType, ColumnType } from 'antd/lib/table';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import Download from 'container/Download/Download';
|
import Download from 'container/Download/Download';
|
||||||
import { filterDropdown } from 'container/ServiceApplication/Filter/FilterDropdown';
|
import { filterDropdown } from 'container/ServiceApplication/Filter/FilterDropdown';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
@ -29,6 +30,8 @@ import {
|
|||||||
function TopOperationsTable({
|
function TopOperationsTable({
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isEntryPoint,
|
||||||
|
onEntryPointToggle,
|
||||||
}: TopOperationsTableProps): JSX.Element {
|
}: TopOperationsTableProps): JSX.Element {
|
||||||
const searchInput = useRef<InputRef>(null);
|
const searchInput = useRef<InputRef>(null);
|
||||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||||
@ -174,20 +177,45 @@ function TopOperationsTable({
|
|||||||
hideOnSinglePage: true,
|
hideOnSinglePage: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const entryPointSpanInfo = {
|
||||||
|
text: 'Shows the spans where requests enter new services for the first time',
|
||||||
|
url:
|
||||||
|
'https://signoz.io/docs/traces-management/guides/entry-point-spans-service-overview/',
|
||||||
|
urlText: 'Learn more about Entrypoint Spans.',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="top-operation">
|
<div className="top-operation">
|
||||||
<div className="top-operation--download">
|
<div className="top-operation__controls">
|
||||||
|
<div className="top-operation__download">
|
||||||
<Download
|
<Download
|
||||||
data={downloadableData}
|
data={downloadableData}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
fileName={`top-operations-${servicename}`}
|
fileName={`top-operations-${servicename}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="top-operation__entry-point">
|
||||||
|
<Switch
|
||||||
|
checked={isEntryPoint}
|
||||||
|
onChange={onEntryPointToggle}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<span className="top-operation__entry-point-label">Entrypoint Spans</span>
|
||||||
|
<TextToolTip
|
||||||
|
text={entryPointSpanInfo.text}
|
||||||
|
url={entryPointSpanInfo.url}
|
||||||
|
useFilledIcon={false}
|
||||||
|
urlText={entryPointSpanInfo.urlText}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
showHeader
|
showHeader
|
||||||
title={(): string => 'Key Operations'}
|
title={(): string =>
|
||||||
|
isEntryPoint ? 'Key Entrypoint Operations' : 'Key Operations'
|
||||||
|
}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
rowKey="name"
|
rowKey="name"
|
||||||
@ -209,6 +237,8 @@ export interface TopOperationList {
|
|||||||
interface TopOperationsTableProps {
|
interface TopOperationsTableProps {
|
||||||
data: TopOperationList[];
|
data: TopOperationList[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isEntryPoint: boolean;
|
||||||
|
onEntryPointToggle: (checked: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopOperationsTable;
|
export default TopOperationsTable;
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import { QueryClient } from 'react-query';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
import { TopOperationList } from '../TopOperationsTable';
|
import { TopOperationList } from '../TopOperationsTable';
|
||||||
|
|
||||||
interface TopOperation {
|
interface TopOperation {
|
||||||
@ -17,3 +20,59 @@ export const getTopOperationList = ({
|
|||||||
p95: 0,
|
p95: 0,
|
||||||
p99: 0,
|
p99: 0,
|
||||||
} as TopOperationList);
|
} as TopOperationList);
|
||||||
|
|
||||||
|
export const defaultApiCallExpectation = {
|
||||||
|
service: 'test-service',
|
||||||
|
start: 1640995200000,
|
||||||
|
end: 1641081600000,
|
||||||
|
selectedTags: [],
|
||||||
|
isEntryPoint: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockStore = configureStore([]);
|
||||||
|
export const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mockTopOperationsData: TopOperationList[] = [
|
||||||
|
{
|
||||||
|
name: 'GET /api/users',
|
||||||
|
p50: 1000000,
|
||||||
|
p95: 2000000,
|
||||||
|
p99: 3000000,
|
||||||
|
numCalls: 100,
|
||||||
|
errorCount: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'POST /api/orders',
|
||||||
|
p50: 1500000,
|
||||||
|
p95: 2500000,
|
||||||
|
p99: 3500000,
|
||||||
|
numCalls: 80,
|
||||||
|
errorCount: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const mockEntryPointData: TopOperationList[] = [
|
||||||
|
{
|
||||||
|
name: 'GET /api/health',
|
||||||
|
p50: 500000,
|
||||||
|
p95: 1000000,
|
||||||
|
p99: 1500000,
|
||||||
|
numCalls: 200,
|
||||||
|
errorCount: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const createMockStore = (): any =>
|
||||||
|
mockStore({
|
||||||
|
globalTime: {
|
||||||
|
minTime: 1640995200000,
|
||||||
|
maxTime: 1641081600000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@ -0,0 +1,300 @@
|
|||||||
|
import {
|
||||||
|
act,
|
||||||
|
fireEvent,
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
waitFor,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
|
import { server } from 'mocks-server/server';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
import { QueryClientProvider } from 'react-query';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createMockStore,
|
||||||
|
defaultApiCallExpectation,
|
||||||
|
mockEntryPointData,
|
||||||
|
mockTopOperationsData,
|
||||||
|
queryClient,
|
||||||
|
} from '../__mocks__/getTopOperation';
|
||||||
|
import TopOperation from '../Tabs/Overview/TopOperation';
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
jest.mock('hooks/useResourceAttribute');
|
||||||
|
jest.mock('hooks/useResourceAttribute/utils', () => ({
|
||||||
|
convertRawQueriesToTraceSelectedTags: (): any[] => [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/useSafeNavigate', () => ({
|
||||||
|
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||||
|
safeNavigate: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useParams: (): { servicename: string } => ({
|
||||||
|
servicename: encodeURIComponent('test-service'),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the util functions that TopOperationsTable uses
|
||||||
|
jest.mock('../Tabs/util', () => ({
|
||||||
|
useGetAPMToTracesQueries: (): any => ({
|
||||||
|
builder: {
|
||||||
|
queryData: [
|
||||||
|
{
|
||||||
|
filters: {
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the resourceAttributesToTracesFilterItems function
|
||||||
|
jest.mock('container/TraceDetail/utils', () => ({
|
||||||
|
resourceAttributesToTracesFilterItems: (): any[] => [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockedUseResourceAttribute = useResourceAttribute as jest.MockedFunction<
|
||||||
|
typeof useResourceAttribute
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const KEY_OPERATIONS_TEXT = 'Key Operations';
|
||||||
|
const KEY_ENTRY_POINT_OPERATIONS_TEXT = 'Key Entrypoint Operations';
|
||||||
|
const ENTRY_POINT_SPANS_TEXT = 'Entrypoint Spans';
|
||||||
|
const TOP_OPERATIONS_ENDPOINT = 'top_operations';
|
||||||
|
const ENTRY_POINT_OPERATIONS_ENDPOINT = 'entry_point_operations';
|
||||||
|
|
||||||
|
const renderComponent = (store = createMockStore()): any =>
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<TopOperation />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</Provider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Helper function to wait for initial render and verify basic functionality
|
||||||
|
const waitForInitialRender = async (): Promise<void> => {
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(KEY_OPERATIONS_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to click toggle and wait for data to load
|
||||||
|
const clickToggleAndWaitForDataLoad = async (): Promise<HTMLElement> => {
|
||||||
|
const toggleSwitch = screen.getByRole('switch');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(toggleSwitch);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(KEY_ENTRY_POINT_OPERATIONS_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
return toggleSwitch;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('TopOperation API Integration', () => {
|
||||||
|
let apiCalls: { endpoint: string; body: any }[] = [];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
queryClient.clear();
|
||||||
|
apiCalls = [];
|
||||||
|
|
||||||
|
mockedUseResourceAttribute.mockReturnValue({
|
||||||
|
queries: [],
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
rest.post(
|
||||||
|
'http://localhost/api/v1/service/top_operations',
|
||||||
|
async (req, res, ctx) => {
|
||||||
|
const body = await req.json();
|
||||||
|
apiCalls.push({ endpoint: TOP_OPERATIONS_ENDPOINT, body });
|
||||||
|
return res(ctx.status(200), ctx.json(mockTopOperationsData));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
rest.post(
|
||||||
|
'http://localhost/api/v1/service/entry_point_operations',
|
||||||
|
async (req, res, ctx) => {
|
||||||
|
const body = await req.json();
|
||||||
|
apiCalls.push({ endpoint: ENTRY_POINT_OPERATIONS_ENDPOINT, body });
|
||||||
|
return res(ctx.status(200), ctx.json({ data: mockEntryPointData }));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with default key operations on initial load', async () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
await waitForInitialRender();
|
||||||
|
|
||||||
|
// Verify the toggle is present and unchecked
|
||||||
|
const toggleSwitch = screen.getByRole('switch');
|
||||||
|
expect(toggleSwitch).not.toBeChecked();
|
||||||
|
expect(screen.getByText(ENTRY_POINT_SPANS_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls top_operations API on initial render', async () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
await waitForInitialRender();
|
||||||
|
|
||||||
|
// Wait a bit more for API calls to be captured
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(apiCalls.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that only the top_operations endpoint was called
|
||||||
|
expect(apiCalls).toHaveLength(1);
|
||||||
|
expect(apiCalls[0].endpoint).toBe(TOP_OPERATIONS_ENDPOINT);
|
||||||
|
expect(apiCalls[0].body).toEqual({
|
||||||
|
start: `${defaultApiCallExpectation.start}`,
|
||||||
|
end: `${defaultApiCallExpectation.end}`,
|
||||||
|
service: defaultApiCallExpectation.service,
|
||||||
|
tags: defaultApiCallExpectation.selectedTags,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls entry_point_operations API when toggle is switched to entry point', async () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
// Wait for initial render
|
||||||
|
await waitForInitialRender();
|
||||||
|
|
||||||
|
// Wait for initial API call
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(apiCalls.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear previous API calls
|
||||||
|
apiCalls = [];
|
||||||
|
|
||||||
|
// Toggle to entry point
|
||||||
|
await clickToggleAndWaitForDataLoad();
|
||||||
|
|
||||||
|
// Wait for the API call to be captured
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(apiCalls.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that the entry_point_operations endpoint was called
|
||||||
|
expect(apiCalls).toHaveLength(1);
|
||||||
|
expect(apiCalls[0].endpoint).toBe(ENTRY_POINT_OPERATIONS_ENDPOINT);
|
||||||
|
expect(apiCalls[0].body).toEqual({
|
||||||
|
start: `${defaultApiCallExpectation.start}`,
|
||||||
|
end: `${defaultApiCallExpectation.end}`,
|
||||||
|
service: defaultApiCallExpectation.service,
|
||||||
|
tags: defaultApiCallExpectation.selectedTags,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('switches to entry point operations when toggle is clicked', async () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
// Wait for initial render
|
||||||
|
await waitForInitialRender();
|
||||||
|
|
||||||
|
// Find and click the toggle switch
|
||||||
|
const toggleSwitch = screen.getByRole('switch');
|
||||||
|
expect(toggleSwitch).not.toBeChecked();
|
||||||
|
|
||||||
|
await clickToggleAndWaitForDataLoad();
|
||||||
|
|
||||||
|
// Check that the switch is now checked and title updates
|
||||||
|
expect(toggleSwitch).toBeChecked();
|
||||||
|
expect(screen.getByText(KEY_ENTRY_POINT_OPERATIONS_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls correct APIs when toggling multiple times', async () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
// Wait for initial render
|
||||||
|
await waitForInitialRender();
|
||||||
|
|
||||||
|
// Wait for initial API call
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(apiCalls.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should have called top_operations initially
|
||||||
|
expect(apiCalls).toHaveLength(1);
|
||||||
|
expect(apiCalls[0].endpoint).toBe(TOP_OPERATIONS_ENDPOINT);
|
||||||
|
|
||||||
|
// Toggle to entry point
|
||||||
|
await clickToggleAndWaitForDataLoad();
|
||||||
|
|
||||||
|
// Wait for the second API call
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(apiCalls.length).toBeGreaterThan(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should now have called entry_point_operations
|
||||||
|
expect(apiCalls).toHaveLength(2);
|
||||||
|
expect(apiCalls[1].endpoint).toBe(ENTRY_POINT_OPERATIONS_ENDPOINT);
|
||||||
|
|
||||||
|
// Toggle back to regular operations
|
||||||
|
const toggleSwitch = screen.getByRole('switch');
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(toggleSwitch);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(KEY_OPERATIONS_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the third API call
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(apiCalls.length).toBeGreaterThan(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should have called top_operations again
|
||||||
|
expect(apiCalls).toHaveLength(3);
|
||||||
|
expect(apiCalls[2].endpoint).toBe(TOP_OPERATIONS_ENDPOINT);
|
||||||
|
|
||||||
|
expect(toggleSwitch).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays entry point toggle with correct label', async () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(ENTRY_POINT_SPANS_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleSwitch = screen.getByRole('switch');
|
||||||
|
expect(toggleSwitch).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('switches back to key operations when toggle is clicked twice', async () => {
|
||||||
|
renderComponent();
|
||||||
|
|
||||||
|
// Wait for initial render
|
||||||
|
await waitForInitialRender();
|
||||||
|
|
||||||
|
// Toggle on (to entry point)
|
||||||
|
await clickToggleAndWaitForDataLoad();
|
||||||
|
expect(screen.getByText(KEY_ENTRY_POINT_OPERATIONS_TEXT)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Toggle off (back to key operations)
|
||||||
|
const toggleSwitch = screen.getByRole('switch');
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(toggleSwitch);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(KEY_OPERATIONS_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(toggleSwitch).not.toBeChecked();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -6,6 +6,7 @@ export interface Props {
|
|||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
selectedTags: Tags[];
|
selectedTags: Tags[];
|
||||||
|
isEntryPoint?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PayloadProps = TopOperationList[];
|
export type PayloadProps = TopOperationList[];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user