Skip to content

Commit 87380f5

Browse files
authored
[ENDPOINT] First version of the trusted apps list. (#76304)
* First version of the trusted apps list. * Added proper visualisation of OS and Date Created columns. * Small change in naming in middleware. * Renamed function to avoid naming confusion. * Migrated to usage of selectors and memo in list component. * Added explicit return types. * Changed to use server schema for service parameter. * Removed some over generalisation in types. * Renamed types and properties related to trusted apps page state. * Renamed types and properties related to trusted apps page state. * Renamed the action type to be namespaced to trusted apps. * Merged the exports and declarations in reducer and used constants for defaults. * Memoization of pagination data structure. * Used a shared constant for REST API path. * Improvements and consistency on pagination across tabs. * Added a bit more typing and used Partial<> * Made constants readonly and added some useMemo usages. * Fixed extracting page index from URI. * Fixed the case of infinite refreshes when there is loading failure (need to rethink a bit conditions when to refresh). * Resetting state to initial when we navigate away from trusted apps list. * Fixed mapping page index to the table pagination. * Changed to using AppAction in reducer. * Made ServerApiError a default error type for data binding. * Renamed all types related to data binding to resource state. * Created index file for state types. * Fixed parameter extracting code to meet expectations of endpoints list behavior. * Updated snapshot. * Changed middleware to only use selectors. * Added tests for routing. * Added documentation to the types in async resource state module. * Added tests for async resource state module. * Added tests for store selectors. * Added tests for reducer. * Moved around imports. * Added tests for the middleware. * Added list component tests. * Removed a redundant function. * Commiting snapshots.
1 parent a127766 commit 87380f5

30 files changed

+7310
-46
lines changed

x-pack/plugins/security_solution/public/common/store/actions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
import { EndpointAction } from '../../management/pages/endpoint_hosts/store/action';
88
import { PolicyListAction } from '../../management/pages/policy/store/policy_list';
99
import { PolicyDetailsAction } from '../../management/pages/policy/store/policy_details';
10+
import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store/action';
1011

1112
export { appActions } from './app';
1213
export { dragAndDropActions } from './drag_and_drop';
1314
export { inputsActions } from './inputs';
1415
import { RoutingAction } from './routing';
1516

16-
export type AppAction = EndpointAction | RoutingAction | PolicyListAction | PolicyDetailsAction;
17+
export type AppAction =
18+
| EndpointAction
19+
| RoutingAction
20+
| PolicyListAction
21+
| PolicyDetailsAction
22+
| TrustedAppsPageAction;

x-pack/plugins/security_solution/public/common/store/routing/action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { AppLocation, Immutable } from '../../../../common/endpoint/types';
88

9-
interface UserChangedUrl {
9+
export interface UserChangedUrl {
1010
readonly type: 'userChangedUrl';
1111
readonly payload: Immutable<AppLocation>;
1212
}

x-pack/plugins/security_solution/public/management/common/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ export const MANAGEMENT_STORE_POLICY_LIST_NAMESPACE = 'policyList';
2424
export const MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE = 'policyDetails';
2525
/** Namespace within the Management state where endpoint-host state is maintained */
2626
export const MANAGEMENT_STORE_ENDPOINTS_NAMESPACE = 'endpoints';
27+
/** Namespace within the Management state where trusted apps page state is maintained */
28+
export const MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE = 'trustedApps';
29+
30+
export const MANAGEMENT_PAGE_SIZE_OPTIONS: readonly number[] = [10, 20, 50];
31+
export const MANAGEMENT_DEFAULT_PAGE = 0;
32+
export const MANAGEMENT_DEFAULT_PAGE_SIZE = 10;
2733

2834
// --[ DEFAULTS ]---------------------------------------------------------------------------
2935
/** The default polling interval to start all polling pages */
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { extractListPaginationParams, getTrustedAppsListPath } from './routing';
8+
import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from './constants';
9+
10+
describe('routing', () => {
11+
describe('extractListPaginationParams()', () => {
12+
it('extracts default page index when not provided', () => {
13+
expect(extractListPaginationParams({}).page_index).toBe(MANAGEMENT_DEFAULT_PAGE);
14+
});
15+
16+
it('extracts default page index when too small value provided', () => {
17+
expect(extractListPaginationParams({ page_index: '-1' }).page_index).toBe(
18+
MANAGEMENT_DEFAULT_PAGE
19+
);
20+
});
21+
22+
it('extracts default page index when not a number provided', () => {
23+
expect(extractListPaginationParams({ page_index: 'a' }).page_index).toBe(
24+
MANAGEMENT_DEFAULT_PAGE
25+
);
26+
});
27+
28+
it('extracts only last page index when multiple values provided', () => {
29+
expect(extractListPaginationParams({ page_index: ['1', '2'] }).page_index).toBe(2);
30+
});
31+
32+
it('extracts proper page index when single valid value provided', () => {
33+
expect(extractListPaginationParams({ page_index: '2' }).page_index).toBe(2);
34+
});
35+
36+
it('extracts default page size when not provided', () => {
37+
expect(extractListPaginationParams({}).page_size).toBe(MANAGEMENT_DEFAULT_PAGE_SIZE);
38+
});
39+
40+
it('extracts default page size when invalid option provided', () => {
41+
expect(extractListPaginationParams({ page_size: '25' }).page_size).toBe(
42+
MANAGEMENT_DEFAULT_PAGE_SIZE
43+
);
44+
});
45+
46+
it('extracts default page size when not a number provided', () => {
47+
expect(extractListPaginationParams({ page_size: 'a' }).page_size).toBe(
48+
MANAGEMENT_DEFAULT_PAGE_SIZE
49+
);
50+
});
51+
52+
it('extracts only last page size when multiple values provided', () => {
53+
expect(extractListPaginationParams({ page_size: ['10', '20'] }).page_size).toBe(20);
54+
});
55+
56+
it('extracts proper page size when single valid value provided', () => {
57+
expect(extractListPaginationParams({ page_size: '20' }).page_size).toBe(20);
58+
});
59+
});
60+
61+
describe('getTrustedAppsListPath()', () => {
62+
it('builds proper path when no parameters provided', () => {
63+
expect(getTrustedAppsListPath()).toEqual('/trusted_apps');
64+
});
65+
66+
it('builds proper path when empty parameters provided', () => {
67+
expect(getTrustedAppsListPath({})).toEqual('/trusted_apps');
68+
});
69+
70+
it('builds proper path when no page index provided', () => {
71+
expect(getTrustedAppsListPath({ page_size: 20 })).toEqual('/trusted_apps?page_size=20');
72+
});
73+
74+
it('builds proper path when no page size provided', () => {
75+
expect(getTrustedAppsListPath({ page_index: 2 })).toEqual('/trusted_apps?page_index=2');
76+
});
77+
78+
it('builds proper path when both page index and size provided', () => {
79+
expect(getTrustedAppsListPath({ page_index: 2, page_size: 20 })).toEqual(
80+
'/trusted_apps?page_index=2&page_size=20'
81+
);
82+
});
83+
84+
it('builds proper path when page index is equal to default', () => {
85+
const path = getTrustedAppsListPath({
86+
page_index: MANAGEMENT_DEFAULT_PAGE,
87+
page_size: 20,
88+
});
89+
90+
expect(path).toEqual('/trusted_apps?page_size=20');
91+
});
92+
93+
it('builds proper path when page size is equal to default', () => {
94+
const path = getTrustedAppsListPath({
95+
page_index: 2,
96+
page_size: MANAGEMENT_DEFAULT_PAGE_SIZE,
97+
});
98+
99+
expect(path).toEqual('/trusted_apps?page_index=2');
100+
});
101+
102+
it('builds proper path when both page index and size are equal to default', () => {
103+
const path = getTrustedAppsListPath({
104+
page_index: MANAGEMENT_DEFAULT_PAGE,
105+
page_size: MANAGEMENT_DEFAULT_PAGE_SIZE,
106+
});
107+
108+
expect(path).toEqual('/trusted_apps');
109+
});
110+
});
111+
});

x-pack/plugins/security_solution/public/management/common/routing.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { generatePath } from 'react-router-dom';
1010
import querystring from 'querystring';
1111

1212
import {
13+
MANAGEMENT_DEFAULT_PAGE,
14+
MANAGEMENT_DEFAULT_PAGE_SIZE,
15+
MANAGEMENT_PAGE_SIZE_OPTIONS,
1316
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
1417
MANAGEMENT_ROUTING_POLICIES_PATH,
1518
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH,
@@ -86,8 +89,61 @@ export const getPolicyDetailPath = (policyId: string, search?: string) => {
8689
})}${appendSearch(search)}`;
8790
};
8891

89-
export const getTrustedAppsListPath = (search?: string) => {
90-
return `${generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, {
92+
interface ListPaginationParams {
93+
page_index: number;
94+
page_size: number;
95+
}
96+
97+
const isDefaultOrMissing = (value: number | undefined, defaultValue: number) => {
98+
return value === undefined || value === defaultValue;
99+
};
100+
101+
const normalizeListPaginationParams = (
102+
params?: Partial<ListPaginationParams>
103+
): Partial<ListPaginationParams> => {
104+
if (params) {
105+
return {
106+
...(!isDefaultOrMissing(params.page_index, MANAGEMENT_DEFAULT_PAGE)
107+
? { page_index: params.page_index }
108+
: {}),
109+
...(!isDefaultOrMissing(params.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE)
110+
? { page_size: params.page_size }
111+
: {}),
112+
};
113+
} else {
114+
return {};
115+
}
116+
};
117+
118+
const extractFirstParamValue = (query: querystring.ParsedUrlQuery, key: string): string => {
119+
const value = query[key];
120+
121+
return Array.isArray(value) ? value[value.length - 1] : value;
122+
};
123+
124+
const extractPageIndex = (query: querystring.ParsedUrlQuery): number => {
125+
const pageIndex = Number(extractFirstParamValue(query, 'page_index'));
126+
127+
return !Number.isFinite(pageIndex) || pageIndex < 0 ? MANAGEMENT_DEFAULT_PAGE : pageIndex;
128+
};
129+
130+
const extractPageSize = (query: querystring.ParsedUrlQuery): number => {
131+
const pageSize = Number(extractFirstParamValue(query, 'page_size'));
132+
133+
return MANAGEMENT_PAGE_SIZE_OPTIONS.includes(pageSize) ? pageSize : MANAGEMENT_DEFAULT_PAGE_SIZE;
134+
};
135+
136+
export const extractListPaginationParams = (
137+
query: querystring.ParsedUrlQuery
138+
): ListPaginationParams => ({
139+
page_index: extractPageIndex(query),
140+
page_size: extractPageSize(query),
141+
});
142+
143+
export const getTrustedAppsListPath = (params?: Partial<ListPaginationParams>): string => {
144+
const path = generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, {
91145
tabName: AdministrationSubTab.trustedApps,
92-
})}${appendSearch(search)}`;
146+
});
147+
148+
return `${path}${appendSearch(querystring.stringify(normalizeListPaginationParams(params)))}`;
93149
};

x-pack/plugins/security_solution/public/management/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ export class Management {
4747
* Cast the ImmutableReducer to a regular reducer for compatibility with
4848
* the subplugin architecture (which expects plain redux reducers.)
4949
*/
50-
reducer: {
51-
management: managementReducer,
52-
} as ManagementPluginReducer,
50+
reducer: { management: managementReducer } as ManagementPluginReducer,
5351
middleware: managementMiddlewareFactory(core, plugins),
5452
},
5553
};

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ import {
1515
HostPolicyResponseActionStatus,
1616
} from '../../../../../common/endpoint/types';
1717
import { EndpointState, EndpointIndexUIQueryParams } from '../types';
18-
import { MANAGEMENT_ROUTING_ENDPOINTS_PATH } from '../../../common/constants';
19-
20-
const PAGE_SIZES = Object.freeze([10, 20, 50]);
18+
import { extractListPaginationParams } from '../../../common/routing';
19+
import {
20+
MANAGEMENT_DEFAULT_PAGE,
21+
MANAGEMENT_DEFAULT_PAGE_SIZE,
22+
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
23+
} from '../../../common/constants';
2124

2225
export const listData = (state: Immutable<EndpointState>) => state.hosts;
2326

@@ -129,17 +132,17 @@ export const uiQueryParams: (
129132
) => Immutable<EndpointIndexUIQueryParams> = createSelector(
130133
(state: Immutable<EndpointState>) => state.location,
131134
(location: Immutable<EndpointState>['location']) => {
132-
const data: EndpointIndexUIQueryParams = { page_index: '0', page_size: '10' };
135+
const data: EndpointIndexUIQueryParams = {
136+
page_index: String(MANAGEMENT_DEFAULT_PAGE),
137+
page_size: String(MANAGEMENT_DEFAULT_PAGE_SIZE),
138+
};
139+
133140
if (location) {
134141
// Removes the `?` from the beginning of query string if it exists
135142
const query = querystring.parse(location.search.slice(1));
143+
const paginationParams = extractListPaginationParams(query);
136144

137-
const keys: Array<keyof EndpointIndexUIQueryParams> = [
138-
'selected_endpoint',
139-
'page_size',
140-
'page_index',
141-
'show',
142-
];
145+
const keys: Array<keyof EndpointIndexUIQueryParams> = ['selected_endpoint', 'show'];
143146

144147
for (const key of keys) {
145148
const value: string | undefined =
@@ -160,17 +163,10 @@ export const uiQueryParams: (
160163
}
161164
}
162165

163-
// Check if page size is an expected size, otherwise default to 10
164-
if (!PAGE_SIZES.includes(Number(data.page_size))) {
165-
data.page_size = '10';
166-
}
167-
168-
// Check if page index is a valid positive integer, otherwise default to 0
169-
const pageIndexAsNumber = Number(data.page_index);
170-
if (!Number.isFinite(pageIndexAsNumber) || pageIndexAsNumber < 0) {
171-
data.page_index = '0';
172-
}
166+
data.page_size = String(paginationParams.page_size);
167+
data.page_index = String(paginationParams.page_index);
173168
}
169+
174170
return data;
175171
}
176172
);

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
3434
import { CreateStructuredSelector } from '../../../../common/store';
3535
import { Immutable, HostInfo } from '../../../../../common/endpoint/types';
36-
import { DEFAULT_POLL_INTERVAL } from '../../../common/constants';
36+
import { DEFAULT_POLL_INTERVAL, MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants';
3737
import { PolicyEmptyState, HostsEmptyState } from '../../../components/management_empty_state';
3838
import { FormattedDate } from '../../../../common/components/formatted_date';
3939
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
@@ -99,7 +99,7 @@ export const EndpointList = () => {
9999
pageIndex,
100100
pageSize,
101101
totalItemCount,
102-
pageSizeOptions: [10, 20, 50],
102+
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
103103
hidePerPageOptions: false,
104104
};
105105
}, [pageIndex, pageSize, totalItemCount]);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { HttpStart } from 'kibana/public';
8+
import { TRUSTED_APPS_LIST_API } from '../../../../../common/endpoint/constants';
9+
import {
10+
GetTrustedListAppsResponse,
11+
GetTrustedAppsListRequest,
12+
} from '../../../../../common/endpoint/types/trusted_apps';
13+
14+
export interface TrustedAppsService {
15+
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedListAppsResponse>;
16+
}
17+
18+
export class TrustedAppsHttpService implements TrustedAppsService {
19+
constructor(private http: HttpStart) {}
20+
21+
async getTrustedAppsList(request: GetTrustedAppsListRequest) {
22+
return this.http.get<GetTrustedListAppsResponse>(TRUSTED_APPS_LIST_API, {
23+
query: request,
24+
});
25+
}
26+
}

0 commit comments

Comments
 (0)