[APM] Service overview: Dependencies table#83416
Conversation
…iew-dependencies-table
…iew-dependencies-table
…iew-dependencies-table
…iew-dependencies-table
…iew-dependencies-table
…iew-dependencies-table
…iew-dependencies-table
…iew-dependencies-table
…iew-dependencies-table
|
Pinging @elastic/apm-ui (Team:apm) |
|
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). |
…iew-dependencies-table
…iew-dependencies-table
x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx
Show resolved
Hide resolved
| </ServiceMapLink> | ||
| </TableLinkFlexItem> | ||
| </EuiFlexGroup> | ||
| <ServiceOverviewDependenciesTable serviceName={serviceName} /> |
There was a problem hiding this comment.
I think we could drop the Overview in the name, ServiceDependenciesTable is clear enough and can be reused elsewhere.
| <ServiceOverviewDependenciesTable serviceName={serviceName} /> | |
| <ServiceDependenciesTable serviceName={serviceName} /> |
|
|
||
| const TooltipWrapper = styled.div` | ||
| width: 100%; | ||
| .euiToolTipAnchor { |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Shouldn't TS recognize that service is available in this scope since you checked it?
There was a problem hiding this comment.
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).
| spanType: | ||
| 'span.type' in destination ? destination[SPAN_TYPE] : undefined, |
There was a problem hiding this comment.
If SPAN_TYPE doesn't exist in the destination, it'll return undefined, right? so no reason for the ternary here?!
| spanType: | |
| 'span.type' in destination ? destination[SPAN_TYPE] : undefined, | |
| spanType: destination[SPAN_TYPE] |
There was a problem hiding this comment.
I need the refinement so TS allows access to the property.
| // 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')); |
There was a problem hiding this comment.
I think this test should be placed inside Services not Service overview, since we don't have service_overview API.
There was a problem hiding this comment.
What do you mean with "we don't have a service_overview API"?
There was a problem hiding this comment.
Service Overview is the Page, it consumes the transactions, services, errors APIs.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Alright let's chat to @sqren about it later.
| @@ -0,0 +1,90 @@ | |||
| /* | |||
There was a problem hiding this comment.
I think this test should be placed inside /Services not /service_overview, since we don't have service_overview API.
…iew-dependencies-table
|
|
||
| /** | ||
| * Convert a microsecond value to decimal milliseconds. Normally we use | ||
| * `asDuration`, but this is used in places like tables where we always want |
There was a problem hiding this comment.
Great improvements in this file! 😉
| prev.push(item); | ||
| } else { | ||
| Object.assign(item, current); | ||
| item = mergeFn(item, current); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
Wouldn't this have the same effect?
| series={latency.timeseries ?? undefined} | |
| series={latency.timeseries} |
If it's because latency.timeseries can be null can we change that?
There was a problem hiding this comment.
Or alternatively handle this in SparkPlotWithValueLabel
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Habit 😁 One that I'm trying to get rid of. Will change it.
| <EuiFlexItem> | ||
| <TableFetchWrapper status={status}> | ||
| <ServiceOverviewTableContainer | ||
| isEmptyAndLoading={ |
There was a problem hiding this comment.
Instead of using the wrapper <ServiceOverviewTableContainer isEmptyAndLoading={...} /> what about using EuiBasicTable directly:
<EuiBasicTable noItemsMessage={status === FETCH_STATUS.LOADING ? "Loading" : "No dependencies"} />There was a problem hiding this comment.
There's a bunch of styling in ServiceOverviewTableContainer - don't we want that here as well?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
...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.
There was a problem hiding this comment.
I'm using a EuiInMemoryTable rather than a EuiBasicTable. What I can do is export only the container, would that be better?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I'll make sure to do that in the service instances PR.
| getDestinationMap({ | ||
| apmEventClient, | ||
| serviceName, | ||
| start, | ||
| end, | ||
| environment, | ||
| }), | ||
| ]); |
There was a problem hiding this comment.
What about just forwarding setup thereby making the other arguments more noticeable:
| getDestinationMap({ | |
| apmEventClient, | |
| serviceName, | |
| start, | |
| end, | |
| environment, | |
| }), | |
| ]); | |
| getDestinationMap({ setup, serviceName, environment }), | |
| ]); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
| spanType: | ||
| 'span.type' in destination ? destination[SPAN_TYPE] : undefined, |
There was a problem hiding this comment.
Nit: this feels a bit heavyhanded. Does tsc complain if you do this?
| spanType: | |
| 'span.type' in destination ? destination[SPAN_TYPE] : undefined, | |
| spanType: destination[SPAN_TYPE] |
There was a problem hiding this comment.
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[]; |
There was a problem hiding this comment.
nit: To avoid the explicit type you can use our existing helper that has a type guard:
| .filter((n) => n !== null) as number[]; | |
| .filter(isValidCoordinateValue) |
You should probably rename it though
| .filter((n) => n !== null) as number[]; | |
| .filter(isNumber) |
There was a problem hiding this comment.
Does that work with filter though?
There was a problem hiding this comment.
Yes, type guards work in filter and will return a nice clean array without nullables :)
| expectSnapshot(names.sort()).toMatchInline(); | ||
|
|
||
| expectSnapshot(serviceNames.sort()).toMatchInline(); | ||
|
|
||
| expectSnapshot(latencyValues).toMatchInline(); | ||
|
|
||
| expectSnapshot(throughputValues).toMatchInline(); |
There was a problem hiding this comment.
nit: Can you split these into separate its? That makes it easier when debugging failing test cases.
|
|
||
| const latencySums = metricsByResolvedAddress | ||
| .map((metrics) => metrics.latency.value) | ||
| .filter(isFiniteNumber); |
There was a problem hiding this comment.
Nice, forgot we already had this!
|
|
||
| export function getSpanIcon(type?: string, subtype?: string) { | ||
| if (!type) { | ||
| return; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
smith
left a comment
There was a problem hiding this comment.
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:
…iew-dependencies-table
…iew-dependencies-table
|
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! Or maybe we leave it but don't make it a link so people know it's a self-reference. |
|
@elasticmachine merge upstream |
|
@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. |
💚 Build SucceededMetrics [docs]Module Count
Async chunks
Distributable file count
Page load bundle
History
To update your PR or re-run it, just comment with: |
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
* 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) ...



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