[Ingest Manager] Add ability to sort to agent configs and package configs (#70676)
* Add sorting params to list endpoints; allow sorting on agent config and package config tables; normalize casing of 'desc' and 'asc' * Fix es archiver data * Fix tests
This commit is contained in:
parent
5226ea2112
commit
54348a761e
16 changed files with 83 additions and 49 deletions
|
@ -5,7 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ListWithKuery {
|
export interface ListWithKuery {
|
||||||
page: number;
|
page?: number;
|
||||||
perPage: number;
|
perPage?: number;
|
||||||
|
sortField?: string;
|
||||||
|
sortOrder?: 'desc' | 'asc';
|
||||||
kuery?: string;
|
kuery?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ export { useLink } from './use_link';
|
||||||
export { useKibanaLink } from './use_kibana_link';
|
export { useKibanaLink } from './use_kibana_link';
|
||||||
export { usePackageIconType, UsePackageIconType } from './use_package_icon_type';
|
export { usePackageIconType, UsePackageIconType } from './use_package_icon_type';
|
||||||
export { usePagination, Pagination } from './use_pagination';
|
export { usePagination, Pagination } from './use_pagination';
|
||||||
|
export { useSorting } from './use_sorting';
|
||||||
export { useDebounce } from './use_debounce';
|
export { useDebounce } from './use_debounce';
|
||||||
export * from './use_request';
|
export * from './use_request';
|
||||||
export * from './use_input';
|
export * from './use_input';
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* 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 { useState } from 'react';
|
||||||
|
import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table';
|
||||||
|
|
||||||
|
export function useSorting<T>(defaultSorting: CriteriaWithPagination<T>['sort']) {
|
||||||
|
const [sorting, setSorting] = useState<CriteriaWithPagination<T>['sort']>(defaultSorting);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sorting,
|
||||||
|
setSorting,
|
||||||
|
};
|
||||||
|
}
|
|
@ -31,7 +31,12 @@ export const StepSelectConfig: React.FunctionComponent<{
|
||||||
data: agentConfigsData,
|
data: agentConfigsData,
|
||||||
error: agentConfigsError,
|
error: agentConfigsError,
|
||||||
isLoading: isAgentConfigsLoading,
|
isLoading: isAgentConfigsLoading,
|
||||||
} = useGetAgentConfigs();
|
} = useGetAgentConfigs({
|
||||||
|
page: 1,
|
||||||
|
perPage: 1000,
|
||||||
|
sortField: 'name',
|
||||||
|
sortOrder: 'asc',
|
||||||
|
});
|
||||||
const agentConfigs = agentConfigsData?.items || [];
|
const agentConfigs = agentConfigsData?.items || [];
|
||||||
const agentConfigsById = agentConfigs.reduce(
|
const agentConfigsById = agentConfigs.reduce(
|
||||||
(acc: { [key: string]: GetAgentConfigsResponseItem }, config) => {
|
(acc: { [key: string]: GetAgentConfigsResponseItem }, config) => {
|
||||||
|
|
|
@ -118,6 +118,7 @@ export const PackageConfigsTable: React.FunctionComponent<Props> = ({
|
||||||
(): EuiInMemoryTableProps<InMemoryPackageConfig>['columns'] => [
|
(): EuiInMemoryTableProps<InMemoryPackageConfig>['columns'] => [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
|
sortable: true,
|
||||||
name: i18n.translate(
|
name: i18n.translate(
|
||||||
'xpack.ingestManager.configDetails.packageConfigsTable.nameColumnTitle',
|
'xpack.ingestManager.configDetails.packageConfigsTable.nameColumnTitle',
|
||||||
{
|
{
|
||||||
|
@ -137,6 +138,7 @@ export const PackageConfigsTable: React.FunctionComponent<Props> = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'packageTitle',
|
field: 'packageTitle',
|
||||||
|
sortable: true,
|
||||||
name: i18n.translate(
|
name: i18n.translate(
|
||||||
'xpack.ingestManager.configDetails.packageConfigsTable.packageNameColumnTitle',
|
'xpack.ingestManager.configDetails.packageConfigsTable.packageNameColumnTitle',
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
EuiTableFieldDataColumnType,
|
EuiTableFieldDataColumnType,
|
||||||
EuiTextColor,
|
EuiTextColor,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage, FormattedDate } from '@kbn/i18n/react';
|
import { FormattedMessage, FormattedDate } from '@kbn/i18n/react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
@ -27,6 +28,7 @@ import {
|
||||||
useCapabilities,
|
useCapabilities,
|
||||||
useGetAgentConfigs,
|
useGetAgentConfigs,
|
||||||
usePagination,
|
usePagination,
|
||||||
|
useSorting,
|
||||||
useLink,
|
useLink,
|
||||||
useConfig,
|
useConfig,
|
||||||
useUrlParams,
|
useUrlParams,
|
||||||
|
@ -84,6 +86,10 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
||||||
: urlParams.kuery ?? ''
|
: urlParams.kuery ?? ''
|
||||||
);
|
);
|
||||||
const { pagination, pageSizeOptions, setPagination } = usePagination();
|
const { pagination, pageSizeOptions, setPagination } = usePagination();
|
||||||
|
const { sorting, setSorting } = useSorting<AgentConfig>({
|
||||||
|
field: 'updated_at',
|
||||||
|
direction: 'desc',
|
||||||
|
});
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const isCreateAgentConfigFlyoutOpen = 'create' in urlParams;
|
const isCreateAgentConfigFlyoutOpen = 'create' in urlParams;
|
||||||
const setIsCreateAgentConfigFlyoutOpen = useCallback(
|
const setIsCreateAgentConfigFlyoutOpen = useCallback(
|
||||||
|
@ -106,6 +112,8 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
||||||
const { isLoading, data: agentConfigData, sendRequest } = useGetAgentConfigs({
|
const { isLoading, data: agentConfigData, sendRequest } = useGetAgentConfigs({
|
||||||
page: pagination.currentPage,
|
page: pagination.currentPage,
|
||||||
perPage: pagination.pageSize,
|
perPage: pagination.pageSize,
|
||||||
|
sortField: sorting?.field,
|
||||||
|
sortOrder: sorting?.direction,
|
||||||
kuery: search,
|
kuery: search,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,6 +124,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
||||||
> = [
|
> = [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
|
sortable: true,
|
||||||
name: i18n.translate('xpack.ingestManager.agentConfigList.nameColumnTitle', {
|
name: i18n.translate('xpack.ingestManager.agentConfigList.nameColumnTitle', {
|
||||||
defaultMessage: 'Name',
|
defaultMessage: 'Name',
|
||||||
}),
|
}),
|
||||||
|
@ -158,6 +167,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'updated_at',
|
field: 'updated_at',
|
||||||
|
sortable: true,
|
||||||
name: i18n.translate('xpack.ingestManager.agentConfigList.updatedOnColumnTitle', {
|
name: i18n.translate('xpack.ingestManager.agentConfigList.updatedOnColumnTitle', {
|
||||||
defaultMessage: 'Last updated on',
|
defaultMessage: 'Last updated on',
|
||||||
}),
|
}),
|
||||||
|
@ -240,6 +250,16 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
||||||
[createAgentConfigButton]
|
[createAgentConfigButton]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onTableChange = (criteria: CriteriaWithPagination<AgentConfig>) => {
|
||||||
|
const newPagination = {
|
||||||
|
...pagination,
|
||||||
|
currentPage: criteria.page.index + 1,
|
||||||
|
pageSize: criteria.page.size,
|
||||||
|
};
|
||||||
|
setPagination(newPagination);
|
||||||
|
setSorting(criteria.sort);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AgentConfigListPageLayout>
|
<AgentConfigListPageLayout>
|
||||||
{isCreateAgentConfigFlyoutOpen ? (
|
{isCreateAgentConfigFlyoutOpen ? (
|
||||||
|
@ -276,7 +296,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
||||||
<EuiSpacer size="m" />
|
<EuiSpacer size="m" />
|
||||||
<EuiBasicTable
|
<EuiBasicTable<AgentConfig>
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
hasActions={true}
|
hasActions={true}
|
||||||
noItemsMessage={
|
noItemsMessage={
|
||||||
|
@ -314,14 +334,8 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => {
|
||||||
totalItemCount: agentConfigData ? agentConfigData.total : 0,
|
totalItemCount: agentConfigData ? agentConfigData.total : 0,
|
||||||
pageSizeOptions,
|
pageSizeOptions,
|
||||||
}}
|
}}
|
||||||
onChange={({ page }: { page: { index: number; size: number } }) => {
|
sorting={{ sort: sorting }}
|
||||||
const newPagination = {
|
onChange={onTableChange}
|
||||||
...pagination,
|
|
||||||
currentPage: page.index + 1,
|
|
||||||
pageSize: page.size,
|
|
||||||
};
|
|
||||||
setPagination(newPagination);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</AgentConfigListPageLayout>
|
</AgentConfigListPageLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,7 +36,10 @@ export const AgentReassignConfigFlyout: React.FunctionComponent<Props> = ({ onCl
|
||||||
agent.config_id
|
agent.config_id
|
||||||
);
|
);
|
||||||
|
|
||||||
const agentConfigsRequest = useGetAgentConfigs();
|
const agentConfigsRequest = useGetAgentConfigs({
|
||||||
|
page: 1,
|
||||||
|
perPage: 1000,
|
||||||
|
});
|
||||||
const agentConfigs = agentConfigsRequest.data ? agentConfigsRequest.data.items : [];
|
const agentConfigs = agentConfigsRequest.data ? agentConfigsRequest.data.items : [];
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
|
@ -119,8 +119,7 @@ const savedObjectTypes: { [key: string]: SavedObjectsType } = {
|
||||||
},
|
},
|
||||||
mappings: {
|
mappings: {
|
||||||
properties: {
|
properties: {
|
||||||
id: { type: 'keyword' },
|
name: { type: 'keyword' },
|
||||||
name: { type: 'text' },
|
|
||||||
description: { type: 'text' },
|
description: { type: 'text' },
|
||||||
namespace: { type: 'keyword' },
|
namespace: { type: 'keyword' },
|
||||||
is_default: { type: 'boolean' },
|
is_default: { type: 'boolean' },
|
||||||
|
|
|
@ -143,10 +143,12 @@ class AgentConfigService {
|
||||||
soClient: SavedObjectsClientContract,
|
soClient: SavedObjectsClientContract,
|
||||||
options: ListWithKuery
|
options: ListWithKuery
|
||||||
): Promise<{ items: AgentConfig[]; total: number; page: number; perPage: number }> {
|
): Promise<{ items: AgentConfig[]; total: number; page: number; perPage: number }> {
|
||||||
const { page = 1, perPage = 20, kuery } = options;
|
const { page = 1, perPage = 20, sortField = 'updated_at', sortOrder = 'desc', kuery } = options;
|
||||||
|
|
||||||
const agentConfigs = await soClient.find<AgentConfigSOAttributes>({
|
const agentConfigs = await soClient.find<AgentConfigSOAttributes>({
|
||||||
type: SAVED_OBJECT_TYPE,
|
type: SAVED_OBJECT_TYPE,
|
||||||
|
sortField,
|
||||||
|
sortOrder,
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
// To ensure users don't need to know about SO data structure...
|
// To ensure users don't need to know about SO data structure...
|
||||||
|
@ -273,7 +275,6 @@ class AgentConfigService {
|
||||||
soClient,
|
soClient,
|
||||||
id,
|
id,
|
||||||
{
|
{
|
||||||
...oldAgentConfig,
|
|
||||||
package_configs: uniq(
|
package_configs: uniq(
|
||||||
[...((oldAgentConfig.package_configs || []) as string[])].filter(
|
[...((oldAgentConfig.package_configs || []) as string[])].filter(
|
||||||
(pkgConfigId) => !packageConfigIds.includes(pkgConfigId)
|
(pkgConfigId) => !packageConfigIds.includes(pkgConfigId)
|
||||||
|
|
|
@ -12,20 +12,24 @@ import {
|
||||||
AGENT_TYPE_EPHEMERAL,
|
AGENT_TYPE_EPHEMERAL,
|
||||||
AGENT_POLLING_THRESHOLD_MS,
|
AGENT_POLLING_THRESHOLD_MS,
|
||||||
} from '../../constants';
|
} from '../../constants';
|
||||||
import { AgentSOAttributes, Agent, AgentEventSOAttributes } from '../../types';
|
import { AgentSOAttributes, Agent, AgentEventSOAttributes, ListWithKuery } from '../../types';
|
||||||
import { savedObjectToAgent } from './saved_objects';
|
import { savedObjectToAgent } from './saved_objects';
|
||||||
import { escapeSearchQueryPhrase } from '../saved_object';
|
import { escapeSearchQueryPhrase } from '../saved_object';
|
||||||
|
|
||||||
export async function listAgents(
|
export async function listAgents(
|
||||||
soClient: SavedObjectsClientContract,
|
soClient: SavedObjectsClientContract,
|
||||||
options: {
|
options: ListWithKuery & {
|
||||||
page: number;
|
|
||||||
perPage: number;
|
|
||||||
kuery?: string;
|
|
||||||
showInactive: boolean;
|
showInactive: boolean;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { page, perPage, kuery, showInactive = false } = options;
|
const {
|
||||||
|
page = 1,
|
||||||
|
perPage = 20,
|
||||||
|
sortField = 'enrolled_at',
|
||||||
|
sortOrder = 'desc',
|
||||||
|
kuery,
|
||||||
|
showInactive = false,
|
||||||
|
} = options;
|
||||||
|
|
||||||
const filters = [];
|
const filters = [];
|
||||||
|
|
||||||
|
@ -49,10 +53,11 @@ export async function listAgents(
|
||||||
|
|
||||||
const { saved_objects, total } = await soClient.find<AgentSOAttributes>({
|
const { saved_objects, total } = await soClient.find<AgentSOAttributes>({
|
||||||
type: AGENT_SAVED_OBJECT_TYPE,
|
type: AGENT_SAVED_OBJECT_TYPE,
|
||||||
|
sortField,
|
||||||
|
sortOrder,
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
filter: _joinFilters(filters),
|
filter: _joinFilters(filters),
|
||||||
..._getSortFields(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const agents: Agent[] = saved_objects.map(savedObjectToAgent);
|
const agents: Agent[] = saved_objects.map(savedObjectToAgent);
|
||||||
|
@ -137,23 +142,6 @@ export async function deleteAgent(soClient: SavedObjectsClientContract, agentId:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getSortFields(sortOption?: string) {
|
|
||||||
switch (sortOption) {
|
|
||||||
case 'ASC':
|
|
||||||
return {
|
|
||||||
sortField: 'enrolled_at',
|
|
||||||
sortOrder: 'ASC',
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'DESC':
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
sortField: 'enrolled_at',
|
|
||||||
sortOrder: 'DESC',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _joinFilters(filters: string[], operator = 'AND') {
|
function _joinFilters(filters: string[], operator = 'AND') {
|
||||||
return filters.reduce((acc: string | undefined, filter) => {
|
return filters.reduce((acc: string | undefined, filter) => {
|
||||||
if (acc) {
|
if (acc) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export async function getAgentEvents(
|
||||||
perPage,
|
perPage,
|
||||||
page,
|
page,
|
||||||
sortField: 'timestamp',
|
sortField: 'timestamp',
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'desc',
|
||||||
defaultSearchOperator: 'AND',
|
defaultSearchOperator: 'AND',
|
||||||
search: agentId,
|
search: agentId,
|
||||||
searchFields: ['agent_id'],
|
searchFields: ['agent_id'],
|
||||||
|
|
|
@ -61,7 +61,7 @@ async function getEventsCount(soClient: SavedObjectsClientContract, configId?: s
|
||||||
perPage: 0,
|
perPage: 0,
|
||||||
page: 1,
|
page: 1,
|
||||||
sortField: 'timestamp',
|
sortField: 'timestamp',
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'desc',
|
||||||
defaultSearchOperator: 'AND',
|
defaultSearchOperator: 'AND',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ export async function listEnrollmentApiKeys(
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
sortOrder: 'DESC',
|
sortOrder: 'desc',
|
||||||
filter:
|
filter:
|
||||||
kuery && kuery !== ''
|
kuery && kuery !== ''
|
||||||
? kuery.replace(
|
? kuery.replace(
|
||||||
|
|
|
@ -145,10 +145,12 @@ class PackageConfigService {
|
||||||
soClient: SavedObjectsClientContract,
|
soClient: SavedObjectsClientContract,
|
||||||
options: ListWithKuery
|
options: ListWithKuery
|
||||||
): Promise<{ items: PackageConfig[]; total: number; page: number; perPage: number }> {
|
): Promise<{ items: PackageConfig[]; total: number; page: number; perPage: number }> {
|
||||||
const { page = 1, perPage = 20, kuery } = options;
|
const { page = 1, perPage = 20, sortField = 'updated_at', sortOrder = 'desc', kuery } = options;
|
||||||
|
|
||||||
const packageConfigs = await soClient.find<PackageConfigSOAttributes>({
|
const packageConfigs = await soClient.find<PackageConfigSOAttributes>({
|
||||||
type: SAVED_OBJECT_TYPE,
|
type: SAVED_OBJECT_TYPE,
|
||||||
|
sortField,
|
||||||
|
sortOrder,
|
||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
// To ensure users don't need to know about SO data structure...
|
// To ensure users don't need to know about SO data structure...
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
import { schema, TypeOf } from '@kbn/config-schema';
|
import { schema, TypeOf } from '@kbn/config-schema';
|
||||||
|
|
||||||
export const ListWithKuerySchema = schema.object({
|
export const ListWithKuerySchema = schema.object({
|
||||||
page: schema.number({ defaultValue: 1 }),
|
page: schema.maybe(schema.number({ defaultValue: 1 })),
|
||||||
perPage: schema.number({ defaultValue: 20 }),
|
perPage: schema.maybe(schema.number({ defaultValue: 20 })),
|
||||||
|
sortField: schema.maybe(schema.string()),
|
||||||
|
sortOrder: schema.maybe(schema.oneOf([schema.literal('desc'), schema.literal('asc')])),
|
||||||
kuery: schema.maybe(schema.string()),
|
kuery: schema.maybe(schema.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -220,8 +220,7 @@
|
||||||
],
|
],
|
||||||
"revision": 2,
|
"revision": 2,
|
||||||
"updated_at": "2020-05-07T19:34:42.533Z",
|
"updated_at": "2020-05-07T19:34:42.533Z",
|
||||||
"updated_by": "system",
|
"updated_by": "system"
|
||||||
"id": "config1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue