mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Fleet] support sorting agent list (#135218)
* support sorting agent list * updated openapi * updated openapi
This commit is contained in:
parent
637671a43a
commit
158e4f693d
15 changed files with 235 additions and 14 deletions
|
@ -1191,6 +1191,29 @@
|
|||
}
|
||||
},
|
||||
"operationId": "get-agents",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/page_size"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/page_index"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/kuery"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/show_inactive"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/show_upgradeable"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/sort_field"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/sort_order"
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"basicAuth": []
|
||||
|
@ -3314,7 +3337,7 @@
|
|||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 50
|
||||
"default": 20
|
||||
}
|
||||
},
|
||||
"page_index": {
|
||||
|
@ -3333,6 +3356,42 @@
|
|||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"show_inactive": {
|
||||
"name": "showInactive",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"show_upgradeable": {
|
||||
"name": "showUpgradeable",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"sort_field": {
|
||||
"name": "sortField",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"sort_order": {
|
||||
"name": "sortOrder",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
|
|
|
@ -737,6 +737,14 @@ paths:
|
|||
schema:
|
||||
$ref: '#/components/schemas/get_agents_response'
|
||||
operationId: get-agents
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page_size'
|
||||
- $ref: '#/components/parameters/page_index'
|
||||
- $ref: '#/components/parameters/kuery'
|
||||
- $ref: '#/components/parameters/show_inactive'
|
||||
- $ref: '#/components/parameters/show_upgradeable'
|
||||
- $ref: '#/components/parameters/sort_field'
|
||||
- $ref: '#/components/parameters/sort_order'
|
||||
security:
|
||||
- basicAuth: []
|
||||
/agents/bulk_upgrade:
|
||||
|
@ -2047,7 +2055,7 @@ components:
|
|||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
default: 50
|
||||
default: 20
|
||||
page_index:
|
||||
name: page
|
||||
in: query
|
||||
|
@ -2061,6 +2069,33 @@ components:
|
|||
required: false
|
||||
schema:
|
||||
type: string
|
||||
show_inactive:
|
||||
name: showInactive
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
show_upgradeable:
|
||||
name: showUpgradeable
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
sort_field:
|
||||
name: sortField
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
sort_order:
|
||||
name: sortOrder
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
schemas:
|
||||
fleet_setup_response:
|
||||
title: Fleet Setup response
|
||||
|
|
|
@ -4,4 +4,4 @@ description: The number of items to return
|
|||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
default: 50
|
||||
default: 20
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
name: showInactive
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
|
@ -0,0 +1,5 @@
|
|||
name: showUpgradeable
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
|
@ -0,0 +1,5 @@
|
|||
name: sortField
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
|
@ -0,0 +1,6 @@
|
|||
name: sortOrder
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum: [asc, desc]
|
|
@ -9,5 +9,13 @@ get:
|
|||
schema:
|
||||
$ref: ../components/schemas/get_agents_response.yaml
|
||||
operationId: get-agents
|
||||
parameters:
|
||||
- $ref: ../components/parameters/page_size.yaml
|
||||
- $ref: ../components/parameters/page_index.yaml
|
||||
- $ref: ../components/parameters/kuery.yaml
|
||||
- $ref: ../components/parameters/show_inactive.yaml
|
||||
- $ref: ../components/parameters/show_upgradeable.yaml
|
||||
- $ref: ../components/parameters/sort_field.yaml
|
||||
- $ref: ../components/parameters/sort_order.yaml
|
||||
security:
|
||||
- basicAuth: []
|
||||
|
|
|
@ -156,4 +156,43 @@ describe('agent_list_page', () => {
|
|||
|
||||
utils.getByText('4 agents selected');
|
||||
});
|
||||
|
||||
it('should pass sort parameters on table sort', () => {
|
||||
act(() => {
|
||||
fireEvent.click(utils.getByTitle('Last activity'));
|
||||
});
|
||||
|
||||
expect(mockedSendGetAgents).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sortField: 'last_checkin',
|
||||
sortOrder: 'asc',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass keyword field on table sort on version', () => {
|
||||
act(() => {
|
||||
fireEvent.click(utils.getByTitle('Version'));
|
||||
});
|
||||
|
||||
expect(mockedSendGetAgents).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sortField: 'local_metadata.elastic.agent.version.keyword',
|
||||
sortOrder: 'asc',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass keyword field on table sort on hostname', () => {
|
||||
act(() => {
|
||||
fireEvent.click(utils.getByTitle('Host'));
|
||||
});
|
||||
|
||||
expect(mockedSendGetAgents).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sortField: 'local_metadata.host.hostname.keyword',
|
||||
sortOrder: 'asc',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -83,6 +83,10 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
const [selectedAgents, setSelectedAgents] = useState<Agent[]>([]);
|
||||
const tableRef = useRef<EuiBasicTable<Agent>>(null);
|
||||
const { pagination, pageSizeOptions, setPagination } = usePagination();
|
||||
const [sortField, setSortField] = useState<keyof Agent>('enrolled_at');
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||
const VERSION_FIELD = 'local_metadata.elastic.agent.version';
|
||||
const HOSTNAME_FIELD = 'local_metadata.host.hostname';
|
||||
|
||||
const onSubmitSearch = useCallback(
|
||||
(newKuery: string) => {
|
||||
|
@ -199,6 +203,20 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
const [totalAgents, setTotalAgents] = useState(0);
|
||||
const [totalInactiveAgents, setTotalInactiveAgents] = useState(0);
|
||||
|
||||
const getSortFieldForAPI = (field: keyof Agent): string => {
|
||||
if ([VERSION_FIELD, HOSTNAME_FIELD].includes(field as string)) {
|
||||
return `${field}.keyword`;
|
||||
}
|
||||
return field;
|
||||
};
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: sortField,
|
||||
direction: sortOrder,
|
||||
},
|
||||
};
|
||||
|
||||
// Request to fetch agents and agent status
|
||||
const currentRequestRef = useRef<number>(0);
|
||||
const fetchData = useCallback(
|
||||
|
@ -214,6 +232,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
page: pagination.currentPage,
|
||||
perPage: pagination.pageSize,
|
||||
kuery: kuery && kuery !== '' ? kuery : undefined,
|
||||
sortField: getSortFieldForAPI(sortField),
|
||||
sortOrder,
|
||||
showInactive,
|
||||
showUpgradeable,
|
||||
}),
|
||||
|
@ -280,6 +300,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
showUpgradeable,
|
||||
allTags,
|
||||
notifications.toasts,
|
||||
sortField,
|
||||
sortOrder,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -360,7 +382,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
|
||||
const columns = [
|
||||
{
|
||||
field: 'local_metadata.host.hostname',
|
||||
field: HOSTNAME_FIELD,
|
||||
sortable: true,
|
||||
name: i18n.translate('xpack.fleet.agentList.hostColumnTitle', {
|
||||
defaultMessage: 'Host',
|
||||
}),
|
||||
|
@ -373,6 +396,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
},
|
||||
{
|
||||
field: 'active',
|
||||
sortable: false,
|
||||
width: '85px',
|
||||
name: i18n.translate('xpack.fleet.agentList.statusColumnTitle', {
|
||||
defaultMessage: 'Status',
|
||||
|
@ -381,6 +405,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
},
|
||||
{
|
||||
field: 'tags',
|
||||
sortable: false,
|
||||
width: '210px',
|
||||
name: i18n.translate('xpack.fleet.agentList.tagsColumnTitle', {
|
||||
defaultMessage: 'Tags',
|
||||
|
@ -389,6 +414,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
},
|
||||
{
|
||||
field: 'policy_id',
|
||||
sortable: true,
|
||||
name: i18n.translate('xpack.fleet.agentList.policyColumnTitle', {
|
||||
defaultMessage: 'Agent policy',
|
||||
}),
|
||||
|
@ -417,7 +443,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
field: 'local_metadata.elastic.agent.version',
|
||||
field: VERSION_FIELD,
|
||||
sortable: true,
|
||||
width: '135px',
|
||||
name: i18n.translate('xpack.fleet.agentList.versionTitle', {
|
||||
defaultMessage: 'Version',
|
||||
|
@ -444,6 +471,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
},
|
||||
{
|
||||
field: 'last_checkin',
|
||||
sortable: true,
|
||||
name: i18n.translate('xpack.fleet.agentList.lastCheckinTitle', {
|
||||
defaultMessage: 'Last activity',
|
||||
}),
|
||||
|
@ -657,14 +685,23 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
return '';
|
||||
},
|
||||
}}
|
||||
onChange={({ page }: { page: { index: number; size: number } }) => {
|
||||
onChange={({
|
||||
page,
|
||||
sort,
|
||||
}: {
|
||||
page?: { index: number; size: number };
|
||||
sort?: { field: keyof Agent; direction: 'asc' | 'desc' };
|
||||
}) => {
|
||||
const newPagination = {
|
||||
...pagination,
|
||||
currentPage: page.index + 1,
|
||||
pageSize: page.size,
|
||||
currentPage: page!.index + 1,
|
||||
pageSize: page!.size,
|
||||
};
|
||||
setPagination(newPagination);
|
||||
setSortField(sort!.field);
|
||||
setSortOrder(sort!.direction);
|
||||
}}
|
||||
sorting={sorting}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -128,6 +128,8 @@ export const getAgentsHandler: RequestHandler<
|
|||
showInactive: request.query.showInactive,
|
||||
showUpgradeable: request.query.showUpgradeable,
|
||||
kuery: request.query.kuery,
|
||||
sortField: request.query.sortField,
|
||||
sortOrder: request.query.sortOrder,
|
||||
});
|
||||
const totalInactive = request.query.showInactive
|
||||
? await AgentService.countInactiveAgents(esClient, {
|
||||
|
|
|
@ -203,6 +203,8 @@ export async function getAgentsByKuery(
|
|||
esClient: ElasticsearchClient,
|
||||
options: ListWithKuery & {
|
||||
showInactive: boolean;
|
||||
sortField?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
}
|
||||
): Promise<{
|
||||
agents: Agent[];
|
||||
|
@ -213,8 +215,8 @@ export async function getAgentsByKuery(
|
|||
const {
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
sortField = 'enrolled_at',
|
||||
sortOrder = 'desc',
|
||||
sortField = options.sortField ?? 'enrolled_at',
|
||||
sortOrder = options.sortOrder ?? 'desc',
|
||||
kuery,
|
||||
showInactive = false,
|
||||
showUpgradeable,
|
||||
|
|
|
@ -18,6 +18,8 @@ export const GetAgentsRequestSchema = {
|
|||
kuery: schema.maybe(schema.string()),
|
||||
showInactive: schema.boolean({ defaultValue: false }),
|
||||
showUpgradeable: schema.boolean({ defaultValue: false }),
|
||||
sortField: schema.maybe(schema.string()),
|
||||
sortOrder: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -71,5 +71,17 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const agent = apiResponse.items[0];
|
||||
expect(agent.access_api_key_id).to.eql('api-key-2');
|
||||
});
|
||||
|
||||
it('should return a 200 when given sort options', async () => {
|
||||
const { body: apiResponse } = await supertest
|
||||
.get(`/api/fleet/agents?sortField=last_checkin&sortOrder=desc`)
|
||||
.expect(200);
|
||||
expect(apiResponse.items.map((agent: { id: string }) => agent.id)).to.eql([
|
||||
'agent4',
|
||||
'agent3',
|
||||
'agent2',
|
||||
'agent1',
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
"type": "PERMANENT",
|
||||
"local_metadata": {},
|
||||
"user_provided_metadata": {},
|
||||
"enrolled_at": "2022-06-21T12:14:25Z"
|
||||
"enrolled_at": "2022-06-21T12:14:25Z",
|
||||
"last_checkin": "2022-06-27T12:26:29Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +28,8 @@
|
|||
"type": "PERMANENT",
|
||||
"local_metadata": {},
|
||||
"user_provided_metadata": {},
|
||||
"enrolled_at": "2022-06-21T12:15:25Z"
|
||||
"enrolled_at": "2022-06-21T12:15:25Z",
|
||||
"last_checkin": "2022-06-27T12:27:29Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +46,8 @@
|
|||
"type": "PERMANENT",
|
||||
"local_metadata": {},
|
||||
"user_provided_metadata": {},
|
||||
"enrolled_at": "2022-06-21T12:16:25Z"
|
||||
"enrolled_at": "2022-06-21T12:16:25Z",
|
||||
"last_checkin": "2022-06-27T12:28:29Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +64,8 @@
|
|||
"type": "PERMANENT",
|
||||
"local_metadata": {},
|
||||
"user_provided_metadata": {},
|
||||
"enrolled_at": "2022-06-21T12:17:25Z"
|
||||
"enrolled_at": "2022-06-21T12:17:25Z",
|
||||
"last_checkin": "2022-06-27T12:29:29Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue