`;
exports[`ErrorGroupOverview -> List should render with data 1`] = `
-.c8 {
- font-size: 12px;
- color: #999999;
-}
-
.c0 {
- max-width: none;
-}
-
-.c0.kuiTableHeaderCell--alignRight > button > span {
- -webkit-box-pack: end;
- -webkit-justify-content: flex-end;
- -ms-flex-pack: end;
- justify-content: flex-end;
-}
-
-.c1 {
- max-width: none;
- width: 100px;
-}
-
-.c2 {
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
}
-.c3 {
+.c1 {
max-width: 512px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
-.c4 {
+.c2 {
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
font-size: 16px;
max-width: 100%;
@@ -231,520 +284,573 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
text-overflow: ellipsis;
}
-.c5 {
+.c3 {
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
}
-.c6 {
- max-width: none;
- width: 100px;
-}
-
-.c7 {
- max-width: none;
-}
-
-
+
-
-
-
- 0
- –
- 4
- of
- 4
-
-
-
-
-
-
-
-
-
- Group ID
-
- |
-
-
- Error message and culprit
-
- |
-
-
- |
-
- |
+
+
+
- elasticapm.contrib.django.client.capture
+
+ a0ce2
+
-
- |
-
-
- |
-
-
- 75
-
- |
-
-
- 1515578797
-
- |
-
-
-
-
+
-
- f3ac9
-
-
- |
-
-
+
+ |
+
-
+ |
+
+
- AssertionError:
-
+ 75
+
+ |
+
- opbeans.views.oopsie
+ 1515578797
-
- |
-
-
- |
-
-
- 75
-
- |
-
-
- 1515578797
-
- |
- |
-
-
-
+ |
+
+
-
- e9086
-
-
- |
-
-
+ f3ac9
+
+
+ |
+
-
- AssertionError: Bad luck!
-
+
+
+ |
+
+ |
+
+
- opbeans.tasks.update_stats
+ 75
-
- |
-
-
- |
-
-
- 24
-
- |
-
-
- 1515578796
-
- |
-
-
-
- |
+
+
-
- Customer with ID 8517 not found
-
+
+ e9086
+
+
+ |
+
- opbeans.views.customer
+
-
- |
-
-
- |
-
-
- 15
-
- |
-
-
- 1515578773
-
- |
-
-
-
-
+
+
+
+ |
+
+
+ 24
+
+ |
+
+
+ 1515578796
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ 15
+
+ |
+
+
+ 1515578773
+
+ |
+
+
+
+
+
+ className="euiSpacer euiSpacer--m"
+ />
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.js
index af2f87f9c3b7..a49ca9a12d6c 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.js
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.js
@@ -6,131 +6,156 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { toQuery, fromQuery, history } from '../../../../utils/url';
-import { debounce } from 'lodash';
-import APMTable, {
- AlignmentKuiTableHeaderCell
-} from '../../../shared/APMTable/APMTable';
-import ListItem from './ListItem';
-
-const ITEMS_PER_PAGE = 20;
+import { EuiBasicTable, EuiBadge } from '@elastic/eui';
+import numeral from '@elastic/numeral';
+import moment from 'moment';
+import {
+ toQuery,
+ fromQuery,
+ history,
+ RelativeLink
+} from '../../../../utils/url';
+import TooltipOverlay from '../../../shared/TooltipOverlay';
+import styled from 'styled-components';
+import {
+ unit,
+ px,
+ fontFamilyCode,
+ fontSizes,
+ truncate
+} from '../../../../style/variables';
+
+function paginateItems({ items, pageIndex, pageSize }) {
+ return items.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
+}
+
+const GroupIdLink = styled(RelativeLink)`
+ font-family: ${fontFamilyCode};
+`;
+
+const MessageAndCulpritCell = styled.div`
+ ${truncate(px(unit * 32))};
+`;
+
+const MessageLink = styled(RelativeLink)`
+ font-family: ${fontFamilyCode};
+ font-size: ${fontSizes.large};
+ ${truncate('100%')};
+`;
+
+const Culprit = styled.div`
+ font-family: ${fontFamilyCode};
+`;
+
class List extends Component {
- updateQuery = getNextQuery => {
+ state = {
+ page: {
+ index: 0,
+ size: 25
+ }
+ };
+
+ onTableChange = ({ page = {}, sort = {} }) => {
+ this.setState({ page });
+
const { location } = this.props;
- const prevQuery = toQuery(location.search);
history.push({
...location,
- search: fromQuery(getNextQuery(prevQuery))
+ search: fromQuery({
+ ...toQuery(location.search),
+ sortField: sort.field,
+ sortDirection: sort.direction
+ })
});
};
- onClickNext = () => {
- const { page } = this.props.urlParams;
- this.updateQuery(prevQuery => ({
- ...prevQuery,
- page: page + 1
- }));
- };
-
- onClickPrev = () => {
- const { page } = this.props.urlParams;
- this.updateQuery(prevQuery => ({
- ...prevQuery,
- page: page - 1
- }));
- };
-
- onFilter = debounce(q => {
- this.updateQuery(prevQuery => ({
- ...prevQuery,
- page: 0,
- q
- }));
- }, 300);
-
- onSort = key => {
- this.updateQuery(prevQuery => ({
- ...prevQuery,
- sortBy: key,
- sortOrder: this.props.urlParams.sortOrder === 'asc' ? 'desc' : 'asc'
- }));
- };
-
render() {
const { items } = this.props;
- const {
- sortBy = 'latestOccurrenceAt',
- sortOrder = 'desc',
- page,
- serviceName
- } = this.props.urlParams;
-
- const renderHead = () => {
- const cells = [
- { key: 'groupId', sortable: false, label: 'Group ID' },
- { key: 'message', sortable: false, label: 'Error message and culprit' },
- { key: 'handled', sortable: false, label: '', alignRight: true },
- {
- key: 'occurrenceCount',
- sortable: true,
- label: 'Occurrences',
- alignRight: true
- },
- {
- key: 'latestOccurrenceAt',
- sortable: true,
- label: 'Latest occurrence',
- alignRight: true
+ const { serviceName, sortDirection, sortField } = this.props.urlParams;
+
+ const paginatedItems = paginateItems({
+ items,
+ pageIndex: this.state.page.index,
+ pageSize: this.state.page.size
+ });
+
+ const columns = [
+ {
+ name: 'Group ID',
+ field: 'groupId',
+ sortable: false,
+ width: px(unit * 6),
+ render: groupId => {
+ return (
+
+ {groupId.slice(0, 5) || 'N/A'}
+
+ );
+ }
+ },
+ {
+ name: 'Error message and culprit',
+ field: 'message',
+ sortable: false,
+ width: '50%',
+ render: (message, item) => {
+ return (
+
+
+
+ {message || 'N/A'}
+
+
+
+ {item.culprit || 'N/A'}
+
+
+ );
}
- ].map(({ key, sortable, label, alignRight }) => (
-
this.onSort(key),
- isSorted: sortBy === key,
- isSortAscending: sortOrder === 'asc'
- }
- : {})}
- >
- {label}
-
- ));
-
- return cells;
- };
-
- const renderBody = errorGroups => {
- return errorGroups.map(error => {
- return (
-
- );
- });
- };
-
- const startNumber = page * ITEMS_PER_PAGE;
- const endNumber = (page + 1) * ITEMS_PER_PAGE;
- const currentPageItems = items.slice(startNumber, endNumber);
+ },
+ {
+ name: '',
+ field: 'handled',
+ sortable: false,
+ align: 'right',
+ render: isUnhandled =>
+ isUnhandled === false && (
+
Unhandled
+ )
+ },
+ {
+ name: 'Occurrences',
+ field: 'occurrenceCount',
+ sortable: true,
+ dataType: 'number',
+ render: value => (value ? numeral(value).format('0.[0]a') : 'N/A')
+ },
+ {
+ field: 'latestOccurrenceAt',
+ sortable: true,
+ name: 'Latest occurrence',
+ align: 'right',
+ render: value => (value ? moment(value).fromNow() : 'N/A')
+ }
+ ];
return (
-
);
}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.js b/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.js
index 4a3ccc717983..55a91a3b3a81 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.js
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/List/index.js
@@ -16,7 +16,6 @@ import {
asDecimal,
tpmUnit
} from '../../../../utils/formatters';
-// import ImpactTooltip from './ImpactTooltip';
import { fontFamilyCode, truncate } from '../../../../style/variables';
import ImpactSparkline from './ImpactSparkLine';
@@ -38,7 +37,7 @@ const TransactionNameLink = styled(RelativeLink)`
font-family: ${fontFamilyCode};
`;
-export class List extends Component {
+class List extends Component {
state = {
page: {
index: 0,
diff --git a/x-pack/plugins/apm/public/components/shared/APMTable/APMTable.js b/x-pack/plugins/apm/public/components/shared/APMTable/APMTable.js
deleted file mode 100644
index 9987d8888121..000000000000
--- a/x-pack/plugins/apm/public/components/shared/APMTable/APMTable.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import styled from 'styled-components';
-import PropTypes from 'prop-types';
-
-import {
- KuiControlledTable,
- KuiEmptyTablePromptPanel,
- KuiPager,
- KuiTable,
- KuiTableBody,
- KuiTableHeader,
- KuiTableHeaderCell,
- KuiToolBar,
- KuiToolBarFooter,
- KuiToolBarFooterSection,
- KuiToolBarSearchBox,
- KuiToolBarSection
-} from '@kbn/ui-framework/components';
-
-import EmptyMessage from '../EmptyMessage';
-import { fontSizes, colors } from '../../../style/variables';
-
-export const FooterText = styled.div`
- font-size: ${fontSizes.small};
- color: ${colors.gray3};
-`;
-
-export const AlignmentKuiTableHeaderCell = styled(KuiTableHeaderCell)`
- max-width: none;
-
- &.kuiTableHeaderCell--alignRight > button > span {
- justify-content: flex-end;
- }
-`; // Fixes alignment for sortable KuiTableHeaderCell children
-
-function APMTable({
- defaultSearchQuery,
- emptyMessageHeading,
- emptyMessageSubHeading,
- items,
- itemsPerPage,
- onClickNext,
- onClickPrev,
- onFilter,
- inputPlaceholder,
- page,
- renderBody,
- renderFooterText,
- renderHead,
- totalItems
-}) {
- const startNumber = page * itemsPerPage;
- const endNumber = (page + 1) * itemsPerPage;
-
- const pagination = (
-
- 0}
- onNextPage={onClickNext}
- onPreviousPage={onClickPrev}
- />
-
- );
-
- return (
-
-
- e.stopPropagation()}
- onFilter={onFilter}
- placeholder={inputPlaceholder}
- />
- {pagination}
-
-
- {items.length === 0 && (
-
-
-
- )}
-
- {items.length > 0 && (
-
- {renderHead()}
- {renderBody(items)}
-
- )}
-
-
-
- {renderFooterText()}
-
- {pagination}
-
-
- );
-}
-
-APMTable.propTypes = {
- defaultSearchQuery: PropTypes.string,
- emptyMessageHeading: PropTypes.string,
- items: PropTypes.array,
- itemsPerPage: PropTypes.number.isRequired,
- onClickNext: PropTypes.func.isRequired,
- onClickPrev: PropTypes.func.isRequired,
- onFilter: PropTypes.func.isRequired,
- page: PropTypes.number.isRequired,
- renderBody: PropTypes.func.isRequired,
- renderFooterText: PropTypes.func,
- renderHead: PropTypes.func.isRequired,
- totalItems: PropTypes.number.isRequired
-};
-
-APMTable.defaultProps = {
- items: [],
- page: 0,
- renderFooterText: () => {},
- totalItems: 0
-};
-
-export default APMTable;
diff --git a/x-pack/plugins/apm/public/services/rest/apm.js b/x-pack/plugins/apm/public/services/rest/apm.js
index 6ac23c408443..5681ec139d57 100644
--- a/x-pack/plugins/apm/public/services/rest/apm.js
+++ b/x-pack/plugins/apm/public/services/rest/apm.js
@@ -165,9 +165,8 @@ export async function loadErrorGroupList({
end,
kuery,
size,
- q,
- sortBy,
- sortOrder
+ sortField,
+ sortDirection
}) {
return callApi({
pathname: `/api/apm/services/${serviceName}/errors`,
@@ -175,9 +174,8 @@ export async function loadErrorGroupList({
start,
end,
size,
- q,
- sortBy,
- sortOrder,
+ sortField,
+ sortDirection,
esFilterQuery: await getEncodedEsQuery(kuery)
}
});
diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js b/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js
index 4ae6dd135de6..3d566940f704 100644
--- a/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js
+++ b/x-pack/plugins/apm/public/store/reactReduxRequest/errorGroupList.js
@@ -18,7 +18,14 @@ export function getErrorGroupList(state) {
}
export function ErrorGroupDetailsRequest({ urlParams, render }) {
- const { serviceName, start, end, q, sortBy, sortOrder, kuery } = urlParams;
+ const {
+ serviceName,
+ start,
+ end,
+ sortField,
+ sortDirection,
+ kuery
+ } = urlParams;
if (!(serviceName && start && end)) {
return null;
@@ -28,7 +35,7 @@ export function ErrorGroupDetailsRequest({ urlParams, render }) {
diff --git a/x-pack/plugins/apm/public/store/urlParams.js b/x-pack/plugins/apm/public/store/urlParams.js
index bd3f48bdb931..23a8313f6b90 100644
--- a/x-pack/plugins/apm/public/store/urlParams.js
+++ b/x-pack/plugins/apm/public/store/urlParams.js
@@ -38,9 +38,8 @@ function urlParams(state = {}, action) {
detailTab,
spanId,
page,
- sortBy,
- sortOrder,
- q,
+ sortDirection,
+ sortField,
kuery
} = toQuery(action.location.search);
@@ -48,9 +47,8 @@ function urlParams(state = {}, action) {
...state,
// query params
- q,
- sortBy,
- sortOrder,
+ sortDirection,
+ sortField,
page: toNumber(page) || 0,
transactionId,
detailTab,
diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.js b/x-pack/plugins/apm/server/lib/errors/get_error_groups.js
index 28d6afb059bc..c2cfd84daab9 100644
--- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.js
+++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.js
@@ -17,9 +17,8 @@ import { get } from 'lodash';
export async function getErrorGroups({
serviceName,
- q,
- sortBy,
- sortOrder = 'desc',
+ sortField,
+ sortDirection = 'desc',
setup
}) {
const { start, end, esFilterQuery, client, config } = setup;
@@ -50,7 +49,7 @@ export async function getErrorGroups({
terms: {
field: ERROR_GROUP_ID,
size: 500,
- order: { _count: sortOrder }
+ order: { _count: sortDirection }
},
aggs: {
sample: {
@@ -78,9 +77,9 @@ export async function getErrorGroups({
}
// sort buckets by last occurence of error
- if (sortBy === 'latestOccurrenceAt') {
+ if (sortField === 'latestOccurrenceAt') {
params.body.aggs.error_groups.terms.order = {
- max_timestamp: sortOrder
+ max_timestamp: sortDirection
};
params.body.aggs.error_groups.aggs.max_timestamp = {
@@ -88,23 +87,6 @@ export async function getErrorGroups({
};
}
- // match query against error fields
- if (q) {
- params.body.query.bool.must = [
- {
- simple_query_string: {
- fields: [
- ERROR_EXC_MESSAGE,
- ERROR_LOG_MESSAGE,
- ERROR_CULPRIT,
- ERROR_GROUP_ID
- ],
- query: q
- }
- }
- ];
- }
-
const resp = await client('search', params);
const hits = get(resp, 'aggregations.error_groups.buckets', []).map(
bucket => {
diff --git a/x-pack/plugins/apm/server/routes/errors.js b/x-pack/plugins/apm/server/routes/errors.js
index 70bc4fb78bb2..8ff6abd41cc5 100644
--- a/x-pack/plugins/apm/server/routes/errors.js
+++ b/x-pack/plugins/apm/server/routes/errors.js
@@ -28,22 +28,20 @@ export function initErrorsApi(server) {
pre,
validate: {
query: withDefaultValidators({
- q: Joi.string().allow(''),
- sortBy: Joi.string(),
- sortOrder: Joi.string()
+ sortField: Joi.string(),
+ sortDirection: Joi.string()
})
}
},
handler: (req, reply) => {
const { setup } = req.pre;
const { serviceName } = req.params;
- const { q, sortBy, sortOrder } = req.query;
+ const { sortField, sortDirection } = req.query;
return getErrorGroups({
serviceName,
- q,
- sortBy,
- sortOrder,
+ sortField,
+ sortDirection,
setup
})
.then(reply)