Skip to content

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

Merged
merged 15 commits into from
Oct 15, 2018

Conversation

toddbeckman-wf
Copy link
Contributor

@toddbeckman-wf toddbeckman-wf commented Jul 31, 2018

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 the module_entered_first_useful_state span is created after the data is loaded. It should have a followsFrom reference to the load_module span with the same startTime and module.instanceId.

@aviary-wf
Copy link

aviary-wf commented Jul 31, 2018

Security Insights

No security relevant content was detected by automated scans.

Action Items

  • Review PR for security impact; comment "security review required" if needed or unsure
  • Verify aviary.yaml coverage of security relevant code

Questions or Comments? Reach out on HipChat: InfoSec Forum.

@codecov-io
Copy link

codecov-io commented Jul 31, 2018

Codecov Report

Merging #115 into master will increase coverage by 0.14%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
lib/src/lifecycle_module.dart 96.65% <100%> (+0.14%) ⬆️
lib/src/timing_specifiers.dart 100% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bd03bf4...4d40242. Read the comment docs.

@@ -0,0 +1,16 @@
class StartupTimingSpecifier {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not my favorite name.

Copy link
Contributor

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.

Copy link
Contributor

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);

@toddbeckman-wf toddbeckman-wf changed the title AF-1540 consumer specified timing AF-1540 + AF-1541 consumer specified timing Aug 1, 2018
static const StartupTimingSpecifier firstReadable =
const StartupTimingSpecifier._('module_entered_first_readable_state');

static const StartupTimingSpecifier firstUseful =
Copy link
Contributor

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?

Copy link
Contributor

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).

Copy link
Contributor Author

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.

@@ -0,0 +1,16 @@
class StartupTimingSpecifier {
Copy link
Contributor

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.

@evanweible-wf
Copy link
Contributor

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;
Copy link
Contributor

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
Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

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,
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

@@ -0,0 +1,16 @@
class StartupTimingSpecifier {
Copy link
Contributor

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);

static const StartupTimingSpecifier firstReadable =
const StartupTimingSpecifier._('module_entered_first_readable_state');

static const StartupTimingSpecifier firstUseful =
Copy link
Contributor

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
Copy link
Contributor

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 {},
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@@ -0,0 +1,16 @@
class StartupTimingSpecifier {
Copy link
Contributor

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

@@ -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);
Copy link
Contributor

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

Copy link
Contributor Author

@toddbeckman-wf toddbeckman-wf Aug 8, 2018

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.

Copy link
Contributor

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();
+  });
 }

@@ -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);
Copy link
Contributor

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

}) {
// Load didn't start
if (_loadContext == null || _startLoadTime == null) {
return null;
Copy link
Contributor

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?

final String name;
const StartupTimingType._(this.name);

static const StartupTimingType firstComponentRender =
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

const StartupTimingType._(this.name);

/// Specifies the completion of the module's first render.
static const StartupTimingType firstComponentRender =
Copy link
Contributor

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?

@toddbeckman-wf toddbeckman-wf changed the title AF-1540 + AF-1541 consumer specified timing AF-1540 consumer-specified timing for "first useful state" Sep 7, 2018
@toddbeckman-wf
Copy link
Contributor Author

toddbeckman-wf commented Sep 7, 2018

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.

///
/// Any [tags] or [references] specified will be added to this span.
@protected
void specifyEnterFirstUsefulStateTiming({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Supercalifragilisticexpialidocious

Copy link
Contributor

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)?

Copy link
Contributor

@greglittlefield-wf greglittlefield-wf left a 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.

///
/// Any [tags] or [references] specified will be added to this span.
@protected
void specifyEnterFirstUsefulStateTiming({
Copy link
Contributor

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)?

@toddbeckman-wf
Copy link
Contributor Author

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

@toddbeckman-wf toddbeckman-wf force-pushed the AF-1540_consumer-specified-timing branch from 911f867 to b2d7551 Compare October 9, 2018 19:53
@greglittlefield-wf
Copy link
Contributor

Sorry, in that conversation I was suggesting just removing the shorthand.

Copy link
Contributor

@greglittlefield-wf greglittlefield-wf left a 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));
Copy link
Contributor

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));

.listen(expectAsync1((span) {
expect(span.startTime, startTime);
})));
module.specifyStartupTiming(specifier);
Copy link
Contributor

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
Copy link
Contributor

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)

@@ -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));
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@toddbeckman-wf toddbeckman-wf changed the title AF-1540 consumer-specified timing for "first useful state" AF-1540 AF-2674 consumer-specified timing for "first useful state" Oct 11, 2018
Copy link
Contributor

@greglittlefield-wf greglittlefield-wf left a 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


In cases like these, use `specifyStartupTiming`:

```
Copy link
Contributor

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.

}

/// 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,
Copy link
Contributor

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;
Copy link
Contributor

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

Copy link
Contributor Author

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);
Copy link
Contributor

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"
Copy link
Contributor Author

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

Copy link
Contributor

@greglittlefield-wf greglittlefield-wf left a comment

Choose a reason for hiding this comment

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

+10

@toddbeckman-wf
Copy link
Contributor Author

@Workiva/release-management-p

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants