@samitouri / QOS-React / commits / 3ba64e5955

Cleaned up edge cases around filtered commits

Brian Vaughn committed Mar 25, 2019 at 10:18 UTC 3ba64e595517a7579a099709543ddc148b82e988
9 files changed +249 -126
src/devtools/views/Profiler/CommitFilterModalContext.js new
+35
@@ -0,0 +1,35 @@
1 +// @flow
2 +
3 +import React, { createContext, useMemo, useState } from 'react';
4 +
5 +type Context = {|
6 + isFilterModalShowing: boolean,
7 + setIsFilterModalShowing: (value: boolean) => void,
8 +|};
9 +
10 +const CommitFilterModalContext = createContext<Context>(((null: any): Context));
11 +CommitFilterModalContext.displayName = 'CommitFilterModalContext';
12 +
13 +type Props = {|
14 + children: React$Node,
15 +|};
16 +
17 +function CommitFilterModalContextController({ children }: Props) {
18 + const [isFilterModalShowing, setIsFilterModalShowing] = useState(false);
19 +
20 + const value = useMemo(
21 + () => ({
22 + isFilterModalShowing,
23 + setIsFilterModalShowing,
24 + }),
25 + [isFilterModalShowing]
26 + );
27 +
28 + return (
29 + <CommitFilterModalContext.Provider value={value}>
30 + {children}
31 + </CommitFilterModalContext.Provider>
32 + );
33 +}
34 +
35 +export { CommitFilterModalContext, CommitFilterModalContextController };
src/devtools/views/Profiler/CommitFlamegraph.js
+71 -52
@@ -13,6 +13,7 @@ import { StoreContext } from '../context';
13 import styles from './CommitFlamegraph.css';
14
15 import type { ChartData, ChartNode } from './FlamegraphChartBuilder';
16 +import type { CommitDetails, CommitTree } from './types';
17
18 export type ItemData = {|
19 chartData: ChartData,
@@ -24,7 +25,11 @@ export type ItemData = {|
25 |};
26
27 export default function CommitFlamegraphAutoSizer(_: {||}) {
27 - const { selectFiber } = useContext(ProfilerContext);
28 + const { profilingCache } = useContext(StoreContext);
29 + const { rendererID, rootID, selectedCommitIndex, selectFiber } = useContext(
30 + ProfilerContext
31 + );
32 +
33 const deselectCurrentFiber = useCallback(
34 event => {
35 event.stopPropagation();
@@ -33,58 +38,78 @@ export default function CommitFlamegraphAutoSizer(_: {||}) {
38 [selectFiber]
39 );
40
36 - return (
37 - <div className={styles.Container} onClick={deselectCurrentFiber}>
38 - <AutoSizer>
39 - {({ height, width }) => (
40 - <CommitFlamegraph height={height} width={width} />
41 - )}
42 - </AutoSizer>
43 - </div>
44 - );
45 -}
46 -
47 -function CommitFlamegraph({
48 - height,
49 - width,
50 -}: {|
51 - height: number,
52 - width: number,
53 -|}) {
54 - const {
55 - rendererID,
56 - rootID,
57 - selectedCommitIndex,
58 - selectFiber,
59 - selectedFiberID,
60 - } = useContext(ProfilerContext);
61 -
62 - const { profilingCache } = useContext(StoreContext);
63 -
41 const profilingSummary = profilingCache.ProfilingSummary.read({
42 rendererID: ((rendererID: any): number),
43 rootID: ((rootID: any): number),
44 });
45
69 - const commitDetails = profilingCache.CommitDetails.read({
70 - commitIndex: ((selectedCommitIndex: any): number),
71 - rendererID: ((rendererID: any): number),
72 - rootID: ((rootID: any): number),
73 - });
46 + let commitDetails: CommitDetails | null = null;
47 + let commitTree: CommitTree | null = null;
48 + let chartData: ChartData | null = null;
49 + if (selectedCommitIndex !== null) {
50 + commitDetails = profilingCache.CommitDetails.read({
51 + commitIndex: selectedCommitIndex,
52 + rendererID: ((rendererID: any): number),
53 + rootID: ((rootID: any): number),
54 + });
55 +
56 + commitTree = profilingCache.getCommitTree({
57 + commitIndex: selectedCommitIndex,
58 + profilingSummary,
59 + rendererID: ((rendererID: any): number),
60 + rootID: ((rootID: any): number),
61 + });
62 +
63 + chartData = profilingCache.getFlamegraphChartData({
64 + commitDetails,
65 + commitIndex: selectedCommitIndex,
66 + commitTree,
67 + rootID: ((rootID: any): number),
68 + });
69 + }
70
75 - const commitTree = profilingCache.getCommitTree({
76 - commitIndex: ((selectedCommitIndex: any): number),
77 - profilingSummary,
78 - rendererID: ((rendererID: any): number),
79 - rootID: ((rootID: any): number),
80 - });
71 + if (
72 + commitDetails != null &&
73 + commitTree != null &&
74 + chartData != null &&
75 + chartData.depth > 0
76 + ) {
77 + return (
78 + <div className={styles.Container} onClick={deselectCurrentFiber}>
79 + <AutoSizer>
80 + {({ height, width }) => (
81 + <CommitFlamegraph
82 + chartData={((chartData: any): ChartData)}
83 + commitDetails={((commitDetails: any): CommitDetails)}
84 + commitTree={((commitTree: any): CommitTree)}
85 + height={height}
86 + width={width}
87 + />
88 + )}
89 + </AutoSizer>
90 + </div>
91 + );
92 + } else {
93 + return <NoCommitData />;
94 + }
95 +}
96
82 - const chartData = profilingCache.getFlamegraphChartData({
83 - commitDetails,
84 - commitIndex: ((selectedCommitIndex: any): number),
85 - commitTree,
86 - rootID: ((rootID: any): number),
87 - });
97 +type Props = {|
98 + chartData: ChartData,
99 + commitDetails: CommitDetails,
100 + commitTree: CommitTree,
101 + height: number,
102 + width: number,
103 +|};
104 +
105 +function CommitFlamegraph({
106 + chartData,
107 + commitDetails,
108 + commitTree,
109 + height,
110 + width,
111 +}: Props) {
112 + const { selectFiber, selectedFiberID } = useContext(ProfilerContext);
113
114 const selectedChartNodeIndex = useMemo<number>(() => {
115 if (selectedFiberID === null) {
@@ -123,12 +148,6 @@ function CommitFlamegraph({
148 [chartData, selectedChartNode, selectedChartNodeIndex, selectFiber, width]
149 );
150
126 - // If a commit contains no fibers with an actualDuration > 0,
127 - // Display a fallback message.
128 - if (chartData.depth === 0) {
129 - return <NoCommitData height={height} width={width} />;
130 - }
131 -
151 return (
152 <FixedSizeList
153 height={height}
src/devtools/views/Profiler/CommitRanked.js
+71 -44
@@ -13,6 +13,7 @@ import { StoreContext } from '../context';
13 import styles from './CommitRanked.css';
14
15 import type { ChartData } from './RankedChartBuilder';
16 +import type { CommitDetails, CommitTree } from './types';
17
18 export type ItemData = {|
19 chartData: ChartData,
@@ -24,7 +25,11 @@ export type ItemData = {|
25 |};
26
27 export default function CommitRankedAutoSizer(_: {||}) {
27 - const { selectFiber } = useContext(ProfilerContext);
28 + const { profilingCache } = useContext(StoreContext);
29 + const { rendererID, rootID, selectedCommitIndex, selectFiber } = useContext(
30 + ProfilerContext
31 + );
32 +
33 const deselectCurrentFiber = useCallback(
34 event => {
35 event.stopPropagation();
@@ -33,50 +38,78 @@ export default function CommitRankedAutoSizer(_: {||}) {
38 [selectFiber]
39 );
40
36 - return (
37 - <div className={styles.Container} onClick={deselectCurrentFiber}>
38 - <AutoSizer>
39 - {({ height, width }) => <CommitRanked height={height} width={width} />}
40 - </AutoSizer>
41 - </div>
42 - );
43 -}
44 -
45 -function CommitRanked({ height, width }: {| height: number, width: number |}) {
46 - const {
47 - rendererID,
48 - rootID,
49 - selectedCommitIndex,
50 - selectedFiberID,
51 - selectFiber,
52 - } = useContext(ProfilerContext);
53 -
54 - const { profilingCache } = useContext(StoreContext);
55 -
41 const profilingSummary = profilingCache.ProfilingSummary.read({
42 rendererID: ((rendererID: any): number),
43 rootID: ((rootID: any): number),
44 });
45
61 - const commitDetails = profilingCache.CommitDetails.read({
62 - commitIndex: ((selectedCommitIndex: any): number),
63 - rendererID: ((rendererID: any): number),
64 - rootID: ((rootID: any): number),
65 - });
46 + let commitDetails: CommitDetails | null = null;
47 + let commitTree: CommitTree | null = null;
48 + let chartData: ChartData | null = null;
49 + if (selectedCommitIndex !== null) {
50 + commitDetails = profilingCache.CommitDetails.read({
51 + commitIndex: selectedCommitIndex,
52 + rendererID: ((rendererID: any): number),
53 + rootID: ((rootID: any): number),
54 + });
55 +
56 + commitTree = profilingCache.getCommitTree({
57 + commitIndex: selectedCommitIndex,
58 + profilingSummary,
59 + rendererID: ((rendererID: any): number),
60 + rootID: ((rootID: any): number),
61 + });
62 +
63 + chartData = profilingCache.getRankedChartData({
64 + commitDetails,
65 + commitIndex: selectedCommitIndex,
66 + commitTree,
67 + rootID: ((rootID: any): number),
68 + });
69 + }
70
67 - const commitTree = profilingCache.getCommitTree({
68 - commitIndex: ((selectedCommitIndex: any): number),
69 - profilingSummary,
70 - rendererID: ((rendererID: any): number),
71 - rootID: ((rootID: any): number),
72 - });
71 + if (
72 + commitDetails != null &&
73 + commitTree != null &&
74 + chartData != null &&
75 + chartData.nodes.length > 0
76 + ) {
77 + return (
78 + <div className={styles.Container} onClick={deselectCurrentFiber}>
79 + <AutoSizer>
80 + {({ height, width }) => (
81 + <CommitRanked
82 + chartData={((chartData: any): ChartData)}
83 + commitDetails={((commitDetails: any): CommitDetails)}
84 + commitTree={((commitTree: any): CommitTree)}
85 + height={height}
86 + width={width}
87 + />
88 + )}
89 + </AutoSizer>
90 + </div>
91 + );
92 + } else {
93 + return <NoCommitData />;
94 + }
95 +}
96
74 - const chartData = profilingCache.getRankedChartData({
75 - commitDetails,
76 - commitIndex: ((selectedCommitIndex: any): number),
77 - commitTree,
78 - rootID: ((rootID: any): number),
79 - });
97 +type Props = {|
98 + chartData: ChartData,
99 + commitDetails: CommitDetails,
100 + commitTree: CommitTree,
101 + height: number,
102 + width: number,
103 +|};
104 +
105 +function CommitRanked({
106 + chartData,
107 + commitDetails,
108 + commitTree,
109 + height,
110 + width,
111 +}: Props) {
112 + const { selectedFiberID, selectFiber } = useContext(ProfilerContext);
113
114 const selectedFiberIndex = useMemo(
115 () => getNodeIndex(chartData, selectedFiberID),
@@ -95,12 +128,6 @@ function CommitRanked({ height, width }: {| height: number, width: number |}) {
128 [chartData, selectedFiberID, selectedFiberIndex, selectFiber, width]
129 );
130
98 - // If a commit contains no fibers with an actualDuration > 0,
99 - // Display a fallback message.
100 - if (chartData.nodes.length === 0) {
101 - return <NoCommitData height={height} width={width} />;
102 - }
103 -
131 return (
132 <FixedSizeList
133 height={height}
src/devtools/views/Profiler/NoCommitData.css
+5
@@ -10,3 +10,8 @@
10 .Header {
11 font-size: var(--font-size-sans-large);
12 }
13 +
14 +.FilterMessage {
15 + display: flex;
16 + align-items: center;
17 +}
src/devtools/views/Profiler/NoCommitData.js
+31 -20
@@ -1,26 +1,37 @@
1 // @flow
2
3 -import React from 'react';
3 +import React, { useContext } from 'react';
4 +import { ProfilerContext } from './ProfilerContext';
5 +import ToggleCommitFilterModalButton from './ToggleCommitFilterModalButton';
6
7 import styles from './NoCommitData.css';
8
7 -export default function NoCommitData({
8 - height,
9 - width,
10 -}: {|
11 - height: number,
12 - width: number,
13 -|}) {
14 - return (
15 - <div className={styles.NoCommitData} style={{ height, width }}>
16 - <p className={styles.Header}>
17 - There is no timing data to display for the currently selected commit.
18 - </p>
19 - <p>
20 - This can indicate that a render occurred too quickly for the timing API
21 - to measure. Try selecting another commit in the upper, right-hand
22 - corner.
23 - </p>
24 - </div>
25 - );
9 +export default function NoCommitData(_: {||}) {
10 + const { rootHasProfilingData } = useContext(ProfilerContext);
11 +
12 + if (rootHasProfilingData) {
13 + return (
14 + <div className={styles.NoCommitData}>
15 + <p className={styles.Header}>
16 + There is no data matching the current filter criteria.
17 + </p>
18 + <p className={styles.FilterMessage}>
19 + Try adjusting the commit filter <ToggleCommitFilterModalButton />
20 + </p>
21 + </div>
22 + );
23 + } else {
24 + return (
25 + <div className={styles.NoCommitData}>
26 + <p className={styles.Header}>
27 + There is no timing data to display for the currently selected commit.
28 + </p>
29 + <p>
30 + This can indicate that a render occurred too quickly for the timing
31 + API to measure. Try selecting another commit in the upper, right-hand
32 + corner.
33 + </p>
34 + </div>
35 + );
36 + }
37 }
src/devtools/views/Profiler/Profiler.js
+15 -9
@@ -1,10 +1,12 @@
1 // @flow
2
3 -import React, { Suspense, useCallback, useContext, useState } from 'react';
3 +import React, { Suspense, useCallback, useContext } from 'react';
4 import { createPortal } from 'react-dom';
5 +import {
6 + CommitFilterModalContext,
7 + CommitFilterModalContextController,
8 +} from './CommitFilterModalContext';
9 import { ProfilerContext } from './ProfilerContext';
6 -import Button from '../Button';
7 -import ButtonIcon from '../ButtonIcon';
10 import TabBar from '../TabBar';
11 import CommitFlamegraph from './CommitFlamegraph';
12 import CommitRanked from './CommitRanked';
@@ -15,6 +17,7 @@ import ReloadAndProfileButton from './ReloadAndProfileButton';
17 import SnapshotSelector from './SnapshotSelector';
18 import SidebarCommitInfo from './SidebarCommitInfo';
19 import SidebarInteractions from './SidebarInteractions';
20 +import ToggleCommitFilterModalButton from './ToggleCommitFilterModalButton';
21
22 import styles from './Profiler.css';
23
@@ -41,7 +44,11 @@ export default function Profiler({
44 />
45 );
46 } else {
44 - children = <SuspendingProfiler />;
47 + children = (
48 + <CommitFilterModalContextController>
49 + <SuspendingProfiler />
50 + </CommitFilterModalContextController>
51 + );
52 }
53
54 return portalContainer != null
@@ -105,9 +112,10 @@ function ProfilerFallback() {
112 // NOTE that the structure of this UI should mirror NonSuspendingProfiler.
113 function SuspendingProfiler() {
114 const { selectedTabID, selectTab } = useContext(ProfilerContext);
108 - const [isFilterModalShowing, setIsFilterModalShowing] = useState(false);
115 + const { isFilterModalShowing, setIsFilterModalShowing } = useContext(
116 + CommitFilterModalContext
117 + );
118
110 - const showFilterModal = useCallback(() => setIsFilterModalShowing(true));
119 const dismissFilterModal = useCallback(() => setIsFilterModalShowing(false));
120
121 let view = null;
@@ -153,9 +161,7 @@ function SuspendingProfiler() {
161 tabs={tabs}
162 />
163 <div className={styles.Spacer} />
156 - <Button onClick={showFilterModal} title="Filter commits by duration">
157 - <ButtonIcon type="filter" />
158 - </Button>
164 + <ToggleCommitFilterModalButton />
165 <Suspense fallback={<ProfilerFallback />}>
166 <SnapshotSelector />
167 </Suspense>
src/devtools/views/Profiler/SidebarCommitInfo.js
+1 -1
@@ -32,7 +32,7 @@ export default function SidebarCommitInfo(_: Props) {
32 );
33
34 const { interactions } = profilingCache.CommitDetails.read({
35 - commitIndex: ((selectedCommitIndex: any): number),
35 + commitIndex: selectedCommitIndex,
36 rendererID: ((rendererID: any): number),
37 rootID: ((rootID: any): number),
38 });
src/devtools/views/Profiler/SnapshotSelector.js
+2
@@ -61,6 +61,8 @@ export default function SnapshotSelector(_: Props) {
61 if (selectedFilteredCommitIndex === null) {
62 if (numFilteredCommits > 0) {
63 selectCommitIndex(0);
64 + } else {
65 + selectCommitIndex(null);
66 }
67 } else if (selectedFilteredCommitIndex >= numFilteredCommits) {
68 selectCommitIndex(numFilteredCommits === 0 ? null : numFilteredCommits - 1);
src/devtools/views/Profiler/ToggleCommitFilterModalButton.js new
+18
@@ -0,0 +1,18 @@
1 +// @flow
2 +
3 +import React, { useCallback, useContext } from 'react';
4 +import { CommitFilterModalContext } from './CommitFilterModalContext';
5 +import Button from '../Button';
6 +import ButtonIcon from '../ButtonIcon';
7 +
8 +export default function ToggleCommitFilterModalButton() {
9 + const { setIsFilterModalShowing } = useContext(CommitFilterModalContext);
10 +
11 + const showFilterModal = useCallback(() => setIsFilterModalShowing(true));
12 +
13 + return (
14 + <Button onClick={showFilterModal} title="Filter commits by duration">
15 + <ButtonIcon type="filter" />
16 + </Button>
17 + );
18 +}