Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Services map] Update redirection/Focus field rework #2264

Merged
merged 30 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6396b33
Change service table click to page redirection
Nov 27, 2024
87cdd23
remove focus on filtering, bug fixes
Nov 28, 2024
c828db3
Merge branch 'main' into serviceViewBug
TackAdam Nov 28, 2024
45af580
add jest test for url redirection on click
Nov 28, 2024
9147f5b
remove filter on focus, update focus functionality, add testing
Nov 30, 2024
f50422e
add query filtering back
Dec 2, 2024
10ef093
change url check to use hook state
Dec 3, 2024
8bc5523
fix location error
Dec 3, 2024
c1ac1c2
move flyout into hashrouter, remove extra filter
Dec 4, 2024
35774e2
Merge branch 'main' into serviceViewBug
TackAdam Dec 5, 2024
c5408a1
Merge branch 'main' into serviceViewBug
TackAdam Dec 5, 2024
69a7d44
Merge branch 'main' into serviceViewBug
TackAdam Dec 5, 2024
1877540
fix applications, address comments
Dec 5, 2024
03610bb
Merge branch 'main' into serviceViewBug
TackAdam Dec 5, 2024
04e30f9
fix flaky application test
Dec 6, 2024
b5e4351
make service map snapshop deep
Dec 6, 2024
4781348
add service map focus cypress testing
Dec 6, 2024
7a14d1f
Merge branch 'main' into serviceViewBug
TackAdam Dec 6, 2024
2645c66
Merge branch 'main' into serviceViewBug
TackAdam Dec 6, 2024
977d8d1
Merge branch 'main' into serviceViewBug
TackAdam Dec 6, 2024
9ffec79
add more tests to cover service map load and click
ps48 Dec 7, 2024
5faeeb6
add comments to cypress
ps48 Dec 7, 2024
747b1b6
remove only from cypress tests
ps48 Dec 7, 2024
8b2af4f
update snapshots
ps48 Dec 7, 2024
b2a2f1c
adjust cypress test
Dec 7, 2024
77bc6f4
adjust cypress testw
Dec 7, 2024
3bb65ec
remove only and comment
Dec 8, 2024
87017b1
switch test back
Dec 9, 2024
30bcd25
Merge branch 'main' into serviceViewBug
TackAdam Dec 9, 2024
c430f5c
add dependancy to use effect
Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,243 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { configure, shallow } from 'enzyme';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { mount } from 'enzyme';
import React from 'react';
import { TEST_SERVICE_MAP } from '../../../../../../../test/constants';
import { act } from '@testing-library/react';
import { ServiceMap } from '../service_map';
import { EuiFieldSearch, EuiSelectable } from '@elastic/eui';
import { TEST_SERVICE_MAP } from '../../../../../../../test/constants';
import Graph from 'react-graph-vis';

configure({ adapter: new Adapter() });

// Mock uuid
jest.mock('uuid', () => ({
v4: jest.fn(() => 'static-uuid'),
}));

// Normalize dynamic values in snapshots
expect.addSnapshotSerializer({
test: (val) => typeof val === 'string' && /^[a-f0-9-]{36}$/.test(val),
print: () => '"<dynamic-uuid>"',
});

// Mock crypto.getRandomValues
const crypto = {
getRandomValues: jest.fn((arr) => arr.fill(0)), // Fill with consistent values
};
Object.defineProperty(global, 'crypto', { value: crypto });

const mockContext = ({
canvas: document.createElement('canvas'),
fillRect: jest.fn(),
clearRect: jest.fn(),
ps48 marked this conversation as resolved.
Show resolved Hide resolved
getImageData: jest.fn(() => ({ data: new Uint8ClampedArray() })),
putImageData: jest.fn(),
createImageData: jest.fn(),
setTransform: jest.fn(),
drawImage: jest.fn(),
save: jest.fn(),
fillText: jest.fn(),
restore: jest.fn(),
beginPath: jest.fn(),
moveTo: jest.fn(),
lineTo: jest.fn(),
closePath: jest.fn(),
stroke: jest.fn(),
translate: jest.fn(),
scale: jest.fn(),
rotate: jest.fn(),
arc: jest.fn(),
fill: jest.fn(),
measureText: jest.fn(() => ({ width: 0 })),
transform: jest.fn(),
rect: jest.fn(),
globalAlpha: 1,
globalCompositeOperation: 'source-over',
filter: 'none',
imageSmoothingEnabled: true,
imageSmoothingQuality: 'low',
strokeStyle: '#000',
fillStyle: '#000',
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,0)',
lineWidth: 1,
lineCap: 'butt',
lineJoin: 'miter',
miterLimit: 10,
lineDashOffset: 0,
font: '10px sans-serif',
textAlign: 'start',
textBaseline: 'alphabetic',
direction: 'ltr',
getContextAttributes: jest.fn(() => ({
alpha: true,
desynchronized: false,
colorSpace: 'srgb',
willReadFrequently: false,
})),
} as unknown) as CanvasRenderingContext2D;

jest
.spyOn(HTMLCanvasElement.prototype, 'getContext')
.mockImplementation((contextId) => (contextId === '2d' ? mockContext : null));

jest.mock('react-graph-vis', () => {
const GraphMock = () => <div data-testid="mock-graph">Mock Graph</div>;
return GraphMock;
});

describe('Service map component', () => {
configure({ adapter: new Adapter() });

it('renders service map', async () => {
const setServiceMapIdSelected = jest.fn((e) => {});
const wrapper = shallow(
<ServiceMap
serviceMap={TEST_SERVICE_MAP}
idSelected="latency"
setIdSelected={setServiceMapIdSelected}
page="dashboard"
/>
);
async function setFocusOnService(wrapper: ReturnType<typeof mount>, serviceName: string) {
const searchField = wrapper.find(EuiFieldSearch).first();
expect(searchField.exists()).toBeTruthy();

await act(async () => {
const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
target: { value: '' },
};
searchField.prop('onClick')?.((mockEvent as unknown) as React.MouseEvent<HTMLInputElement>);
});
wrapper.update();

const selectable = wrapper.find(EuiSelectable);
const onChange = selectable.prop('onChange');

if (onChange) {
await act(async () => {
onChange([{ label: serviceName, checked: 'on' }]);
});
wrapper.update();
} else {
throw new Error('onChange handler is undefined on EuiSelectable');
}
expect(wrapper.find(EuiFieldSearch).prop('placeholder')).toBe(serviceName);
}

describe('ServiceMap Component', () => {
const defaultProps = {
serviceMap: TEST_SERVICE_MAP,
idSelected: 'latency' as 'latency' | 'error_rate' | 'throughput',
setIdSelected: jest.fn(),
page: 'dashboard' as
| 'app'
| 'appCreate'
| 'dashboard'
| 'traces'
| 'services'
| 'serviceView'
| 'detailFlyout'
| 'traceView',
mode: 'jaeger',
currService: '',
filters: [],
setFilters: jest.fn(),
addFilter: jest.fn(),
removeFilter: jest.fn(),
isLoading: false,
};

beforeEach(() => {
jest.clearAllMocks();
});

it('renders service map component', () => {
const wrapper = mount(<ServiceMap {...defaultProps} />);
expect(wrapper).toMatchSnapshot();
});

it('renders application composition map title when page is app', () => {
const wrapper = mount(<ServiceMap {...defaultProps} page="app" />);
expect(wrapper.find('PanelTitle').prop('title')).toBe('Application Composition Map');
});

it('renders service map title for other pages', () => {
const wrapper = mount(<ServiceMap {...defaultProps} />);
expect(wrapper.find('PanelTitle').prop('title')).toBe('Service map');
});

describe('Service search and selection', () => {
it('updates placeholder when service is focused', async () => {

Check warning on line 168 in public/components/trace_analytics/components/common/plots/__tests__/service_map.test.tsx

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
const wrapper = mount(<ServiceMap {...defaultProps} />);
await setFocusOnService(wrapper, 'order');
});

it('clears focus with refresh button', async () => {
const wrapper = mount(<ServiceMap {...defaultProps} />);
await setFocusOnService(wrapper, 'order');

// Verify focus is set
expect(wrapper.find(EuiFieldSearch).prop('placeholder')).toBe('order');

// Find and click the refresh button
const refreshButton = wrapper.find('button[data-test-subj="serviceMapRefreshButton"]');
expect(refreshButton.exists()).toBeTruthy();

await act(async () => {
refreshButton.simulate('click');
});
wrapper.update();

// Verify the search field is cleared
const updatedSearchField = wrapper.find(EuiFieldSearch).first();
expect(updatedSearchField.prop('value')).toBe('');
expect(updatedSearchField.prop('placeholder')).not.toBe('order');
});
});

describe('Metric selection', () => {
it('changes selected metric', async () => {
const setIdSelected = jest.fn();
const wrapper = mount(<ServiceMap {...defaultProps} setIdSelected={setIdSelected} />);

const buttonGroup = wrapper.find('EuiButtonGroup');
const onChange = buttonGroup.prop('onChange');

if (onChange) {
await act(async () => {
onChange('error_rate' as any);

Check warning on line 206 in public/components/trace_analytics/components/common/plots/__tests__/service_map.test.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
});
wrapper.update();
} else {
throw new Error('onChange handler is undefined on EuiButtonGroup');
}

expect(setIdSelected).toHaveBeenCalledWith('error_rate');
});
});

describe('Service dependencies', () => {
it('shows related services when focusing on a service', async () => {
const wrapper = mount(<ServiceMap {...defaultProps} />);
await setFocusOnService(wrapper, 'order');

// Verify that the graph exists and has nodes
const graph = wrapper.find(Graph);
expect(graph.exists()).toBeTruthy();

const graphProps = graph.props() as { graph: { nodes: any[] } };

Check warning on line 226 in public/components/trace_analytics/components/common/plots/__tests__/service_map.test.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
expect(graphProps.graph).toBeDefined();
expect(graphProps.graph.nodes.length).toBeGreaterThan(0);
});
});

describe('Loading state', () => {
it('shows loading indicator when isLoading is true', () => {
const wrapper = mount(<ServiceMap {...defaultProps} />);
expect(wrapper.find('.euiLoadingSpinner').exists()).toBeTruthy();
});
});

describe('Empty state', () => {
it('handles empty service map', () => {
const wrapper = mount(<ServiceMap {...defaultProps} serviceMap={{}} />);
expect(wrapper.find('Graph').exists()).toBeFalsy();
});
});
});
Loading
Loading