Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions locales/en-US/app.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -397,13 +397,26 @@ MarkerContextMenu--select-the-receiver-thread =
MarkerContextMenu--select-the-sender-thread =
Select the sender thread “<strong>{ $threadName }</strong>”

## MarkerFiltersContextMenu
## This is the menu when filter icon is clicked in Marker Chart and Marker Table
## panels.

# This string is used on the marker filters menu item when clicked on the filter icon.
# Variables:
# $filter (String) - Search string that will be used to filter the markers.
MarkerFiltersContextMenu--drop-samples-outside-of-markers-matching =
Drop samples outside of markers matching “<strong>{ $filter }</strong>”

## MarkerSettings
## This is used in all panels related to markers.

MarkerSettings--panel-search =
.label = Filter Markers:
.title = Only display markers that match a certain name

MarkerSettings--marker-filters =
.title = Marker Filters

## MarkerSidebar
## This is the sidebar component that is used in Marker Table panel.

Expand Down Expand Up @@ -972,6 +985,11 @@ TransformNavigator--collapse-direct-recursion-only = Collapse direct recursion o
# $item (String) - Name of the function that transform applied to.
TransformNavigator--collapse-function-subtree = Collapse subtree: { $item }

# "Drop samples outside of markers matching ..." transform.
# Variables:
# $item (String) - Search filter of the markers that transform will apply to.
TransformNavigator--drop-samples-outside-of-markers-matching = Drop samples outside of markers matching: “{ $item }”

## "Bottom box" - a view which contains the source view and the assembly view,
## at the bottom of the profiler UI
##
Expand Down
7 changes: 7 additions & 0 deletions res/img/svg/filter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/components/shared/CallNodeContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
});
break;
}
case 'filter-samples':
throw new Error(
"Filter samples transform can't be applied from the call node context menu."
);
default:
assertExhaustiveCheck(type);
}
Expand Down
87 changes: 87 additions & 0 deletions src/components/shared/MarkerFiltersContextMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* 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 React, { PureComponent } from 'react';
import { MenuItem } from '@firefox-devtools/react-contextmenu';
import { Localized } from '@fluent/react';

import { ContextMenu } from './ContextMenu';
import explicitConnect from 'firefox-profiler/utils/connect';
import {
getMarkersSearchString,
getSelectedThreadsKey,
} from 'firefox-profiler/selectors/url-state';
import { addTransformToStack } from 'firefox-profiler/actions/profile-view';

import type { ThreadsKey } from 'firefox-profiler/types';
import type { ConnectedProps } from 'firefox-profiler/utils/connect';

type OwnProps = {|
+onShow: () => void,
+onHide: () => void,
|};

type StateProps = {|
+searchString: string,
+threadsKey: ThreadsKey,
|};

type DispatchProps = {|
+addTransformToStack: typeof addTransformToStack,
|};

type Props = ConnectedProps<OwnProps, StateProps, DispatchProps>;

class MarkerFiltersContextMenuImpl extends PureComponent<Props> {
filterSamplesByMarker = () => {
const { searchString, threadsKey, addTransformToStack } = this.props;
addTransformToStack(threadsKey, {
type: 'filter-samples',
filterType: 'marker-search',
filter: searchString,
});
};

render() {
const { searchString, onShow, onHide } = this.props;
return (
<ContextMenu
id="MarkerFiltersContextMenu"
className="markerFiltersContextMenu"
onShow={onShow}
onHide={onHide}
>
<MenuItem onClick={this.filterSamplesByMarker}>
<Localized
id="MarkerFiltersContextMenu--drop-samples-outside-of-markers-matching"
vars={{ filter: searchString }}
elems={{ strong: <strong /> }}
>
{/* Using a fragment here so we can have a strong tag inside. */}
<>
Drop samples outside of markers matching “
<strong>${searchString}</strong>”
</>
</Localized>
</MenuItem>
</ContextMenu>
);
}
}

export const MarkerFiltersContextMenu = explicitConnect<
OwnProps,
StateProps,
DispatchProps
>({
mapStateToProps: (state) => ({
searchString: getMarkersSearchString(state),
threadsKey: getSelectedThreadsKey(state),
}),
mapDispatchToProps: {
addTransformToStack,
},
component: MarkerFiltersContextMenuImpl,
});
28 changes: 28 additions & 0 deletions src/components/shared/MarkerSettings.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,31 @@
padding: 0;
line-height: 25px;
}

.filterMarkersButton {
position: relative;
width: 24px;
height: 24px;
flex: none;
padding-right: 30px;
margin: 0 4px;
background-image: url(firefox-profiler-res/img/svg/filter.svg);
background-position: 4px center;
background-repeat: no-repeat;
}

/* This is the dropdown arrow on the right of the button. */
.filterMarkersButton::after {
position: absolute;
top: 2px;
right: 2px;
border-top: 6px solid;
border-right: 4px solid transparent;
border-bottom: 0 solid transparent;
border-left: 4px solid transparent;
margin-top: 7px;
margin-right: 4px;
margin-left: 4px;
color: var(--grey-90);
content: '';
}
78 changes: 77 additions & 1 deletion src/components/shared/MarkerSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

import React, { PureComponent } from 'react';
import { Localized } from '@fluent/react';
import classNames from 'classnames';
import { showMenu } from '@firefox-devtools/react-contextmenu';

import explicitConnect from 'firefox-profiler/utils/connect';
import { changeMarkersSearchString } from 'firefox-profiler/actions/profile-view';
import { getMarkersSearchString } from 'firefox-profiler/selectors/url-state';
import { PanelSearch } from './PanelSearch';
import { StackImplementationSetting } from 'firefox-profiler/components/shared/StackImplementationSetting';
import { MarkerFiltersContextMenu } from './MarkerFiltersContextMenu';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';

Expand All @@ -28,13 +31,65 @@ type DispatchProps = {|

type Props = ConnectedProps<{||}, StateProps, DispatchProps>;

class MarkerSettingsImpl extends PureComponent<Props> {
type State = {|
+isMarkerFiltersMenuVisible: boolean,
// react-contextmenu library automatically hides the menu on mousedown even
// if it's already visible. That's why we need to handle the mousedown event
// as well and check if the menu is visible or not before it hides it.
// Otherwise, if we check this in onClick event, the state will always be
// `false` since the library already hid it on mousedown.
+isFilterMenuVisibleOnMouseDown: boolean,
|};

class MarkerSettingsImpl extends PureComponent<Props, State> {
state = {
isMarkerFiltersMenuVisible: false,
isFilterMenuVisibleOnMouseDown: false,
};

_onSearch = (value: string) => {
this.props.changeMarkersSearchString(value);
};

_onClickToggleFilterButton = (event: SyntheticMouseEvent<HTMLElement>) => {
const { isFilterMenuVisibleOnMouseDown } = this.state;
if (isFilterMenuVisibleOnMouseDown) {
// Do nothing as we would like to hide the menu if the menu was already visible on mouse down.
return;
}
Comment on lines +55 to +59

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aaaah so that's what we should do for the track menu as well 😁

I wonder if we could be able to extract this for an easier reuse...
(not for now)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking about the track menu as well while doing this :) We can possibly extract it.


const rect = event.currentTarget.getBoundingClientRect();
// FIXME: Currently we assume that the context menu is 250px wide, but ideally
// we should get the real width. It's not so easy though, because the context
// menu is not rendered yet.
const isRightAligned = rect.right > window.innerWidth - 250;

showMenu({
data: null,
id: 'MarkerFiltersContextMenu',
position: { x: isRightAligned ? rect.right : rect.left, y: rect.bottom },
target: event.target,
});
};

_onShowFiltersContextMenu = () => {
this.setState({ isMarkerFiltersMenuVisible: true });
};

_onHideFiltersContextMenu = () => {
this.setState({ isMarkerFiltersMenuVisible: false });
};

_onMouseDownToggleFilterButton = () => {
this.setState((state) => ({
isFilterMenuVisibleOnMouseDown: state.isMarkerFiltersMenuVisible,
}));
};

render() {
const { searchString } = this.props;
const { isMarkerFiltersMenuVisible } = this.state;

return (
<div className="markerSettings">
<ul className="panelSettingsList">
Expand All @@ -52,6 +107,27 @@ class MarkerSettingsImpl extends PureComponent<Props> {
onSearch={this._onSearch}
/>
</Localized>
<Localized id="MarkerSettings--marker-filters" attrs={{ title: true }}>
<button
className={classNames(
'filterMarkersButton',
'photon-button',
'photon-button-ghost',
{
'photon-button-ghost--checked': isMarkerFiltersMenuVisible,
}
)}
title="Marker filters"
type="button"
onClick={this._onClickToggleFilterButton}
onMouseDown={this._onMouseDownToggleFilterButton}
disabled={!searchString}
/>
</Localized>
<MarkerFiltersContextMenu
onShow={this._onShowFiltersContextMenu}
onHide={this._onHideFiltersContextMenu}
/>
</div>
);
}
Expand Down
Loading