-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
[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.
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.
<ServiceOverviewDependenciesTable serviceName={serviceName} /> | |
<ServiceDependenciesTable serviceName={serviceName} /> |
|
||
const TooltipWrapper = styled.div` | ||
width: 100%; | ||
.euiToolTipAnchor { |
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.
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.
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?
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 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.
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?!
spanType: | |
'span.type' in destination ? destination[SPAN_TYPE] : undefined, | |
spanType: destination[SPAN_TYPE] |
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 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')); |
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 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.
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"?
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.
Service Overview is the Page, it consumes the transactions
, services
, errors
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.
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.
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.
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.
Alright let's chat to @sqren about it later.
@@ -0,0 +1,90 @@ | |||
/* |
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 this test should be placed inside /Services
not /service_overview
, since we don't have service_overview API.
…iew-dependencies-table
@@ -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 |
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.
Great improvements in this file! 😉
prev.push(item); | ||
} else { | ||
Object.assign(item, current); | ||
item = mergeFn(item, current); |
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.
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.
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} |
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.
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.
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
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, 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why snake case?
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.
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.
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"} />
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's a bunch of styling in ServiceOverviewTableContainer
- don't we want that here as well?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@smith, any thoughts here?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm 👍
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.
...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.
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?
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 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.
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.
getDestinationMap({ | ||
apmEventClient, | ||
serviceName, | ||
start, | ||
end, | ||
environment, | ||
}), | ||
]); |
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 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.
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, |
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.
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.
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).
spanType: | ||
'span.type' in destination ? destination[SPAN_TYPE] : 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.
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.
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[]; |
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: 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.
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?
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, 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.
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 it
s? That makes it easier when debugging failing test cases.
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.
👍
|
||
const latencySums = metricsByResolvedAddress | ||
.map((metrics) => metrics.latency.value) | ||
.filter(isFiniteNumber); |
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.
Nice, forgot we already had this!
|
||
export function getSpanIcon(type?: string, subtype?: string) { | ||
if (!type) { | ||
return; |
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 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.
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.
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.
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
).
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 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 <[email protected]>
Co-authored-by: Kibana Machine <[email protected]> Co-authored-by: Kibana Machine <[email protected]>
* 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.