-
Notifications
You must be signed in to change notification settings - Fork 66
Refactor for buttons array with SurveyButton
class
#134
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
Refactor for buttons array with SurveyButton
class
#134
Conversation
@DanTup do you mind taking a look if this looks like something you can work with easily on the vs code side? The change we made to the json file will allow us to have a configurable number of buttons that each have their own redirect url (if applicable). Using the |
} | ||
] | ||
"description": "description123", | ||
"dismissForMinutes": "10", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be better called "snoozeForMinutes", since "dismiss" seems to be used elsewhere to mean a permanent dismissal, and "snooze" is used to meant temporary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good call, would help reduce any confusion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks fine to me - the notifications in VS Code can have multiple buttons and we can do different things on each. In this case the analysis server would check which button was clicked, open the url
(if provided) from that button and record the action.
A possible tweak to the API:
{
"buttonText": "Take Survey",
"action": "accept",
"url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2"
}
final bool accepted = true;
// This will ensure that the survey doesn't show up again
analytics.dismissSurvey(
survey: survey,
surveyAccepted: accepted
);
It seems like the specific "action" here doesn't really matter to the client. If the client just passed the raw action
instead of interpreting it (as snooze/accept/dismiss), it would be possibly to add additional actions in future without any changes to the "client".
For example one of your examples has a "More Info" button which snoozes, but maybe you don't want to snooze there. You could add a new kind of action
and the client doesn't need to interpret it, you just do that in the analytics.dismissSurvey
(or whatever it would be called)?
Oh, one thing to note though - is that right now any "More Info" button would dismiss the notification, so the user would get more info, but not actually be able to get back to accept the survey. That's probably not an issue if the More Info page also has a link to the survey, but if you want it to be re-shown on the client (we can't make it persist on the screen in VS Code, but we could immediately re-show it), then we'd need some way to signal that.
So the analytics.dismissSurvey(
survey: survey,
surveyAccepted: accepted
); The The same is done for
And great catch on this one too, I spoke to @jayoung-lee about this and she seemed to be okay with the pop up coming back up after observing its snooze period. But ideally, the more info redirect should have a link to the survey as well (maybe as a banner or something) so that users are not confused about where to go to fill the survey |
Thanks for looping me in! Yes, since the popup would reappear later, I think it would be acceptable to snooze it. (In a perfect scenario, we would prefer to keep the popup open until the developers check the "More Info" and return. However, IIUC, the popups currently disappear after a set duration of time...) I find the idea of adding the surveys link to the "More Info" web page appealing. I'll discuss this with the DevRel team to explore the possibility of automatically updating the page with the JSON file as well! |
That sounds like a good idea to me. One thing to keep in mind is that we probably want to use some kind of query parameter in the "More Info" |
@DanTup additionally, I agree your approach of just passing the string for the action field. Making the updates now. The snippet below is the implementation for the dismiss method and it seemed overkill to have the enum @override
void dismissSurvey({required Survey survey, required bool surveyAccepted}) {
_surveyHandler.dismiss(survey, true);
final status = surveyAccepted ? 'accepted' : 'dismissed';
send(Event.surveyAction(surveyId: survey.uniqueId, status: status));
} |
I believe there's value in transparently listing all the surveys currently running on the website. But you made a valid point. I'll add a string at the end of the survey URL on the website (like (Nevertheless, the sampling remains crucial in preventing excessive annoyance!) |
I don't think I conveyed what I meant very well. My suggestion was to avoid the client needing to do any interpretation of the For example: final survey = (await analytics.fetchAvailableSurveys()).first;
// Client shows the survey and gets back the button the user clicked
final selectedButton = await displaySurvey(survey.title, survey.buttons);
// If the button has a URL, open it
if (selectedButton?.url != null) {
openUrl(selectedButton?.url);
}
// Tell the package which button was clicked
// Package handles reading "action" and recording appropriately
analytics.surveyInteracted(survey, selectedButton); This means in future if you wanted to add a new type of button with a new action, the logic for it (whether it snoozes, dismisses, etc.) could be inside the package. I don't know if it's likely more actions will be added, but it seemed a little cleaner to avoid the client having to interpret the string
Yep, we can't necessarily keep the popup on screen for a long time - however if the user clicks "More Info" we could at least re-show the notification so a) it's visible for the next 10-15 sec, and b) it's still in the notification area if they choose to look in there. If we don't re-show it, then after the user clicks More Info they would have to wait another x hours/days to see the prompt again (and we also wouldn't know if they followed the link from the More Info page, so we might show them the prompt again even though they went through and completed it). So for ex., we could add a field to the buttons to control whether the prompt should "remain visible" after clicking (which for VS code would actually be an explicit re-show):
(we could also use |
Ah I see now. Passing the final survey = (await analytics.fetchAvailableSurveys()).first;
// vvvvvvvvv <-- will this class be the same i pass you?
final SurveyButton selectedButton = await displaySurvey(survey.title, survey.buttons); If the above is possible, then I can refactor to accept the button class instead of an action string {
"buttonText": "More Info",
"action": "moreInfo",
"url": "http://example.org/",
"promptRemainsVisible": true // may not be necessary if we can lump this functionality into the `moreInfo` action
} This also makes sense to me but i'll defer to @jayoung-lee... this seems to be better since we won't have to add more functionality to the website, all we need to do is set aside a button
|
@DanTup would an explicit field on |
Yes, I believe so. Although we're round-tripping via JSON to the client here, the code in the server will just be
My suggestion above for using a separate field was to avoid the client needing to do any interpretation of Again, I'm not sure how useful this would actually be, but I feel like the less logic (such as what each |
Great so I can start to refactor What kind of response can you receive from the JSON when a button is clicked? I can setup a
I see, so instead of interacting with the |
For LSP, the original behaviour was that we just get back the text of the button that was clicked, although LSP now also supports round-tripping arbitrary data with each button, so we can include an
If you provide it we could use it there to save making our own - although it also feels quite LSP-specific, so having the analysis server produce a Map also seems reasonable to me. If you do expose a
Assuming "we" here means the client of the package (eg. analysis server), yep. The survey package presumably would still use the |
Yep, that just reminded me why I did put it in a list originally now; to preserve the correct order. With that consideration, I will have the analytics package just provide a list and leave it up to the client of this package to determine which button was clicked (since this may be different for each client). But I will still have
I agree this is a big value add as well, I'll make the necessary updates to include this new field for each button in the json {
"buttonText": "More Info",
"action": "moreInfo",
"url": "http://example.org/",
"promptRemainsVisible": true
} |
Per the discussion in dart-lang/tools#134 (comment)
Per the discussion in dart-lang/tools#134 (comment)
Okay the updates I mentioned last have been committed. The example code below shows the workflow // Use the analytics instance to fetch the first available survey
final Survey survey = (await analytics.fetchAvailableSurveys()).first;
// Invoke the method to let the analytics package know that the survey
// has been shown
//
// This will automatically "snooze" the first survey so that subsequent windows
// in VS Code don't show the same survey
analytics.surveyShown(survey);
// Client shows the survey and gets back the button the user clicked
//
// For this example, I am assuming you are receiving the index of the button
// that was clicked so i can grab the button from the button list by index
//
// Real implementation may use a Map if needed; we just need to be able to identify
// which button in `survey.buttonList` was selected
final int selectedButtonIndex = await displaySurvey(survey.description, survey.buttonList);
final SurveyButton selectedButton = survey.buttonList[selectedButtonIndex];
// If the button has a URL, open it
if (selectedButton?.url != null) {
openUrl(selectedButton?.url);
}
// Tell the package which button was clicked
// Package handles reading "action" and recording appropriately
analytics.surveyInteracted(survey, selectedButton);
// If the button clicked requires the pop up to remain up
if (selectedButton.promptRemainsVisible) {
// Code to re-prompt the survey and handle the above again
} @DanTup if the above looks good to you, then I can go ahead with the merge. This marks the final merge before we can begin implementation |
void main() async { | ||
late final MemoryFileSystem fs; | ||
late final Analytics analytics; | ||
late final Directory home; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DanTup this example file should make it easier for us to discuss any changes to the workflow (if any are needed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bwilkerson also an fyi if this example file workflow... from the analysis server side, does this workflow make sense to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All sounds good to me!
// Conditional to check what simulating a popup to stay up | ||
if (selectedSurveyButton.promptRemainsVisible) { | ||
print('***This button has its promptRemainsVisible field set to `true` ' | ||
'so this simulates what seeing a pop up again would look like***\n'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is useful info for debugging, printing when there's a URL that would be opened might be useful too.
* Survey handler functionality to fetch available surveys (#91) * Add constant for endpoint that stores metadata json file * Development began on survey handler class with fetch * Update survey_handler.dart * Parsing functionality added in `survey_handler` * Condition class `operator` relabeled to `operatorString` * `Analytics` test and default constructors to use `SurveyHandler` * Refactor + cleanup + error handling * `dart format` fix * Evaluating functionality added to `Analytics` * Format fix * `!=` operator added to `Condition` class * Refactor for fake survey handler to use list of surveys or string * Initial test cases added * Tests added to use json in `FakeSurveyHandler` * Fix nit * Early exit if on null `logFileStats` * Test to check each field in `Survey` and `Condition` * Documentation update * No surveys returned for opted out users * Revert "No surveys returned for opted out users" This reverts commit f6d9f8e. * No surveys for opted out users (#99) * Check `okToSend` before fetching surveys * Added test * dart format fix * Update CHANGELOG.md * Mark as dev * Change version suffix * `dart fix --apply --code=combinators_ordering` * Fix `survey_handler.dart` with new lints * Add'l fixes to survey_handler * Remove left hand types from `analytics.dart` * Fix `survey_handler_test.dart` with new lints * Fix tests with survey_handler class from lint fixes * `dart format` fix * Sampling rate functionality added (#122) * Sampling rate functionality added * Update tests to have 100% sampling rate * Tests added to test sampling rate * Update survey_handler_test.dart * Fix type for `jsonDecode` * New utility function to convert string into integer * Fix tests with new outputs for sample rate * Use uniqueId for survey instead of description * Add hyphen to lookup * Fix documentation * Fix survey handler tests to use new .send method * Fix tests to use new maps for `LogFileStats` * Dismissing and persisting surveys (#127) * Add constant for new file name + clean up session handler Removing NoOp session instance since that was only being used before `2.0.0` * Updating survey handler to create file to persist ids * Revert changes to session handler * Update constant to be a json file * Initialize dismiss surveys file with empty json * Initializer for dismissed file made static * Functionality added to check if survey snoozed or dismissed * Dismiss functionality added * `dismissForDays` -> `dismissForMinutes` * Update survey_handler_test.dart * Clean up external functions to be class methods * Tests added for snoozing and dismissing permanently * Test added for malformed json * Check sample rate before using LogFileStats * Add `surveyShown` API to snooze surveys * Use new URL for survey metadata * Error handling for missing json file * Sample rate example added (#130) * Added example file * Including example's output in code * Update sample_rate.dart * Fix nits * Send event for surveys shown and surveys dismissed (#133) * Added enum and event constructor survey actions * Fix format errors * Using two events for survey shown and survey action * Created mock class to confirm events are sent * Clean up constructors * Fix nits * Refactor for buttons array with `SurveyButton` class (#134) * Added newe `SurveyButton` class * Fix tests * Add documentation for enums * Update sample_rate.dart * Update tests to check for `SurveyButton` classes * Remove enum for status of action * Use `snoozeForMinutes` instead of dismiss * Expose `SurveyButton` * Fixing documentation for event class * Order members in survey handler * Refactor to pass button to `surveyInteracted(..)` * `surveyButtonList` --> `buttonList` renaming * Adding example file for how to use survey handler feature * Adding conditional check for url to display * Format fix * Allow surveys with no conditions to be passed Only checking if `logFileStats` is null if there is a condition in the condition array in the json * Update version * Simplify utility functions for sample rate + check date * `const` constructor for `Survey` unnamed constructor * Fix test to unit test sampling function * Fix dartdocs + check for null outside loop + breaks removed * Add documentation to example files * `dart format` * Catch `TypeError` when parsing json survey * Adding tests for the sampling rate with margin of error
* use package:lints; rev pubspec version * use package:lints 2.0.0 * rev CI to 2.17 * use super initializers
Reference issue:
With the new update to the json to include a button array, this PR includes a new class that will be generated for each button that is listed with a given survey
Example of the json update
Example for how clients using this package can fetch and work with the
SurveyButton
classContribution guidelines:
dart format
.Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.