Skip to content

Commit 8efea66

Browse files
committed
fix: restore multi select options functionality
1 parent bdd78b1 commit 8efea66

File tree

5 files changed

+122
-89
lines changed

5 files changed

+122
-89
lines changed

src/2024/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type TaskSpanKind =
99
| 'render'
1010
| 'asset'
1111
| 'iframe'
12+
| 'resource-ember'
1213
export type PerformanceEntryLike = Omit<PerformanceEntry, 'toJSON'>
1314

1415
export interface SpanMetadata<Kind extends SpanKind> {

src/v2/visualizer/components/OperationVisualization.tsx

Lines changed: 40 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,21 @@ import type {
3030
TaskDataEmbeddedInOperation,
3131
TaskSpanKind,
3232
} from '../../../2024/types'
33+
import {
34+
type FilterOption,
35+
BAR_FILL_COLOR,
36+
COLLAPSE_ASSET_SPANS_TEXT,
37+
COLLAPSE_EMBER_RESOURCE_SPANS,
38+
COLLAPSE_IFRAME_SPANS,
39+
COLLAPSE_RENDER_SPANS_TEXT,
40+
FILTER_OPTIONS,
41+
MEASURES_TEXT,
42+
RESOURCES_TEXT,
43+
} from '../constants'
3344
import { MappedOperation } from '../mapTicketActivationData'
3445

3546
const rootOperation = TicketData
3647

37-
const BAR_FILL_COLOR: Record<TaskSpanKind | 'resource-ember', string> = {
38-
render: '#ff7f0e',
39-
measure: '#2ca02c',
40-
resource: '#1f77b4',
41-
'resource-ember': '#17becf',
42-
longtask: '#d62728',
43-
mark: '#9467bd',
44-
asset: '#8c564b',
45-
iframe: '#e377c2',
46-
element: '#7f7f7f',
47-
action: '#bcbd22',
48-
49-
error: '#ff9896',
50-
vital: '#ffbb78',
51-
'first-input': '#aec7e8',
52-
'largest-contentful-paint': '#98df8a',
53-
'layout-shift': '#ff9896',
54-
'visibility-state': '#ff9896',
55-
event: '#ff9896',
56-
navigation: '#ff9896',
57-
paint: '#ff9896',
58-
taskattribution: '#ff9896',
59-
}
60-
6148
const DEFAULT_MARGIN = { top: 50, left: 200, right: 120, bottom: 30 }
6249

6350
export interface TTLineProps {
@@ -146,30 +133,28 @@ const TTLine: React.FC<TTLineProps> = ({
146133
)
147134
}
148135

149-
const RESOURCES_TEXT = 'Show Resources'
150-
const MEASURES_TEXT = 'Show Measures'
151-
const COLLAPSE_RENDER_SPANS_TEXT = 'Collapse Render Spans'
152-
const COLLAPSE_ASSET_SPANS_TEXT = 'Collapse Asset Spans'
153-
const COLLAPSE_EMBER_RESOURCE_SPANS = 'Collapse Ember Resource Spans'
154-
const COLLAPSE_IFRAME_SPANS = 'Collapse iframe Spans'
155-
156-
type FilterOption =
157-
| typeof RESOURCES_TEXT
158-
| typeof MEASURES_TEXT
159-
| typeof COLLAPSE_RENDER_SPANS_TEXT
160-
| typeof COLLAPSE_ASSET_SPANS_TEXT
161-
| typeof COLLAPSE_EMBER_RESOURCE_SPANS
162-
| typeof COLLAPSE_IFRAME_SPANS
163-
164-
const FILTER_OPTIONS: FilterOption[] = [
165-
RESOURCES_TEXT,
166-
MEASURES_TEXT,
167-
COLLAPSE_RENDER_SPANS_TEXT,
168-
COLLAPSE_ASSET_SPANS_TEXT,
169-
COLLAPSE_EMBER_RESOURCE_SPANS,
170-
COLLAPSE_IFRAME_SPANS,
171-
]
172-
136+
function handleOption({
137+
selectionValue,
138+
setter,
139+
type,
140+
text,
141+
}: {
142+
selectionValue: OptionValue[]
143+
setter: React.Dispatch<React.SetStateAction<Record<string, boolean>>>
144+
type: string
145+
text: string
146+
}) {
147+
if (selectionValue?.includes(text)) {
148+
setter((prev) => ({ ...prev, [text]: true }))
149+
} else if (
150+
!selectionValue?.includes(text) &&
151+
(type === 'input:keyDown:Enter' ||
152+
type === 'option:click' ||
153+
type === 'fn:setSelectionValue')
154+
) {
155+
setter((prev) => ({ ...prev, [text]: false }))
156+
}
157+
}
173158
export interface MultiSelectProps {
174159
setState: React.Dispatch<React.SetStateAction<Record<FilterOption, boolean>>>
175160
state: Record<string, boolean>
@@ -273,29 +258,6 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ state, setState }) => {
273258
)
274259
}
275260

276-
function handleOption({
277-
selectionValue,
278-
setter,
279-
type,
280-
text,
281-
}: {
282-
selectionValue: OptionValue[]
283-
setter: React.Dispatch<React.SetStateAction<Record<string, boolean>>>
284-
type: string
285-
text: string
286-
}) {
287-
if (selectionValue?.includes(text)) {
288-
setter((prev) => ({ ...prev, [text]: true }))
289-
} else if (
290-
!selectionValue?.includes(text) &&
291-
(type === 'input:keyDown:Enter' ||
292-
type === 'option:click' ||
293-
type === 'fn:setSelectionValue')
294-
) {
295-
setter((prev) => ({ ...prev, [text]: false }))
296-
}
297-
}
298-
299261
function LegendDemo({
300262
title,
301263
children,
@@ -332,21 +294,19 @@ function LegendDemo({
332294
export interface OperationVisualizationProps {
333295
width: number
334296
operation: MappedOperation
297+
setDisplayOptions: React.Dispatch<
298+
React.SetStateAction<Record<FilterOption, boolean>>
299+
>
300+
displayOptions: Record<FilterOption, boolean>
335301
margin?: { top: number; right: number; bottom: number; left: number }
336302
}
337303
const OperationVisualization: React.FC<OperationVisualizationProps> = ({
338304
width,
339305
operation,
306+
displayOptions,
307+
setDisplayOptions,
340308
margin = DEFAULT_MARGIN,
341309
}) => {
342-
const [state, setState] = useState({
343-
[RESOURCES_TEXT]: true,
344-
[MEASURES_TEXT]: true,
345-
[COLLAPSE_RENDER_SPANS_TEXT]: true,
346-
[COLLAPSE_ASSET_SPANS_TEXT]: true,
347-
[COLLAPSE_EMBER_RESOURCE_SPANS]: false,
348-
[COLLAPSE_IFRAME_SPANS]: false,
349-
})
350310
const {
351311
ttrData,
352312
ttiData,
@@ -533,7 +493,7 @@ const OperationVisualization: React.FC<OperationVisualizationProps> = ({
533493
height: `${footerHeight}px`,
534494
}}
535495
>
536-
<MultiSelect setState={setState} state={state} />
496+
<MultiSelect setState={setDisplayOptions} state={displayOptions} />
537497
<LegendDemo title="Legend">
538498
<LegendOrdinal
539499
scale={colorScale}

src/v2/visualizer/constants.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { TaskSpanKind } from '../../2024/types'
2+
3+
export const RESOURCES_TEXT = 'Show Resources'
4+
export const MEASURES_TEXT = 'Show Measures'
5+
export const COLLAPSE_RENDER_SPANS_TEXT = 'Collapse Render Spans'
6+
export const COLLAPSE_ASSET_SPANS_TEXT = 'Collapse Asset Spans'
7+
export const COLLAPSE_EMBER_RESOURCE_SPANS = 'Collapse Ember Resource Spans'
8+
export const COLLAPSE_IFRAME_SPANS = 'Collapse iframe Spans'
9+
10+
export type FilterOption =
11+
| typeof RESOURCES_TEXT
12+
| typeof MEASURES_TEXT
13+
| typeof COLLAPSE_RENDER_SPANS_TEXT
14+
| typeof COLLAPSE_ASSET_SPANS_TEXT
15+
| typeof COLLAPSE_EMBER_RESOURCE_SPANS
16+
| typeof COLLAPSE_IFRAME_SPANS
17+
18+
export const FILTER_OPTIONS: FilterOption[] = [
19+
RESOURCES_TEXT,
20+
MEASURES_TEXT,
21+
COLLAPSE_RENDER_SPANS_TEXT,
22+
COLLAPSE_ASSET_SPANS_TEXT,
23+
COLLAPSE_EMBER_RESOURCE_SPANS,
24+
COLLAPSE_IFRAME_SPANS,
25+
]
26+
27+
export const BAR_FILL_COLOR: Record<TaskSpanKind | 'resource-ember', string> = {
28+
render: '#ff7f0e',
29+
measure: '#2ca02c',
30+
resource: '#1f77b4',
31+
'resource-ember': '#17becf',
32+
longtask: '#d62728',
33+
mark: '#9467bd',
34+
asset: '#8c564b',
35+
iframe: '#e377c2',
36+
element: '#7f7f7f',
37+
action: '#bcbd22',
38+
39+
error: '#ff9896',
40+
vital: '#ffbb78',
41+
'first-input': '#aec7e8',
42+
'largest-contentful-paint': '#98df8a',
43+
'layout-shift': '#ff9896',
44+
'visibility-state': '#ff9896',
45+
event: '#ff9896',
46+
navigation: '#ff9896',
47+
paint: '#ff9896',
48+
taskattribution: '#ff9896',
49+
}

src/v2/visualizer/index.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,39 @@ import { Operation } from '../../2024/types'
33
import { DropTarget } from './components/DropTarget'
44
import FileUploadButton from './components/FileUploadButton'
55
import OperationVisualization from './components/OperationVisualization'
6+
import {
7+
COLLAPSE_ASSET_SPANS_TEXT,
8+
COLLAPSE_EMBER_RESOURCE_SPANS,
9+
COLLAPSE_IFRAME_SPANS,
10+
COLLAPSE_RENDER_SPANS_TEXT,
11+
MEASURES_TEXT,
12+
RESOURCES_TEXT,
13+
} from './constants'
614
import { mapTicketActivationData } from './mapTicketActivationData'
715

816
export interface OperationVisualizerProps {
917
width: number
1018
margin?: { top: number; right: number; bottom: number; left: number }
1119
}
20+
1221
const OperationVisualizer = ({ width, margin }: OperationVisualizerProps) => {
13-
const [fileContent, setFileContent] = useState<Operation | null>(null)
22+
const [displayOptions, setDisplayOptions] = useState({
23+
[RESOURCES_TEXT]: true,
24+
[MEASURES_TEXT]: true,
25+
[COLLAPSE_RENDER_SPANS_TEXT]: true,
26+
[COLLAPSE_ASSET_SPANS_TEXT]: true,
27+
[COLLAPSE_EMBER_RESOURCE_SPANS]: false,
28+
[COLLAPSE_IFRAME_SPANS]: false,
29+
})
1430

31+
const [fileContent, setFileContent] = useState<Operation | null>(null)
1532
const readFile = (file: File | undefined) => {
1633
if (file && file.type === 'application/json') {
1734
const reader = new FileReader()
1835
reader.addEventListener('load', (e) => {
1936
const result = e.target?.result
2037
if (result && typeof result === 'string') {
21-
// should validate the file
38+
// should validate the file?
2239
setFileContent(JSON.parse(result) as Operation)
2340
}
2441
})
@@ -40,9 +57,15 @@ const OperationVisualizer = ({ width, margin }: OperationVisualizerProps) => {
4057
const mappedFileContent = useMemo(() => {
4158
if (!fileContent) return null
4259

43-
// TODO: should have option state for collapsing spans
44-
return mapTicketActivationData(fileContent)
45-
}, [fileContent])
60+
return mapTicketActivationData(fileContent, {
61+
collapseRenders: displayOptions[COLLAPSE_RENDER_SPANS_TEXT],
62+
collapseAssets: displayOptions[COLLAPSE_ASSET_SPANS_TEXT],
63+
collapseEmberResources: displayOptions[COLLAPSE_EMBER_RESOURCE_SPANS],
64+
collapseIframes: displayOptions[COLLAPSE_IFRAME_SPANS],
65+
displayResources: displayOptions[RESOURCES_TEXT],
66+
displayMeasures: displayOptions[MEASURES_TEXT],
67+
})
68+
}, [fileContent, displayOptions])
4669

4770
if (!fileContent) {
4871
return (
@@ -56,15 +79,17 @@ const OperationVisualizer = ({ width, margin }: OperationVisualizerProps) => {
5679
)
5780
}
5881

59-
// If we failed validation or the mapping returned a null for some reason
60-
if (!mappedFileContent) return <div>'Some error state'</div>
82+
// If we failed validation or the mapping returned a null for some reason. Alternatively could wrap the whole thing in an ErrorBoundary?
83+
if (!mappedFileContent) return <div>Some error state</div>
6184

6285
return (
6386
<DropTarget onDrop={handleDrop}>
6487
<OperationVisualization
6588
width={width}
6689
margin={margin}
6790
operation={mappedFileContent}
91+
displayOptions={displayOptions}
92+
setDisplayOptions={setDisplayOptions}
6893
/>
6994
</DropTarget>
7095
)

src/v2/visualizer/mapTicketActivationData.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export const mapTicketActivationData = (
4545
includedCommonTaskNames: _,
4646
// this function depends on the tasks being sorted by startTime
4747
tasks: allTasks,
48-
...operation
4948
} = operationData
5049

5150
const OPERATION_SPAN_NAME = 'performance/ticket/activation'
@@ -170,7 +169,6 @@ export const mapTicketActivationData = (
170169
// Create a new operation object without the TTR and TTI tasks;
171170
// this avoids any side effects from modifying tempOperation directly.
172171
return {
173-
operation,
174172
tasks,
175173
includedCommonTaskNames,
176174
ttrData,

0 commit comments

Comments
 (0)