Skip to content

Commit

Permalink
Show examples in category table row expansions
Browse files Browse the repository at this point in the history
  • Loading branch information
weltenwort committed Jan 13, 2020
1 parent 2455a9f commit fce8a3b
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* 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.
*/

export * from './row_expansion_button';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useCallback } from 'react';

export const RowExpansionButton = <Item extends any>({
isExpanded,
item,
onCollapse,
onExpand,
}: {
isExpanded: boolean;
item: Item;
onCollapse: (item: Item) => void;
onExpand: (item: Item) => void;
}) => {
const handleClick = useCallback(() => (isExpanded ? onCollapse(item) : onExpand(item)), [
isExpanded,
item,
onCollapse,
onExpand,
]);

return (
<EuiButtonIcon
onClick={handleClick}
aria-label={isExpanded ? collapseAriaLabel : expandAriaLabel}
iconType={isExpanded ? 'arrowUp' : 'arrowDown'}
/>
);
};

const collapseAriaLabel = i18n.translate('xpack.infra.table.collapseRowLabel', {
defaultMessage: 'Collapse',
});

const expandAriaLabel = i18n.translate('xpack.infra.table.expandRowLabel', {
defaultMessage: 'Expand',
});
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => {
onChangeDatasetSelection={setCategoryQueryDatasets}
onRequestRecreateMlJob={viewSetupForReconfiguration}
selectedDatasets={categoryQueryDatasets}
sourceId={sourceId}
timeRange={categoryQueryTimeRange.timeRange}
topCategories={topLogEntryCategories}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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, { useEffect } from 'react';

import { TimeRange } from '../../../../../../common/http_api/shared';
import { useLogEntryCategoryExamples } from '../../use_log_entry_category_examples';

export const CategoryDetailsRow: React.FunctionComponent<{
categoryId: number;
timeRange: TimeRange;
sourceId: string;
}> = ({ categoryId, timeRange, sourceId }) => {
const { getLogEntryCategoryExamples, logEntryCategoryExamples } = useLogEntryCategoryExamples({
categoryId,
endTime: timeRange.endTime,
exampleCount: 5,
sourceId,
startTime: timeRange.startTime,
});

useEffect(() => {
getLogEntryCategoryExamples();
}, [getLogEntryCategoryExamples]);

return (
<div>
{logEntryCategoryExamples.map(categoryExample => (
<div>{categoryExample.message}</div>
))}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const TopCategoriesSection: React.FunctionComponent<{
onChangeDatasetSelection: (datasets: string[]) => void;
onRequestRecreateMlJob: () => void;
selectedDatasets: string[];
sourceId: string;
timeRange: TimeRange;
topCategories: LogEntryCategory[];
}> = ({
Expand All @@ -34,6 +35,7 @@ export const TopCategoriesSection: React.FunctionComponent<{
onChangeDatasetSelection,
onRequestRecreateMlJob,
selectedDatasets,
sourceId,
timeRange,
topCategories,
}) => {
Expand Down Expand Up @@ -64,7 +66,11 @@ export const TopCategoriesSection: React.FunctionComponent<{
isLoading={isLoadingTopCategories}
loadingChildren={<LoadingOverlayContent />}
>
<TopCategoriesTable timeRange={timeRange} topCategories={topCategories} />
<TopCategoriesTable
sourceId={sourceId}
timeRange={timeRange}
topCategories={topCategories}
/>
</LoadingOverlayWrapper>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import { useSet } from 'react-use';

import euiStyled from '../../../../../../../../common/eui_styled_components';
import {
Expand All @@ -19,22 +20,55 @@ import { AnomalySeverityIndicator } from './anomaly_severity_indicator';
import { RegularExpressionRepresentation } from './category_expression';
import { DatasetsList } from './datasets_list';
import { LogEntryCountSparkline } from './log_entry_count_sparkline';
import { RowExpansionButton } from '../../../../../components/basic_table';
import { CategoryDetailsRow } from './category_details_row';

export const TopCategoriesTable = euiStyled(
({
className,
sourceId,
timeRange,
topCategories,
}: {
className?: string;
sourceId: string;
timeRange: TimeRange;
topCategories: LogEntryCategory[];
}) => {
const columns = useMemo(() => createColumns(timeRange), [timeRange]);
const [expandedCategories, { add: expandCategory, remove: collapseCategory }] = useSet<number>(
new Set()
);

const columns = useMemo(
() => createColumns(timeRange, expandedCategories, expandCategory, collapseCategory),
[collapseCategory, expandCategory, expandedCategories, timeRange]
);

const expandedRowContentsById = useMemo(
() =>
[...expandedCategories].reduce<Record<number, React.ReactNode>>(
(aggregatedCategoryRows, categoryId) => ({
...aggregatedCategoryRows,
[categoryId]: (
<div>
<CategoryDetailsRow
categoryId={categoryId}
sourceId={sourceId}
timeRange={timeRange}
/>
</div>
),
}),
{}
),
[expandedCategories, sourceId, timeRange]
);

return (
<EuiBasicTable
columns={columns}
itemIdToExpandedRowMap={expandedRowContentsById}
itemId="categoryId"
items={topCategories}
rowProps={{ className: `${className} euiTableRow--topAligned` }}
/>
Expand All @@ -46,7 +80,12 @@ export const TopCategoriesTable = euiStyled(
}
`;

const createColumns = (timeRange: TimeRange): Array<EuiBasicTableColumn<LogEntryCategory>> => [
const createColumns = (
timeRange: TimeRange,
expandedCategories: Set<number>,
expandCategory: (categoryId: number) => void,
collapseCategory: (categoryId: number) => void
): Array<EuiBasicTableColumn<LogEntryCategory>> => [
{
align: 'right',
field: 'logEntryCount',
Expand Down Expand Up @@ -103,4 +142,19 @@ const createColumns = (timeRange: TimeRange): Array<EuiBasicTableColumn<LogEntry
),
width: '160px',
},
{
align: 'right',
isExpander: true,
render: (item: LogEntryCategory) => {
return (
<RowExpansionButton
isExpanded={expandedCategories.has(item.categoryId)}
item={item.categoryId}
onCollapse={collapseCategory}
onExpand={expandCategory}
/>
);
},
width: '40px',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { npStart } from 'ui/new_platform';

import {
getLogEntryCategoryExamplesRequestPayloadRT,
getLogEntryCategoryExamplesSuccessReponsePayloadRT,
LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH,
} from '../../../../../common/http_api/log_analysis';
import { createPlainError, throwErrors } from '../../../../../common/runtime_types';

export const callGetLogEntryCategoryExamplesAPI = async (
sourceId: string,
startTime: number,
endTime: number,
categoryId: number,
exampleCount: number
) => {
const response = await npStart.core.http.fetch(
LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH,
{
method: 'POST',
body: JSON.stringify(
getLogEntryCategoryExamplesRequestPayloadRT.encode({
data: {
categoryId,
exampleCount,
sourceId,
timeRange: {
startTime,
endTime,
},
},
})
),
}
);

return pipe(
getLogEntryCategoryExamplesSuccessReponsePayloadRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { useMemo, useState } from 'react';

import { LogEntryCategoryExample } from '../../../../common/http_api';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { callGetLogEntryCategoryExamplesAPI } from './service_calls/get_log_entry_category_examples';

export const useLogEntryCategoryExamples = ({
categoryId,
endTime,
exampleCount,
sourceId,
startTime,
}: {
categoryId: number;
endTime: number;
exampleCount: number;
sourceId: string;
startTime: number;
}) => {
const [logEntryCategoryExamples, setLogEntryCategoryExamples] = useState<
LogEntryCategoryExample[]
>([]);

const [getLogEntryCategoryExamplesRequest, getLogEntryCategoryExamples] = useTrackedPromise(
{
cancelPreviousOn: 'creation',
createPromise: async () => {
return await callGetLogEntryCategoryExamplesAPI(
sourceId,
startTime,
endTime,
categoryId,
exampleCount
);
},
onResolve: ({ data: { examples } }) => {
setLogEntryCategoryExamples(examples);
},
},
[categoryId, endTime, exampleCount, sourceId, startTime]
);

const isLoadingLogEntryCategoryExamples = useMemo(
() => getLogEntryCategoryExamplesRequest.state === 'pending',
[getLogEntryCategoryExamplesRequest.state]
);

return {
getLogEntryCategoryExamples,
isLoadingLogEntryCategoryExamples,
logEntryCategoryExamples,
};
};
Loading

0 comments on commit fce8a3b

Please sign in to comment.