diff --git a/src/chrome/RatingButton.js b/src/chrome/RatingButton.js new file mode 100644 index 000000000..757e645bd --- /dev/null +++ b/src/chrome/RatingButton.js @@ -0,0 +1,77 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {Component, type Element} from 'react'; +import {Glyph} from 'flipper'; +import {getInstance as getLogger} from '../fb-stubs/Logger'; +import GK from '../fb-stubs/GK'; + +type Props = { + rating: ?number, + onRatingChanged: number => void, +}; + +type State = { + hoveredRating: ?number, +}; + +export default class RatingButton extends Component { + state = { + hoveredRating: null, + }; + + onRatingChanged(rating: number) { + const previousRating = this.props.rating; + if (rating === previousRating) { + return; + } + this.props.onRatingChanged(rating); + getLogger().track('usage', 'flipper-rating-changed', { + rating, + previousRating, + }); + } + + render() { + if (!GK.get('flipper_rating')) { + return null; + } + const rating = this.props.rating || 0; + if (rating < 0 || rating > 5) { + throw new Error(`Rating must be between 0 and 5. Value: ${rating}`); + } + const stars = Array(5) + .fill(true) + .map>((_, index) => ( +
{ + this.setState({hoveredRating: index + 1}); + }} + onMouseLeave={() => { + this.setState({hoveredRating: null}); + }} + onClick={() => { + this.onRatingChanged(index + 1); + }}> + +
+ )); + return stars; + } +} diff --git a/src/chrome/TitleBar.tsx b/src/chrome/TitleBar.tsx index 109f368eb..20bf8e205 100644 --- a/src/chrome/TitleBar.tsx +++ b/src/chrome/TitleBar.tsx @@ -23,7 +23,9 @@ import { toggleLeftSidebarVisible, toggleRightSidebarVisible, ACTIVE_SHEET_BUG_REPORTER, + setFlipperRating, } from '../reducers/application.js'; +import RatingButton from './RatingButton.js'; import DevicesButton from './DevicesButton.js'; import ScreenCaptureButtons from './ScreenCaptureButtons.js'; import AutoUpdateVersion from './AutoUpdateVersion.js'; @@ -62,6 +64,7 @@ type DispatchFromProps = { toggleLeftSidebarVisible: (visible?: boolean) => void, toggleRightSidebarVisible: (visible?: boolean) => void, setActiveSheet: (sheet: ActiveSheet) => void, + setFlipperRating: number => void, }; type StateFromProps = { @@ -71,6 +74,7 @@ type StateFromProps = { rightSidebarAvailable: boolean, downloadingImportData: boolean, launcherMsg: LauncherMsg, + flipperRating: ?number, }; const VersionText = styled(Text)({ @@ -124,7 +128,14 @@ class TitleBar extends React.Component {  Importing data... )} + + {config.showFlipperRating ? ( + + ) : null} {this.props.version + (isProduction() ? '' : '-dev')} {isAutoUpdaterEnabled() ? ( @@ -175,6 +186,7 @@ export default connect( rightSidebarAvailable, downloadingImportData, launcherMsg, + flipperRating, }, }) => ({ windowIsFocused, @@ -183,10 +195,12 @@ export default connect( rightSidebarAvailable, downloadingImportData, launcherMsg, + flipperRating, }), { setActiveSheet, toggleLeftSidebarVisible, toggleRightSidebarVisible, + setFlipperRating, }, )(TitleBar); diff --git a/src/dispatcher/tracking.js b/src/dispatcher/tracking.js index 9ddae8857..20d0b27b9 100644 --- a/src/dispatcher/tracking.js +++ b/src/dispatcher/tracking.js @@ -47,6 +47,8 @@ export default (store: Store, logger: Logger) => { clients, } = store.getState().connections; + const {flipperRating} = store.getState().application; + if (!selectedDevice || !selectedPlugin) { return; } @@ -70,6 +72,7 @@ export default (store: Store, logger: Logger) => { plugin: selectedPlugin, app, sdkVersion, + flipperRating, }; // reset dropped frames counter diff --git a/src/fb-stubs/config.js b/src/fb-stubs/config.js index 00da5267e..2de59bef7 100644 --- a/src/fb-stubs/config.js +++ b/src/fb-stubs/config.js @@ -9,4 +9,5 @@ export default { updateServer: 'https://www.facebook.com/fbflipper/public/latest.json', bugReportButtonVisible: false, showLogin: false, + showFlipperRating: false, }; diff --git a/src/reducers/application.js b/src/reducers/application.js index 3468dd653..1a5ed9c4b 100644 --- a/src/reducers/application.js +++ b/src/reducers/application.js @@ -46,6 +46,7 @@ export type State = { serverPorts: ServerPorts, downloadingImportData: boolean, launcherMsg: LauncherMsg, + flipperRating: ?number, }; type BooleanActionType = @@ -81,6 +82,12 @@ export type Action = severity: 'warning' | 'error', message: string, }, + } + | { + type: 'SET_FLIPPER_RATING', + payload: { + rating: number, + }, }; const initialState: () => State = () => ({ @@ -100,6 +107,7 @@ const initialState: () => State = () => ({ severity: 'warning', message: '', }, + flipperRating: null, }); export default function reducer(state: State, action: Action): State { @@ -146,6 +154,11 @@ export default function reducer(state: State, action: Action): State { ...state, launcherMsg: action.payload, }; + } else if (action.type === 'SET_FLIPPER_RATING') { + return { + ...state, + flipperRating: action.payload.rating, + }; } else { return state; } @@ -183,3 +196,10 @@ export const toggleRightSidebarAvailable = (payload?: boolean): Action => ({ type: 'rightSidebarAvailable', payload, }); + +export const setFlipperRating = (rating: number): Action => ({ + type: 'SET_FLIPPER_RATING', + payload: { + rating, + }, +}); diff --git a/src/reducers/index.js b/src/reducers/index.js index a3633a364..3c4ae8152 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -64,7 +64,14 @@ export type Store = ReduxStore; export type MiddlewareAPI = ReduxMiddlewareAPI; export default combineReducers<_, Actions>({ - application, + application: persistReducer( + { + key: 'application', + storage, + whitelist: ['flipperRating'], + }, + application, + ), connections: persistReducer( { key: 'connections',