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
+}