Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions packages/jaeger-ui/src/components/DependencyGraph/DAG.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ShallowRenderer from 'react-test-renderer/shallow';
import { render, screen } from '@testing-library/react';
import { LayoutManager } from '@jaegertracing/plexus';
import DAG, { renderNode } from './DAG';
import { DAG_MAX_NUM_SERVICES } from '../../constants';

// mock canvas API (we don't care about canvas results)

Expand Down Expand Up @@ -186,6 +187,22 @@ describe('<DAG>', () => {
expect(element.props.children.props.vertices.length).toBe(0);
expect(element.props.children.props.edges.length).toBe(0);
});

it('shows error message when too many services to render', () => {
const serviceCalls = Array.from({ length: DAG_MAX_NUM_SERVICES + 1 }, (_, i) => ({
parent: `service-${i}`,
child: `service-${i + 1}`,
callCount: 1,
}));

renderer.render(<DAG serviceCalls={serviceCalls} selectedLayout="dot" />);
const element = renderer.getRenderOutput();

expect(element.type).toBe('div');
expect(element.props.className).toBe('DAG');
expect(element.props.children.type).toBe('div');
expect(element.props.children.props.className).toBe('DAG--error');
});
});

describe('renderNode', () => {
Expand Down
13 changes: 13 additions & 0 deletions packages/jaeger-ui/src/components/DependencyGraph/DAG.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TEdge, TVertex } from '@jaegertracing/plexus/lib/types';
import { TLayoutOptions } from '@jaegertracing/plexus/lib/LayoutManager/types';

import './dag.css';
import { DAG_MAX_NUM_SERVICES } from '../../constants';

type TServiceCall = {
parent: string;
Expand Down Expand Up @@ -134,6 +135,8 @@ export default function DAG({ serviceCalls = [], selectedLayout, selectedDepth,
[serviceCalls, selectedService, selectedDepth]
);

const forceFocalServiceSelection = data.nodes.length > DAG_MAX_NUM_SERVICES;

const layoutManager = React.useMemo(() => {
const config: TLayoutOptions =
selectedLayout === 'dot'
Expand Down Expand Up @@ -164,6 +167,16 @@ export default function DAG({ serviceCalls = [], selectedLayout, selectedDepth,
};
}, [layoutManager]);

if (forceFocalServiceSelection) {
return (
<div className="DAG">
<div className="DAG--error">
{`Too many services to render (${data.nodes.length}). Hierarchical layout is disabled. For the Force-Directed layout, please select a focal service and the depth.`}
</div>
</div>
);
}

return (
<div className="DAG">
<Digraph<TVertex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@

.dag-options {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: flex-start;
justify-content: flex-start;
}

.selector-group {
Expand Down Expand Up @@ -100,3 +102,9 @@
.reset-icon:hover {
color: #000;
}

.data-selector {
margin-left: auto;
align-self: flex-end;
min-width: 200px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import React from 'react';
import { render, screen, fireEvent, within } from '@testing-library/react';
import '@testing-library/jest-dom';
import DAGOptions from './DAGOptions';
import * as constants from '../../utils/constants';

const mockDependencies = [
{ parent: 'service-1', child: 'service-2', callCount: 1000 },
Expand All @@ -32,6 +33,9 @@ const defaultProps = {
selectedDepth: 5,
onReset: jest.fn(),
isHierarchicalDisabled: false,
selectedSampleDatasetType: null,
onSampleDatasetTypeChange: jest.fn(),
sampleDatasetTypes: ['Small Graph', 'Large Graph'],
};

describe('DAGOptions', () => {
Expand Down Expand Up @@ -276,4 +280,52 @@ describe('DAGOptions', () => {

expect(screen.getByDisplayValue('0')).toBeInTheDocument();
});

it('renders sample dataset type selector in development mode', () => {
jest.spyOn(constants, 'getAppEnvironment').mockReturnValue('development');
render(<DAGOptions {...defaultProps} />);

expect(screen.getByTestId('sample-dataset-type-select')).toBeInTheDocument();
});

it('does not render sample dataset type selector in production mode', () => {
jest.spyOn(constants, 'getAppEnvironment').mockReturnValue('production');
render(<DAGOptions {...defaultProps} />);

expect(screen.queryByTestId('sample-dataset-type-select')).not.toBeInTheDocument();
});

it('handles sample dataset type selection', () => {
jest.spyOn(constants, 'getAppEnvironment').mockReturnValue('development');
render(<DAGOptions {...defaultProps} />);
const sampleDatasetTypeSelect = screen.getByTestId('sample-dataset-type-select');

const selectElement = within(sampleDatasetTypeSelect).getByRole('combobox');
fireEvent.mouseDown(selectElement);

const sampleDatasetTypeOption = screen.getByTestId('sample-dataset-type-option-Small Graph');
expect(sampleDatasetTypeOption).toHaveClass('ant-select-item ant-select-item-option');
const optionContent = sampleDatasetTypeOption.querySelector('.ant-select-item-option-content');
expect(optionContent).toHaveTextContent('Small Graph');

fireEvent.click(sampleDatasetTypeOption);
expect(defaultProps.onSampleDatasetTypeChange).toHaveBeenCalledWith(
'Small Graph',
expect.objectContaining({
value: 'Small Graph',
children: 'Small Graph',
'data-testid': 'sample-dataset-type-option-Small Graph',
})
);
});

it('maintains selected sample dataset type value correctly', () => {
jest.spyOn(constants, 'getAppEnvironment').mockReturnValue('development');
render(<DAGOptions {...defaultProps} selectedSampleDatasetType="Small Graph" />);

const sampleDatasetTypeSelect = screen.getByTestId('sample-dataset-type-select');
const sampleDatasetTypeValue = within(sampleDatasetTypeSelect).getByRole('combobox');
expect(sampleDatasetTypeValue).toHaveAttribute('aria-expanded', 'false');
expect(within(sampleDatasetTypeSelect).getByText('Small Graph')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Select, InputNumber, Popover } from 'antd';
import { IoHelp, IoRefresh } from 'react-icons/io5';
import SearchableSelect from '../common/SearchableSelect';
import './DAGOptions.css';
import { getAppEnvironment } from '../../utils/constants';

const { Option } = Select;

Expand All @@ -36,6 +37,9 @@ interface IDAGOptionsProps {
selectedDepth?: number;
onReset: () => void;
isHierarchicalDisabled: boolean;
selectedSampleDatasetType: string;
onSampleDatasetTypeChange: (type: string) => void;
sampleDatasetTypes: string[];
}

const LAYOUT_OPTIONS = [
Expand All @@ -53,6 +57,9 @@ const DAGOptions: React.FC<IDAGOptionsProps> = ({
selectedDepth = 0,
onReset,
isHierarchicalDisabled,
selectedSampleDatasetType,
onSampleDatasetTypeChange,
sampleDatasetTypes,
}) => {
const services = React.useMemo(() => {
const uniqueServices = new Set<string>();
Expand Down Expand Up @@ -175,6 +182,26 @@ const DAGOptions: React.FC<IDAGOptionsProps> = ({
/>
</div>
</div>
{getAppEnvironment() === 'development' && (
<div className="selector-container data-selector">
<div className="selector-label-container">
<span className="selector-label">Sample Dataset</span>
</div>
<SearchableSelect
value={selectedSampleDatasetType}
onChange={onSampleDatasetTypeChange}
placeholder="Select Sample Dataset Type"
className="select-sample-dataset-type-input"
data-testid="sample-dataset-type-select"
>
{sampleDatasetTypes.map(type => (
<Option key={type} value={type} data-testid={`sample-dataset-type-option-${type}`}>
{type}
</Option>
))}
</SearchableSelect>
</div>
)}
</div>
);
};
Expand Down
4 changes: 4 additions & 0 deletions packages/jaeger-ui/src/components/DependencyGraph/dag.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@
color: #7f7f7f;
background-color: #fafafa;
}

.DAG--error {
margin: 10px;
}
63 changes: 60 additions & 3 deletions packages/jaeger-ui/src/components/DependencyGraph/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,36 @@

import './index.css';
import withRouteProps from '../../utils/withRouteProps';
import { getAppEnvironment } from '../../utils/constants';

// export for tests
export const GRAPH_TYPES = {
DAG: { type: 'DAG', name: 'DAG' },
};
export const sampleDatasetTypes = ['Backend', 'Small Graph', 'Large Graph'];

const dagMaxNumServices = getConfigValue('dependencies.dagMaxNumServices') || FALLBACK_DAG_MAX_NUM_SERVICES;

const createSampleDataManager = () => {
let sampleDAGDataset = [];
return {
getSampleData: () => sampleDAGDataset,
loadSampleData: async type => {
let module = {};
const isDev = getAppEnvironment() === 'development';
if (isDev && type === 'Small Graph') {
module = await import('./sample_data/small.json');
} else if (isDev && type === 'Large Graph') {
module = await import('./sample_data/large.json');

Check warning on line 53 in packages/jaeger-ui/src/components/DependencyGraph/index.jsx

View check run for this annotation

Codecov / codecov/patch

packages/jaeger-ui/src/components/DependencyGraph/index.jsx#L53

Added line #L53 was not covered by tests
}
sampleDAGDataset = module?.default ?? [];
return sampleDAGDataset;
},
};
};

const { getSampleData, loadSampleData } = createSampleDataManager();

// export for tests
export class DependencyGraphPageImpl extends Component {
static propTypes = {
Expand Down Expand Up @@ -65,16 +87,33 @@
super(props);
this.state = {
selectedService: null,
selectedLayout: props.dependencies.length > dagMaxNumServices ? 'sfdp' : 'dot',
selectedLayout: null,
selectedDepth: 5,
debouncedDepth: 5,
selectedSampleDatasetType: 'Backend',
};
}

componentDidMount() {
this.props.fetchDependencies();
this.updateLayout();
}

componentDidUpdate(prevProps) {
if (prevProps.dependencies !== this.props.dependencies) {
this.updateLayout();
}
}

updateLayout = () => {
const { dependencies } = this.props;
const dataset = getSampleData().length > 0 ? getSampleData() : dependencies;
const selectedLayout = dataset.length > dagMaxNumServices ? 'sfdp' : 'dot';
if (selectedLayout !== this.state.selectedLayout) {
this.setState({ selectedLayout, selectedService: null, selectedDepth: 5, debouncedDepth: 5 });
}
};

handleServiceSelect = service => {
this.setState({ selectedService: service });
};
Expand All @@ -94,6 +133,13 @@
}
};

handleSampleDatasetTypeChange = selectedSampleDatasetType => {
this.setState({ selectedSampleDatasetType });
loadSampleData(selectedSampleDatasetType).then(() => {
this.props.fetchDependencies();
});
};

handleReset = () => {
this.setState({
selectedService: null,
Expand All @@ -104,7 +150,8 @@

render() {
const { nodes, links, error, loading, dependencies } = this.props;
const { selectedService, selectedLayout, selectedDepth, debouncedDepth } = this.state;
const { selectedService, selectedLayout, selectedDepth, debouncedDepth, selectedSampleDatasetType } =
this.state;

if (loading) {
return <LoadingIndicator className="u-mt-vast" centered />;
Expand Down Expand Up @@ -144,6 +191,9 @@
selectedDepth={selectedDepth}
onReset={this.handleReset}
isHierarchicalDisabled={isHierarchicalDisabled}
selectedSampleDatasetType={selectedSampleDatasetType}
onSampleDatasetTypeChange={this.handleSampleDatasetTypeChange}
sampleDatasetTypes={sampleDatasetTypes}
/>
</div>
<div className="DependencyGraph--graphWrapper">
Expand Down Expand Up @@ -212,7 +262,14 @@
links = formatted.links;
nodes = formatted.nodes;
}
return { loading, error, nodes, links, dependencies };
const dataset = getSampleData().length > 0 ? getSampleData() : dependencies;
return {
loading,
error,
nodes,
links,
dependencies: dataset,
};
}

// export for tests
Expand Down
Loading
Loading