Skip to content
This repository has been archived by the owner. It is now read-only.

feat(devices): add devices modal and additional metrics #4131

Merged
merged 7 commits into from Sep 19, 2016
Merged

feat(devices): add devices modal and additional metrics #4131

merged 7 commits into from Sep 19, 2016

Conversation

@vladikoff
Copy link
Contributor

@vladikoff vladikoff commented Sep 8, 2016

Fixes #4124

@vladikoff
Copy link
Contributor Author

@vladikoff vladikoff commented Sep 15, 2016

@shane-tomlinson @vbudhram

Still WIP but you can play around with the fancy modals:

dev-rm

@vladikoff
Copy link
Contributor Author

@vladikoff vladikoff commented Sep 16, 2016

@@ -30,11 +31,12 @@ define(function (require, exports, module) {
*
* @param {object} inputEl - input element whose placeholder
* should be shown.
* @param {String} text - custom text for the floating label.

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

Super pedantic nit, can you add [] around text since it's optional?

@@ -90,6 +92,18 @@ define(function (require, exports, module) {
this.focusFloatingPlaceholder($inputEl);
},

/**
* The ridiculous name is to avoid collisions with

This comment has been minimized.

floatingPlaceholderMixinOnSelect: function (event) {
var $inputEl = $(event.currentTarget);
this.showFloatingPlaceholder($inputEl, $inputEl.find('option:first').text());
this.focusFloatingPlaceholder($inputEl);

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

I realize focusFloatingPlaceholder was already there, but it seems misnamed because the placeholder element itself isn't being focused, only the styles are updated. I also can't think of a better name.

@@ -20,8 +20,9 @@ define(function (require, exports, module) {
},

events: {
'click .avatar-panel #back': BaseView.preventDefaultThen('_returnToAvatarChange'),
'click .cancel': BaseView.preventDefaultThen('_closePanelReturnToSettings')
'click .cancel': BaseView.preventDefaultThen('_closePanelReturnToSettings'),

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

I don't see BaseView referenced anywhere else in this module. Maybe change the BaseView reference on line 13 to preventDefaultThen and use that reference here to make each line slightly more concise.

@@ -45,6 +46,12 @@ define(function (require, exports, module) {
this.closePanel();
},

_closePanelReturnToClients: function () {
this.navigate('settings/clients');

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

I understand why you did this, and makes sense, but I fear we are going to end up with a generic mixin with a lot of view specific routes and logic. I can easily imagine us adding a #back button to a non-avatar related view in the future and not understanding why clicking the button keeps going to settings/avatar/change. Can we generalize somehow, either by embedding the next screen view as an attribute on the button, or making the back buttons links whose href is set to the next step?

This comment has been minimized.

@vladikoff

vladikoff Sep 16, 2016
Author Contributor

Yea we can do a lot of refactoring here. Follow up issue okay?

This comment has been minimized.

@vladikoff

vladikoff Sep 19, 2016
Author Contributor

Filed here: #4166

this.reasonHelp = REASON_HELP[selectedValue];
if (item.get('isCurrentDevice')) {
// if disconnected the current device then sign out
this._closePanelReturnToClients();

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

Calling _closePanelReturnToClients then navigateToSignIn will call this.navigate twice. What happens if you just call this.navigateToSignIn, that should close the panel, shouldn't it?

this.toDisconnect = false;
this.reasonHelp = REASON_HELP[selectedValue];
if (item.get('isCurrentDevice')) {
// if disconnected the current device then sign out

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

If the user destroyed the current device, the user model will clear the signed in account and notify the browser. This comment should probably read if disconnected the current device, the user is automatically signed out.

This comment has been minimized.

@vladikoff

vladikoff Sep 16, 2016
Author Contributor

👍

* Closes the panel if device was disconnected.
*/
closePanelIfDisconnected () {
if (! this.toDisconnect) {

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

I wonder if the logic would be more clear if toDisconnect was renamed to hasDisconnected and the boolean checks reversed?

This comment has been minimized.

@vladikoff

vladikoff Sep 16, 2016
Author Contributor

👍


/**
* Called on panel interaction.
* Closes the panel if device was disconnected.

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

I'm a bit confused by how this works, does the user disconnect and then have to click on anything for the panel to close?

This comment has been minimized.


return this.user.destroyAccountClient(this.user.getSignedInAccount(), item)
.then(() => {
// user has disconnect the device

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

Once the client is destroyed, do you need to remove it from the clients collection passed in from the clients view?

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

No, no you don't, I remembered the Clients collection listens for destroy events and does the right thing.

const WindowMock = require('../../../mocks/window');

describe('views/settings/client_disconnect', () => {
var metrics;

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

You can use let on all these! Can you sort them too?


// test cancel
.then(click('.cancel-disconnect'))
.then(FunctionalHelpers.pollUntil(function () {

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

It looks like you are waiting for client-disconnect to go away. I've always looked for a reason to use leadfoot's waitForDeletedByCssSelector. Valid use?

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

Even if waitForDeletedByCssSelector doesn't work, I see this pattern repeated several times, it's probably worth creating a helper function in lib/helpers.js and using that.

This comment has been minimized.

@vladikoff

vladikoff Sep 16, 2016
Author Contributor

+1 to add a helper, in the past waitForDeletedByCssSelector. did not work well, pollUntil makes more sense.

.then(click('.client:nth-child(2) .client-disconnect'))
.then(click('select.disconnect-reasons > option[value="lost"]'))

// wait until button is enabled

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

is enabled or has gone away? Can waitForDeletedByCssSelector be used here too?

This comment has been minimized.

@vladikoff

vladikoff Sep 19, 2016
Author Contributor

enabled


.then(click('#client-disconnect .primary'))

.then(click('#client-disconnect .reason-help'))

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

What should clicking help do here?

This comment has been minimized.

return this.navigate('settings/clients');
}

this.item = clients.get(deviceId);

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

Instead of this.item, would this.client be easier to follow?

// receive the device collection and the item to delete
// if deleted the collection will be automatically updated in the settings panel.
let clients = this.model.get('clients');
let deviceId = this.model.get('itemId');

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

Niether clients nor itemId are used past this point, would it be simpler to just pass in the expected client from clients.js?

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

If not, itemId could stand to be renamed to clientId.

This comment has been minimized.

@vladikoff

vladikoff Sep 19, 2016
Author Contributor

"clientId" 👍

Copy link
Member

@shane-tomlinson shane-tomlinson left a comment

Overall this PR is pretty solid, nothing worrying. Most of my comments try to make the code more concise, or easier to understand, or just remove duplicate logic that has already been generalized.

I have not yet tested this manually.

// if a device then ask for confirmation
if (clientType === Constants.CLIENT_TYPE_DEVICE) {
this.navigate('settings/clients/disconnect', {
clients: this._attachedClients,

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

This seems to be where it'd be simpler to just pass in the client since neither clients nor the id are used for anything except fetching the client.

This comment has been minimized.

@vladikoff

vladikoff Sep 16, 2016
Author Contributor

Yeah I had it working that away initially, but client must be destroyed from the clients collection. Otherwise it will be calling Backbone sync on destroy and XHR our: http://backbonejs.org/#Model-destroy and also this helps with: https://github.com/mozilla/fxa/pull/181/files#diff-9d765e5787806afc80bdaa86191886a8R199 ( After device removal has been confirmed, the dialog closes and, the row is overtaken by the area beneath it)

*
* @param {Event} event
*/
selectOption (event) {

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

Should trying to submit the form print a tooltip if no option has been selected?

This comment has been minimized.

@vladikoff

vladikoff Sep 16, 2016
Author Contributor

We can refine that later if needed IMHO

*/
selectOption (event) {
let optionIndex = this.$el.find(event.currentTarget).find(':selected').index();
if (optionIndex === 0) {

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 16, 2016
Member

I think this function and it's attached event could possibly be removed by overriding isValidStart -

isValidStart () {
  return this.$(':selected').index() > 0;
},

My theory is the change form event handler will call enableSubmitIfValid which will call isValidStart and do the right thing.

@shane-tomlinson
Copy link
Member

@shane-tomlinson shane-tomlinson commented Sep 16, 2016

Some more questions/comments.

The "disconnect reason" screen is not keyboard accessible. Check out this video:

no-keyboard-accessibility

Can the option elements be styled? Something tells me no and we may have to use some progressive enhancement.

screen shot 2016-09-16 at 13 42 39

Once a disconnection is successful, if the user is shown "help text" it's not obvious how to close the screen. Keyboard users have no way of doing so.

screen shot 2016-09-16 at 13 48 20

screen shot 2016-09-16 at 13 54 31

The second point on the help text, both include "You should change your Firefox Account password" but no easy way to do so is proviced. Should we link to /settings/change_password to make this easy?

Should a user who is currently going through signin confirmation be listed even though they have not yet synced?

screen shot 2016-09-16 at 13 49 54

It's fun to play with on multiple browsers and see how quickly the user is disconnected!

@vladikoff
Copy link
Contributor Author

@vladikoff vladikoff commented Sep 16, 2016

Can the option elements be styled? Something tells me no and we may have to use some progressive enhancement.

Maybe later after Phase 1 deploy

Once a disconnection is successful, if the user is shown "help text" it's not obvious how to close the screen. Keyboard users have no way of doing so.

Just following the feature doc here. We can add keyboard close in this version though

The second point on the help text, both include "You should change your Firefox Account password" but no easy way to do so is proviced. Should we link to /settings/change_password to make this easy?

After phase 1 probably

Should a user who is currently going through signin confirmation be listed even though they have not yet synced?

Post phase 1 but there is an issue mozilla/fxa#186

Copy link
Contributor

@vbudhram vbudhram left a comment

Looks really cool! Just a few minor things from me.

* @param {Event} event
*/
floatingPlaceholderMixinOnSelect: function (event) {
var $inputEl = $(event.currentTarget);

This comment has been minimized.

@vbudhram

vbudhram Sep 16, 2016
Contributor

Should this be const? Noticed file uses var as well, but I think they could be updated to use const. File is not to big, so might be worthwhile to make consistent?

text-align: left;
}

html[dir='rtl'] & {

This comment has been minimized.

@vbudhram

vbudhram Sep 16, 2016
Contributor

👍

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

Was this a problem before with the old panels?

it('has props', () => {
view.beforeRender();
assert.ok(view.context().deviceName);
assert.ok(view.context().toDisconnect);

This comment has been minimized.

@vbudhram

vbudhram Sep 16, 2016
Contributor

Looks like context returns reasonHelp as well, could add a check.

return view.render().then(() => {
assert.ok(view.disableForm.calledOnce);
assert.notOk(view.enableForm.calledOnce);
assert.ok($(view.el).find('.primary.disabled').length, 'disabled button at first');

This comment has been minimized.

@vbudhram

vbudhram Sep 16, 2016
Contributor

nit: disabled button at first wording threw me off a little. Maybe some like submit button disabled or has disabled class?

return view.submit().then(() => {
assert.notOk(view.toDisconnect);
assert.ok(view.render.calledOnce, 'not rendered, current device');
assert.ok(TestHelpers.isEventLogged(metrics, 'settings.clients.disconnect.submit.suspicious'));

This comment has been minimized.

@vbudhram

vbudhram Sep 16, 2016
Contributor

Mind updating the metrics docs with new ones?

@ryanfeeley
Copy link
Contributor

@ryanfeeley ryanfeeley commented Sep 19, 2016

@vladikoff Don't forget the … after Disconnect… as specificed in the doc.

Now needed as what follows is revocable.

@vladikoff
Copy link
Contributor Author

@vladikoff vladikoff commented Sep 19, 2016

@vladikoff
Copy link
Contributor Author

@vladikoff vladikoff commented Sep 19, 2016

Travis not showing status but it is here: https://travis-ci.org/mozilla/fxa-content-server/pull_requests

Copy link
Contributor

@vbudhram vbudhram left a comment

@vladikoff Just a couple notes. It was a little jarring when I disconnected only device and got signed out. Almost feel like there should be a prompt Yo dwag, you are disconnecting the current session. You sure?. That shouldn't block PR though.

Otherwise, r+ from me!

<label class="label-helper"></label>
<div class="select-row">
<select class="disconnect-reasons">
<option value="reason">{{#t}}Reason for disconnecting...{{/t}}</option>

This comment has been minimized.

@vbudhram

vbudhram Sep 19, 2016
Contributor

Placeholder text does not disappear when going back to default option. Is this intended, I can see it either way.
screen shot 2016-09-19 at 4 11 56 pm

@vladikoff
Copy link
Contributor Author

@vladikoff vladikoff commented Sep 19, 2016

@vladikoff Just a couple notes. It was a little jarring when I disconnected only device and got signed out. Almost feel like there should be a prompt Yo dwag, you are disconnecting the current session. You sure?. That shouldn't block PR though.

Yea that's a good point. Maybe in the future we can tweak the messaging for 'current' device disconnect.

Copy link
Member

@shane-tomlinson shane-tomlinson left a comment

This is in great shape! Only two changes requested:

  • Use … to let the font handle ellipsis
  • Add the suffix possibilities to settings.clients.[clientType].disconnect

Otherwise, looks great, tests well. Can you file follow-on issues for keyboard accessibility, styling of the select, possibly showing a close button on the "help text" to make it obvious what next steps are, and allow users to click on a "change password" link from the help text to make that path simpler?

r+ with minor nits addressed.

<label class="label-helper"></label>
<div class="select-row">
<select class="disconnect-reasons">
<option value="reason">{{#t}}Reason for disconnecting...{{/t}}</option>

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

@ryanfeeley - should this be ... or &hellip;? &hellip; will render the font's ellipsis.

@@ -0,0 +1,40 @@
<div id="client-disconnect">
<section class="modal-panel">
{{^hasDisconnected}}

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

I like the hasDisconnected

@@ -26,7 +26,7 @@
{{^isCurrentDevice}}
<div class="last-connected">{{lastAccessTimeFormatted}}</div>
{{/isCurrentDevice}}
<button class="settings-button warning client-disconnect" data-id="{{id}}" data-type="{{clientType}}">{{#t}}Disconnect{{/t}}</button>
<button class="settings-button warning client-disconnect" data-id="{{id}}" data-type="{{clientType}}">{{#t}}Disconnect{{/t}}</button>

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

It looks like here you've used the unicode ellipsis. Should this be &hellip;?

@@ -63,11 +65,11 @@ define(function (require, exports, module) {
}
},

focusFloatingPlaceholder: function (inputEl) {
focusLabelHelper: function (inputEl) {

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

👍 - dig the name change.


const REASON_SELECTOR = '.disconnect-reasons';
const REASON_HELP = {
'lost': t('We\'re sorry to hear about this. You should change your Firefox Account password, and look for ' +

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

Thanks for checking.

* @returns {Boolean}
*/
isValidStart () {
return this.$(':selected').index() > 0;

This comment has been minimized.

* Closes the panel if device was disconnected.
*/
closePanelAfterDisconnect () {
if (this.hasDisconnected) {

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

The inversion of variable semantics from this.needsDisconnect to this.hasDisconnected makes this much easier to read.

text-align: left;
}

html[dir='rtl'] & {

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

Was this a problem before with the old panels?


#### settings/clients

* settings.clients.[clientType].disconnect - user is attempting to disconnect a client type

This comment has been minimized.

@shane-tomlinson

shane-tomlinson Sep 19, 2016
Member

Can you add the suffix's you have above - old, no, lost, suspicious?

This comment has been minimized.

@vladikoff

vladikoff Sep 19, 2016
Author Contributor

Added

* @param {Number} [timeout]
* Timeout to wait until element is gone
*/
function pollUntilGoneByQSA(selector, timeout) {

This comment has been minimized.

@vladikoff
Copy link
Contributor Author

@vladikoff vladikoff commented Sep 19, 2016

Was this a problem before with the old panels?

( I cannot reply to that comment inline the review, sent GitHub a support request).

Anyway, it was used for avatar-modal, I moved it out of avatar css because it is not only used for avatars now.

@vladikoff vladikoff merged commit aed667e into master Sep 19, 2016
4 checks passed
4 checks passed
ci/circleci Your tests passed on CircleCI!
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details
coverage/coveralls Coverage remained the same at 96.769%
Details
@shane-tomlinson shane-tomlinson deleted the i4124 branch Sep 20, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants