Skip to content

Commit 90886fd

Browse files
stevegoltonGerrit Code Review
authored andcommitted
Merge "ui: Add track selection" into main
2 parents c5bb75a + 6f09422 commit 90886fd

File tree

6 files changed

+128
-28
lines changed

6 files changed

+128
-28
lines changed

ui/src/core/selection_manager.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ export class SelectionManagerImpl implements SelectionManager {
9292
);
9393
}
9494

95+
selectTrack(trackUri: string, opts?: SelectionOpts) {
96+
this.setSelection({kind: 'track', trackUri}, opts);
97+
}
98+
9599
selectNote(args: {id: string}, opts?: SelectionOpts) {
96100
this.setSelection(
97101
{
@@ -230,8 +234,9 @@ export class SelectionManagerImpl implements SelectionManager {
230234
}
231235
switch (source) {
232236
case 'track':
233-
this.scrollHelper.scrollTo({
234-
track: {uri: trackUri, expandGroup: true},
237+
this.selectTrack(trackUri, {
238+
clearSearch: false,
239+
scrollToSelection: true,
235240
});
236241
break;
237242
case 'cpu':
@@ -262,6 +267,7 @@ export class SelectionManagerImpl implements SelectionManager {
262267
const uri = (() => {
263268
switch (this.selection.kind) {
264269
case 'track_event':
270+
case 'track':
265271
return this.selection.trackUri;
266272
// TODO(stevegolton): Handle scrolling to area and note selections.
267273
default:

ui/src/core_plugins/track_utils/index.ts

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414

1515
import {OmniboxMode} from '../../core/omnibox_manager';
1616
import {Trace} from '../../public/trace';
17-
import {PromptOption} from '../../public/omnibox';
1817
import {PerfettoPlugin, PluginDescriptor} from '../../public/plugin';
1918
import {AppImpl} from '../../core/app_impl';
2019
import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
20+
import {exists} from '../../base/utils';
21+
import {TrackNode} from '../../public/workspace';
2122

2223
class TrackUtilsPlugin implements PerfettoPlugin {
2324
async onTraceLoad(ctx: Trace): Promise<void> {
@@ -37,13 +38,22 @@ class TrackUtilsPlugin implements PerfettoPlugin {
3738

3839
ctx.commands.registerCommand({
3940
// Selects & reveals the first track on the timeline with a given URI.
40-
id: 'perfetto.FindTrack',
41-
name: 'Find track by URI',
41+
id: 'perfetto.FindTrackByName',
42+
name: 'Find track by name',
4243
callback: async () => {
43-
const tracks = ctx.tracks.getAllTracks();
44-
const options = tracks.map(({uri}): PromptOption => {
45-
return {key: uri, displayName: uri};
46-
});
44+
const tracks = ctx.workspace.flatTracks;
45+
const options = tracks
46+
.map((node) => (exists(node.uri) ? {uri: node.uri, node} : undefined))
47+
.filter((pair) => pair !== undefined)
48+
.map(({uri, node}) => {
49+
let parent = node.parent;
50+
let fullPath = [node.title];
51+
while (parent && parent instanceof TrackNode) {
52+
fullPath = [parent.title, ...fullPath];
53+
parent = parent.parent;
54+
}
55+
return {key: uri, displayName: fullPath.join(' \u2023 ')};
56+
});
4757

4858
// Sort tracks in a natural sort order
4959
const collator = new Intl.Collator('en', {
@@ -59,12 +69,38 @@ class TrackUtilsPlugin implements PerfettoPlugin {
5969
sortedOptions,
6070
);
6171
if (selectedUri === undefined) return; // Prompt cancelled.
62-
ctx.scrollTo({track: {uri: selectedUri, expandGroup: true}});
63-
ctx.selection.selectArea({
64-
start: ctx.traceInfo.start,
65-
end: ctx.traceInfo.end,
66-
trackUris: [selectedUri],
72+
ctx.selection.selectTrack(selectedUri, {scrollToSelection: true});
73+
},
74+
});
75+
76+
ctx.commands.registerCommand({
77+
// Selects & reveals the first track on the timeline with a given URI.
78+
id: 'perfetto.FindTrackByUri',
79+
name: 'Find track by URI',
80+
callback: async () => {
81+
const tracks = ctx.workspace.flatTracks;
82+
const options = tracks
83+
.map((track) => track.uri)
84+
.filter((uri) => uri !== undefined)
85+
.map((uri) => {
86+
return {key: uri, displayName: uri};
87+
});
88+
89+
// Sort tracks in a natural sort order
90+
const collator = new Intl.Collator('en', {
91+
numeric: true,
92+
sensitivity: 'base',
93+
});
94+
const sortedOptions = options.sort((a, b) => {
95+
return collator.compare(a.displayName, b.displayName);
6796
});
97+
98+
const selectedUri = await ctx.omnibox.prompt(
99+
'Choose a track...',
100+
sortedOptions,
101+
);
102+
if (selectedUri === undefined) return; // Prompt cancelled.
103+
ctx.selection.selectTrack(selectedUri, {scrollToSelection: true});
68104
},
69105
});
70106
}

ui/src/frontend/tab_panel.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import {TraceAttrs} from '../public/trace';
2727
import {Monitor} from '../base/monitor';
2828
import {AsyncLimiter} from '../base/async_limiter';
2929
import {TrackEventDetailsPanel} from '../public/details_panel';
30+
import {DetailsShell} from '../widgets/details_shell';
31+
import {GridLayout, GridLayoutColumn} from '../widgets/grid_layout';
32+
import {Section} from '../widgets/section';
33+
import {Tree, TreeNode} from '../widgets/tree';
3034

3135
interface TabWithContent extends Tab {
3236
content: m.Children;
@@ -194,6 +198,13 @@ export class TabPanel implements m.ClassComponent<TabPanelAttrs> {
194198
};
195199
}
196200

201+
if (currentSelection.kind === 'track') {
202+
return {
203+
isLoading: false,
204+
content: this.renderTrackDetailsPanel(currentSelection.trackUri),
205+
};
206+
}
207+
197208
// If there is a details panel present, show this
198209
const dpRenderable = this.trackEventDetailsPanel;
199210
if (dpRenderable) {
@@ -230,6 +241,42 @@ export class TabPanel implements m.ClassComponent<TabPanelAttrs> {
230241
};
231242
}
232243
}
244+
245+
private renderTrackDetailsPanel(trackUri: string) {
246+
const track = globals.trackManager.getTrack(trackUri);
247+
if (track) {
248+
return m(
249+
DetailsShell,
250+
{title: 'Track', description: track.title},
251+
m(
252+
GridLayout,
253+
m(
254+
GridLayoutColumn,
255+
m(
256+
Section,
257+
{title: 'Details'},
258+
m(
259+
Tree,
260+
m(TreeNode, {left: 'Name', right: track.title}),
261+
m(TreeNode, {left: 'URI', right: track.uri}),
262+
m(TreeNode, {left: 'Plugin ID', right: track.pluginId}),
263+
m(
264+
TreeNode,
265+
{left: 'Tags'},
266+
track.tags &&
267+
Object.entries(track.tags).map(([key, value]) => {
268+
return m(TreeNode, {left: key, right: value?.toString()});
269+
}),
270+
),
271+
),
272+
),
273+
),
274+
),
275+
);
276+
} else {
277+
return undefined; // TODO show something sensible here
278+
}
279+
}
233280
}
234281

235282
const FADE_TIME_MS = 50;

ui/src/frontend/track_panel.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,13 @@ function isHighlighted(node: TrackNode) {
263263
return true;
264264
}
265265
}
266+
267+
if (globals.selectionManager.selection.kind === 'track') {
268+
if (globals.selectionManager.selection.trackUri === node.uri) {
269+
return true;
270+
}
271+
}
272+
266273
return false;
267274
}
268275

@@ -421,6 +428,10 @@ function renderTrackDetailsButton(
421428
}),
422429
m(TreeNode, {left: 'Path', right: fullPath}),
423430
m(TreeNode, {left: 'Title', right: node.title}),
431+
m(TreeNode, {
432+
left: 'Workspace',
433+
right: node.workspace?.title ?? '[no workspace]',
434+
}),
424435
td && m(TreeNode, {left: 'Plugin ID', right: td.pluginId}),
425436
td &&
426437
m(

ui/src/frontend/ui_main.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import {AppImpl} from '../core/app_impl';
5050
import {NotesEditorTab} from './notes_panel';
5151
import {NotesListEditor} from './notes_list_editor';
5252
import {getTimeSpanOfSelectionOrVisibleWindow} from '../public/utils';
53-
import {scrollTo} from '../public/scroll_helper';
5453

5554
const OMNIBOX_INPUT_REF = 'omnibox';
5655

@@ -377,19 +376,6 @@ export class UiMainPerTrace implements m.ClassComponent {
377376
},
378377
defaultHotkey: 'Mod+A',
379378
},
380-
{
381-
id: 'perfetto.ScrollToTrack',
382-
name: 'Scroll to track',
383-
callback: async () => {
384-
const opts = trace.tracks
385-
.getAllTracks()
386-
.map((td) => ({key: td.uri, displayName: td.uri}));
387-
const result = await trace.omnibox.prompt('Choose a track', opts);
388-
if (result) {
389-
scrollTo({track: {uri: result, expandGroup: true}});
390-
}
391-
},
392-
},
393379
];
394380

395381
// Register each command with the command manager

ui/src/public/selection.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ export interface SelectionManager {
3737
opts?: SelectionOpts,
3838
): void;
3939

40+
/**
41+
* Select a track.
42+
*
43+
* @param trackUri - The URI for the track to select.
44+
* @param opts - Additional options.
45+
*/
46+
selectTrack(trackUri: string, opts?: SelectionOpts): void;
47+
4048
/**
4149
* Select a track event via a sql table name + id.
4250
*
@@ -81,6 +89,7 @@ export interface AreaSelectionAggregator {
8189

8290
export type Selection =
8391
| TrackEventSelection
92+
| TrackSelection
8493
| AreaSelection
8594
| NoteSelection
8695
| UnionSelection
@@ -99,6 +108,11 @@ export interface TrackEventSelection extends TrackEventDetails {
99108
readonly eventId: number;
100109
}
101110

111+
export interface TrackSelection {
112+
readonly kind: 'track';
113+
readonly trackUri: string;
114+
}
115+
102116
export interface TrackEventDetails {
103117
// ts and dur are required by the core, and must be provided.
104118
readonly ts: time;

0 commit comments

Comments
 (0)