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] Transaction breakdown charts #39531

Merged
merged 7 commits into from
Jul 2, 2019

Conversation

dgieselaar
Copy link
Member

@dgieselaar dgieselaar commented Jun 24, 2019

Closes #36441.

To test this with the apm-integration-testing server, take the following steps:

  • Open the APM Server Dockerfile located at docker/apm-server/Dockerfile
  • Add ENV setup.template.overwrite=true below FROM ${apm_server_base_image} on line 23
  • Start the apm-integration-testing server with the following command: ~/dev/apm-integration-testing/scripts/compose.py start master --with-opbeans-java --opbeans-java-agent-branch=pr/588/head --force-build --no-kibana
  • Start kibana from source
  • Delete the apm-x-metric index, and wait until new data comes in
  • Navigate to the service overview and select opbeans-java

image

Definition of Done for APM UI

  • Flag and create issues of things that are left out
  • Before/after images or gif of UI changes
  • Release labels
  • Designer sign-off for UI related changes
  • PM approval for bigger, user-facing features

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch from 26c86e1 to 9bef809 Compare June 24, 2019 19:23
@elasticmachine
Copy link
Contributor

💔 Build Failed

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch 3 times, most recently from ca232ba to d27fe32 Compare June 27, 2019 10:19
@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch 2 times, most recently from 1fe4eaf to 85985c0 Compare June 27, 2019 20:01
@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch from 85985c0 to 7b6608f Compare June 28, 2019 16:04
Copy link
Member Author

@dgieselaar dgieselaar left a comment

Choose a reason for hiding this comment

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

I've bailed on refactoring CustomPlot and its sibilings, I ran into some issues w/ react-vis not having typings and it generally just being too much work to get through the process on such short notice. Would definitely like to pick it up some time soon, feel like we can gain a lot by improving our coverage there.

@@ -53,8 +54,9 @@ class StaticPlot extends PureComponent {
curve={'curveMonotoneX'}
data={serie.data}
color={serie.color}
stroke={serie.color}
stroke={serie.stack ? 'rgba(0,0,0,0)' : serie.color}
Copy link
Member Author

Choose a reason for hiding this comment

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

stroke has to be transparent for a stacked area chart, because we stick a line chart on top of it to achieve the look that we want.

@@ -151,6 +191,12 @@ export class InnerCustomPlot extends PureComponent {
tickFormatX={this.props.tickFormatX}
/>

{stacked
? React.cloneElement(staticPlot, {
Copy link
Member Author

Choose a reason for hiding this comment

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

AFAICT, there's only one way to achieve the look that we want (stroke on just one side of the area), and that is by drawing another line chart on top of it. Seems overkill, but it's the recommendation from the docs:

To create a chart that has a fill, no distinct lines to the left, right or bottom, but a different line style at the top, you may create an area chart with a line chart on top.

Copy link
Contributor

Choose a reason for hiding this comment

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

that's unfortunate, but tolerable as long as there's not any noticeable drag to page performance.

Copy link
Member

Choose a reason for hiding this comment

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

@dgieselaar Doesn't this duplicate the the XAxis, YAxis and HorizontalGridLines? What about just adding an additional LineSeries to the chart?

setTime: (time: number | null) => unknown;
} | null>(null);

const ChartsTimeContextProvider: React.FC = ({ children }) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

Simply syncs time across all charts in its context.

Copy link
Member

@sorenlouv sorenlouv Jul 2, 2019

Choose a reason for hiding this comment

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

@@ -5,7 +5,10 @@
*/

import { getTransactionBreakdown } from '.';
// using .ts files results in OOM during typecheck :)
Copy link
Member Author

Choose a reason for hiding this comment

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

maybe we can use .json files here too?

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed to .json


return breakdowns;
const filteredTimeseriesPerSubtype = pick(
Copy link
Member Author

Choose a reason for hiding this comment

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

Ensures timeseries that are not in the top 20 KPIs are filtered out.

@dgieselaar dgieselaar changed the title Transaction breakdown graph [APM] Transaction breakdown graph Jun 28, 2019
@dgieselaar dgieselaar changed the title [APM] Transaction breakdown graph [APM] Transaction breakdown charts Jun 28, 2019
@dgieselaar dgieselaar requested review from formgeist and ogupte June 28, 2019 16:13
@dgieselaar dgieselaar marked this pull request as ready for review June 28, 2019 16:16
@dgieselaar dgieselaar requested a review from a team as a code owner June 28, 2019 16:16
@elasticmachine
Copy link
Contributor

💔 Build Failed

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch from 7b6608f to 06b9ea7 Compare July 1, 2019 11:29
@formgeist
Copy link
Contributor

@dgieselaar A few UI problems I've found in relation to the chart.

Screenshot 2019-07-01 at 14 49 57

It seems that the lines are pushed to the left (outside the x-axis area) every now and then. I'm running 15 minutes date range here, not sure how it might be related.

Screenshot 2019-07-01 at 14 50 23

Can we add some space / margin on those labels or value text to split them a little apart in this list?

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch from e5befc2 to e59ec19 Compare July 1, 2019 19:48
@dgieselaar
Copy link
Member Author

@formgeist both should be fixed now, thanks for catching this!

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch from e59ec19 to bd659c0 Compare July 1, 2019 19:51
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@ogupte
Copy link
Contributor

ogupte commented Jul 2, 2019

probably not a blocker, but i noticed a weird visual bug:
transaction-breakdown-charts-0

@@ -14,5 +14,17 @@ export interface RectCoordinate {
x0: number;
}

export interface TimeSerie {
Copy link
Contributor

Choose a reason for hiding this comment

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

is this spelling of TimeSerie intentional or should it be TimeSeries?

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed, changed to TimeSeries

value: serviceName
}
}
},
{
term: {
'transaction.type.keyword': {
'transaction.type': {
Copy link
Contributor

Choose a reason for hiding this comment

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

constants for 'transaction.type' and 'transaction.name'

Copy link
Member Author

Choose a reason for hiding this comment

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

changed all of the field names to constants from common/elasticsearch_fieldnames.ts

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch from bd659c0 to 556c3a6 Compare July 2, 2019 11:21
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

const noHits = series.every(
serie =>
serie.data.filter(value => 'y' in value && value.y !== null).length === 0
);
Copy link
Member

@sorenlouv sorenlouv Jul 2, 2019

Choose a reason for hiding this comment

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

In the rest of the code base noHits refers to whether Elasticsearch actually returned any hits. We cannot just look at series.data because we might populate that with empty state data so the graph draws the x and y-axis.

Therefore other chart endpoints return the series.totalHits (coming from ES hits.total):

I think we should do the same here if that makes sense to you?

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 would be more consistent, but I have some doubts about using hits.total to determine whether there is no data. Gut tells me there's a lot of cases where that will be a false positive. Using the actual data passed to the charts to determine if there are any data points by filtering out all non-null values feels like a better heuristic to me. Does that make sense?

Copy link
Member

@sorenlouv sorenlouv Jul 2, 2019

Choose a reason for hiding this comment

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

Gut tells me there's a lot of cases where that will be a false positive. Using the actual data passed to the charts to determine if there are any data points by filtering out all non-null values feels like a better heuristic to me. Does that make sense?

That does make sense. My originally assumption was that if there are any hits then the aggregation will contain (timeseries) data, and it's been true for the aggs we do for the transaction charts. But this doesn't hold true for things like derivative aggs which requires at least two data points. So using hits.total as an indicator for data might not be a good idea in general.

If we go the direction you suggest (with a custom check) we should get rid of the noHits, right? Eg:

I think it might make sense to go over how we handle this across the different visualizations. It seems like a pretty standard thing for a chart to be able to know whether it has data or not, and would be great if we can stick to one approach.

@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch 2 times, most recently from 56865ed to 714ec19 Compare July 2, 2019 14:06
@dgieselaar dgieselaar force-pushed the transaction-breakdown-graph branch from 714ec19 to c808e71 Compare July 2, 2019 14:07
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@dgieselaar dgieselaar merged commit 73bb21b into elastic:master Jul 2, 2019
@dgieselaar dgieselaar deleted the transaction-breakdown-graph branch July 2, 2019 15:14
dgieselaar added a commit to dgieselaar/kibana that referenced this pull request Jul 2, 2019
* [APM] Transaction breakdown graphs

Closes elastic#36441.

* Don't use .keyword now that mapping is correct

* Use better heuristic for determining noHits; wrap App in memo to prevent unnecessary re-renders

* Remove nested panel around TransactionBreakdown

* Fix display issues in charts

* Address review feedback

* Address additional review feedback
dgieselaar added a commit that referenced this pull request Jul 2, 2019
* [APM] Transaction breakdown graphs

Closes #36441.

* Don't use .keyword now that mapping is correct

* Use better heuristic for determining noHits; wrap App in memo to prevent unnecessary re-renders

* Remove nested panel around TransactionBreakdown

* Fix display issues in charts

* Address review feedback

* Address additional review feedback
const xMax = d3.max(flattenedCoordinates, d => d.x);
const xMin = start ? start : d3.min(flattenedCoordinates, d => d.x);

const xMax = end ? end : d3.max(flattenedCoordinates, d => d.x);
Copy link
Member

@sorenlouv sorenlouv Jul 2, 2019

Choose a reason for hiding this comment

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

Shouldn't start be called xMin and end be called xMax?

XY_WIDTH: width
XY_HEIGHT: height || XY_HEIGHT,
XY_WIDTH: width,
...(stacked ? { stackBy: 'y' } : {})
Copy link
Member

@sorenlouv sorenlouv Jul 2, 2019

Choose a reason for hiding this comment

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

Does this need to be conditional? Doesn't it only take effect when things are stacked? So setting it to stackBy: 'y' only affects stacked charts.

};
}

export function SharedPlot({ plotValues, ...props }) {
const { XY_HEIGHT: height, XY_MARGIN: margin, XY_WIDTH: width } = plotValues;
Copy link
Member

Choose a reason for hiding this comment

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

I'm generally not a fan of renaming thing like this. If the names are not good enough, why not fix them at the root? Instead we end up with multiple names for the same thing.

() => {
return timeseries.map(timeseriesConfig => {
return {
title: timeseriesConfig.name,
Copy link
Member

Choose a reason for hiding this comment

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

Why is this rename necessary? Shouldn't timeseriesConfig.name be timeseriesConfig.title in the first place? Same with timeseriesConfig.values - shouldn't that be timeseriesConfig.data in the first place?

I actually prefer values over data but then we should change that everywhere.

}
});

const { timeseries_per_subtype: timeseriesPerSubtype } = response;
Copy link
Member

Choose a reason for hiding this comment

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

Why are we using snake case in the first place? All APM endpoints return camel cased data currently.

Copy link
Member

Choose a reason for hiding this comment

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

timeseries_per_subtype: timeseriesPerSubtype


const breakdown = kpis.find(
b => b.name === legend.name
) as typeof kpis[0];
Copy link
Member

@sorenlouv sorenlouv Jul 2, 2019

Choose a reason for hiding this comment

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

What if no matches are found? Then breakdown will be undefined and cause a runtime error below - or is that not logically possible?


const {
data,
status,
receivedDataDuringLifetime
} = useTransactionBreakdown();

const kpis = useMemo(
const kpis = data ? data.kpis : undefined;
const timeseriesPerSubtype = data ? data.timeseries_per_subtype : undefined;
Copy link
Member

Choose a reason for hiding this comment

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

I see this access checks a lot of places which makes the code a bit harder to read. What about setting a default response like we do in:

const INITIAL_DATA: MetricsChartsByAgentAPIResponse = {
charts: []
};

Could be something like:

const INITIAL_DATA: TransactionBreakdownAPIResponse = {
      kpis: [],
      timeseries_per_subtype: null
    }

timeseries: Array<{
name: string;
color: string;
values: Array<{ x: number; y: number | null }>;
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 this should be consistent with the other charts we have (name -> title and values -> data).
As mentioned somewhere else I prefer values over data but might be a bigger job to change that everywhere.

Instead of having several different interfaces can we make a single timeseries interface that is re-used?

title: React.ReactNode;
titleShort?: React.ReactNode;
data: Array<Coordinate | RectCoordinate>;
type: string;
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this a duplicate of

export interface TimeSeries {
title: string;
titleShort?: string;
hideLegend?: boolean;
hideTooltipValue?: boolean;
data: Array<Coordinate | RectCoordinate>;
legendValue?: string;
type: string;
color: string;
areaColor?: string;
}
?

Copy link
Member

Choose a reason for hiding this comment

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

Or rather: can they be combined?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[APM] Transaction breakdown graph: Add stacked area time series graph
5 participants