-
Notifications
You must be signed in to change notification settings - Fork 3
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
New fsApi / version 2 support #235
base: main
Are you sure you want to change the base?
Conversation
… checks for that vs destination # [solrdeploy] uncomment this line if this requires solr deploy.
… checks for that vs destination # [solrdeploy] uncomment this line if this requires solr deploy.
# [solrdeploy] uncomment this line if this requires solr deploy.
# [solrdeploy] uncomment this line if this requires solr deploy.
# [solrdeploy] uncomment this line if this requires solr deploy.
# [solrdeploy] uncomment this line if this requires solr deploy.
src/observer.ts
Outdated
@@ -9,6 +9,10 @@ import { FunctionOperator } from './operators'; | |||
import DataLayerTarget from './target'; | |||
import MonitorFactory from './monitor-factory'; | |||
import { errorType, Telemetry, telemetryType } from './utils/telemetry'; | |||
import TrackEventOperator from './operators/fsApi/trackEvent'; |
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: these can be exported from the index.ts
in /operators
and imported as a single statement.
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.
Fixed
src/observer.ts
Outdated
readOnLoad?: boolean; | ||
url?: string; | ||
id?: string; | ||
description?: string; | ||
monitor?: boolean; | ||
waitUntil?: number | Function; | ||
maxRetry?: number; | ||
version?: number; | ||
fsApi?: string; |
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.
fsApi
is one of FS_API_CONSTANTS
, no? Should that be the type and not string
.
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.
fixed
src/observer.ts
Outdated
if (fsApi) { | ||
switch (fsApi) { | ||
case FS_API_CONSTANTS.SET_IDENTITY: | ||
handler.push(new SetIdentityOperator({ name: 'setIdentity' })); |
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 could just reuse the FS_API_CONSTANTS
member as the value of 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.
Fixed
src/observer.ts
Outdated
*/ | ||
private addOperators(handler: DataHandler, options: OperatorOptions[], destination: string | Function) { | ||
private addOperators(handler: DataHandler, options: OperatorOptions[], | ||
destination: string | Function | undefined = undefined, fsApi: string | undefined = undefined, |
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.
destination
and fsApi
can be declared optional with destination?
and fsApi?
. Is there a need to add a union type with undefined
and assign it undefined
? That would be automatic with the optional.
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 started with this, but optional parameters have to go at the end, and version is optional but with a hardcoded default value. I'd have to move version in front of them, and then anyone using the api would have to put a version in if they wanted to specify destination or fsApi. The other option would be to give these default values of undefined as well. Thoughts?
return; | ||
} | ||
|
||
if (destination && fsApi) { |
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.
While conceptually incorrect (competing destinations), I wonder if we should prefer fsApi if it is also used with destination. In this scenario, we still still send data versus nothing. We could emit an error to indicate the problem, and the user would be able to see the event in the replay along with the indication there are competing destinations.
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 tend to think if someone specified both it was probably done in error, and I'd rather bomb out than have them hunt down why it isn't working. This happens with DLO unfortunately and it can be tricky to figure out why things are broken. But not a hill for me to die on either, so could change it.
// end with destination | ||
const func = previewMode ? previewDestination : destination; | ||
handler.push(new FunctionOperator({ name: 'function', func })); | ||
if (fsApi) { |
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.
Given these are operators, my skew is that we re-use the OperatorFactory
and this.getOperator
to instantiate operators. I may have to get into the code to see if that creates unwanted complexity.
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 wanted it explicit in the code, but could move those into registered operators with an OperatorFactory if desired. I tend to favor explicit code in special situations like this, but not a hill for me to die on.
export default class SetPagePropertiesOperator extends FSApiOperator { | ||
// eslint-disable-next-line class-methods-use-this | ||
prepareData(inputData:any[]): any[] | null { | ||
if (inputData === null || inputData.length < 1) { |
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 we throw an Error
? The operator expects inputData, right? Is there a scenario where not receiving data but expecting it to be sent to setProperties
is OK? Returning null
is typically used to halt the operator chain under expected conditions - like when a query doesn't match and the chain is expected to stop.
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.
Fixed
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.
Hi @irv-fs ! Thanks for your patience on this. Everything looks good here. I'm going to go schedule some time to go over this with @ndonze-fs to make sure I understand what's going on and then we'll
// useful if every rule needs the same operator run before the destination | ||
if (beforeDestination) { | ||
if (beforeDestination && (version === 1)) { |
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.
Will this prevent the application of any beforeDestination
operators? If so, is this safe/intended? I expect we're trying to avoid having the suffix
operator added. However, we also convert
by default and insert the dlo
capture source (not sure how capture sources work in v2 APIs).
[{ name: 'convert', enumerate: true, preserveArray: true, index: -1 },{ name: 'suffix' },{ name: 'insert', value: 'dlo', position: -1 }]
I wonder if instead we should be thinking about noop'ing the suffix
operator given v2 client APIs.
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, the way this is coded, if you use V2 of DLC you are in charge of putting all of your operators that are needed, which would include the convert operator. I do automatically insert dlo as an additional parameter in the fsApi base operator, so that piece is taken care of. You can use the new fsApi stuff with version 1 however if desired.
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 very nervous to just make the suffix operator a no-op. I know we have done a lot of work to get around suffixes, allowing people to use specific names. I instead decided to introduce a rule version, which I think gives us more flexibility. Anybody who wants this new functionality needs to explicitly set the rule version, which they knowingly opt into. We can also use this for future backwards incompatible changes at the rule level. I think it gives us another lever that was missing in the past.
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.
A side effect of nixing beforeDestinationOperators
is that moving to FS API V2 is coupled with a capture behavior change. Previously, we added convert
for you. Now, given the same set of rules upgraded to V2, we'll no longer convert. if you had a string amount e.g. "10.75"
, we would've previously been converting that to a real. Now, we'll no longer do that. So, we both stop adding suffixes (good relative to V2) and we change the capture behavior. If a customer would be negatively impacted by the change in capture behavior (e.g. now having mixed real and str values), they can't be easily be migrated to FS API V2.
This coupling doesn't feel great for me as it limits the on-ramp to V2. I wonder if it's worth soliciting a product perspective on this decision.
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.
When a customer moves to V2, it won't be as easy as flipping a switch or setting a simple attribute as each rule will have to be visited as some inserts, etc will need to be removed. The structure of V2 is just different, so it won't be a simple path regardless. Forcing them to also consider convert seems small compared to what is already needed, and gets us away from the hardcoded beforeDestination stuff while keeping backwards compatibility.
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 you provide examples of other concerns we'll have moving customers from V1 to V2? You mentioned the structure of V2 being different which suggests that -- even if we were to position the V2 API as a destination instead of an operator -- we wouldn't be able to take an existing operator chain and have it call V2 APIs without capture behavior changes.
I'm pulling on this thread because in the past we've seen capture behavior changes (e.g. when we've changed beforeDestinationOperators
) as breaking changes which motivated us to:
- Bump the DLO major version
- Feature switch such that we could migrate customers between major versions over time
In this case, it's a little different because we're introducing a rule version within the overall DLO library version.
Between data sinks becoming operators and opting into breaking changes via rule specs, we're shifting responsibilities around vs. where they've lived in the past. This may be okay, though I think we should have a good awareness of these shifts as they may complicate what changes we can make and where over time e.g. not being able to count on beforeDestinationOperators
to make changes with client integration scripts since we can no longer count on those to be effective for all customers.
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.
Looking back on an earlier comment, is this true?
You can use the new fsApi stuff with version 1 however if desired.
With FS API calls currently being positioned as operators instead of destinations, wouldn't the operator to call FS API V2 execute before the beforeDestinationOperators
which are appended to the end of the operators array?
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.
Opening up a rule version would allow those writing rules to gradually migrate as they see fit, even rule by rule, versus an all or nothing change, and doesn't require any major hassles on our end (as far as migrating folks). Given that most of the rule writers are inside Fullstory, this seems to be a benefit?
private addOperators(handler: DataHandler, options: OperatorOptions[], destination: string | Function) { | ||
private addOperators(handler: DataHandler, options: OperatorOptions[], | ||
destination: string | Function | undefined = undefined, fsApi: string | undefined = undefined, | ||
version: number = 1) { |
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.
version
feels generic here without a fuller name/jsdoc. Wonder if we should make this clear that we're referring to FS client API version -- and maybe further constrain to known versions with a union instead of using a number
.
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 jsDoc. That was an oversight.
# [solrdeploy] uncomment this line if this requires solr deploy.
# [solrdeploy] uncomment this line if this requires solr deploy.
Telemetry.error(errorType.invalidRuleError); | ||
return; | ||
} | ||
|
||
// sanity check destination and fsApi parameters | ||
if (!destination && !fsApi) { |
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've been thinking a bit more on this. If we think about DLO as ETL (extract from source, transform through operators, load through destination), as implemented this positions the FS API V2 destination as an operator rather than a destination. How do we feel about this overall positioning? Operators can now be transformations, or they could actually be sending data -- whereas previously there was a separation between operators (transformations) and destinations (data sinks). As a side effect, this kind of forces us away from beforeDestinationOperators
because with V2 there is no destination.
As a potential alternative to consider, we could position destination as a string (as is) or as an FS API V2 destination which would be a more complex destination object. As a crude example, destination: { target: 'fs', method: 'setIdentity' }
.
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.
In my design doc I had originally positioned the V2 as just a special string inside a destination, but it seemed more folks wanted the separate attribute (which I also prefer as it is clearer the intention).
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.
In this case, we wouldn't have a special string; rather, we would have a more complex destination object that wouldn't look terribly different than operator definitions. That said, this is not a hill I will die on if other folks think it's sensible to start merging the ideas of operators/transformations and data sinks. 😆
// useful if every rule needs the same operator run before the destination | ||
if (beforeDestination) { | ||
if (beforeDestination && (version === 1)) { |
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.
A side effect of nixing beforeDestinationOperators
is that moving to FS API V2 is coupled with a capture behavior change. Previously, we added convert
for you. Now, given the same set of rules upgraded to V2, we'll no longer convert. if you had a string amount e.g. "10.75"
, we would've previously been converting that to a real. Now, we'll no longer do that. So, we both stop adding suffixes (good relative to V2) and we change the capture behavior. If a customer would be negatively impacted by the change in capture behavior (e.g. now having mixed real and str values), they can't be easily be migrated to FS API V2.
This coupling doesn't feel great for me as it limits the on-ramp to V2. I wonder if it's worth soliciting a product perspective on this decision.
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 all looks good from my end. I think once @ndonze-fs signs off we can get this deployed.
Sorry for the delay on this.
# [solrdeploy] uncomment this line if this requires solr deploy./gd
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.
LGTM
# [solrdeploy] uncomment this line if this requires solr deploy./gd
No description provided.