Skip to content

Commit eaaaa0e

Browse files
google-genai-botcopybara-github
authored andcommitted
ADK changes
PiperOrigin-RevId: 810439442
1 parent 3b9440e commit eaaaa0e

File tree

3 files changed

+276
-18
lines changed

3 files changed

+276
-18
lines changed

src/app/components/event-tab/event-tab.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
@if (isTraceView()) {
2525
<p>Trace</p>
2626
}
27-
@if (traceData()) {
27+
@if (traceData().length > 0) {
2828
<mat-button-toggle-group name="fontStyle" aria-label="Font Style" style="scale: 0.8" [(ngModel)]="view">
2929
<mat-button-toggle value="events">Events</mat-button-toggle>
3030
@if (isTraceEnabledObs | async) {

src/app/components/event-tab/event-tab.component.spec.ts

Lines changed: 274 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,65 +15,323 @@
1515
* limitations under the License.
1616
*/
1717

18+
import {HarnessLoader} from '@angular/cdk/testing';
19+
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
1820
import {ComponentFixture, TestBed} from '@angular/core/testing';
19-
import {MatDialogModule, MatDialogRef} from '@angular/material/dialog';
20-
21+
import {MatButtonToggleHarness} from '@angular/material/button-toggle/testing';
22+
import {MatDialog} from '@angular/material/dialog';
23+
import {MatListHarness} from '@angular/material/list/testing';
24+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
2125

26+
import {Span} from '../../core/models/Trace';
2227
import {FEATURE_FLAG_SERVICE} from '../../core/services/feature-flag.service';
2328
import {MockFeatureFlagService} from '../../core/services/testing/mock-feature-flag.service';
29+
2430
import {EventTabComponent} from './event-tab.component';
31+
import {TraceChartComponent} from './trace-chart/trace-chart.component';
32+
33+
const MOCK_TRACE_DATA: Span[] = [
34+
{
35+
name: 'agent.act',
36+
start_time: 1733084700000000000,
37+
end_time: 1733084760000000000,
38+
span_id: 'span-1',
39+
trace_id: 'trace-1',
40+
attributes: {
41+
'event_id': 1,
42+
'gcp.vertex.agent.invocation_id': '21332-322222',
43+
'gcp.vertex.agent.llm_request':
44+
'{"contents":[{"role":"user","parts":[{"text":"Hello"}]},{"role":"agent","parts":[{"text":"Hi. What can I help you with?"}]},{"role":"user","parts":[{"text":"I need help with my project."}]}]}',
45+
},
46+
},
47+
{
48+
name: 'tool.invoke',
49+
start_time: 1733084705000000000,
50+
end_time: 1733084755000000000,
51+
span_id: 'span-2',
52+
parent_span_id: 'span-1',
53+
trace_id: 'trace-1',
54+
attributes: {
55+
'tool_name': 'project_helper',
56+
},
57+
children: [
58+
{
59+
name: 'sub-tool-1.invoke',
60+
start_time: 1733084710000000000,
61+
end_time: 1733084750000000000,
62+
span_id: 'span-3',
63+
parent_span_id: 'span-2',
64+
trace_id: 'trace-1',
65+
attributes: {
66+
'sub_tool_name': 'sub_project_helper_1',
67+
},
68+
children: [
69+
{
70+
name: 'sub-tool-2.invoke',
71+
start_time: 1733084715000000000,
72+
end_time: 1733084745000000000,
73+
span_id: 'span-4',
74+
parent_span_id: 'span-3',
75+
trace_id: 'trace-1',
76+
attributes: {
77+
'sub_tool_name': 'sub_project_helper_2',
78+
},
79+
children: [
80+
{
81+
name: 'sub-tool-3.invoke',
82+
start_time: 1733084720000000000,
83+
end_time: 1733084740000000000,
84+
span_id: 'span-5',
85+
parent_span_id: 'span-4',
86+
trace_id: 'trace-1',
87+
attributes: {
88+
'sub_tool_name': 'sub_project_helper_3',
89+
},
90+
children: [],
91+
},
92+
],
93+
},
94+
],
95+
},
96+
],
97+
}
98+
] as Span[];
99+
100+
const MOCK_EVENTS_MAP = new Map<string, any>([
101+
['event1', {title: 'Event 1 Title'}],
102+
['event2', {title: 'Event 2 Title'}],
103+
]);
25104

26105
describe('EventTabComponent', () => {
27106
let component: EventTabComponent;
28107
let fixture: ComponentFixture<EventTabComponent>;
29108
let featureFlagService: MockFeatureFlagService;
109+
let loader: HarnessLoader;
110+
let matDialogSpy: jasmine.SpyObj<MatDialog>;
30111
const mockDialogRef = {
31112
close: jasmine.createSpy('close'),
32113
};
33114

34115
beforeEach(async () => {
35116
featureFlagService = new MockFeatureFlagService();
117+
matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
36118

37119
featureFlagService.isTraceEnabledResponse.next(true);
38120

39-
await TestBed.configureTestingModule({
40-
imports: [MatDialogModule, EventTabComponent],
41-
providers: [
42-
{provide: MatDialogRef, useValue: mockDialogRef},
43-
{provide: FEATURE_FLAG_SERVICE, useValue: featureFlagService},
44-
],
45-
}).compileComponents();
121+
await TestBed
122+
.configureTestingModule({
123+
imports: [EventTabComponent, NoopAnimationsModule],
124+
providers: [
125+
{provide: MatDialog, useValue: matDialogSpy},
126+
{provide: FEATURE_FLAG_SERVICE, useValue: featureFlagService},
127+
],
128+
})
129+
.compileComponents();
46130

47131
fixture = TestBed.createComponent(EventTabComponent);
48132
component = fixture.componentInstance;
133+
loader = TestbedHarnessEnvironment.loader(fixture);
134+
matDialogSpy.open.calls.reset();
49135
fixture.detectChanges();
50136
});
51137

52138
it('should create', () => {
53139
expect(component).toBeTruthy();
54140
});
141+
142+
it('should display "No conversations" if eventsMap is empty', () => {
143+
expect(fixture.nativeElement.textContent).toContain('No conversations');
144+
});
145+
146+
describe('with events', () => {
147+
beforeEach(async () => {
148+
fixture.componentRef.setInput('eventsMap', MOCK_EVENTS_MAP);
149+
fixture.detectChanges();
150+
await fixture.whenStable();
151+
});
152+
153+
it('should display events list by default', async () => {
154+
const list = await loader.getHarness(MatListHarness);
155+
const items = await list.getItems();
156+
expect(items.length).toBe(2);
157+
expect(await items[0].getFullText()).toContain('Event 1 Title');
158+
expect(await items[1].getFullText()).toContain('Event 2 Title');
159+
});
160+
161+
it('should emit selectedEvent on event click', async () => {
162+
spyOn(component.selectedEvent, 'emit');
163+
const list = await loader.getHarness(MatListHarness);
164+
const items = await list.getItems();
165+
await (await items[0].host()).click();
166+
expect(component.selectedEvent.emit).toHaveBeenCalledWith('event1');
167+
});
168+
169+
it('should not show toggle if traceData is empty', async () => {
170+
const hasToggleGroup = fixture.nativeElement.querySelector(
171+
'mat-button-toggle-group',
172+
);
173+
expect(hasToggleGroup).toBeNull();
174+
});
175+
});
176+
177+
describe('with trace data', () => {
178+
beforeEach(async () => {
179+
fixture.componentRef.setInput('eventsMap', MOCK_EVENTS_MAP);
180+
fixture.componentRef.setInput('traceData', MOCK_TRACE_DATA);
181+
fixture.detectChanges();
182+
await fixture.whenStable();
183+
});
184+
185+
it('should show toggle buttons', async () => {
186+
const toggles = await loader.getAllHarnesses(MatButtonToggleHarness);
187+
expect(toggles.length).toBe(2);
188+
expect(await toggles[0].getText()).toBe('Events');
189+
expect(await toggles[1].getText()).toBe('Trace');
190+
});
191+
192+
it('should switch to trace view and display traces', async () => {
193+
const traceToggle = await loader.getHarness(
194+
MatButtonToggleHarness.with({text: 'Trace'}),
195+
);
196+
await traceToggle.check();
197+
fixture.detectChanges();
198+
199+
const list = await loader.getHarness(MatListHarness);
200+
const items = await list.getItems();
201+
expect(items.length).toBe(1);
202+
expect(await items[0].getFullText()).toContain('Invocation 21332-322222');
203+
});
204+
205+
it('should open dialog when trace item is clicked', async () => {
206+
const traceToggle = await loader.getHarness(
207+
MatButtonToggleHarness.with({text: 'Trace'}),
208+
);
209+
await traceToggle.check();
210+
fixture.detectChanges();
211+
212+
const list = await loader.getHarness(MatListHarness);
213+
const items = await list.getItems();
214+
await (await items[0].host()).click();
215+
216+
expect(matDialogSpy.open).toHaveBeenCalledWith(TraceChartComponent, {
217+
width: 'auto',
218+
maxWidth: '90vw',
219+
data: {
220+
spans: component.spansByTraceId().get('trace-1'),
221+
invocId: '21332-322222',
222+
},
223+
});
224+
});
225+
226+
it('should display multiple traces if present', async () => {
227+
const MOCK_TRACE_DATA_WITH_MULTIPLE_TRACES: Span[] = [
228+
...MOCK_TRACE_DATA,
229+
{
230+
name: 'agent.act-2',
231+
start_time: 1733084700000000000,
232+
end_time: 1733084760000000000,
233+
span_id: 'span-10',
234+
trace_id: 'trace-2',
235+
attributes: {
236+
'event_id': 10,
237+
'gcp.vertex.agent.invocation_id': 'invoc-2',
238+
'gcp.vertex.agent.llm_request': '{}',
239+
},
240+
},
241+
];
242+
fixture.componentRef.setInput(
243+
'traceData',
244+
MOCK_TRACE_DATA_WITH_MULTIPLE_TRACES,
245+
);
246+
fixture.detectChanges();
247+
await fixture.whenStable();
248+
249+
const traceToggle = await loader.getHarness(
250+
MatButtonToggleHarness.with({text: 'Trace'}),
251+
);
252+
await traceToggle.check();
253+
fixture.detectChanges();
254+
255+
const list = await loader.getHarness(MatListHarness);
256+
const items = await list.getItems();
257+
expect(items.length).toBe(2);
258+
expect(await items[0].getFullText()).toContain('Invocation 21332-322222');
259+
expect(await items[1].getFullText()).toContain('Invocation invoc-2');
260+
});
261+
});
262+
});
263+
264+
describe('EventTabComponent feature disabling', () => {
265+
let component: EventTabComponent;
266+
let fixture: ComponentFixture<EventTabComponent>;
267+
let featureFlagService: MockFeatureFlagService;
268+
let matDialogSpy: jasmine.SpyObj<MatDialog>;
269+
const mockDialogRef = {
270+
close: jasmine.createSpy('close'),
271+
};
272+
273+
beforeEach(async () => {
274+
featureFlagService = new MockFeatureFlagService();
275+
matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
276+
277+
featureFlagService.isTraceEnabledResponse.next(false);
278+
279+
await TestBed
280+
.configureTestingModule({
281+
imports: [EventTabComponent, NoopAnimationsModule],
282+
providers: [
283+
{provide: MatDialog, useValue: matDialogSpy},
284+
{provide: FEATURE_FLAG_SERVICE, useValue: featureFlagService},
285+
],
286+
})
287+
.compileComponents();
288+
289+
fixture = TestBed.createComponent(EventTabComponent);
290+
component = fixture.componentInstance;
291+
fixture.componentRef.setInput('traceData', [
292+
{
293+
trace_id: '1',
294+
span_id: '1',
295+
start_time: 1,
296+
end_time: 2,
297+
name: 'test',
298+
},
299+
]);
300+
fixture.detectChanges();
301+
});
302+
303+
it('should hide the Trace mat-button-toggle', () => {
304+
const traceToggle = fixture.nativeElement.querySelector(
305+
'mat-button-toggle[value="trace"]',
306+
);
307+
expect(traceToggle).toBeNull();
308+
});
55309
});
56310

57311
describe('EventTabComponent feature disabling', () => {
58312
let component: EventTabComponent;
59313
let fixture: ComponentFixture<EventTabComponent>;
60314
let featureFlagService: MockFeatureFlagService;
315+
let matDialogSpy: jasmine.SpyObj<MatDialog>;
61316
const mockDialogRef = {
62317
close: jasmine.createSpy('close'),
63318
};
64319

65320
beforeEach(async () => {
66321
featureFlagService = new MockFeatureFlagService();
322+
matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
67323

68324
featureFlagService.isTraceEnabledResponse.next(false);
69325

70-
await TestBed.configureTestingModule({
71-
imports: [MatDialogModule, EventTabComponent],
72-
providers: [
73-
{provide: MatDialogRef, useValue: mockDialogRef},
74-
{provide: FEATURE_FLAG_SERVICE, useValue: featureFlagService},
75-
],
76-
}).compileComponents();
326+
await TestBed
327+
.configureTestingModule({
328+
imports: [EventTabComponent, NoopAnimationsModule],
329+
providers: [
330+
{provide: MatDialog, useValue: matDialogSpy},
331+
{provide: FEATURE_FLAG_SERVICE, useValue: featureFlagService},
332+
],
333+
})
334+
.compileComponents();
77335

78336
fixture = TestBed.createComponent(EventTabComponent);
79337
component = fixture.componentInstance;

src/app/components/event-tab/event-tab.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class EventTabComponent {
5252
readonly view = signal<string>('events');
5353
readonly isTraceView = computed(() => this.view() === 'trace');
5454
readonly spansByTraceId = computed(() => {
55-
if (!this.traceData || this.traceData.length == 0) {
55+
if (!this.traceData() || this.traceData().length == 0) {
5656
return new Map<string, Span[]>();
5757
}
5858
return this.traceData().reduce((map, span) => {

0 commit comments

Comments
 (0)