Skip to content

[Feature] Ratings prompt #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Sep 29, 2016
Merged

[Feature] Ratings prompt #22

merged 46 commits into from
Sep 29, 2016

Conversation

iconix
Copy link
Contributor

@iconix iconix commented Sep 12, 2016

This feature enables the Web Clipper to receive positive and negative feedback from "active" users.

When a user's engagement with the Web Clipper qualifies them, we will show an additional sub-panel after a successful clip, which asks: "Enjoying the Web Clipper?" We will then log their response ("Yes, it's great!" vs "Not really...") depending on button clicked.

Additionally, if rate and feedback URLs are provided in Web Clipper settings, we will show the user additional prompts that will direct them to places where they can leave further feedback.

The "when to show" logic for this ratings sub-panel is where things get a little complicated. In broad strokes, the checks break down as follows:

  1. Is this feature enabled for the client being used (e.g., ChromeExtension, EdgeExtension, Bookmarklet, etc.)?
  2. Is the user "active" (dependent on # successful clips)?
  3. Have we shown this ratings panel to the user before? If so, what kind of response did we get (no response, positive response, negative response)?

Please see method descriptions in RatingsHelper.ts for further breakdown of the "when to show" logic.

PENDING TO-DOs as of initial PR publish:

  1. Animations, as spec'd by the design team
  2. Fix commented-out tests in ratingsHelper_tests.tsx
  3. Finalize logic on 1) how to handle users who never interact with the ratings prompt, and 2) how to determine enough time has passed after negative feedback to re-prompt users? (email discussion ongoing)

iconix added 28 commits August 31, 2016 12:26
export var noOpTrackerTimeoutDuration = 20 * 1000; // 20 seconds
export var fontSizeStep = 2;
export var maxClipSuccessForRatingsPrompt = 12; // TODO waiting for consensus on this value
export var maximumJSTimeValue = 8640000000000000; // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
Copy link
Contributor

Choose a reason for hiding this comment

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

can you make this multiplied out like the others?

Copy link
Contributor Author

@iconix iconix Sep 14, 2016

Choose a reason for hiding this comment

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

sorry, what would the multiplication you're suggesting here be? e.g., do you find 100,000,000 (days) * 86,400,000 (ms) more readable (as described by the spec linked to in the comment)?

Copy link
Contributor

@VishaalK VishaalK Sep 14, 2016

Choose a reason for hiding this comment

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

Eh I guess it doesn't matter since its a max time value.

100000000 * 24 * 60 * 60 * 1000; // 100K days in milliseconds, http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1

Copy link
Contributor Author

@iconix iconix Sep 15, 2016

Choose a reason for hiding this comment

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

hm, ok I can do that!

#Pending -> #Resolved

@@ -52,10 +52,11 @@ test("helper function checkValueAndDescriptionOnObject should assert when given
ok(checkValueAndDescriptionOnObject(valid));
});

test("All entries in setting.json should have a non-empty Value and Description", () => {
// TODO is a non-empty check necessary?
/*test("All entries in setting.json should have a non-empty Value and Description", () => {
Copy link
Contributor

@VishaalK VishaalK Sep 19, 2016

Choose a reason for hiding this comment

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

Commented out test?

Copy link
Contributor Author

@iconix iconix Sep 19, 2016

Choose a reason for hiding this comment

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

Yeah, wasn't sure if there was a technical reason why we disallow empty values... because now I'd like to start using empty values. I'll assume it's all good and delete this test.

#Pending -> #Resolved


// MOCK STORAGE

let mockStorage: { [key: string]: string };
Copy link
Contributor

@VishaalK VishaalK Sep 19, 2016

Choose a reason for hiding this comment

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

We have 3-ish versions of mockStorage out there. I see one in tooltipHelper_tests.ts, another in sectionPicker_tests.ts. Can you add a low pri PBI to unify these? Not necessary for this PR (may never be necessary 😜).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#Resolved (opened #41)

* Public for testing
*/
public static clipSuccessDelayIsOver(numClips: number): boolean {
// TODO MVP+? when # of successful clips > nMax, collapse panel into a Rate Us hyperlink in the footer that is always available
Copy link
Contributor

Choose a reason for hiding this comment

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

similar to another comment. Create a task for this somewhere

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#Resolved (in VSO)

strictEqual(shouldShowRatingsPrompt, false);
});

// test("", () => { });
Copy link
Contributor

Choose a reason for hiding this comment

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

Tests for RatingsHelper looks very thorough !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😃

});
}
break;
case RatingsPromptStage.Feedback:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you split these into functions for each state for readability? The logic looks good though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#WontFix after in-person discussion: any readability concern seems to apply only to the Init stage functions, and overall isn't too concerning


export function preCacheValues(storageKeys: string[]): void {
for (let key of storageKeys) {
Clipper.Storage.getValue(key, () => { }, true);
Copy link
Contributor

Choose a reason for hiding this comment

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

I know Matthew made a getValues but you'd have to add in the caching in again that you already added to getValue (if we want to switch to that function). Just a heads up depending on who merges first.

Copy link
Contributor Author

@iconix iconix Sep 20, 2016

Choose a reason for hiding this comment

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

#Pending -> #Resolved (merged with master)

let doNotPromptRatingsStr: string = Clipper.Storage.getCachedValue(Constants.StorageKeys.doNotPromptRatings);
let lastBadRatingDateAsStr: string = Clipper.Storage.getCachedValue(Constants.StorageKeys.lastBadRatingDate);
let lastBadRatingVersion: string = Clipper.Storage.getCachedValue(Constants.StorageKeys.lastBadRatingVersion);
let lastSeenVersion: string = Clipper.Storage.getCachedValue(Constants.StorageKeys.lastSeenVersion);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is gonna sound crappy but I am wary of this paradigm of knowing when a value is cached versus when it isn't. I think since you have tested it pretty thoroughly it will work here. The options are:

  1. Always get the fresh value from localStorage, which means dealing with callbacks, which is why we switched to this design in the first place
  2. Risk not having a fresh value. If the caller needs a fresh value, force them to use the async version.

What happens when you attempt getCachedValue but a corresponding getValue was never called? It is hard to differentiate between not being in storage, as we have a lot of cases where it doesn't exist yet, versus it not being in the cache. From preCacheNeededValues I know that these values are fine but we should discuss how we want to treat this in the future.

TL;DR: async makes everything difficult -.-

Copy link
Contributor

Choose a reason for hiding this comment

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

Time to take another hard look at being able to use async/await?

Copy link
Contributor

Choose a reason for hiding this comment

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

^ I think we should too. I think there's lots of benefit in this.

Copy link
Contributor Author

@iconix iconix Sep 20, 2016

Choose a reason for hiding this comment

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

@VishaalK I definitely get your concern here - getCachedValue is easy to accidentally misuse.

@VishaalK @leakymemory @mttwc I assume we're okay with using this paradigm for this PR, even if we need a better permanent solution... so how should we facilitate discussing the future?

Looks like async/await didn't make it into ES7 (ES2016), and only "has a chance" of making ES8 (ES2017)... so do we want to polyfill it? I can open a task for investigation if we all agree.

And what happened with the first hard look at async/await?

#Pending -> #Resolved (opened #43)

@iconix
Copy link
Contributor Author

iconix commented Sep 19, 2016

Noting a bug that @VishaalK informed me of: height-animator doesn't expand out enough anymore when on the SignInNeeded Panel (sign out of the clipper to see that the footer is cut off some - Feedback link is too close to the bottom)

#Pending -> #Resolved

… on the SignInPanel; fix what that broke on RatingsPanel
@iconix iconix force-pushed the feature/ratings-prompt branch from 7bbbced to fa09552 Compare September 26, 2016 21:34
@iconix iconix force-pushed the feature/ratings-prompt branch from fa09552 to d0f287d Compare September 26, 2016 21:36
@leakymemory leakymemory merged commit 092bfa9 into master Sep 29, 2016
@leakymemory leakymemory deleted the feature/ratings-prompt branch September 29, 2016 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants