From 8efea66a381b949c65b94a810a5d881615893902 Mon Sep 17 00:00:00 2001 From: Evan Jenkins Date: Thu, 13 Jun 2024 23:41:13 -0700 Subject: [PATCH] fix: restore multi select options functionality --- src/2024/types.ts | 1 + .../components/OperationVisualization.tsx | 120 ++++++------------ src/v2/visualizer/constants.ts | 49 +++++++ src/v2/visualizer/index.tsx | 39 +++++- src/v2/visualizer/mapTicketActivationData.ts | 2 - 5 files changed, 122 insertions(+), 89 deletions(-) create mode 100644 src/v2/visualizer/constants.ts diff --git a/src/2024/types.ts b/src/2024/types.ts index 8b030fe..fef9cb4 100644 --- a/src/2024/types.ts +++ b/src/2024/types.ts @@ -9,6 +9,7 @@ export type TaskSpanKind = | 'render' | 'asset' | 'iframe' + | 'resource-ember' export type PerformanceEntryLike = Omit export interface SpanMetadata { diff --git a/src/v2/visualizer/components/OperationVisualization.tsx b/src/v2/visualizer/components/OperationVisualization.tsx index 5e3531d..7d2d258 100644 --- a/src/v2/visualizer/components/OperationVisualization.tsx +++ b/src/v2/visualizer/components/OperationVisualization.tsx @@ -30,34 +30,21 @@ import type { TaskDataEmbeddedInOperation, TaskSpanKind, } from '../../../2024/types' +import { + type FilterOption, + BAR_FILL_COLOR, + COLLAPSE_ASSET_SPANS_TEXT, + COLLAPSE_EMBER_RESOURCE_SPANS, + COLLAPSE_IFRAME_SPANS, + COLLAPSE_RENDER_SPANS_TEXT, + FILTER_OPTIONS, + MEASURES_TEXT, + RESOURCES_TEXT, +} from '../constants' import { MappedOperation } from '../mapTicketActivationData' const rootOperation = TicketData -const BAR_FILL_COLOR: Record = { - render: '#ff7f0e', - measure: '#2ca02c', - resource: '#1f77b4', - 'resource-ember': '#17becf', - longtask: '#d62728', - mark: '#9467bd', - asset: '#8c564b', - iframe: '#e377c2', - element: '#7f7f7f', - action: '#bcbd22', - - error: '#ff9896', - vital: '#ffbb78', - 'first-input': '#aec7e8', - 'largest-contentful-paint': '#98df8a', - 'layout-shift': '#ff9896', - 'visibility-state': '#ff9896', - event: '#ff9896', - navigation: '#ff9896', - paint: '#ff9896', - taskattribution: '#ff9896', -} - const DEFAULT_MARGIN = { top: 50, left: 200, right: 120, bottom: 30 } export interface TTLineProps { @@ -146,30 +133,28 @@ const TTLine: React.FC = ({ ) } -const RESOURCES_TEXT = 'Show Resources' -const MEASURES_TEXT = 'Show Measures' -const COLLAPSE_RENDER_SPANS_TEXT = 'Collapse Render Spans' -const COLLAPSE_ASSET_SPANS_TEXT = 'Collapse Asset Spans' -const COLLAPSE_EMBER_RESOURCE_SPANS = 'Collapse Ember Resource Spans' -const COLLAPSE_IFRAME_SPANS = 'Collapse iframe Spans' - -type FilterOption = - | typeof RESOURCES_TEXT - | typeof MEASURES_TEXT - | typeof COLLAPSE_RENDER_SPANS_TEXT - | typeof COLLAPSE_ASSET_SPANS_TEXT - | typeof COLLAPSE_EMBER_RESOURCE_SPANS - | typeof COLLAPSE_IFRAME_SPANS - -const FILTER_OPTIONS: FilterOption[] = [ - RESOURCES_TEXT, - MEASURES_TEXT, - COLLAPSE_RENDER_SPANS_TEXT, - COLLAPSE_ASSET_SPANS_TEXT, - COLLAPSE_EMBER_RESOURCE_SPANS, - COLLAPSE_IFRAME_SPANS, -] - +function handleOption({ + selectionValue, + setter, + type, + text, +}: { + selectionValue: OptionValue[] + setter: React.Dispatch>> + type: string + text: string +}) { + if (selectionValue?.includes(text)) { + setter((prev) => ({ ...prev, [text]: true })) + } else if ( + !selectionValue?.includes(text) && + (type === 'input:keyDown:Enter' || + type === 'option:click' || + type === 'fn:setSelectionValue') + ) { + setter((prev) => ({ ...prev, [text]: false })) + } +} export interface MultiSelectProps { setState: React.Dispatch>> state: Record @@ -273,29 +258,6 @@ const MultiSelect: React.FC = ({ state, setState }) => { ) } -function handleOption({ - selectionValue, - setter, - type, - text, -}: { - selectionValue: OptionValue[] - setter: React.Dispatch>> - type: string - text: string -}) { - if (selectionValue?.includes(text)) { - setter((prev) => ({ ...prev, [text]: true })) - } else if ( - !selectionValue?.includes(text) && - (type === 'input:keyDown:Enter' || - type === 'option:click' || - type === 'fn:setSelectionValue') - ) { - setter((prev) => ({ ...prev, [text]: false })) - } -} - function LegendDemo({ title, children, @@ -332,21 +294,19 @@ function LegendDemo({ export interface OperationVisualizationProps { width: number operation: MappedOperation + setDisplayOptions: React.Dispatch< + React.SetStateAction> + > + displayOptions: Record margin?: { top: number; right: number; bottom: number; left: number } } const OperationVisualization: React.FC = ({ width, operation, + displayOptions, + setDisplayOptions, margin = DEFAULT_MARGIN, }) => { - const [state, setState] = useState({ - [RESOURCES_TEXT]: true, - [MEASURES_TEXT]: true, - [COLLAPSE_RENDER_SPANS_TEXT]: true, - [COLLAPSE_ASSET_SPANS_TEXT]: true, - [COLLAPSE_EMBER_RESOURCE_SPANS]: false, - [COLLAPSE_IFRAME_SPANS]: false, - }) const { ttrData, ttiData, @@ -533,7 +493,7 @@ const OperationVisualization: React.FC = ({ height: `${footerHeight}px`, }} > - + = { + render: '#ff7f0e', + measure: '#2ca02c', + resource: '#1f77b4', + 'resource-ember': '#17becf', + longtask: '#d62728', + mark: '#9467bd', + asset: '#8c564b', + iframe: '#e377c2', + element: '#7f7f7f', + action: '#bcbd22', + + error: '#ff9896', + vital: '#ffbb78', + 'first-input': '#aec7e8', + 'largest-contentful-paint': '#98df8a', + 'layout-shift': '#ff9896', + 'visibility-state': '#ff9896', + event: '#ff9896', + navigation: '#ff9896', + paint: '#ff9896', + taskattribution: '#ff9896', +} diff --git a/src/v2/visualizer/index.tsx b/src/v2/visualizer/index.tsx index 414b80a..a91484d 100644 --- a/src/v2/visualizer/index.tsx +++ b/src/v2/visualizer/index.tsx @@ -3,22 +3,39 @@ import { Operation } from '../../2024/types' import { DropTarget } from './components/DropTarget' import FileUploadButton from './components/FileUploadButton' import OperationVisualization from './components/OperationVisualization' +import { + COLLAPSE_ASSET_SPANS_TEXT, + COLLAPSE_EMBER_RESOURCE_SPANS, + COLLAPSE_IFRAME_SPANS, + COLLAPSE_RENDER_SPANS_TEXT, + MEASURES_TEXT, + RESOURCES_TEXT, +} from './constants' import { mapTicketActivationData } from './mapTicketActivationData' export interface OperationVisualizerProps { width: number margin?: { top: number; right: number; bottom: number; left: number } } + const OperationVisualizer = ({ width, margin }: OperationVisualizerProps) => { - const [fileContent, setFileContent] = useState(null) + const [displayOptions, setDisplayOptions] = useState({ + [RESOURCES_TEXT]: true, + [MEASURES_TEXT]: true, + [COLLAPSE_RENDER_SPANS_TEXT]: true, + [COLLAPSE_ASSET_SPANS_TEXT]: true, + [COLLAPSE_EMBER_RESOURCE_SPANS]: false, + [COLLAPSE_IFRAME_SPANS]: false, + }) + const [fileContent, setFileContent] = useState(null) const readFile = (file: File | undefined) => { if (file && file.type === 'application/json') { const reader = new FileReader() reader.addEventListener('load', (e) => { const result = e.target?.result if (result && typeof result === 'string') { - // should validate the file + // should validate the file? setFileContent(JSON.parse(result) as Operation) } }) @@ -40,9 +57,15 @@ const OperationVisualizer = ({ width, margin }: OperationVisualizerProps) => { const mappedFileContent = useMemo(() => { if (!fileContent) return null - // TODO: should have option state for collapsing spans - return mapTicketActivationData(fileContent) - }, [fileContent]) + return mapTicketActivationData(fileContent, { + collapseRenders: displayOptions[COLLAPSE_RENDER_SPANS_TEXT], + collapseAssets: displayOptions[COLLAPSE_ASSET_SPANS_TEXT], + collapseEmberResources: displayOptions[COLLAPSE_EMBER_RESOURCE_SPANS], + collapseIframes: displayOptions[COLLAPSE_IFRAME_SPANS], + displayResources: displayOptions[RESOURCES_TEXT], + displayMeasures: displayOptions[MEASURES_TEXT], + }) + }, [fileContent, displayOptions]) if (!fileContent) { return ( @@ -56,8 +79,8 @@ const OperationVisualizer = ({ width, margin }: OperationVisualizerProps) => { ) } - // If we failed validation or the mapping returned a null for some reason - if (!mappedFileContent) return
'Some error state'
+ // If we failed validation or the mapping returned a null for some reason. Alternatively could wrap the whole thing in an ErrorBoundary? + if (!mappedFileContent) return
Some error state
return ( @@ -65,6 +88,8 @@ const OperationVisualizer = ({ width, margin }: OperationVisualizerProps) => { width={width} margin={margin} operation={mappedFileContent} + displayOptions={displayOptions} + setDisplayOptions={setDisplayOptions} /> ) diff --git a/src/v2/visualizer/mapTicketActivationData.ts b/src/v2/visualizer/mapTicketActivationData.ts index d569e5e..2e0d5fa 100644 --- a/src/v2/visualizer/mapTicketActivationData.ts +++ b/src/v2/visualizer/mapTicketActivationData.ts @@ -45,7 +45,6 @@ export const mapTicketActivationData = ( includedCommonTaskNames: _, // this function depends on the tasks being sorted by startTime tasks: allTasks, - ...operation } = operationData const OPERATION_SPAN_NAME = 'performance/ticket/activation' @@ -170,7 +169,6 @@ export const mapTicketActivationData = ( // Create a new operation object without the TTR and TTI tasks; // this avoids any side effects from modifying tempOperation directly. return { - operation, tasks, includedCommonTaskNames, ttrData,