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

[APM] Service overview: Dependencies table #83416

Merged

Conversation

dgieselaar
Copy link
Member

@dgieselaar dgieselaar commented Nov 16, 2020

Closes #83152.

image

(Draft PR without tests until #83152 (comment) is resolved.

@dgieselaar dgieselaar added release_note:enhancement Team:APM All issues that need APM UI Team support v7.11.0 labels Nov 16, 2020
@sorenlouv sorenlouv changed the title [APM] Service overview dependencies table [APM] Service overview: Dependencies table Nov 24, 2020
@dgieselaar dgieselaar marked this pull request as ready for review December 3, 2020 09:06
@dgieselaar dgieselaar requested a review from a team as a code owner December 3, 2020 09:06
@elasticmachine
Copy link
Contributor

Pinging @elastic/apm-ui (Team:apm)

@dgieselaar
Copy link
Member Author

No tests yet - I need an updated ES archive and running into some issues with getting the ML data the work (seemingly something with spaces).

</ServiceMapLink>
</TableLinkFlexItem>
</EuiFlexGroup>
<ServiceOverviewDependenciesTable serviceName={serviceName} />
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could drop the Overview in the name, ServiceDependenciesTable is clear enough and can be reused elsewhere.

Suggested change
<ServiceOverviewDependenciesTable serviceName={serviceName} />
<ServiceDependenciesTable serviceName={serviceName} />


const TooltipWrapper = styled.div`
width: 100%;
.euiToolTipAnchor {
Copy link
Contributor

Choose a reason for hiding this comment

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

EUI team advise against it, if the CSS name changes this wouldn't work anymore. You could use styled_component and set the anchorClassName to the EuiToolTip. Here is an example: https://github.com/elastic/kibana/blob/master/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx#L47-L56


if ('service' in destination) {
return {
name: destination.service!.name,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't TS recognize that service is available in this scope since you checked it?

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't, for some reason. (Technically, a key can be in an object while it's value is undefined, you just don't see TS complaining about it often).

Comment on lines 178 to 179
spanType:
'span.type' in destination ? destination[SPAN_TYPE] : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

If SPAN_TYPE doesn't exist in the destination, it'll return undefined, right? so no reason for the ternary here?!

Suggested change
spanType:
'span.type' in destination ? destination[SPAN_TYPE] : undefined,
spanType: destination[SPAN_TYPE]

Copy link
Member Author

Choose a reason for hiding this comment

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

I need the refinement so TS allows access to the property.

@@ -26,6 +26,7 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont
// TODO: we should not have a service overview.
describe('Service overview', function () {
loadTestFile(require.resolve('./service_overview/error_groups'));
loadTestFile(require.resolve('./service_overview/dependencies'));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this test should be placed inside Services not Service overview, since we don't have service_overview API.

Copy link
Member Author

Choose a reason for hiding this comment

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

What do you mean with "we don't have a service_overview API"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Service Overview is the Page, it consumes the transactions, services, errors APIs.

Copy link
Member Author

Choose a reason for hiding this comment

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

The errors APIs are in separate files as well (server/routes/errors.ts). Metrics are service-specific as well, but both their tests and API definitions are in separate files. Moreover, the error APIs on the service overview page and the service error group page have different data needs. I agree some clean up is needed - but I think we should consider being more specific instead of less. Thoughts @sqren?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that the current API definition and where it's placed is a bit of a mess right now, but I think from an API point of view there is no Service overview. What I understood is Service Overview is a page which shows information of different APIs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright let's chat to @sqren about it later.

@@ -0,0 +1,90 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this test should be placed inside /Services not /service_overview, since we don't have service_overview API.

@@ -181,7 +181,6 @@ export function asDuration(
const formatter = getDurationFormatter(value);
return formatter(value, { defaultValue }).formatted;
}

/**
* Convert a microsecond value to decimal milliseconds. Normally we use
* `asDuration`, but this is used in places like tables where we always want
Copy link
Member

Choose a reason for hiding this comment

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

Great improvements in this file! 😉

prev.push(item);
} else {
Object.assign(item, current);
item = mergeFn(item, current);
Copy link
Member

Choose a reason for hiding this comment

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

Probably just Monday me who can't read code but what is this line doing? It's assigning the results to item but item is never used.

Copy link
Member Author

Choose a reason for hiding this comment

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

bug! thanks for catching this. It should have replaced the item. I'll add a test for it.

return (
<SparkPlotWithValueLabel
color="euiColorVis1"
series={latency.timeseries ?? undefined}
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't this have the same effect?

Suggested change
series={latency.timeseries ?? undefined}
series={latency.timeseries}

If it's because latency.timeseries can be null can we change that?

Copy link
Member

Choose a reason for hiding this comment

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

Or alternatively handle this in SparkPlotWithValueLabel

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'll make sure to handle this in a more elegant manner.

error_rate_value: item.error_rate.value,
latency_value: item.latency.value,
throughput_value: item.throughput.value,
impact_value: item.impact,
Copy link
Member

Choose a reason for hiding this comment

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

why snake case?

Copy link
Member Author

Choose a reason for hiding this comment

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

Habit 😁 One that I'm trying to get rid of. Will change it.

<EuiFlexItem>
<TableFetchWrapper status={status}>
<ServiceOverviewTableContainer
isEmptyAndLoading={
Copy link
Member

@sorenlouv sorenlouv Dec 8, 2020

Choose a reason for hiding this comment

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

Instead of using the wrapper <ServiceOverviewTableContainer isEmptyAndLoading={...} /> what about using EuiBasicTable directly:

<EuiBasicTable noItemsMessage={status === FETCH_STATUS.LOADING ? "Loading" : "No dependencies"} />

Copy link
Member Author

Choose a reason for hiding this comment

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

There's a bunch of styling in ServiceOverviewTableContainer - don't we want that here as well?

Copy link
Member

Choose a reason for hiding this comment

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

Good point. But I'd prefer to keep the custom styling to a minimum. So if we can avoid isEmptyAndLoading and the resulting visibility styling that would be better.

Also: have we talked to EUI about better ways at getting the styles we want, instead of the custom route?

Copy link
Member Author

Choose a reason for hiding this comment

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

@smith, any thoughts here?

Copy link
Contributor

Choose a reason for hiding this comment

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

ServiceOverviewTableContainer makes a fixed-size table and the styling pins the pagination to the bottom. elastic/eui#4281 is open to make it so we don't have to override styles if EUI were to implement this natively.

elastic/eui#4282 was opened by me and closed by the team to handle the empty loading state. The default behavior makes it so there's a cell with text in it while it's loading, which looks much worse than what we have now, where it's empty and shows the loading bar, which looks pretty nice.

Copy link
Member

Choose a reason for hiding this comment

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

sgtm 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

...but, the ServiceOverviewTable was meant to be used for each of the tables so I don't understand why we need to export the container.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm using a EuiInMemoryTable rather than a EuiBasicTable. What I can do is export only the container, would that be better?

Copy link
Contributor

Choose a reason for hiding this comment

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

What I can do is export only the container, would that be better?

Yes since we're just passing the props along to the basic or in-memory table that should work.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll make sure to do that in the service instances PR.

Comment on lines 62 to 69
getDestinationMap({
apmEventClient,
serviceName,
start,
end,
environment,
}),
]);
Copy link
Member

Choose a reason for hiding this comment

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

What about just forwarding setup thereby making the other arguments more noticeable:

Suggested change
getDestinationMap({
apmEventClient,
serviceName,
start,
end,
environment,
}),
]);
getDestinationMap({ setup, serviceName, environment }),
]);

Copy link
Member Author

Choose a reason for hiding this comment

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

I wanted to be explicit but I agree that your suggestion emphasises that which is more relevant 👍

if ('service' in destination) {
return {
name: destination.service!.name,
serviceName: destination.service!.name,
Copy link
Member

Choose a reason for hiding this comment

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

Would be better if we could avoid forcing the compiler (using !.)
I'm actually surprised this is necessary - doesn't typescript narrow the type inside the if block?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was surprised as well, it might be because of the way joinByKey returns its type. Will have a look, and I can possibly add && destination.service here (though I think I would prefer an error here).

Comment on lines 178 to 179
spanType:
'span.type' in destination ? destination[SPAN_TYPE] : undefined,
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this feels a bit heavyhanded. Does tsc complain if you do this?

Suggested change
spanType:
'span.type' in destination ? destination[SPAN_TYPE] : undefined,
spanType: destination[SPAN_TYPE]

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. But let me see if I can change the data structure. I wanted something flat but it requires a lot of refinements.


const latencySums = metricsByResolvedAddress
.map((metrics) => metrics.latency.value)
.filter((n) => n !== null) as number[];
Copy link
Member

Choose a reason for hiding this comment

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

nit: To avoid the explicit type you can use our existing helper that has a type guard:

Suggested change
.filter((n) => n !== null) as number[];
.filter(isValidCoordinateValue)

You should probably rename it though

Suggested change
.filter((n) => n !== null) as number[];
.filter(isNumber)

Copy link
Member Author

Choose a reason for hiding this comment

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

Does that work with filter though?

Copy link
Member

@sorenlouv sorenlouv Dec 8, 2020

Choose a reason for hiding this comment

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

Yes, type guards work in filter and will return a nice clean array without nullables :)

Comment on lines 80 to 86
expectSnapshot(names.sort()).toMatchInline();

expectSnapshot(serviceNames.sort()).toMatchInline();

expectSnapshot(latencyValues).toMatchInline();

expectSnapshot(throughputValues).toMatchInline();
Copy link
Member

Choose a reason for hiding this comment

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

nit: Can you split these into separate its? That makes it easier when debugging failing test cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍


const latencySums = metricsByResolvedAddress
.map((metrics) => metrics.latency.value)
.filter(isFiniteNumber);
Copy link
Member

Choose a reason for hiding this comment

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

Nice, forgot we already had this!


export function getSpanIcon(type?: string, subtype?: string) {
if (!type) {
return;
Copy link
Member

Choose a reason for hiding this comment

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

We don't want to return a default icon in this case?
Btw. I though span.type was always specified. Is that not the case?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm aggregating on span.destination.service.resource only, so I can't guarantee that (due to needing to match with span events). But I will add a top_metrics aggregation on span.type and span.subtype to make sure span.type is always there as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

span.type is not available on span metrics, so I can't guarantee that it will actually be there (it should be in most cases, if we find a span event with the same span.destination.service.resource).

Copy link
Contributor

@smith smith left a comment

Choose a reason for hiding this comment

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

image

Should we have these circles enabled on the sparklines? I'm seeing them all the tables, not just dependencies.

How does the deps table work with pagination? I see that we're using EuiInMemoryTable instead of EuiBasicTable and I don't see any pagination controls.

Can we also make the Impact column the same width as it is on the Transactions table, because it's cut off for me:

image

@dgieselaar dgieselaar requested a review from smith December 9, 2020 20:32
@smith
Copy link
Contributor

smith commented Dec 9, 2020

Thanks for the fixes. It's looking great.

One other thing I noticed that's not a blocker is we probably should exclude the service if it has a link to itself. In this screenshot clicking the link does nothing because I'm already here!

image

Or maybe we leave it but don't make it a link so people know it's a self-reference.

@dgieselaar
Copy link
Member Author

@elasticmachine merge upstream

@dgieselaar
Copy link
Member Author

dgieselaar commented Dec 10, 2020

@smith That shouldn't happen in normal situations (this is because of the way the observability test environment routes traffic). I'm wary of introducing additional code here to deal with such an edge case, if the consequences are rather small.

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
apm 1720 1724 +4

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
apm 5.4MB 5.4MB +7.1KB

Distributable file count

id before after diff
default 46991 47754 +763

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
observability 74.9KB 75.1KB +107.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@dgieselaar dgieselaar merged commit 04428ff into elastic:master Dec 10, 2020
@dgieselaar dgieselaar deleted the service-overview-dependencies-table branch December 10, 2020 09:32
dgieselaar added a commit to dgieselaar/kibana that referenced this pull request Dec 10, 2020
dgieselaar added a commit that referenced this pull request Dec 10, 2020
gmmorris added a commit to gmmorris/kibana that referenced this pull request Dec 10, 2020
* master: (53 commits)
  Fixing recovered instance reference bug (elastic#85412)
  Switch to new elasticsearch client for Visualizations (elastic#85245)
  Switch to new elasticsearch client for TSVB (elastic#85275)
  Switch to new elasticsearch client for Vega (elastic#85280)
  [ILM] Add shrink field to hot phase (elastic#84087)
  Add rolling-file appender to core logging (elastic#84735)
  [APM] Service overview: Dependencies table (elastic#83416)
  [Uptime ]Update empty message for certs list (elastic#78575)
  [Graph] Fix graph saved object references (elastic#85295)
  [APM] Create new API's to return Latency and Throughput charts (elastic#85242)
  [Advanced settings] Reset to default for empty strings (elastic#85137)
  [SECURITY SOLUTION] Bundles _source -> Fields + able to sort on multiple fields in Timeline (elastic#83761)
  [Fleet] Update agent listing for better status reporting (elastic#84798)
  [APM] enable 'sanitize_field_names' for Go (elastic#85373)
  Update dependency @elastic/charts to v24.4.0 (elastic#85452)
  Introduce external url service (elastic#81234)
  Deprecate disabling the security plugin (elastic#85159)
  [FLEET] New Integration Policy Details page for use in Integrations section (elastic#85355)
  [Security Solutions][Detection Engine] Fixes one liner access control with find_rules REST API
  chore: 🤖 remove extraPublicDirs (elastic#85454)
  ...
@sorenlouv sorenlouv mentioned this pull request Dec 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release_note:enhancement Team:APM All issues that need APM UI Team support v7.11.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[APM] Service overview: dependencies table
6 participants