[Logs UI] Add sorting capabilities to categories page (#88051)
* Add sorting capabilities to categories page Co-authored-by: Felix Stürmer <weltenwort@users.noreply.github.com>
This commit is contained in:
parent
b8d43139f3
commit
53f4b21a81
|
@ -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<typeof categorySortRT>;
|
||||
|
||||
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)
|
||||
|
|
|
@ -87,6 +87,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
|
|||
isLoadingTopLogEntryCategories,
|
||||
logEntryCategoryDatasets,
|
||||
topLogEntryCategories,
|
||||
sortOptions,
|
||||
changeSortOptions,
|
||||
} = useLogEntryCategoriesResults({
|
||||
categoriesCount: 25,
|
||||
endTime: categoryQueryTimeRange.timeRange.endTime,
|
||||
|
@ -145,7 +147,12 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
|
|||
|
||||
useEffect(() => {
|
||||
getTopLogEntryCategories();
|
||||
}, [getTopLogEntryCategories, categoryQueryDatasets, categoryQueryTimeRange.lastChangedTime]);
|
||||
}, [
|
||||
getTopLogEntryCategories,
|
||||
categoryQueryDatasets,
|
||||
categoryQueryTimeRange.lastChangedTime,
|
||||
sortOptions,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
getLogEntryCategoryDatasets();
|
||||
|
@ -219,6 +226,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
|
|||
sourceId={sourceId}
|
||||
timeRange={categoryQueryTimeRange.timeRange}
|
||||
topCategories={topLogEntryCategories}
|
||||
sortOptions={sortOptions}
|
||||
changeSortOptions={changeSortOptions}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</LoadingOverlayWrapper>
|
||||
</>
|
||||
|
|
|
@ -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<number>(
|
||||
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) => (
|
||||
<AnomalySeverityIndicatorList datasets={item.datasets} />
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
),
|
||||
|
|
|
@ -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<SortOptions>({
|
||||
field: 'maximumAnomalyScore',
|
||||
direction: 'desc',
|
||||
});
|
||||
const { services } = useKibanaContextForPlugin();
|
||||
const [topLogEntryCategories, setTopLogEntryCategories] = useState<TopLogEntryCategories>([]);
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Reference in a new issue