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:
committed by
Facebook Github Bot
parent
4204562fee
commit
612cfd81ae
@@ -5,45 +5,80 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {Component, Fragment} from 'react';
|
import React, {Component, ReactElement} from 'react';
|
||||||
import {Glyph, Tooltip} from 'flipper';
|
import {Glyph, Popover, FlexColumn, FlexRow, Button, Checkbox} from 'flipper';
|
||||||
import {getInstance as getLogger} from '../fb-stubs/Logger';
|
|
||||||
import GK from '../fb-stubs/GK';
|
import GK from '../fb-stubs/GK';
|
||||||
|
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
rating: number | null | undefined;
|
|
||||||
onRatingChanged: (rating: number) => void;
|
onRatingChanged: (rating: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
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 = {
|
state = {
|
||||||
hoveredRating: null,
|
rating: null,
|
||||||
|
hoveredRating: 0,
|
||||||
|
allowUserInfoSharing: true,
|
||||||
|
nextAction: 'select-rating' as NextAction,
|
||||||
|
predefinedComments: {'Too slow': false, Rubbish: false},
|
||||||
};
|
};
|
||||||
|
onSubmitRating(newRating: number) {
|
||||||
onRatingChanged(rating: number) {
|
const nextAction = newRating <= 2 ? 'leave-comment' : 'finished';
|
||||||
const previousRating = this.props.rating;
|
this.setState({rating: newRating, nextAction: nextAction});
|
||||||
if (rating === previousRating) {
|
this.props.submitRating(newRating);
|
||||||
return;
|
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() {
|
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)
|
const stars = Array(5)
|
||||||
.fill(true)
|
.fill(true)
|
||||||
.map<JSX.Element>((_, index) => (
|
.map<JSX.Element>((_, index) => (
|
||||||
@@ -55,10 +90,10 @@ export default class RatingButton extends Component<Props, State> {
|
|||||||
this.setState({hoveredRating: index + 1});
|
this.setState({hoveredRating: index + 1});
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
this.setState({hoveredRating: null});
|
this.setState({hoveredRating: 0});
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.onRatingChanged(index + 1);
|
this.onSubmitRating(index + 1);
|
||||||
}}>
|
}}>
|
||||||
<Glyph
|
<Glyph
|
||||||
name="star"
|
name="star"
|
||||||
@@ -66,20 +101,116 @@ export default class RatingButton extends Component<Props, State> {
|
|||||||
variant={
|
variant={
|
||||||
(this.state.hoveredRating
|
(this.state.hoveredRating
|
||||||
? index < this.state.hoveredRating
|
? index < this.state.hoveredRating
|
||||||
: index < rating)
|
: index < (this.state.rating || 0))
|
||||||
? 'filled'
|
? 'filled'
|
||||||
: 'outline'
|
: 'outline'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
const button = <Fragment>{stars}</Fragment>;
|
let body: Array<ReactElement>;
|
||||||
return (
|
switch (this.state.nextAction) {
|
||||||
<Tooltip
|
case 'select-rating':
|
||||||
options={{position: 'toLeft'}}
|
body = [
|
||||||
title="How would you rate Flipper?"
|
<FlexRow>
|
||||||
children={button}
|
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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -161,10 +161,7 @@ class TitleBar extends React.Component<Props, StateFromProps> {
|
|||||||
)}
|
)}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{config.showFlipperRating ? (
|
{config.showFlipperRating ? (
|
||||||
<RatingButton
|
<RatingButton onRatingChanged={this.props.setFlipperRating} />
|
||||||
rating={this.props.flipperRating}
|
|
||||||
onRatingChanged={this.props.setFlipperRating}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
<Version>{this.props.version + (isProduction() ? '' : '-dev')}</Version>
|
<Version>{this.props.version + (isProduction() ? '' : '-dev')}</Version>
|
||||||
|
|
||||||
|
|||||||
31
src/fb-stubs/UserFeedback.tsx
Normal file
31
src/fb-stubs/UserFeedback.tsx
Normal 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.');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user