From 612cfd81aee9af41876baf926b9c9082a2d2934f Mon Sep 17 00:00:00 2001 From: John Knox Date: Fri, 6 Sep 2019 08:31:18 -0700 Subject: [PATCH] Change feedback to use ITSR Summary: This isn't ready to release yet, it's still behind a GK so noone will see it. There has been no styling applied to the popover so it looks bad but is fully functional. What it also doesn't have yet: * Get the prompt text from the API (including the predefined selectable comments) * Check with the server whether it should pop up proactively, it's completely passive at the moment. Reviewed By: passy Differential Revision: D17206158 fbshipit-source-id: f1734f3d6bc555c860ebbaad7515d4675e1700cb --- src/chrome/RatingButton.tsx | 203 ++++++++++++++++++++++++++++------ src/chrome/TitleBar.tsx | 5 +- src/fb-stubs/UserFeedback.tsx | 31 ++++++ 3 files changed, 199 insertions(+), 40 deletions(-) create mode 100644 src/fb-stubs/UserFeedback.tsx diff --git a/src/chrome/RatingButton.tsx b/src/chrome/RatingButton.tsx index 3da8b2326..5c15c42e9 100644 --- a/src/chrome/RatingButton.tsx +++ b/src/chrome/RatingButton.tsx @@ -5,45 +5,80 @@ * @format */ -import React, {Component, Fragment} from 'react'; -import {Glyph, Tooltip} from 'flipper'; -import {getInstance as getLogger} from '../fb-stubs/Logger'; +import React, {Component, ReactElement} from 'react'; +import {Glyph, Popover, FlexColumn, FlexRow, Button, Checkbox} from 'flipper'; import GK from '../fb-stubs/GK'; +import * as UserFeedback from '../fb-stubs/UserFeedback'; type Props = { - rating: number | null | undefined; onRatingChanged: (rating: number) => void; }; type State = { - hoveredRating: number | null | undefined; + isShown: boolean; }; -export default class RatingButton extends Component { - state = { - hoveredRating: null, - }; +type NextAction = 'select-rating' | 'leave-comment' | 'finished'; - onRatingChanged(rating: number) { - const previousRating = this.props.rating; - if (rating === previousRating) { - return; - } - this.props.onRatingChanged(rating); - getLogger().track('usage', 'flipper-rating-changed', { - rating, - previousRating, - }); +class FeedbackComponent extends Component< + { + submitRating: (rating: number) => void; + submitComment: ( + rating: number, + comment: string, + selectedPredefinedComments: Array, + allowUserInfoSharing: boolean, + ) => void; + close(): void; + }, + { + rating: number | null; + hoveredRating: number; + allowUserInfoSharing: boolean; + nextAction: NextAction; + predefinedComments: {[key: string]: boolean}; + } +> { + state = { + rating: null, + hoveredRating: 0, + allowUserInfoSharing: true, + nextAction: 'select-rating' as NextAction, + predefinedComments: {'Too slow': false, Rubbish: false}, + }; + onSubmitRating(newRating: number) { + const nextAction = newRating <= 2 ? 'leave-comment' : 'finished'; + this.setState({rating: newRating, nextAction: nextAction}); + this.props.submitRating(newRating); + if (nextAction === 'finished') { + setTimeout(this.props.close, 1000); + } + } + onCommentSubmitted(comment: string) { + this.setState({nextAction: 'finished'}); + const selectedPredefinedComments: Array = Object.entries( + this.state.predefinedComments, + ) + .map(x => ({comment: x[0], enabled: x[1]})) + .filter(x => x.enabled) + .map(x => x.comment); + const currentRating = this.state.rating; + if (currentRating) { + this.props.submitComment( + currentRating, + comment, + selectedPredefinedComments, + this.state.allowUserInfoSharing, + ); + } else { + console.error('Illegal state: Submitting comment with no rating set.'); + } + setTimeout(this.props.close, 1000); + } + onAllowUserSharingChanged(allowed: boolean) { + this.setState({allowUserInfoSharing: allowed}); } - 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) => ( @@ -55,10 +90,10 @@ export default class RatingButton extends Component { this.setState({hoveredRating: index + 1}); }} onMouseLeave={() => { - this.setState({hoveredRating: null}); + this.setState({hoveredRating: 0}); }} onClick={() => { - this.onRatingChanged(index + 1); + this.onSubmitRating(index + 1); }}> { variant={ (this.state.hoveredRating ? index < this.state.hoveredRating - : index < rating) + : index < (this.state.rating || 0)) ? 'filled' : 'outline' } /> )); - const button = {stars}; + let body: Array; + switch (this.state.nextAction) { + case 'select-rating': + body = [ + + How would you rate your overall satisfaction with Flipper? + , + {stars}, + ]; + break; + case 'leave-comment': + body = [ + Predefined comment buttons here..., + Comment input box here..., + + + Can contact me.{' '} + + , + ]; + break; + case 'finished': + body = []; + break; + default: { + console.error('Illegal state: nextAction: ' + this.state.nextAction); + return null; + } + } return ( - + + + {this.state.nextAction === 'finished' + ? 'Feedback Received' + : "We'd love your feedback"} + + {body} + ); } } + +export default class RatingButton extends Component { + state = { + isShown: false, + }; + + onClick() { + this.setState({isShown: !this.state.isShown}); + } + + submitRating(rating: number) { + UserFeedback.submitRating(rating); + } + + submitComment( + rating: number, + comment: string, + selectedPredefinedComments: Array, + allowUserInfoSharing: boolean, + ) { + UserFeedback.submitComment( + rating, + comment, + selectedPredefinedComments, + allowUserInfoSharing, + ); + } + + render() { + if (!GK.get('flipper_rating')) { + return null; + } + const stars = ( +
+ +
+ ); + return [ + stars, + this.state.isShown ? ( + {}} + children={ + { + this.setState({isShown: false}); + }} + /> + } + /> + ) : null, + ]; + } +} diff --git a/src/chrome/TitleBar.tsx b/src/chrome/TitleBar.tsx index bfa6c54bc..180c7bf0f 100644 --- a/src/chrome/TitleBar.tsx +++ b/src/chrome/TitleBar.tsx @@ -161,10 +161,7 @@ class TitleBar extends React.Component { )} {config.showFlipperRating ? ( - + ) : null} {this.props.version + (isProduction() ? '' : '-dev')} diff --git a/src/fb-stubs/UserFeedback.tsx b/src/fb-stubs/UserFeedback.tsx new file mode 100644 index 000000000..5cd082667 --- /dev/null +++ b/src/fb-stubs/UserFeedback.tsx @@ -0,0 +1,31 @@ +/** + * 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 + */ + +export type FeedbackPrompt = { + preSubmitHeading: string; + postSubmitHeading: string; + commentPlaceholder: string; + bodyText: string; +}; + +export async function submitRating(rating: number): Promise { + throw new Error('Method not implemented.'); +} +export async function submitComment( + rating: number, + comment: string, + selectedPredefinedComments: string[], + allowUserInfoSharing: boolean, +): Promise { + throw new Error('Method not implemented.'); +} +export async function getPrompt(): Promise { + throw new Error('Method not implemented.'); +} +export async function shouldShowPrompt(): Promise { + throw new Error('Method not implemented.'); +}