-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Adds mechanism to include feature flags in Polymer HTTP requests #5727
Conversation
Add a client_feature_flags property to the DataProvider's RequestContext object. Also add a middleware that extracts the feature flags from the HTTP request and sets them on the RequestContext. client_feature_flags represents feature flags set in the TB webapp client. They will be injected into many HTTP requests originating from the webapp and will be of the form: X-TensorBoard-Feature-Flags: JSON.stringify(currentFeatureFlags) We expose these values as a Dict in the RequestContext object, with string keys and values of arbitrary types (but, in practice, mostly booleans and sometimes arrays of strings). Usage of client_feature_flags should be aware of the key names and the value types. (Googlers, see: http://go/tb-client-feature-flags-in-data-provider for motivation, more details, and high level design.) #5718 and #5727 are complimentary PRs that set the feature flags in requests originating from the Angular and Polymer code bases, respectively.
@bmd3k for when you're back - is this ready for review? (nobody is currently assigned) |
Thanks! Assigned to James since he reviewed similar changes for HTTP requests from Angular. |
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.
Ok these comments are mostly just questions. I am confused about the scope and ownership of the _featureFlags object. I decided not to approve yet because I don't think I can properly review without understanding a bit more of how this works.
// base. In practice they are set one time soon after application load and never | ||
// again for the lifetime of the application. | ||
let _featureFlags: FeatureFlags | null; | ||
initializeFeatureFlags(); |
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 seems strange. So _featureFlags is initialized by this call in the application but this is called manually in tests? Is this s typical structure in the polymer code?
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'm not sure if it is typical. Without testing I would have likely just merged lines 21 to 26 into the following one line:
let _featureFlags: FeatureFlags | null = null;
But I added the initializeFeatureFlags() function to facilitate testing. Unfortunately we don't really test much of the polymer code so I just picked a pattern that worked fo rme.
*/ | ||
@customElement('tf-feature-flags') | ||
class TfFeatureFlags extends PolymerElement { | ||
override _template = 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 means that this is an element with no UI component?
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.
Honestly I don't know. I'm following the pattern established for similar files, like:
https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/components/tf_globals/globals-polymer.ts
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.
Yes, that's what it means. The more above-board way to do this would be the static method described here:
https://polymer-library.polymer-project.org/3.0/docs/devguide/dom-template#elements-with-no-shadow-dom
Overriding _template
looks like it's doing that at a lower level, probably because that's the legacy approach: https://github.com/Polymer/polymer/blob/1e8b246d01ea99adba305ea04c45d26da31f68f1/lib/mixins/element-mixin.js#L476-L515
@customElement('tf-feature-flags') | ||
class TfFeatureFlags extends PolymerElement { | ||
override _template = null; | ||
tf_feature_flags = tf_feature_flags; |
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.
What does this even do? Assigning a variable to itself?
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.
Again, sorry, honestly I don't know. I'm following the pattern established for similar files, like:
https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/components/tf_globals/globals-polymer.ts
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 might look confusing but syntactically it's not assigning a variable to itself; it's defining a property on the element and assigning its value to be the imported module from import * as tf_feature_flags from './feature-flags';
You can see how this mechanism works a bit better with the existing tf_globals code:
-
We create a
tf-globals
custom element that basically just holds atf_globals
property that represents the symbols from the Polymer static utilities module: https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/components/tf_globals/globals-polymer.ts -
We define a type for this property so that the TypeScript code on the Angular side can access it in a typesafe manner: https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/webapp/tb_polymer_interop_types.ts;l=30-32;drc=046667d00df2316eefcda9a4ef058e83221bad77
-
In the Angular code where we actually need to call the
tf_globals
static utilities, we create a custom element, then access itstf_globals
property: https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/webapp/deeplink/hash.ts;l=30-35;drc=367839f30de3968eff5fecb8b38e1d5782434b6b
import {State} from '../store/feature_flag_types'; | ||
|
||
const effectsInitialized = createAction('[FEATURE FLAG] Effects Init'); | ||
|
||
@Injectable() | ||
export class FeatureFlagEffects { | ||
// Ngrx assumes all Effect classes have properties that inherit from the base | ||
// JS Object. `tf_feature_flags` does not, so we wrap it. | ||
private readonly tfFeatureFlags = { |
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.
does this mean that this object is owned by the FeatureFlagEffects class?
Does that |initializeFeatureFlags| function get called when creating this element? Is that what the self assigned variable does?
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 also quite a bit of a mystery to me but I believe this just exposes the interop leayer (tf-feature-flags) to the effects class. I'm not certain at what point, exactly, the class gets initialized.
I'm following the pattern established here:
https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/webapp/core/effects/core_effects.ts;l=90;drc=414434e1b632a569da5a4921db400de0750857b0
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.
You will probably find my responses unsatisfying. The understanding of how or why this interop mechanism works is lost with Stephan. I'm following the pattern established by other Angular<->Polymer interop components, a list of which you can find here, if you'd like to dig more into it:
// base. In practice they are set one time soon after application load and never | ||
// again for the lifetime of the application. | ||
let _featureFlags: FeatureFlags | null; | ||
initializeFeatureFlags(); |
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'm not sure if it is typical. Without testing I would have likely just merged lines 21 to 26 into the following one line:
let _featureFlags: FeatureFlags | null = null;
But I added the initializeFeatureFlags() function to facilitate testing. Unfortunately we don't really test much of the polymer code so I just picked a pattern that worked fo rme.
*/ | ||
@customElement('tf-feature-flags') | ||
class TfFeatureFlags extends PolymerElement { | ||
override _template = 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.
Honestly I don't know. I'm following the pattern established for similar files, like:
https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/components/tf_globals/globals-polymer.ts
@customElement('tf-feature-flags') | ||
class TfFeatureFlags extends PolymerElement { | ||
override _template = null; | ||
tf_feature_flags = tf_feature_flags; |
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.
Again, sorry, honestly I don't know. I'm following the pattern established for similar files, like:
https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/components/tf_globals/globals-polymer.ts
import {State} from '../store/feature_flag_types'; | ||
|
||
const effectsInitialized = createAction('[FEATURE FLAG] Effects Init'); | ||
|
||
@Injectable() | ||
export class FeatureFlagEffects { | ||
// Ngrx assumes all Effect classes have properties that inherit from the base | ||
// JS Object. `tf_feature_flags` does not, so we wrap it. | ||
private readonly tfFeatureFlags = { |
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 also quite a bit of a mystery to me but I believe this just exposes the interop leayer (tf-feature-flags) to the effects class. I'm not certain at what point, exactly, the class gets initialized.
I'm following the pattern established here:
https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/webapp/core/effects/core_effects.ts;l=90;drc=414434e1b632a569da5a4921db400de0750857b0
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.
Thanks for the help understanding these pieces Nick. This LGTM.
// base. In practice they are set one time soon after application load and never | ||
// again for the lifetime of the application. | ||
let _featureFlags: FeatureFlags | null; | ||
initializeFeatureFlags(); |
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 seems strange. So _featureFlags is initialized by this call in the application but this is called manually in tests? Is this s typical structure in the polymer code?
…ow#5721) Add a client_feature_flags property to the DataProvider's RequestContext object. Also add a middleware that extracts the feature flags from the HTTP request and sets them on the RequestContext. client_feature_flags represents feature flags set in the TB webapp client. They will be injected into many HTTP requests originating from the webapp and will be of the form: X-TensorBoard-Feature-Flags: JSON.stringify(currentFeatureFlags) We expose these values as a Dict in the RequestContext object, with string keys and values of arbitrary types (but, in practice, mostly booleans and sometimes arrays of strings). Usage of client_feature_flags should be aware of the key names and the value types. (Googlers, see: http://go/tb-client-feature-flags-in-data-provider for motivation, more details, and high level design.) tensorflow#5718 and tensorflow#5727 are complimentary PRs that set the feature flags in requests originating from the Angular and Polymer code bases, respectively.
…orflow#5727) This adds a mechanism that dumps the client-side FeatureFlag object into each HTTP request that originates from the TB Polymer code base. (Googlers, see: http://go/tb-client-feature-flags-in-data-provider for the motivation and high level design.) See tensorflow#5718 for the corresponding change for Angular and tensorflow#5721 for server-side support. The data is dumped into a custom header. The format is effectively: X-TensorBoard-Feature-Flags: JSON.stringify(currentFeatureFlags) We implement this directly in the RequestManager object, through which all our Polymer HTTP requests are routed. Additional things worth noting: * I added a const for the header name to be shared between the HttpInterceptor and the RequestManager. * I added a new angular<->polymer interop object, tf-feature-flags, modeled after some of the other interop objects we've added in the past. This interop object should now be the source of truth for accessing feature flags from the polymer code base. It is populated by feature_flags_effects soon after we fetch feature flag overrides from the data source. * This will send ALL feature flags in the request header rather than a curated subset of them, just like in tensorflow#5718. In a subsequent PR, we'll add support to mark certain feature flags as "sendToServer" and only send that subset.
…ow#5721) Add a client_feature_flags property to the DataProvider's RequestContext object. Also add a middleware that extracts the feature flags from the HTTP request and sets them on the RequestContext. client_feature_flags represents feature flags set in the TB webapp client. They will be injected into many HTTP requests originating from the webapp and will be of the form: X-TensorBoard-Feature-Flags: JSON.stringify(currentFeatureFlags) We expose these values as a Dict in the RequestContext object, with string keys and values of arbitrary types (but, in practice, mostly booleans and sometimes arrays of strings). Usage of client_feature_flags should be aware of the key names and the value types. (Googlers, see: http://go/tb-client-feature-flags-in-data-provider for motivation, more details, and high level design.) tensorflow#5718 and tensorflow#5727 are complimentary PRs that set the feature flags in requests originating from the Angular and Polymer code bases, respectively.
…orflow#5727) This adds a mechanism that dumps the client-side FeatureFlag object into each HTTP request that originates from the TB Polymer code base. (Googlers, see: http://go/tb-client-feature-flags-in-data-provider for the motivation and high level design.) See tensorflow#5718 for the corresponding change for Angular and tensorflow#5721 for server-side support. The data is dumped into a custom header. The format is effectively: X-TensorBoard-Feature-Flags: JSON.stringify(currentFeatureFlags) We implement this directly in the RequestManager object, through which all our Polymer HTTP requests are routed. Additional things worth noting: * I added a const for the header name to be shared between the HttpInterceptor and the RequestManager. * I added a new angular<->polymer interop object, tf-feature-flags, modeled after some of the other interop objects we've added in the past. This interop object should now be the source of truth for accessing feature flags from the polymer code base. It is populated by feature_flags_effects soon after we fetch feature flag overrides from the data source. * This will send ALL feature flags in the request header rather than a curated subset of them, just like in tensorflow#5718. In a subsequent PR, we'll add support to mark certain feature flags as "sendToServer" and only send that subset.
This adds a mechanism that dumps the client-side FeatureFlag object into each HTTP request that originates from the TB Polymer code base.
(Googlers, see: http://go/tb-client-feature-flags-in-data-provider for the motivation and high level design.)
See #5718 for the corresponding change for Angular and #5721 for server-side support.
The data is dumped into a custom header. The format is effectively:
X-TensorBoard-Feature-Flags: JSON.stringify(currentFeatureFlags)
We implement this directly in the RequestManager object, through which all our Polymer HTTP requests are routed.
Additional things worth noting:
I added a const for the header name to be shared between the HttpInterceptor and the RequestManager.
I added a new angular<->polymer interop object, tf-feature-flags, modeled after some of the other interop objects we've added in the past. This interop object should now be the source of truth for accessing feature flags from the polymer code base. It is populated by feature_flags_effects soon after we fetch feature flag overrides from the data source.
This will send ALL feature flags in the request header rather than a curated subset of them, just like in Adds mechanism to include feature flags in Angular HTTP requests. #5718. In a subsequent PR, we'll add support to mark certain feature flags as "sendToServer" and only send that subset.