From 53f4b21a8158d0becbd7874f75c0d3a85ceefbe5 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 18 Jan 2021 14:12:29 +0000 Subject: [PATCH] [Logs UI] Add sorting capabilities to categories page (#88051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add sorting capabilities to categories page Co-authored-by: Felix Stürmer --- .../results/log_entry_categories.ts | 19 +++++++++++++++ .../page_results_content.tsx | 11 ++++++++- .../top_categories/top_categories_section.tsx | 7 ++++++ .../top_categories/top_categories_table.tsx | 22 ++++++++++++++++- .../get_top_log_entry_categories.ts | 5 +++- .../use_log_entry_categories_results.ts | 13 +++++++++- .../log_entry_categories_analysis.ts | 13 ++++++---- .../queries/top_log_entry_categories.ts | 24 +++++++++++++++++-- .../results/log_entry_categories.ts | 4 +++- 9 files changed, 107 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts index f56462012d2e..0554192398fc 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts @@ -30,6 +30,23 @@ export type LogEntryCategoriesHistogramParameters = rt.TypeOf< typeof logEntryCategoriesHistogramParametersRT >; +const sortOptionsRT = rt.keyof({ + maximumAnomalyScore: null, + logEntryCount: null, +}); + +const sortDirectionsRT = rt.keyof({ + asc: null, + desc: null, +}); + +const categorySortRT = rt.type({ + field: sortOptionsRT, + direction: sortDirectionsRT, +}); + +export type CategorySort = rt.TypeOf; + export const getLogEntryCategoriesRequestPayloadRT = rt.type({ data: rt.intersection([ rt.type({ @@ -41,6 +58,8 @@ export const getLogEntryCategoriesRequestPayloadRT = rt.type({ timeRange: timeRangeRT, // a list of histograms to create histograms: rt.array(logEntryCategoriesHistogramParametersRT), + // the criteria to the categories by + sort: categorySortRT, }), rt.partial({ // the datasets to filter for (optional, unfiltered if not present) diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx index 6fc9ce3d8983..5c1e8f2bdcfc 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -87,6 +87,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent { getTopLogEntryCategories(); - }, [getTopLogEntryCategories, categoryQueryDatasets, categoryQueryTimeRange.lastChangedTime]); + }, [ + getTopLogEntryCategories, + categoryQueryDatasets, + categoryQueryTimeRange.lastChangedTime, + sortOptions, + ]); useEffect(() => { getLogEntryCategoryDatasets(); @@ -219,6 +226,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx index 238300c1a1fb..c7a6c89012a3 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx @@ -16,6 +16,7 @@ import { RecreateJobButton } from '../../../../../components/logging/log_analysi import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results'; import { DatasetsSelector } from '../../../../../components/logging/log_analysis_results/datasets_selector'; import { TopCategoriesTable } from './top_categories_table'; +import { SortOptions, ChangeSortOptions } from '../../use_log_entry_categories_results'; export const TopCategoriesSection: React.FunctionComponent<{ availableDatasets: string[]; @@ -29,6 +30,8 @@ export const TopCategoriesSection: React.FunctionComponent<{ sourceId: string; timeRange: TimeRange; topCategories: LogEntryCategory[]; + sortOptions: SortOptions; + changeSortOptions: ChangeSortOptions; }> = ({ availableDatasets, hasSetupCapabilities, @@ -41,6 +44,8 @@ export const TopCategoriesSection: React.FunctionComponent<{ sourceId, timeRange, topCategories, + sortOptions, + changeSortOptions, }) => { return ( <> @@ -80,6 +85,8 @@ export const TopCategoriesSection: React.FunctionComponent<{ sourceId={sourceId} timeRange={timeRange} topCategories={topCategories} + sortOptions={sortOptions} + changeSortOptions={changeSortOptions} /> diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx index ab4195492a70..96abe4ab4266 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx @@ -7,7 +7,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import useSet from 'react-use/lib/useSet'; import { euiStyled } from '../../../../../../../observability/public'; @@ -24,6 +24,7 @@ import { RegularExpressionRepresentation } from './category_expression'; import { DatasetActionsList } from './datasets_action_list'; import { DatasetsList } from './datasets_list'; import { LogEntryCountSparkline } from './log_entry_count_sparkline'; +import { SortOptions, ChangeSortOptions } from '../../use_log_entry_categories_results'; export const TopCategoriesTable = euiStyled( ({ @@ -32,13 +33,28 @@ export const TopCategoriesTable = euiStyled( sourceId, timeRange, topCategories, + sortOptions, + changeSortOptions, }: { categorizationJobId: string; className?: string; sourceId: string; timeRange: TimeRange; topCategories: LogEntryCategory[]; + sortOptions: SortOptions; + changeSortOptions: ChangeSortOptions; }) => { + const tableSortOptions = useMemo(() => { + return { sort: sortOptions }; + }, [sortOptions]); + + const handleTableChange = useCallback( + ({ sort = {} }) => { + changeSortOptions(sort); + }, + [changeSortOptions] + ); + const [expandedCategories, { add: expandCategory, remove: collapseCategory }] = useSet( new Set() ); @@ -80,6 +96,8 @@ export const TopCategoriesTable = euiStyled( itemId="categoryId" items={topCategories} rowProps={{ className: `${className} euiTableRow--topAligned` }} + onChange={handleTableChange} + sorting={tableSortOptions} /> ); } @@ -102,6 +120,7 @@ const createColumns = ( name: i18n.translate('xpack.infra.logs.logEntryCategories.countColumnTitle', { defaultMessage: 'Message count', }), + sortable: true, render: (logEntryCount: number) => { return numeral(logEntryCount).format('0,0'); }, @@ -147,6 +166,7 @@ const createColumns = ( name: i18n.translate('xpack.infra.logs.logEntryCategories.maximumAnomalyScoreColumnTitle', { defaultMessage: 'Maximum anomaly score', }), + sortable: true, render: (_maximumAnomalyScore: number, item) => ( ), diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts index fd5380379633..a0eaecf04fa4 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts @@ -10,6 +10,7 @@ import { getLogEntryCategoriesRequestPayloadRT, getLogEntryCategoriesSuccessReponsePayloadRT, LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, + CategorySort, } from '../../../../../common/http_api/log_analysis'; import { decodeOrThrow } from '../../../../../common/runtime_types'; @@ -19,13 +20,14 @@ interface RequestArgs { endTime: number; categoryCount: number; datasets?: string[]; + sort: CategorySort; } export const callGetTopLogEntryCategoriesAPI = async ( requestArgs: RequestArgs, fetch: HttpHandler ) => { - const { sourceId, startTime, endTime, categoryCount, datasets } = requestArgs; + const { sourceId, startTime, endTime, categoryCount, datasets, sort } = requestArgs; const intervalDuration = endTime - startTime; const response = await fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, { @@ -58,6 +60,7 @@ export const callGetTopLogEntryCategoriesAPI = async ( bucketCount: 1, }, ], + sort, }, }) ), diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts index 9f193d796e8e..a64b73dea25e 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts @@ -9,6 +9,7 @@ import { useMemo, useState } from 'react'; import { GetLogEntryCategoriesSuccessResponsePayload, GetLogEntryCategoryDatasetsSuccessResponsePayload, + CategorySort, } from '../../../../common/http_api/log_analysis'; import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise'; import { callGetTopLogEntryCategoriesAPI } from './service_calls/get_top_log_entry_categories'; @@ -18,6 +19,9 @@ import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; type TopLogEntryCategories = GetLogEntryCategoriesSuccessResponsePayload['data']['categories']; type LogEntryCategoryDatasets = GetLogEntryCategoryDatasetsSuccessResponsePayload['data']['datasets']; +export type SortOptions = CategorySort; +export type ChangeSortOptions = (sortOptions: CategorySort) => void; + export const useLogEntryCategoriesResults = ({ categoriesCount, filteredDatasets: filteredDatasets, @@ -35,6 +39,10 @@ export const useLogEntryCategoriesResults = ({ sourceId: string; startTime: number; }) => { + const [sortOptions, setSortOptions] = useState({ + field: 'maximumAnomalyScore', + direction: 'desc', + }); const { services } = useKibanaContextForPlugin(); const [topLogEntryCategories, setTopLogEntryCategories] = useState([]); const [ @@ -53,6 +61,7 @@ export const useLogEntryCategoriesResults = ({ endTime, categoryCount: categoriesCount, datasets: filteredDatasets, + sort: sortOptions, }, services.http.fetch ); @@ -70,7 +79,7 @@ export const useLogEntryCategoriesResults = ({ } }, }, - [categoriesCount, endTime, filteredDatasets, sourceId, startTime] + [categoriesCount, endTime, filteredDatasets, sourceId, startTime, sortOptions] ); const [getLogEntryCategoryDatasetsRequest, getLogEntryCategoryDatasets] = useTrackedPromise( @@ -121,5 +130,7 @@ export const useLogEntryCategoriesResults = ({ isLoadingTopLogEntryCategories, logEntryCategoryDatasets, topLogEntryCategories, + sortOptions, + changeSortOptions: setSortOptions, }; }; diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index cf3abc81e97c..7dd5aae9784f 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -12,6 +12,7 @@ import { jobCustomSettingsRT, logEntryCategoriesJobTypes, } from '../../../common/log_analysis'; +import { CategorySort } from '../../../common/http_api/log_analysis'; import { startTracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { MlAnomalyDetectors, MlSystem } from '../../types'; @@ -49,7 +50,8 @@ export async function getTopLogEntryCategories( endTime: number, categoryCount: number, datasets: string[], - histograms: HistogramParameters[] + histograms: HistogramParameters[], + sort: CategorySort ) { const finalizeTopLogEntryCategoriesSpan = startTracingSpan('get top categories'); @@ -68,7 +70,8 @@ export async function getTopLogEntryCategories( startTime, endTime, categoryCount, - datasets + datasets, + sort ); const categoryIds = topLogEntryCategories.map(({ categoryId }) => categoryId); @@ -214,7 +217,8 @@ async function fetchTopLogEntryCategories( startTime: number, endTime: number, categoryCount: number, - datasets: string[] + datasets: string[], + sort: CategorySort ) { const finalizeEsSearchSpan = startTracingSpan('Fetch top categories from ES'); @@ -225,7 +229,8 @@ async function fetchTopLogEntryCategories( startTime, endTime, categoryCount, - datasets + datasets, + sort ), [logEntryCategoriesCountJobId] ) diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts index 5d3d9bc8b403..057054b42722 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts @@ -14,13 +14,33 @@ import { createDatasetsFilters, } from './common'; +import { CategorySort } from '../../../../common/http_api/log_analysis'; + +type CategoryAggregationOrder = + | 'filter_record>maximum_record_score' + | 'filter_model_plot>sum_actual'; +const getAggregationOrderForSortField = ( + field: CategorySort['field'] +): CategoryAggregationOrder => { + switch (field) { + case 'maximumAnomalyScore': + return 'filter_record>maximum_record_score'; + break; + case 'logEntryCount': + return 'filter_model_plot>sum_actual'; + break; + default: + return 'filter_model_plot>sum_actual'; + } +}; + export const createTopLogEntryCategoriesQuery = ( logEntryCategoriesJobId: string, startTime: number, endTime: number, size: number, datasets: string[], - sortDirection: 'asc' | 'desc' = 'desc' + sort: CategorySort ) => ({ ...defaultRequestParameters, body: { @@ -65,7 +85,7 @@ export const createTopLogEntryCategoriesQuery = ( field: 'by_field_value', size, order: { - 'filter_model_plot>sum_actual': sortDirection, + [getAggregationOrderForSortField(sort.field)]: sort.direction, }, }, aggs: { diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts index 7071d38dffe5..da5466ed46f6 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts @@ -33,6 +33,7 @@ export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) sourceId, timeRange: { startTime, endTime }, datasets, + sort, }, } = request.body; @@ -51,7 +52,8 @@ export const initGetLogEntryCategoriesRoute = ({ framework }: InfraBackendLibs) endTime: histogram.timeRange.endTime, id: histogram.id, startTime: histogram.timeRange.startTime, - })) + })), + sort ); return response.ok({