-
Notifications
You must be signed in to change notification settings - Fork 16
AF-1540 AF-2674 consumer-specified timing for "first useful state" #115
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
Conversation
Security InsightsNo security relevant content was detected by automated scans. Action Items
Questions or Comments? Reach out on HipChat: InfoSec Forum. |
Codecov Report
@@ Coverage Diff @@
## master #115 +/- ##
==========================================
+ Coverage 96.67% 96.82% +0.14%
==========================================
Files 3 4 +1
Lines 331 346 +15
==========================================
+ Hits 320 335 +15
Misses 11 11
Continue to review full report at Codecov.
|
lib/src/timing_specifiers.dart
Outdated
@@ -0,0 +1,16 @@ | |||
class StartupTimingSpecifier { |
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 my favorite name.
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.
Yeah I would kind of like to see it reference "mark" or "metric", but I can't think of anything good.
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 thinking "mark" or "indicate" for the verb for use in the method name, and then this could be "type" or un-suffixed or something.
markStartupTiming(StartupTiming.firstEditable);
indicateStartupTiming(StartupTimingType.firstEditable);
lib/src/timing_specifiers.dart
Outdated
static const StartupTimingSpecifier firstReadable = | ||
const StartupTimingSpecifier._('module_entered_first_readable_state'); | ||
|
||
static const StartupTimingSpecifier firstUseful = |
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.
Do we have clear documentation around how these should be determined? How is useful different than editable+readable? or maybe even just readable?
This also seems to overlap with our Experience's onEnterIdleState event: https://github.com/Workiva/wdesk_sdk/blob/master/lib/src/experience_framework/experiences/experience.dart#L138
I'm assuming these would be preferred to that?
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.
@evanweible-wf Yeah, I think the intention is to unify or replace that event with this API.
+1 we should have pretty solid use-cases and guidelines for each type before committing to adding them.
Thoughts on starting with just a constant for the equivalent to onEnterIdleState
(i.e., AF-1540 and not AF-1541).
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.
+1 to replacing that event.
The problem with just going with one option now and moving on is introducing a public API that we might change once we support other events. That was the main motivation to combining the PR's. I can undo it easily, but we should keep that in mind.
lib/src/timing_specifiers.dart
Outdated
@@ -0,0 +1,16 @@ | |||
class StartupTimingSpecifier { |
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.
Yeah I would kind of like to see it reference "mark" or "metric", but I can't think of anything good.
I'm a little concerned with how this overlaps with the existing idleState event from Experience, but as long as we have a plan and clear documentation on what should be used (and maybe deprecate idle state?), then I'm okay with this |
_actions.loadData(); | ||
return new Future.value(); | ||
return null; |
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.
Why is this returning null instead of a future?
@@ -52,12 +54,21 @@ enum LifecycleState { | |||
/// Intended to be extended by most base module classes in order to provide a | |||
/// unified lifecycle API. | |||
abstract class LifecycleModule extends SimpleModule with Disposable { | |||
static int _nextId = 0; | |||
// Used by tracing to tell apart multiple instances of the same module |
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.
Looks like this and other comments need to make their way into the lifecycle tracing PR
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.
Is it alright that this PR is already merging them?
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.
It would be nice if they were in the other PR so that work could be reviewed atomically, but it's fine it it's too much effort to make that happen.
.startSpan( | ||
specifier.name, | ||
references: [tracer.followsFrom(_loadContext)], | ||
startTime: _startLoadTime, |
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 be nice if we started this span at the same time as load as opposed to passing in the start time so that we could tie spans into browser marks/measures
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.
Then we would end up with unfinished spans for all modules that don't implement closing these.
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.
Good point, didn't think about that...
...Is it a problem to have unfinished spans?
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.
Each unfinished span leaks a Completer
in the span object and a whenFinished
stream listener in the tracer (App Intelligence for us). A few might go unnoticed, but I'm not comfortable with the possibility that this will leak memory over time if we're automatically starting them for every module and only finishing them for the handful that actually implement stopping these.
lib/src/timing_specifiers.dart
Outdated
@@ -0,0 +1,16 @@ | |||
class StartupTimingSpecifier { |
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 thinking "mark" or "indicate" for the verb for use in the method name, and then this could be "type" or un-suffixed or something.
markStartupTiming(StartupTiming.firstEditable);
indicateStartupTiming(StartupTimingType.firstEditable);
lib/src/timing_specifiers.dart
Outdated
static const StartupTimingSpecifier firstReadable = | ||
const StartupTimingSpecifier._('module_entered_first_readable_state'); | ||
|
||
static const StartupTimingSpecifier firstUseful = |
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.
@evanweible-wf Yeah, I think the intention is to unify or replace that event with this API.
+1 we should have pretty solid use-cases and guidelines for each type before committing to adding them.
Thoughts on starting with just a constant for the equivalent to onEnterIdleState
(i.e., AF-1540 and not AF-1541).
StartupTimingSpecifier specifier, { | ||
Map<String, dynamic> tags: const {}, | ||
}) { | ||
// Load didn't start |
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.
Should this throw a StateError
instead of returning null?
@protected | ||
void specifyStartupTiming( | ||
StartupTimingSpecifier specifier, { | ||
Map<String, dynamic> tags: const {}, |
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.
Can we allow passing in other references
so this reference can be associated with whatever triggered it, if that thing is traced?
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.
There's already followsFrom
reference to the load span.
lib/src/timing_specifiers.dart
Outdated
@@ -0,0 +1,16 @@ | |||
class StartupTimingSpecifier { |
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 class and its constants need doc comments
test/lifecycle_module_test.dart
Outdated
@@ -281,15 +299,22 @@ void main() { | |||
Logger.root.level = Level.ALL; | |||
final StateError testError = new StateError('You should have expected this'); | |||
|
|||
final TestTracer tracer = new TestTracer(); | |||
initGlobalTracer(tracer); | |||
assert(globalTracer() == tracer); |
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.
Same comments as in the last PR around testing and whether a non-null globalTracer is required
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 testing setup for this is being null or not-null pretty tricky. I'll think on it.
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.
could do something like:
main() {
+ void allOfTheLifecycleModuleTests() {
group('LifecycleModule', () {
...
});
+ }
+
+ group('with test tracer set up', () {
+ setUp(() {
+ initGlobalTracer(new TestTracer());
+ });
+
+ allOfTheLifecycleModuleTests();
+ });
+
+ group('with a null global tracer', () {
+ setUp(() {
+ initGlobalTracer(null);
+ });
+
+ allOfTheLifecycleModuleTests();
+ });
}
test/lifecycle_module_test.dart
Outdated
@@ -281,15 +299,22 @@ void main() { | |||
Logger.root.level = Level.ALL; | |||
final StateError testError = new StateError('You should have expected this'); | |||
|
|||
final TestTracer tracer = new TestTracer(); | |||
initGlobalTracer(tracer); | |||
assert(globalTracer() == tracer); |
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.
Same comments as in the last PR around testing and whether a non-null globalTracer is required
lib/src/lifecycle_module.dart
Outdated
}) { | ||
// Load didn't start | ||
if (_loadContext == null || _startLoadTime == null) { | ||
return null; |
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 comment got collapsed without getting addressed:
Should this throw a
StateError
instead of returning null?
lib/src/timing_specifiers.dart
Outdated
final String name; | ||
const StartupTimingType._(this.name); | ||
|
||
static const StartupTimingType firstComponentRender = |
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.
Continuing this unresolved conversation: #115 (comment)
The problem with just going with one option now and moving on is introducing a public API that we might change once we support other events. That was the main motivation to combining the PR's. I can undo it easily, but we should keep that in mind.
I don't think that removing all but one constant vs leaving them as-is puts us at any more risk for changing the public API.
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 comment was in regards to having it be a specific 'useful state' method call versus this enumeration approach.
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.
It's pretty trivial, if we go this enum route, to have just one vs all of them. I don't see the advantage of splitting them up into multiple tickets.
lib/src/timing_specifiers.dart
Outdated
const StartupTimingType._(this.name); | ||
|
||
/// Specifies the completion of the module's first render. | ||
static const StartupTimingType firstComponentRender = |
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.
Continued conversation from #115 (comment)
@toddbeckman-wf Yeah I'm in agreement with the enum approach.
I'm just concerned about us knowing whether the meaning of the different enum values you have here are useful and well-defined. To @evanweible-wf's point here:
Do we have clear documentation around how these should be determined? How is useful different than editable+readable? or maybe even just readable?
I'm also a little confused by the difference between firstUseful
/firstReadable
/firstEditable
We may need more time to discuss, refine, and document how many values we want to have and what they all mean.
While we do that, is there a single value we can to start with, as an MVP?
Continuing the conversation here so it doesn't get collapsed in the code changes: I am okay with starting with just one. The "first useful" state was the one we originally discussed in Hipchat as the first meaningful one, with AF-1541 giving more optional granularity, so that's the one I started with for now. It's easy enough to refactor to whichever other one we want if we change our minds. |
lib/src/lifecycle_module.dart
Outdated
/// | ||
/// Any [tags] or [references] specified will be added to this span. | ||
@protected | ||
void specifyEnterFirstUsefulStateTiming({ |
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.
Supercalifragilisticexpialidocious
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.
Do we want to have shorthands for each one, or should we just have consumers use specifyStartupTiming(StartupTimingType.firstUseful)
?
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.
One question, but still looks good.
lib/src/lifecycle_module.dart
Outdated
/// | ||
/// Any [tags] or [references] specified will be added to this span. | ||
@protected | ||
void specifyEnterFirstUsefulStateTiming({ |
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.
Do we want to have shorthands for each one, or should we just have consumers use specifyStartupTiming(StartupTimingType.firstUseful)
?
Github doesn't let me respond to that comment so I'll just do it here We can have shorthands if we want. I removed the others from this PR so there's nothing else to have shorthands for. However I'd be down for shortening the name by a few hundred characters. Kinda pointless shorthand if it's longer than without lol |
911f867
to
b2d7551
Compare
Sorry, in that conversation I was suggesting just removing the shorthand. |
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.
Could use a little more coverage, otherwise looks good!
@@ -42,6 +44,8 @@ class DataLoadAsyncModule extends Module { | |||
@protected | |||
Future<Null> onLoad() { | |||
// trigger non-blocking async load of data | |||
_events.didLoadData.first | |||
.then((_) => specifyStartupTiming(StartupTimingType.firstUseful)); |
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 will complete with an error if the module is disposed before didLoadData
is emitted.
Not critical to change since it's sample code, but we should probably set a good example.
Instead, you can do:
listenToStream(_events.didLoadData.take(1),
(_) => specifyStartupTiming(StartupTimingType.firstUseful));
test/lifecycle_module_test.dart
Outdated
.listen(expectAsync1((span) { | ||
expect(span.startTime, startTime); | ||
}))); | ||
module.specifyStartupTiming(specifier); |
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 should have test coverage for custom and default tags/references passed into this. (same for any shorthand methods we have).
smithy.yaml
Outdated
@@ -1,8 +1,7 @@ | |||
project: dart | |||
language: dart | |||
|
|||
# dart 1.24.2 | |||
runner_image: drydock-prod.workiva.net/workiva/smithy-runner-generator:203768 | |||
runner_image: google/dart:1.24.3 |
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.
may as well delete this file.. everything has been building on Workiva Build via the Dockerfile for quite some time
(I suspect Rosie is failing her dependency scan because of this not being an allowed Smithy runner image)
…sumer-specified-timing
@@ -42,6 +44,8 @@ class DataLoadAsyncModule extends Module { | |||
@protected | |||
Future<Null> onLoad() { | |||
// trigger non-blocking async load of data | |||
listenToStream(_events.didLoadData.take(1), | |||
(_) => specifyStartupTiming(StartupTimingType.firstUseful)); |
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 don't think it really matters, but just for my own clarification, this could just be specifyFirstUsefulState()
right?
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, either way works. We weren't decided on whether we wanted shorthand at the time the example was written.
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.
+10 w/ some #nits
documentation/tracing.md
Outdated
|
||
In cases like these, use `specifyStartupTiming`: | ||
|
||
``` |
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 could be a Dart code block.
lib/src/lifecycle_module.dart
Outdated
} | ||
|
||
/// Name of the module for identification in exceptions and debug messages. | ||
// ignore: unnecessary_getters_setters | ||
String get name => _defaultName; | ||
|
||
Map<String, dynamic> get _defaultTags => { | ||
'span.kind': 'client', | ||
'module.instanceId': _instanceId, |
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 I know this wasn't changed in this PR, but should this be .instance_id
, not .instanceId
?
class DataLoadAsyncStore extends Store { | ||
DataLoadAsyncActions _actions; | ||
List<String> _data; | ||
bool _isLoading; | ||
DataLoadAsyncEvents _events; |
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 this is never disposed
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.
Added
_store = new DataLoadAsyncStore(_actions, _events); | ||
_components = new DataLoadAsyncComponents(_actions, _store); | ||
|
||
[_actions, _events, _store].forEach(manageDisposable); |
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 actions and events classes aren't Disposables
type 'DataLoadAsyncActions' is not a subtype of type 'Disposable' of 'disposable' where
DataLoadAsyncActions is from http://localhost:8087/panel/modules/data_load_async_module.dart
Disposable is from package:w_common/src/common/disposable.dart
dart_style: ^0.2.16 | ||
dartdoc: ^0.9.0 | ||
dart_style: ^1.0.7 | ||
dartdoc: ">=0.13.0 <0.16.0" |
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.
Copied from wdesk_sdk. Shouldn't affect OSS consumers since it's a dev dep
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.
+10
@Workiva/release-management-p |
Note
Merges on #114 . Clean diff: tracing_for_lifecycle_transitions...AF-1540_consumer-specified-timing
Description
Allow consumers to specify when the module is actually in a useful state.
Testing
See the
DataLoadAsync
example and verify that themodule_entered_first_useful_state
span is created after the data is loaded. It should have afollowsFrom
reference to theload_module
span with the samestartTime
andmodule.instanceId
.