refactor(client): move flow model resposibilities out of the views #4718
Conversation
Assigning this to me, because I mention wanting to do this over in this PR that I opened today and forgot @philbooth was already on the case. |
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 is moving in the right direction! No problems that I can see, just questions.
app/scripts/lib/metrics.js
Outdated
*/ | ||
_logFlowEvent (data) { | ||
if (! this._flowModel) { | ||
// If there is no flow model, we're not in a recognised flow |
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.
How would "not being in a recognised flow" happen?
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 condition dates from pre-#4676 actually, so the comment should probably be slightly different now. I think the only case now is if the flow model hasn't initialised for some reason (so the DOM attributes are missing). I'll test to make sure my understanding is correct, then update the comment.
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.
Oh, this is also for the case where a user lands on a non-flow-initialising view. Only views that have flow-events-mixin
initialise the flow model, which is what stops flow events from suddenly being emitted when a user lands on /settings
, say. I've updated the comment to try and make this clearer.
app/scripts/lib/metrics.js
Outdated
* @param {Boolean} [data.once] | ||
* If set, emit this event via the `logEventOnce` method. | ||
* Defaults to `false`. | ||
*/ |
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.
nit: Can you add a @private
JSDoc tag too?
app/scripts/lib/metrics.js
Outdated
* @param {Object} data | ||
* Event data. | ||
* | ||
* @param {String} data.event |
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.
We haven't used this format of JSDoc (with blank lines between the parameters), but I can see some advantages.
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.
bike shed - I'm not convinced on introducing a new JSDoc format. It's more readable in ways, but also requires thought when scanning.
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.
Okay, I'll revert this bit. Fwiw, I dislike our preferred style because the param name collides with the description text when you're viewing the code. I find it harder to read.
@@ -36,7 +36,6 @@ define(function (require, exports, module) { | |||
// ephemeral properties like unwrapBKey and keyFetchToken | |||
// that need to be sent to the browser. | |||
this._account = this.user.initAccount(this.model.get('account')); | |||
this.flow = this.model.get('flow'); |
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.
Removals like this are awesome, there were times I scratched my head and asked myself "What's this here for?"
const flowBegin = this.flow.get('flowBegin'); | ||
|
||
this.metrics.logFlowBegin(flowId, flowBegin); | ||
this.logFlowEventOnce('begin'); |
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.
I was just looking at the function signature of logFlowEventOnce
- does it matter that no viewName is passed in? Should the viewName
default to this.viewName
in logFlowEventOnce
and logFlowEvent
?
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.
The flow.begin
event doesn't interpolate the view name by design, because it makes the funnel queries easier (and faster). It's possible some other flow events in the future may not interpolate either. Anyway, for that reason, viewName
is optional.
account: account, | ||
flow: this.flow | ||
}); | ||
return this.navigate('confirm_signin', { account }); |
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.
Just for my own understanding, the flow model is now taken care of by the metrics
model, so no need to pass it from flow to flow?
What did the flow
model provide to the confirmation screen? I was confused why confirm had a reference to it before, was it for the ResumeTokenMixin?
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.
Yep, it was being propagated between views so that flow events would work everywhere we needed them to. That act of explicitly passing it between views is what led to this refactor. The changes were getting out of hand and, not sure if you remember, I tried to refactor it to the base view but couldn't get that to work. Then BANG! Flash of inspiration, I realised we could lean on events to make the pain go away.
} | ||
|
||
const flowModel = new Flow({ | ||
sentryMetrics: this._sentryMetrics, |
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.
Not for this PR - I wonder if we can remove the _sentryMetrics
reference from the Flow model and pass it a notifier instead, which would also mean the metrics model would no longer need a reference to _sentryMetrics
. Whenever the Flow model needs to record an error, it could trigger a message on the notifier that makes its way to both the normal metrics module and Sentry. Lots of ✋ 👋 (hand-waving) here, but it seems feasible.
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.
I already opened #4639 for exactly this reason. Didn't want to do it in this PR though.
logFlowEvent (eventName, viewName) { | ||
this.metrics.logFlowEvent(eventName, viewName); | ||
logFlowEvent (eventName, viewName, data) { | ||
this.notifier.trigger('flow.event', _.assign({}, data, { |
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.
Not for this PR, but it looks like logView, logEvent, logEventOnce, and logViewEvent could all use the same treatment.
2b2d8dc
to
85533a6
Compare
A distinct lack of r+ though. Let me know what else is needed to make you happy! 😄 |
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.
Two minor changes requested, one to rename metrics.init
to metrics.initialize
, and one to use a consistent JSDoc format.
r+, this is going to allow a lot of further improvements @philbooth.
@@ -129,6 +133,8 @@ define(function (require, exports, module) { | |||
this._utmSource = options.utmSource || NOT_REPORTED_VALUE; | |||
this._utmTerm = options.utmTerm || NOT_REPORTED_VALUE; | |||
this._xhr = options.xhr || xhr; | |||
|
|||
this.initialize(options); |
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 module already has an init function that is called from app-start. Having both init
and initialize
is confusing. init
is the odd one out compared to the rest of the repo. Going along with the never settle theme, instead of introducing a similar sounding method that's called from a different place, can you rename init
to initialize
and remove the call to metrics.init()
in app-start? Cocktail should take care of resolving the conflicting methods by calling them both.
app/scripts/lib/metrics.js
Outdated
* @param {Object} data | ||
* Event data. | ||
* | ||
* @param {String} data.event |
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.
bike shed - I'm not convinced on introducing a new JSDoc format. It's more readable in ways, but also requires thought when scanning.
85533a6
to
c2df268
Compare
c2df268
to
baefe77
Compare
Thanks @shane-tomlinson! |
Fixes #4686.
What's this?
A refactoring PR that includes work from the abandoned #4681 and tries to address the outstanding feedback from that PR.
What does it do?
It decouples the flow model from the views, instead moving responsibility for its management into the metrics object and leaning on events to communicate with it.
What outstanding feedback has been addressed?
In #4681 (comment), @shane-tomlinson said the metrics object should use the notifier view mixin instead of manually registering the event handlers for
flow.initialize
andflow.event
itself.Why didn't you just do that in the original PR?
Partly because of PR-fatigue and partly because I wasn't sure how to extract the view mixin for wider re-use away from the views.
And how did you extract that view mixin?
I moved it to
lib/channels/notifier-mixin.js
, so that it lives next to the notifier object on which it operates. Apart from that it's unchanged.How come so many other files changed?
It turns out a lot of unit tests were creating the metrics object, and all those references had to be updated to include the notifier because the notifier mixin expects it to be present. The assertions themselves haven't changed.
Why are there two commits?
Because I wasn't sure about the mixin change and that is responsible for all the unit test changes. I wanted to be able to revert all those easily if it turns out I did it wrong. If/when it comes to merging this, the two commits can be squashed, no probs.
@mozilla/fxa-devs r?