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
This commit is contained in:
John Knox
2019-09-06 08:31:18 -07:00
committed by Facebook Github Bot
parent 4204562fee
commit 612cfd81ae
3 changed files with 199 additions and 40 deletions

View File

@@ -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<Props, State> {
type NextAction = 'select-rating' | 'leave-comment' | 'finished';
class FeedbackComponent extends Component<
{
submitRating: (rating: number) => void;
submitComment: (
rating: number,
comment: string,
selectedPredefinedComments: Array<string>,
allowUserInfoSharing: boolean,
) => void;
close(): void;
},
{
rating: number | null;
hoveredRating: number;
allowUserInfoSharing: boolean;
nextAction: NextAction;
predefinedComments: {[key: string]: boolean};
}
> {
state = {
hoveredRating: null,
rating: null,
hoveredRating: 0,
allowUserInfoSharing: true,
nextAction: 'select-rating' as NextAction,
predefinedComments: {'Too slow': false, Rubbish: false},
};
onRatingChanged(rating: number) {
const previousRating = this.props.rating;
if (rating === previousRating) {
return;
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);
}
this.props.onRatingChanged(rating);
getLogger().track('usage', 'flipper-rating-changed', {
rating,
previousRating,
});
}
onCommentSubmitted(comment: string) {
this.setState({nextAction: 'finished'});
const selectedPredefinedComments: Array<string> = 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<JSX.Element>((_, index) => (
@@ -55,10 +90,10 @@ export default class RatingButton extends Component<Props, State> {
this.setState({hoveredRating: index + 1});
}}
onMouseLeave={() => {
this.setState({hoveredRating: null});
this.setState({hoveredRating: 0});
}}
onClick={() => {
this.onRatingChanged(index + 1);
this.onSubmitRating(index + 1);
}}>
<Glyph
name="star"
@@ -66,20 +101,116 @@ export default class RatingButton extends Component<Props, State> {
variant={
(this.state.hoveredRating
? index < this.state.hoveredRating
: index < rating)
: index < (this.state.rating || 0))
? 'filled'
: 'outline'
}
/>
</div>
));
const button = <Fragment>{stars}</Fragment>;
return (
<Tooltip
options={{position: 'toLeft'}}
title="How would you rate Flipper?"
children={button}
let body: Array<ReactElement>;
switch (this.state.nextAction) {
case 'select-rating':
body = [
<FlexRow>
How would you rate your overall satisfaction with Flipper?
</FlexRow>,
<FlexRow>{stars}</FlexRow>,
];
break;
case 'leave-comment':
body = [
<FlexRow>Predefined comment buttons here...</FlexRow>,
<FlexRow>Comment input box here...</FlexRow>,
<FlexRow>
<Checkbox
checked={this.state.allowUserInfoSharing}
onChange={this.onAllowUserSharingChanged.bind(this)}
/>
Can contact me.{' '}
<Button onClick={() => this.onCommentSubmitted('some comment')}>
Submit
</Button>
</FlexRow>,
];
break;
case 'finished':
body = [];
break;
default: {
console.error('Illegal state: nextAction: ' + this.state.nextAction);
return null;
}
}
return (
<FlexColumn>
<FlexRow>
{this.state.nextAction === 'finished'
? 'Feedback Received'
: "We'd love your feedback"}
</FlexRow>
{body}
</FlexColumn>
);
}
}
export default class RatingButton extends Component<Props, State> {
state = {
isShown: false,
};
onClick() {
this.setState({isShown: !this.state.isShown});
}
submitRating(rating: number) {
UserFeedback.submitRating(rating);
}
submitComment(
rating: number,
comment: string,
selectedPredefinedComments: Array<string>,
allowUserInfoSharing: boolean,
) {
UserFeedback.submitComment(
rating,
comment,
selectedPredefinedComments,
allowUserInfoSharing,
);
}
render() {
if (!GK.get('flipper_rating')) {
return null;
}
const stars = (
<div onClick={this.onClick.bind(this)}>
<Glyph
name="star"
color="grey"
variant={this.state.isShown ? 'filled' : 'outline'}
/>
</div>
);
return [
stars,
this.state.isShown ? (
<Popover
onDismiss={() => {}}
children={
<FeedbackComponent
submitRating={this.submitRating.bind(this)}
submitComment={this.submitComment.bind(this)}
close={() => {
this.setState({isShown: false});
}}
/>
}
/>
) : null,
];
}
}

View File

@@ -161,10 +161,7 @@ class TitleBar extends React.Component<Props, StateFromProps> {
)}
<Spacer />
{config.showFlipperRating ? (
<RatingButton
rating={this.props.flipperRating}
onRatingChanged={this.props.setFlipperRating}
/>
<RatingButton onRatingChanged={this.props.setFlipperRating} />
) : null}
<Version>{this.props.version + (isProduction() ? '' : '-dev')}</Version>

View File

@@ -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<void> {
throw new Error('Method not implemented.');
}
export async function submitComment(
rating: number,
comment: string,
selectedPredefinedComments: string[],
allowUserInfoSharing: boolean,
): Promise<void> {
throw new Error('Method not implemented.');
}
export async function getPrompt(): Promise<FeedbackPrompt> {
throw new Error('Method not implemented.');
}
export async function shouldShowPrompt(): Promise<boolean> {
throw new Error('Method not implemented.');
}