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
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
44a2203
[APM] Service overview dependencies table
dgieselaar Nov 16, 2020
1ec8bd7
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Nov 19, 2020
788f1fc
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Nov 19, 2020
54ddee2
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Nov 20, 2020
a4917e8
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Nov 23, 2020
9ca351d
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Nov 30, 2020
74d5e19
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 1, 2020
4e0f1eb
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 2, 2020
e35bb80
Use top_hits instead of collapse
dgieselaar Dec 2, 2020
6ccb4cf
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 2, 2020
d304420
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 3, 2020
12d9c7d
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 4, 2020
3de214b
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 7, 2020
c412c91
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 7, 2020
c5eed84
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 8, 2020
dab72cd
Make sure result of mergeFn overrides existing item
dgieselaar Dec 8, 2020
8464326
Review feedback
dgieselaar Dec 8, 2020
981c3ff
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 9, 2020
a6687a0
Review feedback
dgieselaar Dec 9, 2020
70b6b72
API tests
dgieselaar Dec 9, 2020
e24e3f3
Merge branch 'master' of github.com:elastic/kibana into service-overv…
dgieselaar Dec 9, 2020
981635a
Merge branch 'master' into service-overview-dependencies-table
kibanamachine Dec 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions x-pack/plugins/apm/common/elasticsearch_fieldnames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export const SPAN_NAME = 'span.name';
export const SPAN_ID = 'span.id';
export const SPAN_DESTINATION_SERVICE_RESOURCE =
'span.destination.service.resource';
export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT =
'span.destination.service.response_time.count';

export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM =
'span.destination.service.response_time.sum.us';

// Parent ID for a transaction or span
export const PARENT_ID = 'parent.id';
Expand Down
3 changes: 1 addition & 2 deletions x-pack/plugins/apm/common/utils/formatters/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { memoize } from 'lodash';
import { NOT_AVAILABLE_LABEL } from '../../../common/i18n';
import { asDecimal, asDecimalOrInteger, asInteger } from './formatters';
import { asDecimalOrInteger, asInteger, asDecimal } from './formatters';
import { TimeUnit } from './datetime';
import { Maybe } from '../../../typings/common';
import { isFiniteNumber } from '../is_finite_number';
Expand Down Expand Up @@ -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! 😉

Expand Down
33 changes: 33 additions & 0 deletions x-pack/plugins/apm/common/utils/join_by_key/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,37 @@ describe('joinByKey', () => {
},
]);
});

it('uses the custom merge fn to replace items', () => {
const joined = joinByKey(
[
{
serviceName: 'opbeans-java',
values: ['a'],
},
{
serviceName: 'opbeans-node',
values: ['a'],
},
{
serviceName: 'opbeans-node',
values: ['b'],
},
{
serviceName: 'opbeans-node',
values: ['c'],
},
],
'serviceName',
(a, b) => ({
...a,
...b,
values: a.values.concat(b.values),
})
);

expect(
joined.find((item) => item.serviceName === 'opbeans-node')?.values
).toEqual(['a', 'b', 'c']);
});
});
33 changes: 26 additions & 7 deletions x-pack/plugins/apm/common/utils/join_by_key/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { UnionToIntersection, ValuesType } from 'utility-types';
import { isEqual } from 'lodash';
import { isEqual, pull } from 'lodash';

/**
* Joins a list of records by a given key. Key can be any type of value, from
Expand All @@ -23,24 +23,43 @@ import { isEqual } from 'lodash';
*/

type JoinedReturnType<
T extends Record<string, any>,
U extends UnionToIntersection<T>
> = Array<
Partial<U> &
{
[k in keyof T]: T[k];
}
>;

export function joinByKey<
T extends Record<string, any>,
U extends UnionToIntersection<T>,
V extends keyof T & keyof U
> = Array<Partial<U> & Record<V, U[V]>>;
>(items: T[], key: V): JoinedReturnType<T, U>;

export function joinByKey<
T extends Record<string, any>,
U extends UnionToIntersection<T>,
V extends keyof T & keyof U
>(items: T[], key: V): JoinedReturnType<T, U, V> {
return items.reduce<JoinedReturnType<T, U, V>>((prev, current) => {
V extends keyof T & keyof U,
W extends JoinedReturnType<T, U>,
X extends (a: T, b: T) => ValuesType<W>
>(items: T[], key: V, mergeFn: X): W;

export function joinByKey(
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
items: Array<Record<string, any>>,
key: string,
mergeFn: Function = (a: Record<string, any>, b: Record<string, any>) =>
Object.assign(a, b)
) {
return items.reduce<Array<Record<string, any>>>((prev, current) => {
let item = prev.find((prevItem) => isEqual(prevItem[key], current[key]));

if (!item) {
item = { ...current } as ValuesType<JoinedReturnType<T, U, V>>;
item = { ...current };
prev.push(item);
} else {
Object.assign(item, current);
pull(prev, item).push(mergeFn(item, current));
}

return prev;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ServiceHealthStatus,
} from '../../../../common/service_health_status';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { defaultIcon, iconForNode } from './icons';
import { iconForNode } from './icons';

export const popoverWidth = 280;

Expand Down Expand Up @@ -116,9 +116,7 @@ const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => {
'background-color': theme.eui.euiColorGhost,
// The DefinitelyTyped definitions don't specify that a function can be
// used here.
'background-image': isIE11
? undefined
: (el: cytoscape.NodeSingular) => iconForNode(el) ?? defaultIcon,
'background-image': (el: cytoscape.NodeSingular) => iconForNode(el),
'background-height': (el: cytoscape.NodeSingular) =>
isService(el) ? '60%' : '40%',
'background-width': (el: cytoscape.NodeSingular) =>
Expand Down
67 changes: 1 addition & 66 deletions x-pack/plugins/apm/public/components/app/ServiceMap/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,73 +10,8 @@ import {
SPAN_SUBTYPE,
SPAN_TYPE,
} from '../../../../common/elasticsearch_fieldnames';
import awsIcon from './icons/aws.svg';
import cassandraIcon from './icons/cassandra.svg';
import databaseIcon from './icons/database.svg';
import defaultIconImport from './icons/default.svg';
import documentsIcon from './icons/documents.svg';
import elasticsearchIcon from './icons/elasticsearch.svg';
import globeIcon from './icons/globe.svg';
import graphqlIcon from './icons/graphql.svg';
import grpcIcon from './icons/grpc.svg';
import handlebarsIcon from './icons/handlebars.svg';
import kafkaIcon from './icons/kafka.svg';
import mongodbIcon from './icons/mongodb.svg';
import mysqlIcon from './icons/mysql.svg';
import postgresqlIcon from './icons/postgresql.svg';
import redisIcon from './icons/redis.svg';
import websocketIcon from './icons/websocket.svg';
import javaIcon from '../../shared/AgentIcon/icons/java.svg';
import { getAgentIcon } from '../../shared/AgentIcon/get_agent_icon';

export const defaultIcon = defaultIconImport;

const defaultTypeIcons: { [key: string]: string } = {
cache: databaseIcon,
db: databaseIcon,
ext: globeIcon,
external: globeIcon,
messaging: documentsIcon,
resource: globeIcon,
};

const typeIcons: { [key: string]: { [key: string]: string } } = {
aws: {
servicename: awsIcon,
},
db: {
cassandra: cassandraIcon,
elasticsearch: elasticsearchIcon,
mongodb: mongodbIcon,
mysql: mysqlIcon,
postgresql: postgresqlIcon,
redis: redisIcon,
},
external: {
graphql: graphqlIcon,
grpc: grpcIcon,
websocket: websocketIcon,
},
messaging: {
jms: javaIcon,
kafka: kafkaIcon,
},
template: {
handlebars: handlebarsIcon,
},
};

function getSpanIcon(type?: string, subtype?: string) {
if (!type) {
return;
}

const types = type ? typeIcons[type] : {};
if (subtype && types && subtype in types) {
return types[subtype];
}
return defaultTypeIcons[type] || defaultIcon;
}
import { defaultIcon, getSpanIcon } from '../../shared/span_icon/get_span_icon';

// IE 11 does not properly load some SVGs, which causes a runtime error and the
// map to not work at all. We would prefer to do some kind of feature detection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import { isRumAgentName } from '../../../../common/agent_name';
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart';
import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart';
import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink';
import { SearchBar } from '../../shared/search_bar';
import { ServiceOverviewErrorsTable } from './service_overview_errors_table';
import { ServiceOverviewDependenciesTable } from './service_overview_dependencies_table';
import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart';
import { ServiceOverviewTransactionsTable } from './service_overview_transactions_table';
import { TableLinkFlexItem } from './table_link_flex_item';

/**
* The height a chart should be if it's next to a table with 5 rows and a title.
Expand Down Expand Up @@ -98,30 +97,7 @@ export function ServiceOverview({
</EuiFlexItem>
<EuiFlexItem grow={6}>
<EuiPanel>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xs">
<h2>
{i18n.translate(
'xpack.apm.serviceOverview.dependenciesTableTitle',
{
defaultMessage: 'Dependencies',
}
)}
</h2>
</EuiTitle>
</EuiFlexItem>
<TableLinkFlexItem>
<ServiceMapLink serviceName={serviceName}>
{i18n.translate(
'xpack.apm.serviceOverview.dependenciesTableLinkText',
{
defaultMessage: 'View service map',
}
)}
</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} />

</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import {
} from '../../../context/apm_plugin/mock_apm_plugin_context';
import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider';
import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern';
import * as useFetcherHooks from '../../../hooks/use_fetcher';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import * as useAnnotationsHooks from '../../../context/annotations/use_annotations_context';
import * as useTransactionBreakdownHooks from '../../shared/charts/transaction_breakdown_chart/use_transaction_breakdown';
import { renderWithTheme } from '../../../utils/testHelpers';
import { ServiceOverview } from './';
import { waitFor } from '@testing-library/dom';
import * as callApmApi from '../../../services/rest/createCallApmApi';

const KibanaReactContext = createKibanaReactContext({
usageCollection: { reportUiCounter: () => {} },
Expand Down Expand Up @@ -54,7 +55,7 @@ function Wrapper({ children }: { children?: ReactNode }) {
}

describe('ServiceOverview', () => {
it('renders', () => {
it('renders', async () => {
jest
.spyOn(useAnnotationsHooks, 'useAnnotationsContext')
.mockReturnValue({ annotations: [] });
Expand All @@ -64,18 +65,29 @@ describe('ServiceOverview', () => {
indexPattern: undefined,
status: FETCH_STATUS.SUCCESS,
});
jest.spyOn(useFetcherHooks, 'useFetcher').mockReturnValue({
data: {
items: [],
tableOptions: {
pageIndex: 0,
sort: { direction: 'desc', field: 'test field' },
},
totalItemCount: 0,
throughput: [],

const calls = {
// eslint-disable-next-line @typescript-eslint/naming-convention
'GET /api/apm/services/{serviceName}/error_groups': {
error_groups: [],
total_error_groups: 0,
},
'GET /api/apm/services/{serviceName}/transactions/groups/overview': {
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
transactionGroups: [],
totalTransactionGroups: 0,
isAggregationAccurate: true,
},
refetch: () => {},
status: FETCH_STATUS.SUCCESS,
'GET /api/apm/services/{serviceName}/dependencies': [],
};

jest.spyOn(callApmApi, 'createCallApmApi').mockImplementation(() => {});

jest.spyOn(callApmApi, 'callApmApi').mockImplementation(({ endpoint }) => {
const response = calls[endpoint as keyof typeof calls];

return response
? Promise.resolve(response)
: Promise.reject(`Response for ${endpoint} is not defined`);
});
jest
.spyOn(useTransactionBreakdownHooks, 'useTransactionBreakdown')
Expand All @@ -85,10 +97,19 @@ describe('ServiceOverview', () => {
status: FETCH_STATUS.SUCCESS,
});

expect(() =>
renderWithTheme(<ServiceOverview serviceName="test service name" />, {
const { findAllByText } = renderWithTheme(
<ServiceOverview serviceName="test service name" />,
{
wrapper: Wrapper,
})
).not.toThrowError();
}
);

await waitFor(() =>
expect(callApmApi.callApmApi).toHaveBeenCalledTimes(
Object.keys(calls).length
)
);

expect((await findAllByText('Latency')).length).toBeGreaterThan(0);
});
});
Loading