-
Notifications
You must be signed in to change notification settings - Fork 522
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
Create parser function and test with mock data #378
Create parser function and test with mock data #378
Conversation
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
Codecov Report
@@ Coverage Diff @@
## master #378 +/- ##
=========================================
+ Coverage 88.8% 89.01% +0.2%
=========================================
Files 159 162 +3
Lines 3556 3640 +84
Branches 813 829 +16
=========================================
+ Hits 3158 3240 +82
- Misses 362 364 +2
Partials 36 36
Continue to review full report at Codecov.
|
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 great. Question about where this stuff should live...?
@@ -0,0 +1,112 @@ | |||
// Copyright (c) 2019 Uber Technologies, Inc. |
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 do you think of moving this to src/model/ddg/
? As there will be other files, seems like adding src/model/ddg/
would be useful.
The types could go into src/model/ddg/types.tsx
? Probably you will have to create additional types, and the types in this file will need to be referenced by other files?
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.
model
seems like a better home for this than utils
...?
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx
Outdated
Show resolved
Hide resolved
|
||
type TServiceMap = Record<string, TService>; | ||
|
||
type TParsedPayload = { |
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 name is not very descriptive.
It seems like this should keep track of the DDG parameters (time-range, etc), as well?
Also, it would be nice to keep track of the hash of the 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.
I had thought that the hash and request parameters would live in the URL? There isn't much need to have them in the redux state, unless I'm missing something.
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 think it also needs to be in the redux store to match with the URL.
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 come? i'm not sure what it is used for aside from validating that the BE response when sharing a link is the same response that the shared DDG was constructed from.
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.
Also, seems like link sharing validation through the data hash is part of the Beta enhancement of shareable links and not part of the core MVP.
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx
Outdated
Show resolved
Hide resolved
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx
Outdated
Show resolved
Hide resolved
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx
Outdated
Show resolved
Hide resolved
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.tsx
Outdated
Show resolved
Hide resolved
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.resources.js
Outdated
Show resolved
Hide resolved
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js
Outdated
Show resolved
Hide resolved
packages/jaeger-ui/src/utils/DeepDependencyGraph/parse-payload.test.js
Outdated
Show resolved
Hide resolved
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
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.
Requesting a couple of changes. LMK if any of them look awry.
I raised an existential question about PathElem
, it's inline in the file. LMK what you think.
|
||
export type TDdgPathElemsByDistance = Map<number, PathElem[]>; | ||
|
||
export type TDdgTransformedDdgData = { |
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 this simply be TDdgModel
?
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.
Model
is fairly generic, but definitely better than DdgTransformedDdgData
. Will update to TDdgModel
but still remain open to a better name.
(pathElemsByDistance.get(member.distance) as PathElem[]).push(member); | ||
} else { | ||
pathElemsByDistance.set(member.distance, [member]); | ||
} |
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 as above, the use of a local var can help typescript along:
let members = pathElemsByDistance.get(member.distance);
if (members) {
members.push(member);
} else {
pathElemsByDistance.set(member.distance, [member]);
}
I think typescript just doesn't have a full appreciation for what Map#has()
offers...
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 think it's an issue with our TS config. This TS playground has no issue with the if (!map.has(key)) map.set(key, value); map.get(key);
pattern:
(Not sure why Github won't let me link to that using [label](link)
even when using ctrl + k
...)
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.
Though I updated to the pattern you suggested and it works well. But then shortly below this in the while
loop cannot be changed in the same way.
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 playground link you provided errors if strictNullChecks
is enabled.
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.
An alternative approach:
// setup
type PathElem = {
visibilityIdx: number;
}
const pathElemsByDistance = new Map<number, PathElem[]>();
// stuff
let upstream = 1;
let downstream = 0;
let visibilityIdx = 0;
function setVisibilityIdx(pathElem: PathElem) {
pathElem.visibilityIdx = visibilityIdx++; // eslint-disable-line no-param-reassign
}
let nextUp: PathElem[] | void = pathElemsByDistance.get(upstream);
let nextDown: PathElem[] | void = pathElemsByDistance.get(downstream);
while (nextUp || nextDown) {
if (nextDown) {
nextDown.forEach(setVisibilityIdx);
nextDown = pathElemsByDistance.get(--downstream);
}
if (nextUp) {
nextUp.forEach(setVisibilityIdx);
nextUp = pathElemsByDistance.get(++upstream);
}
}
Playground – (But, you'll need to enable the strict null checks yourself)
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.
Because as
side-steps the type system, I'm thinking we should avoid using it. Or, if we do use it, provide a comment on why it's necessary.
What do you think?
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 agree that we should avoid as
as much as possible as it generally indicates something is mistyped. Fortunately it isn't a total sidestep but still should be avoided.
If we could somehow "teach" TS Map#has
and any future related shortcomings of TS that'd be ideal, but failing that the code blocks in this thread serve as good examples for helping TS.
I updated the related code, but with a do { /* ... */ } while (/* ... */);
loop instead and it works well.
export class PathElem { | ||
memberOf: TDdgPath; | ||
operation: TDdgOperation; | ||
pathIdx: 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.
At first I thought this field referred to the index of the parent path, memberOf
, within the TDdgTransformedDdgData
.
Maybe memberIdx
would indicate otherwise?
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.
sure, will rename to memberIdx
Is there value in a TDdgPath
knowing it's index in the TDdgModel
? I've been building off of this PR for jag-500 (create edges and vertices for PPE with distinguished operations) and it seems like data structures that contain PathElem
s has been more useful than data structures containing Path
s.
} | ||
|
||
get distance() { | ||
return this.pathIdx - this.memberOf.focalIdx; |
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 like this, but on second thought, the PathElem
knowing details about TDdgPath
like focalIdx
, and being dependent on how focal the index is represented, is unfortunate.
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 of making TDdgPath
a class as well, so that focalIdx
can be a getter. The benefit of that would be the ability to enable/disable distinguishing operations without needing to recalculate all focalIdx
s.
It is somewhat coupled, but the value is high. Through TypeScript (and good tests) it's pretty clear if/when this coupling is broken.
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.
Scratch what I said about focalIdx
as a getter, since if the focalIdx
changes as a result of the focalNode
changing, all visibilityIdx
s would be invalid.
I'm curious about merging this directly into master. We can merge to master, incrementally, and hide behind a feature flag. What do you think? |
That sounds fine to me, would we omit DDG PRs from the |
Signed-off-by: Everett Ross <[email protected]>
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 great. Some small comments, mainly about sorting model.paths
and the use of as
, in general.
LMK if any comment looks awry.
Also, any thoughts re targeting master? I think we should do it.
@@ -78,6 +79,7 @@ export default function transformDdgData( | |||
return path; | |||
}); | |||
|
|||
// Assign visibility indices such there is a positive, dependent correlation between visibilityIdx and distance |
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 think, in order to ensure this is stable, we should sort model.paths
before assigning the visibility indexes.
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.
That sounds reasonable. I implemented the sort as:
const stringifyPayloadEntry = ({ service, operation }: TDdgPayloadEntry) => `${service}::${operation}`;
/* ... */
const paths = payload
.slice()
.sort((a, b) =>
a
.map(stringifyPayloadEntry)
.join()
.localeCompare(b.map(stringifyPayloadEntry).join())
)
It seems to work well and should work at scale. Did you have a different sort in mind?
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 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.
Wow that is really slow! I had no idea. I will update it.
I think for our use of static ordering, not necessarily the most logical ordering (e.g.: aZ
before aa
is fine as long as it is consistent), (a, b) => a > b ? 1 : -1
should be a sufficient comparison to avoid needing to convert to lowercase unnecessarily.
(pathElemsByDistance.get(member.distance) as PathElem[]).push(member); | ||
} else { | ||
pathElemsByDistance.set(member.distance, [member]); | ||
} |
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 playground link you provided errors if strictNullChecks
is enabled.
(pathElemsByDistance.get(member.distance) as PathElem[]).push(member); | ||
} else { | ||
pathElemsByDistance.set(member.distance, [member]); | ||
} |
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.
An alternative approach:
// setup
type PathElem = {
visibilityIdx: number;
}
const pathElemsByDistance = new Map<number, PathElem[]>();
// stuff
let upstream = 1;
let downstream = 0;
let visibilityIdx = 0;
function setVisibilityIdx(pathElem: PathElem) {
pathElem.visibilityIdx = visibilityIdx++; // eslint-disable-line no-param-reassign
}
let nextUp: PathElem[] | void = pathElemsByDistance.get(upstream);
let nextDown: PathElem[] | void = pathElemsByDistance.get(downstream);
while (nextUp || nextDown) {
if (nextDown) {
nextDown.forEach(setVisibilityIdx);
nextDown = pathElemsByDistance.get(--downstream);
}
if (nextUp) {
nextUp.forEach(setVisibilityIdx);
nextUp = pathElemsByDistance.get(++upstream);
}
}
Playground – (But, you'll need to enable the strict null checks yourself)
(pathElemsByDistance.get(member.distance) as PathElem[]).push(member); | ||
} else { | ||
pathElemsByDistance.set(member.distance, [member]); | ||
} |
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.
Because as
side-steps the type system, I'm thinking we should avoid using it. Or, if we do use it, provide a comment on why it's necessary.
What do you think?
TDdgTransformedDdgData, | ||
PathElem, | ||
} from './types'; | ||
import { PathElem, TDdgModel, TDdgPayload, TDdgPath, TDdgPathElemsByDistance, TDdgServiceMap } from './types'; | ||
|
||
export default function transformDdgData( |
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.
Since we moved to the naming convention of filenames reflecting the default export, the file should be named transformDdgData.tsx
. Unfortunately, that was not the initial approach taken, so it's not reflected, everywhere.
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 see, I will rename. The usage of kebab-case
vs PascalCase
vs camelCase
file names definitely seem inconsistent. It might be worth mentioning the style guide portion of CONTRIBUTING.md
.
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 probably is inconsistent due to not renaming everything when adopting the approach... The approach is to name based on default export, if there is one. If not, then a descriptive-name
kebab-case 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, we need to put a bit of time into a style-guide. I started some notes, here:
https://github.com/jaegertracing/jaeger-ui/blob/style-guide-wip/CONTRIBUTING.md
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.
Created a ticket for it: #385
Signed-off-by: Everett Ross <[email protected]>
Signed-off-by: Everett Ross <[email protected]>
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 great! I'd suggest dropping the localeCompare
and considering using a different delimiter for the stringify.
Signed-off-by: Everett Ross <[email protected]>
…arser-function-and-test-with-mock-data Create parser function and test with mock data Signed-off-by: vvvprabhakar <[email protected]>
Which problem is this PR solving?
Short description of the changes