Skip to content
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

Conversation

everett980
Copy link
Collaborator

Which problem is this PR solving?

  • Begin work on DDG, this PR parses expected network payload into The Model.

Short description of the changes

  • Parsing function and tests

@ghost ghost assigned everett980 May 10, 2019
@ghost ghost added the review label May 10, 2019
@codecov
Copy link

codecov bot commented May 10, 2019

Codecov Report

Merging #378 into master will increase coverage by 0.2%.
The diff coverage is 98.8%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
...i/src/model/ddg/transformDdgData.test.resources.js 100% <100%> (ø)
packages/jaeger-ui/src/model/ddg/PathElem.tsx 100% <100%> (ø)
...kages/jaeger-ui/src/model/ddg/transformDdgData.tsx 98.11% <98.11%> (ø)
...eViewer/TimelineHeaderRow/TimelineViewingLayer.tsx 91.52% <0%> (-1.7%) ⬇️

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 bf2baa2...54577cc. Read the comment docs.

@everett980 everett980 requested a review from tiffon May 10, 2019 18:58
Copy link
Member

@tiffon tiffon left a 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.
Copy link
Member

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?

Copy link
Member

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


type TServiceMap = Record<string, TService>;

type TParsedPayload = {
Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

@everett980 everett980 requested a review from tiffon May 14, 2019 16:06
Signed-off-by: Everett Ross <[email protected]>
Copy link
Member

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

packages/jaeger-ui/src/model/ddg/types.tsx Show resolved Hide resolved

export type TDdgPathElemsByDistance = Map<number, PathElem[]>;

export type TDdgTransformedDdgData = {
Copy link
Member

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?

Copy link
Collaborator Author

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.

packages/jaeger-ui/src/model/ddg/types.tsx Outdated Show resolved Hide resolved
packages/jaeger-ui/src/model/ddg/types.tsx Outdated Show resolved Hide resolved
packages/jaeger-ui/src/model/ddg/types.tsx Outdated Show resolved Hide resolved
(pathElemsByDistance.get(member.distance) as PathElem[]).push(member);
} else {
pathElemsByDistance.set(member.distance, [member]);
}
Copy link
Member

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

@everett980 everett980 May 15, 2019

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.

Copy link
Member

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.

Copy link
Member

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)

Copy link
Member

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?

Copy link
Collaborator Author

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.

packages/jaeger-ui/src/model/ddg/transform-ddg-data.tsx Outdated Show resolved Hide resolved
export class PathElem {
memberOf: TDdgPath;
operation: TDdgOperation;
pathIdx: number;
Copy link
Member

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?

Copy link
Collaborator Author

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 PathElems has been more useful than data structures containing Paths.

packages/jaeger-ui/src/model/ddg/types.tsx Outdated Show resolved Hide resolved
}

get distance() {
return this.pathIdx - this.memberOf.focalIdx;
Copy link
Member

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.

Copy link
Collaborator Author

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 focalIdxs.
It is somewhat coupled, but the value is high. Through TypeScript (and good tests) it's pretty clear if/when this coupling is broken.

Copy link
Collaborator Author

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 visibilityIdxs would be invalid.

@tiffon
Copy link
Member

tiffon commented May 15, 2019

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?

@everett980
Copy link
Collaborator Author

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 CHANGELOG.md until the feature flag is removed? If not, there should be some indication that the changes are experimental.

Copy link
Member

@tiffon tiffon left a 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
Copy link
Member

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.

Copy link
Collaborator Author

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?

Copy link
Member

Choose a reason for hiding this comment

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

Not so sure I'd say this will scale, localeCompare is dog slow:

Screen Shot 2019-05-22 at 1 55 11 PM

https://jsperf.com/localecompare/38

Copy link
Collaborator Author

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]);
}
Copy link
Member

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]);
}
Copy link
Member

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]);
}
Copy link
Member

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(
Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

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.

Copy link
Member

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

Copy link
Member

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

@everett980 everett980 changed the base branch from deep-dependency-graph to master May 16, 2019 22:04
Copy link
Member

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

packages/jaeger-ui/src/model/ddg/transformDdgData.tsx Outdated Show resolved Hide resolved
packages/jaeger-ui/src/model/ddg/transformDdgData.tsx Outdated Show resolved Hide resolved
@everett980 everett980 merged commit 1076705 into jaegertracing:master May 24, 2019
vvvprabhakar pushed a commit to vvvprabhakar/jaeger-ui that referenced this pull request Jul 5, 2021
…arser-function-and-test-with-mock-data

Create parser function and test with mock data
Signed-off-by: vvvprabhakar <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants