Skip to content

Commit

Permalink
Move the getVisibleTabs selector to the threadSelectors so that we ca…
Browse files Browse the repository at this point in the history
…n compute it for any thread (Merge PR firefox-devtools#2111)
  • Loading branch information
julienw authored Jul 2, 2019
2 parents e6ee564 + 2974c96 commit ebd5b86
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 80 deletions.
14 changes: 7 additions & 7 deletions src/actions/profile-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,13 +289,13 @@ export function selectTrack(trackReference: TrackReference): ThunkAction<void> {
}
}

if (
selectedTab === 'js-tracer' &&
getThreadSelectors(selectedThreadIndex).getJsTracerTable(getState()) ===
null
) {
// If the user switches to another thread that doesn't have JS Tracer information,
// then switch to the calltree.
const doesNextTrackHaveSelectedTab = getThreadSelectors(selectedThreadIndex)
.getUsefulTabs(getState())
.includes(selectedTab);

if (!doesNextTrackHaveSelectedTab) {
// If the user switches to another track that doesn't have the current
// selectedTab then switch to the calltree.
selectedTab = 'calltree';
}

Expand Down
5 changes: 3 additions & 2 deletions src/components/app/Details.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import selectSidebar from '../sidebar';

import { changeSelectedTab, changeSidebarOpenState } from '../../actions/app';
import { getSelectedTab } from '../../selectors/url-state';
import { getIsSidebarOpen, getVisibleTabs } from '../../selectors/app';
import { getIsSidebarOpen } from '../../selectors/app';
import { selectedThreadSelectors } from '../../selectors/per-thread';
import CallNodeContextMenu from '../shared/CallNodeContextMenu';
import MarkerContextMenu from '../shared/MarkerContextMenu';
import TimelineTrackContextMenu from '../timeline/TrackContextMenu';
Expand Down Expand Up @@ -114,7 +115,7 @@ class ProfileViewer extends PureComponent<Props> {

export default explicitConnect<{||}, StateProps, DispatchProps>({
mapStateToProps: state => ({
visibleTabs: getVisibleTabs(state),
visibleTabs: selectedThreadSelectors.getUsefulTabs(state),
selectedTab: getSelectedTab(state),
isSidebarOpen: getIsSidebarOpen(state),
}),
Expand Down
21 changes: 0 additions & 21 deletions src/selectors/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import {
getHiddenGlobalTracks,
getHiddenLocalTracksByPid,
} from './url-state';
import { tabSlugs } from '../app-logic/tabs-handling';
import { selectedThreadSelectors } from './per-thread';
import { getGlobalTracks, getLocalTracksByPid } from './profile';
import { assertExhaustiveCheck, ensureExists } from '../utils/flow';
import {
Expand Down Expand Up @@ -51,25 +49,6 @@ export const getTrackThreadHeights: Selector<
export const getIsNewlyPublished: Selector<boolean> = state =>
getApp(state).isNewlyPublished;

/**
* Visible tabs are computed based on the current state of the profile. Some
* effort is made to not show a tab when there is no data available for it.
*/
export const getVisibleTabs: Selector<$ReadOnlyArray<TabSlug>> = createSelector(
selectedThreadSelectors.getIsNetworkChartEmptyInFullRange,
selectedThreadSelectors.getJsTracerTable,
(isNetworkChartEmpty, jsTracerTable) => {
let visibleTabs = tabSlugs;
if (isNetworkChartEmpty) {
visibleTabs = visibleTabs.filter(tabSlug => tabSlug !== 'network-chart');
}
if (!jsTracerTable) {
visibleTabs = visibleTabs.filter(tabSlug => tabSlug !== 'js-tracer');
}
return visibleTabs;
}
);

/**
* This selector takes all of the tracks, and deduces the height in CssPixels
* of the timeline. This is here to calculate the max-height of the timeline
Expand Down
65 changes: 65 additions & 0 deletions src/selectors/per-thread/composed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import { createSelector } from 'reselect';

import { tabSlugs, type TabSlug } from '../../app-logic/tabs-handling';

import type { Selector } from '../../types/store';
import type { $ReturnType } from '../../types/utils';
import type { Thread, JsTracerTable } from '../../types/profile';

/**
* Infer the return type from the getStackAndSampleSelectorsPerThread function. This
* is done that so that the local type definition with `Selector<T>` is the canonical
* definition for the type of the selector.
*/
export type ComposedSelectorsPerThread = $ReturnType<
typeof getComposedSelectorsPerThread
>;

/**
* This type contains the selectors needed for the extra selectors defined in
* this file. It's non-exact because the passed object _will_ contain more
* elements that we don't use here, and that's OK.
*/
type NeededThreadSelectors = {
getThread: Selector<Thread>,
getIsNetworkChartEmptyInFullRange: Selector<boolean>,
getJsTracerTable: Selector<JsTracerTable | null>,
};

/**
* Create the selectors for a thread that have to do with either stacks or samples.
*/
export function getComposedSelectorsPerThread(
threadSelectors: NeededThreadSelectors
): * {
/**
* Visible tabs are computed based on the current state of the profile. Some
* effort is made to not show a tab when there is no data available for it or
* when it's absurd.
*/
const getUsefulTabs: Selector<$ReadOnlyArray<TabSlug>> = createSelector(
threadSelectors.getIsNetworkChartEmptyInFullRange,
threadSelectors.getJsTracerTable,
(isNetworkChartEmpty, jsTracerTable) => {
let visibleTabs = tabSlugs;
if (isNetworkChartEmpty) {
visibleTabs = visibleTabs.filter(
tabSlug => tabSlug !== 'network-chart'
);
}
if (!jsTracerTable) {
visibleTabs = visibleTabs.filter(tabSlug => tabSlug !== 'js-tracer');
}
return visibleTabs;
}
);

return {
getUsefulTabs,
};
}
23 changes: 19 additions & 4 deletions src/selectors/per-thread/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
getStackAndSampleSelectorsPerThread,
type StackAndSampleSelectorsPerThread,
} from './stack-sample';
import {
getComposedSelectorsPerThread,
type ComposedSelectorsPerThread,
} from './composed';
import * as ProfileSelectors from '../profile';

import type { ThreadIndex } from '../../types/profile';
Expand All @@ -34,6 +38,7 @@ export type ThreadSelectors = {|
...ThreadSelectorsPerThread,
...MarkerSelectorsPerThread,
...StackAndSampleSelectorsPerThread,
...ComposedSelectorsPerThread,
|};

/**
Expand All @@ -50,11 +55,21 @@ export const getThreadSelectors = (
threadIndex: ThreadIndex
): ThreadSelectors => {
if (!(threadIndex in _threadSelectorsCache)) {
const threadSelectors = getThreadSelectorsPerThread(threadIndex);
// We define the thread selectors in 3 steps to ensure clarity in the
// separate files.
// 1. The basic selectors.
let selectors = getThreadSelectorsPerThread(threadIndex);
// 2. Stack, sample and marker selectors that need the previous basic
// selectors for their own definition.
selectors = {
...selectors,
...getStackAndSampleSelectorsPerThread(selectors),
...getMarkerSelectorsPerThread(selectors),
};
// 3. Other selectors that need selectors from different files to be defined.
_threadSelectorsCache[threadIndex] = {
...threadSelectors,
...getStackAndSampleSelectorsPerThread(threadSelectors),
...getMarkerSelectorsPerThread(threadSelectors),
...selectors,
...getComposedSelectorsPerThread(selectors),
};
}
return _threadSelectorsCache[threadIndex];
Expand Down
46 changes: 0 additions & 46 deletions src/test/store/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ import createStore from '../../app-logic/create-store';
import { withAnalyticsMock } from '../fixtures/mocks/analytics';
import { isolateProcess } from '../../actions/profile-view';
import { getProfileWithNiceTracks } from '../fixtures/profiles/tracks';
import {
getProfileFromTextSamples,
getProfileWithMarkers,
getNetworkMarkers,
getProfileWithJsTracerEvents,
} from '../fixtures/profiles/processed-profile';

import * as AppActions from '../../actions/app';

Expand Down Expand Up @@ -47,46 +41,6 @@ describe('app actions', function() {
});
});

describe('visibleTabs', function() {
it('hides the network chart and JS tracer when no data is in the thread', function() {
const { profile } = getProfileFromTextSamples('A');
const { getState } = storeWithProfile(profile);
expect(AppSelectors.getVisibleTabs(getState())).toEqual([
'calltree',
'flame-graph',
'stack-chart',
'marker-chart',
'marker-table',
]);
});

it('shows the network chart when network markers are present in the thread', function() {
const profile = getProfileWithMarkers(getNetworkMarkers());
const { getState } = storeWithProfile(profile);
expect(AppSelectors.getVisibleTabs(getState())).toEqual([
'calltree',
'flame-graph',
'stack-chart',
'marker-chart',
'marker-table',
'network-chart',
]);
});

it('shows the js tracer when it is available in a thread', function() {
const profile = getProfileWithJsTracerEvents([['A', 0, 10]]);
const { getState } = storeWithProfile(profile);
expect(AppSelectors.getVisibleTabs(getState())).toEqual([
'calltree',
'flame-graph',
'stack-chart',
'marker-chart',
'marker-table',
'js-tracer',
]);
});
});

describe('urlSetupDone', function() {
it('will remember when url setup is done', function() {
const { dispatch, getState } = storeWithSimpleProfile();
Expand Down
54 changes: 54 additions & 0 deletions src/test/store/useful-tabs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow

import { selectedThreadSelectors } from '../../selectors/per-thread';

import { storeWithProfile } from '../fixtures/stores';
import {
getProfileFromTextSamples,
getProfileWithMarkers,
getNetworkMarkers,
getProfileWithJsTracerEvents,
} from '../fixtures/profiles/processed-profile';

describe('getUsefulTabs', function() {
it('hides the network chart and JS tracer when no data is in the thread', function() {
const { profile } = getProfileFromTextSamples('A');
const { getState } = storeWithProfile(profile);
expect(selectedThreadSelectors.getUsefulTabs(getState())).toEqual([
'calltree',
'flame-graph',
'stack-chart',
'marker-chart',
'marker-table',
]);
});

it('shows the network chart when network markers are present in the thread', function() {
const profile = getProfileWithMarkers(getNetworkMarkers());
const { getState } = storeWithProfile(profile);
expect(selectedThreadSelectors.getUsefulTabs(getState())).toEqual([
'calltree',
'flame-graph',
'stack-chart',
'marker-chart',
'marker-table',
'network-chart',
]);
});

it('shows the js tracer when it is available in a thread', function() {
const profile = getProfileWithJsTracerEvents([['A', 0, 10]]);
const { getState } = storeWithProfile(profile);
expect(selectedThreadSelectors.getUsefulTabs(getState())).toEqual([
'calltree',
'flame-graph',
'stack-chart',
'marker-chart',
'marker-table',
'js-tracer',
]);
});
});

0 comments on commit ebd5b86

Please sign in to comment.