[SECURITY_SOLUTION][ENDPOINT] Grid view for trusted apps. (#79485)
* Refactored store code to group properties related to location so that would be easy to introduce a new view type parameter. * Added view type to the location and routing. * Added a simple hook to make navigation easier. * Improved the navigation hook to get params. * Some fix for double notification after creating trusted app. * Added a hook to perform trusted app store actions. * Fixed trusted app card delete callback. * Added grid view. * Fixed the stories structuring. * Shared more logic between grid and list. * Finalized the grid view. * Flattened the props. * Improved memoization. * Moved the flex item elements inside conditions. * Fixed broken stories. * Updated the snapshot. * Updated the snapshot.
This commit is contained in:
parent
11886bf51c
commit
f340d71377
|
@ -15,7 +15,7 @@ addDecorator((storyFn) => (
|
|||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>{storyFn()}</ThemeProvider>
|
||||
));
|
||||
|
||||
storiesOf('Components|ConditionsTable', module)
|
||||
storiesOf('Components/ConditionsTable', module)
|
||||
.add('single item', () => {
|
||||
return <ConditionsTable items={createItems(1)} columns={TEST_COLUMNS} badge="and" />;
|
||||
})
|
||||
|
|
|
@ -14,7 +14,7 @@ addDecorator((storyFn) => (
|
|||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>{storyFn()}</ThemeProvider>
|
||||
));
|
||||
|
||||
storiesOf('Components|ItemDetailsCard', module).add('default', () => {
|
||||
storiesOf('Components/ItemDetailsCard', module).add('default', () => {
|
||||
return (
|
||||
<ItemDetailsCard>
|
||||
<ItemDetailsPropertySummary name={'property 1'} value={'value 1'} />
|
||||
|
|
|
@ -8,16 +8,19 @@ import { ServerApiError } from '../../../../common/types';
|
|||
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
|
||||
import { AsyncResourceState } from '.';
|
||||
|
||||
export interface PaginationInfo {
|
||||
index: number;
|
||||
size: number;
|
||||
export interface Pagination {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
totalItemCount: number;
|
||||
pageSizeOptions: number[];
|
||||
}
|
||||
|
||||
export interface TrustedAppsListData {
|
||||
items: TrustedApp[];
|
||||
totalItemsCount: number;
|
||||
paginationInfo: PaginationInfo;
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
timestamp: number;
|
||||
totalItemsCount: number;
|
||||
}
|
||||
|
||||
/** Store State when an API request has been sent to create a new trusted app entry */
|
||||
|
|
|
@ -9,6 +9,7 @@ import { applyMiddleware, createStore } from 'redux';
|
|||
import { createSpyMiddleware } from '../../../../common/store/test_utils';
|
||||
|
||||
import {
|
||||
createDefaultPagination,
|
||||
createListLoadedResourceState,
|
||||
createLoadedListViewWithPagination,
|
||||
createSampleTrustedApp,
|
||||
|
@ -19,7 +20,7 @@ import {
|
|||
} from '../test_utils';
|
||||
|
||||
import { TrustedAppsService } from '../service';
|
||||
import { PaginationInfo, TrustedAppsListPageState } from '../state';
|
||||
import { Pagination, TrustedAppsListPageState } from '../state';
|
||||
import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer';
|
||||
import { createTrustedAppsPageMiddleware } from './middleware';
|
||||
|
||||
|
@ -31,12 +32,16 @@ Date.now = dateNowMock;
|
|||
|
||||
const initialState = initialTrustedAppsPageState();
|
||||
|
||||
const createGetTrustedListAppsResponse = (pagination: PaginationInfo, totalItemsCount: number) => ({
|
||||
data: createSampleTrustedApps(pagination),
|
||||
page: pagination.index,
|
||||
per_page: pagination.size,
|
||||
total: totalItemsCount,
|
||||
});
|
||||
const createGetTrustedListAppsResponse = (pagination: Partial<Pagination>) => {
|
||||
const fullPagination = { ...createDefaultPagination(), ...pagination };
|
||||
|
||||
return {
|
||||
data: createSampleTrustedApps(pagination),
|
||||
page: fullPagination.pageIndex,
|
||||
per_page: fullPagination.pageSize,
|
||||
total: fullPagination.totalItemCount,
|
||||
};
|
||||
};
|
||||
|
||||
const createTrustedAppsServiceMock = (): jest.Mocked<TrustedAppsService> => ({
|
||||
getTrustedAppsList: jest.fn(),
|
||||
|
@ -74,14 +79,12 @@ describe('middleware', () => {
|
|||
|
||||
describe('refreshing list resource state', () => {
|
||||
it('refreshes the list when location changes and data gets outdated', async () => {
|
||||
const pagination = { index: 2, size: 50 };
|
||||
const pagination = { pageIndex: 2, pageSize: 50 };
|
||||
const location = { page_index: 2, page_size: 50, show: undefined, view_type: 'grid' };
|
||||
const service = createTrustedAppsServiceMock();
|
||||
const { store, spyMiddleware } = createStoreSetup(service);
|
||||
|
||||
service.getTrustedAppsList.mockResolvedValue(
|
||||
createGetTrustedListAppsResponse(pagination, 500)
|
||||
);
|
||||
service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination));
|
||||
|
||||
store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50'));
|
||||
|
||||
|
@ -102,21 +105,19 @@ describe('middleware', () => {
|
|||
|
||||
expect(store.getState()).toStrictEqual({
|
||||
...initialState,
|
||||
listView: createLoadedListViewWithPagination(initialNow, pagination, 500),
|
||||
listView: createLoadedListViewWithPagination(initialNow, pagination),
|
||||
active: true,
|
||||
location,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not refresh the list when location changes and data does not get outdated', async () => {
|
||||
const pagination = { index: 2, size: 50 };
|
||||
const pagination = { pageIndex: 2, pageSize: 50 };
|
||||
const location = { page_index: 2, page_size: 50, show: undefined, view_type: 'grid' };
|
||||
const service = createTrustedAppsServiceMock();
|
||||
const { store, spyMiddleware } = createStoreSetup(service);
|
||||
|
||||
service.getTrustedAppsList.mockResolvedValue(
|
||||
createGetTrustedListAppsResponse(pagination, 500)
|
||||
);
|
||||
service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination));
|
||||
|
||||
store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50'));
|
||||
|
||||
|
@ -127,7 +128,7 @@ describe('middleware', () => {
|
|||
expect(service.getTrustedAppsList).toBeCalledTimes(1);
|
||||
expect(store.getState()).toStrictEqual({
|
||||
...initialState,
|
||||
listView: createLoadedListViewWithPagination(initialNow, pagination, 500),
|
||||
listView: createLoadedListViewWithPagination(initialNow, pagination),
|
||||
active: true,
|
||||
location,
|
||||
});
|
||||
|
@ -135,14 +136,12 @@ describe('middleware', () => {
|
|||
|
||||
it('refreshes the list when data gets outdated with and outdate action', async () => {
|
||||
const newNow = 222222;
|
||||
const pagination = { index: 0, size: 10 };
|
||||
const pagination = { pageIndex: 0, pageSize: 10 };
|
||||
const location = { page_index: 0, page_size: 10, show: undefined, view_type: 'grid' };
|
||||
const service = createTrustedAppsServiceMock();
|
||||
const { store, spyMiddleware } = createStoreSetup(service);
|
||||
|
||||
service.getTrustedAppsList.mockResolvedValue(
|
||||
createGetTrustedListAppsResponse(pagination, 500)
|
||||
);
|
||||
service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination));
|
||||
|
||||
store.dispatch(createUserChangedUrlAction('/trusted_apps'));
|
||||
|
||||
|
@ -157,7 +156,7 @@ describe('middleware', () => {
|
|||
listView: {
|
||||
listResourceState: {
|
||||
type: 'LoadingResourceState',
|
||||
previousState: createListLoadedResourceState(pagination, 500, initialNow),
|
||||
previousState: createListLoadedResourceState(pagination, initialNow),
|
||||
},
|
||||
freshDataTimestamp: newNow,
|
||||
},
|
||||
|
@ -169,7 +168,7 @@ describe('middleware', () => {
|
|||
|
||||
expect(store.getState()).toStrictEqual({
|
||||
...initialState,
|
||||
listView: createLoadedListViewWithPagination(newNow, pagination, 500),
|
||||
listView: createLoadedListViewWithPagination(newNow, pagination),
|
||||
active: true,
|
||||
location,
|
||||
});
|
||||
|
@ -211,11 +210,11 @@ describe('middleware', () => {
|
|||
const newNow = 222222;
|
||||
const entry = createSampleTrustedApp(3);
|
||||
const notFoundError = createServerApiError('Not Found');
|
||||
const pagination = { index: 0, size: 10 };
|
||||
const pagination = { pageIndex: 0, pageSize: 10 };
|
||||
const location = { page_index: 0, page_size: 10, show: undefined, view_type: 'grid' };
|
||||
const getTrustedAppsListResponse = createGetTrustedListAppsResponse(pagination, 500);
|
||||
const listView = createLoadedListViewWithPagination(initialNow, pagination, 500);
|
||||
const listViewNew = createLoadedListViewWithPagination(newNow, pagination, 500);
|
||||
const getTrustedAppsListResponse = createGetTrustedListAppsResponse(pagination);
|
||||
const listView = createLoadedListViewWithPagination(initialNow, pagination);
|
||||
const listViewNew = createLoadedListViewWithPagination(newNow, pagination);
|
||||
const testStartState = { ...initialState, listView, active: true, location };
|
||||
|
||||
it('does not submit when entry is undefined', async () => {
|
||||
|
|
|
@ -75,8 +75,9 @@ const refreshListIfNeeded = async (
|
|||
type: 'LoadedResourceState',
|
||||
data: {
|
||||
items: response.data,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemsCount: response.total,
|
||||
paginationInfo: { index: pageIndex, size: pageSize },
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
})
|
||||
|
|
|
@ -71,8 +71,7 @@ describe('reducer', () => {
|
|||
describe('TrustedAppsListResourceStateChanged', () => {
|
||||
it('sets the current list resource state', () => {
|
||||
const listResourceState = createListLoadedResourceState(
|
||||
{ index: 3, size: 50 },
|
||||
200,
|
||||
{ pageIndex: 3, pageSize: 50 },
|
||||
initialNow
|
||||
);
|
||||
const result = trustedAppsPageReducer(
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
} from './selectors';
|
||||
|
||||
import {
|
||||
createDefaultPaginationInfo,
|
||||
createDefaultPagination,
|
||||
createListComplexLoadingResourceState,
|
||||
createListFailedResourceState,
|
||||
createListLoadedResourceState,
|
||||
|
@ -66,13 +66,13 @@ describe('selectors', () => {
|
|||
});
|
||||
|
||||
it('returns true when current loaded page index is outdated', () => {
|
||||
const listView = createLoadedListViewWithPagination(initialNow, { index: 1, size: 20 });
|
||||
const listView = createLoadedListViewWithPagination(initialNow, { pageIndex: 1 });
|
||||
|
||||
expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when current loaded page size is outdated', () => {
|
||||
const listView = createLoadedListViewWithPagination(initialNow, { index: 0, size: 50 });
|
||||
const listView = createLoadedListViewWithPagination(initialNow, { pageSize: 50 });
|
||||
|
||||
expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true);
|
||||
});
|
||||
|
@ -112,8 +112,7 @@ describe('selectors', () => {
|
|||
...initialState,
|
||||
listView: {
|
||||
listResourceState: createListComplexLoadingResourceState(
|
||||
createDefaultPaginationInfo(),
|
||||
200,
|
||||
createDefaultPagination(),
|
||||
initialNow
|
||||
),
|
||||
freshDataTimestamp: initialNow,
|
||||
|
@ -121,7 +120,7 @@ describe('selectors', () => {
|
|||
};
|
||||
|
||||
expect(getLastLoadedListResourceState(state)).toStrictEqual(
|
||||
createListLoadedResourceState(createDefaultPaginationInfo(), 200, initialNow)
|
||||
createListLoadedResourceState(createDefaultPagination(), initialNow)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -136,17 +135,14 @@ describe('selectors', () => {
|
|||
...initialState,
|
||||
listView: {
|
||||
listResourceState: createListComplexLoadingResourceState(
|
||||
createDefaultPaginationInfo(),
|
||||
200,
|
||||
createDefaultPagination(),
|
||||
initialNow
|
||||
),
|
||||
freshDataTimestamp: initialNow,
|
||||
},
|
||||
};
|
||||
|
||||
expect(getListItems(state)).toStrictEqual(
|
||||
createSampleTrustedApps(createDefaultPaginationInfo())
|
||||
);
|
||||
expect(getListItems(state)).toStrictEqual(createSampleTrustedApps(createDefaultPagination()));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -160,8 +156,7 @@ describe('selectors', () => {
|
|||
...initialState,
|
||||
listView: {
|
||||
listResourceState: createListComplexLoadingResourceState(
|
||||
createDefaultPaginationInfo(),
|
||||
200,
|
||||
createDefaultPagination(),
|
||||
initialNow
|
||||
),
|
||||
freshDataTimestamp: initialNow,
|
||||
|
@ -202,8 +197,7 @@ describe('selectors', () => {
|
|||
...initialState,
|
||||
listView: {
|
||||
listResourceState: createListComplexLoadingResourceState(
|
||||
createDefaultPaginationInfo(),
|
||||
200,
|
||||
createDefaultPagination(),
|
||||
initialNow
|
||||
),
|
||||
freshDataTimestamp: initialNow,
|
||||
|
@ -236,8 +230,7 @@ describe('selectors', () => {
|
|||
...initialState,
|
||||
listView: {
|
||||
listResourceState: createListComplexLoadingResourceState(
|
||||
createDefaultPaginationInfo(),
|
||||
200,
|
||||
createDefaultPagination(),
|
||||
initialNow
|
||||
),
|
||||
freshDataTimestamp: initialNow,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { ServerApiError } from '../../../../common/types';
|
||||
import { Immutable, NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types';
|
||||
import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants';
|
||||
|
||||
import {
|
||||
AsyncResourceState,
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
isLoadingResourceState,
|
||||
isOutdatedResourceState,
|
||||
LoadedResourceState,
|
||||
Pagination,
|
||||
TrustedAppCreateFailure,
|
||||
TrustedAppsListData,
|
||||
TrustedAppsListPageLocation,
|
||||
|
@ -36,8 +38,8 @@ export const needsRefreshOfListData = (state: Immutable<TrustedAppsListPageState
|
|||
state.active &&
|
||||
isOutdatedResourceState(currentPage, (data) => {
|
||||
return (
|
||||
data.paginationInfo.index === location.page_index &&
|
||||
data.paginationInfo.size === location.page_size &&
|
||||
data.pageIndex === location.page_index &&
|
||||
data.pageSize === location.page_size &&
|
||||
data.timestamp >= freshDataTimestamp
|
||||
);
|
||||
})
|
||||
|
@ -74,6 +76,17 @@ export const getListTotalItemsCount = (state: Immutable<TrustedAppsListPageState
|
|||
return getLastLoadedResourceState(state.listView.listResourceState)?.data.totalItemsCount || 0;
|
||||
};
|
||||
|
||||
export const getListPagination = (state: Immutable<TrustedAppsListPageState>): Pagination => {
|
||||
const lastLoadedResourceState = getLastLoadedResourceState(state.listView.listResourceState);
|
||||
|
||||
return {
|
||||
pageIndex: state.location.page_index,
|
||||
pageSize: state.location.page_size,
|
||||
totalItemCount: lastLoadedResourceState?.data.totalItemsCount || 0,
|
||||
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
|
||||
};
|
||||
};
|
||||
|
||||
export const getCurrentLocation = (
|
||||
state: Immutable<TrustedAppsListPageState>
|
||||
): TrustedAppsListPageLocation => state.location;
|
||||
|
|
|
@ -11,6 +11,7 @@ import { RoutingAction } from '../../../../common/store/routing';
|
|||
import {
|
||||
MANAGEMENT_DEFAULT_PAGE,
|
||||
MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
MANAGEMENT_PAGE_SIZE_OPTIONS,
|
||||
MANAGEMENT_STORE_GLOBAL_NAMESPACE,
|
||||
MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE,
|
||||
} from '../../../common/constants';
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
FailedResourceState,
|
||||
LoadedResourceState,
|
||||
LoadingResourceState,
|
||||
PaginationInfo,
|
||||
Pagination,
|
||||
StaleResourceState,
|
||||
TrustedAppsListData,
|
||||
TrustedAppsListPageState,
|
||||
|
@ -44,20 +45,23 @@ export const createSampleTrustedApp = (i: number): TrustedApp => {
|
|||
};
|
||||
};
|
||||
|
||||
export const createSampleTrustedApps = (paginationInfo: PaginationInfo): TrustedApp[] => {
|
||||
return [...new Array(paginationInfo.size).keys()].map(createSampleTrustedApp);
|
||||
export const createSampleTrustedApps = (pagination: Partial<Pagination>): TrustedApp[] => {
|
||||
const fullPagination = { ...createDefaultPagination(), ...pagination };
|
||||
|
||||
return [...new Array(fullPagination.pageSize).keys()].map(createSampleTrustedApp);
|
||||
};
|
||||
|
||||
export const createTrustedAppsListData = (
|
||||
paginationInfo: PaginationInfo,
|
||||
totalItemsCount: number,
|
||||
timestamp: number
|
||||
) => ({
|
||||
items: createSampleTrustedApps(paginationInfo),
|
||||
totalItemsCount,
|
||||
paginationInfo,
|
||||
timestamp,
|
||||
});
|
||||
export const createTrustedAppsListData = (pagination: Partial<Pagination>, timestamp: number) => {
|
||||
const fullPagination = { ...createDefaultPagination(), ...pagination };
|
||||
|
||||
return {
|
||||
items: createSampleTrustedApps(fullPagination),
|
||||
pageSize: fullPagination.pageSize,
|
||||
pageIndex: fullPagination.pageIndex,
|
||||
totalItemsCount: fullPagination.totalItemCount,
|
||||
timestamp,
|
||||
};
|
||||
};
|
||||
|
||||
export const createServerApiError = (message: string) => ({
|
||||
statusCode: 500,
|
||||
|
@ -70,12 +74,11 @@ export const createUninitialisedResourceState = (): UninitialisedResourceState =
|
|||
});
|
||||
|
||||
export const createListLoadedResourceState = (
|
||||
paginationInfo: PaginationInfo,
|
||||
totalItemsCount: number,
|
||||
pagination: Partial<Pagination>,
|
||||
timestamp: number
|
||||
): LoadedResourceState<TrustedAppsListData> => ({
|
||||
type: 'LoadedResourceState',
|
||||
data: createTrustedAppsListData(paginationInfo, totalItemsCount, timestamp),
|
||||
data: createTrustedAppsListData(pagination, timestamp),
|
||||
});
|
||||
|
||||
export const createListFailedResourceState = (
|
||||
|
@ -95,32 +98,28 @@ export const createListLoadingResourceState = (
|
|||
});
|
||||
|
||||
export const createListComplexLoadingResourceState = (
|
||||
paginationInfo: PaginationInfo,
|
||||
totalItemsCount: number,
|
||||
pagination: Partial<Pagination>,
|
||||
timestamp: number
|
||||
): LoadingResourceState<TrustedAppsListData> =>
|
||||
createListLoadingResourceState(
|
||||
createListFailedResourceState(
|
||||
'Internal Server Error',
|
||||
createListLoadedResourceState(paginationInfo, totalItemsCount, timestamp)
|
||||
createListLoadedResourceState(pagination, timestamp)
|
||||
)
|
||||
);
|
||||
|
||||
export const createDefaultPaginationInfo = () => ({
|
||||
index: MANAGEMENT_DEFAULT_PAGE,
|
||||
size: MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
export const createDefaultPagination = (): Pagination => ({
|
||||
pageIndex: MANAGEMENT_DEFAULT_PAGE,
|
||||
pageSize: MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
totalItemCount: 200,
|
||||
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
|
||||
});
|
||||
|
||||
export const createLoadedListViewWithPagination = (
|
||||
freshDataTimestamp: number,
|
||||
paginationInfo: PaginationInfo = createDefaultPaginationInfo(),
|
||||
totalItemsCount: number = 200
|
||||
pagination: Partial<Pagination> = createDefaultPagination()
|
||||
): TrustedAppsListPageState['listView'] => ({
|
||||
listResourceState: createListLoadedResourceState(
|
||||
paginationInfo,
|
||||
totalItemsCount,
|
||||
freshDataTimestamp
|
||||
),
|
||||
listResourceState: createListLoadedResourceState(pagination, freshDataTimestamp),
|
||||
freshDataTimestamp,
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`control_panel ControlPanel should render grid selection correctly 1`] = `
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={1}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
>
|
||||
0 trusted applications
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<ViewTypeToggle
|
||||
onToggle={[Function]}
|
||||
selectedOption="grid"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`control_panel ControlPanel should render list selection correctly 1`] = `
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={1}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
>
|
||||
0 trusted applications
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<ViewTypeToggle
|
||||
onToggle={[Function]}
|
||||
selectedOption="list"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`control_panel ControlPanel should render plural count correctly 1`] = `
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={1}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
>
|
||||
100 trusted applications
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<ViewTypeToggle
|
||||
onToggle={[Function]}
|
||||
selectedOption="grid"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`control_panel ControlPanel should render singular count correctly 1`] = `
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={1}
|
||||
>
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
>
|
||||
1 trusted application
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<ViewTypeToggle
|
||||
onToggle={[Function]}
|
||||
selectedOption="grid"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { storiesOf, addDecorator } from '@storybook/react';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import { ControlPanel, ControlPanelProps } from '.';
|
||||
import { ViewType } from '../../../state';
|
||||
|
||||
addDecorator((storyFn) => (
|
||||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>{storyFn()}</ThemeProvider>
|
||||
));
|
||||
|
||||
const useRenderStory = (props: Omit<ControlPanelProps, 'onViewTypeChange'>) => {
|
||||
const [selectedOption, setSelectedOption] = useState<ViewType>(props.currentViewType);
|
||||
|
||||
return (
|
||||
<ControlPanel
|
||||
{...{ ...props, currentViewType: selectedOption }}
|
||||
onViewTypeChange={setSelectedOption}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf('TrustedApps/ControlPanel', module)
|
||||
.add('list view selected', () => {
|
||||
return useRenderStory({ totalItemCount: 0, currentViewType: 'list' });
|
||||
})
|
||||
.add('plural totals', () => {
|
||||
return useRenderStory({ totalItemCount: 200, currentViewType: 'grid' });
|
||||
})
|
||||
.add('singular totals', () => {
|
||||
return useRenderStory({ totalItemCount: 1, currentViewType: 'grid' });
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { ControlPanel } from '.';
|
||||
|
||||
describe('control_panel', () => {
|
||||
describe('ControlPanel', () => {
|
||||
it('should render grid selection correctly', () => {
|
||||
const element = shallow(
|
||||
<ControlPanel currentViewType={'grid'} totalItemCount={0} onViewTypeChange={() => {}} />
|
||||
);
|
||||
|
||||
expect(element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render list selection correctly', () => {
|
||||
const element = shallow(
|
||||
<ControlPanel currentViewType={'list'} totalItemCount={0} onViewTypeChange={() => {}} />
|
||||
);
|
||||
|
||||
expect(element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render singular count correctly', () => {
|
||||
const element = shallow(
|
||||
<ControlPanel currentViewType={'grid'} totalItemCount={1} onViewTypeChange={() => {}} />
|
||||
);
|
||||
|
||||
expect(element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render plural count correctly', () => {
|
||||
const element = shallow(
|
||||
<ControlPanel currentViewType={'grid'} totalItemCount={100} onViewTypeChange={() => {}} />
|
||||
);
|
||||
|
||||
expect(element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should trigger onViewTypeChange', async () => {
|
||||
const onToggle = jest.fn();
|
||||
const element = render(
|
||||
<ControlPanel currentViewType={'list'} totalItemCount={100} onViewTypeChange={onToggle} />
|
||||
);
|
||||
|
||||
(await element.findAllByTestId('viewTypeToggleButton'))[0].click();
|
||||
|
||||
expect(onToggle).toBeCalledWith('grid');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 React, { memo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ViewType } from '../../../state';
|
||||
import { ViewTypeToggle } from '../view_type_toggle';
|
||||
|
||||
export interface ControlPanelProps {
|
||||
totalItemCount: number;
|
||||
currentViewType: ViewType;
|
||||
onViewTypeChange: (value: ViewType) => void;
|
||||
}
|
||||
|
||||
export const ControlPanel = memo<ControlPanelProps>(
|
||||
({ totalItemCount, currentViewType, onViewTypeChange }) => {
|
||||
return (
|
||||
<EuiFlexGroup direction="row" alignItems="center">
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiText color="subdued" size="xs">
|
||||
{i18n.translate('xpack.securitySolution.trustedapps.list.totalCount', {
|
||||
defaultMessage:
|
||||
'{totalItemCount, plural, one {# trusted application} other {# trusted applications}}',
|
||||
values: { totalItemCount },
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ViewTypeToggle selectedOption={currentViewType} onToggle={onViewTypeChange} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ControlPanel.displayName = 'ControlPanel';
|
|
@ -62,6 +62,7 @@ exports[`trusted_app_card TrustedAppCard should render correctly 1`] = `
|
|||
/>
|
||||
<ItemDetailsAction
|
||||
color="danger"
|
||||
data-test-subj="trustedAppDeleteButton"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
|
@ -132,6 +133,7 @@ exports[`trusted_app_card TrustedAppCard should trim long descriptions 1`] = `
|
|||
/>
|
||||
<ItemDetailsAction
|
||||
color="danger"
|
||||
data-test-subj="trustedAppDeleteButton"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
|
|
|
@ -38,7 +38,7 @@ const SIGNER_CONDITION: WindowsConditionEntry = {
|
|||
value: 'Elastic',
|
||||
};
|
||||
|
||||
storiesOf('TrustedApps|TrustedAppCard', module)
|
||||
storiesOf('TrustedApps/TrustedAppCard', module)
|
||||
.add('default', () => {
|
||||
const trustedApp: TrustedApp = createSampleTrustedApp(5);
|
||||
trustedApp.created_at = '2020-09-17T14:52:33.899Z';
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTableFieldDataColumnType } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
|
@ -23,7 +22,12 @@ import {
|
|||
ItemDetailsPropertySummary,
|
||||
} from '../../../../../../common/components/item_details_card';
|
||||
|
||||
import { OS_TITLES, PROPERTY_TITLES, ENTRY_PROPERTY_TITLES } from '../../translations';
|
||||
import {
|
||||
OS_TITLES,
|
||||
PROPERTY_TITLES,
|
||||
ENTRY_PROPERTY_TITLES,
|
||||
CARD_DELETE_BUTTON_LABEL,
|
||||
} from '../../translations';
|
||||
|
||||
type Entry = MacosLinuxConditionEntry | WindowsConditionEntry;
|
||||
|
||||
|
@ -62,11 +66,11 @@ const getEntriesColumnDefinitions = (): Array<EuiTableFieldDataColumnType<Entry>
|
|||
|
||||
interface TrustedAppCardProps {
|
||||
trustedApp: Immutable<TrustedApp>;
|
||||
onDelete: (id: string) => void;
|
||||
onDelete: (trustedApp: Immutable<TrustedApp>) => void;
|
||||
}
|
||||
|
||||
export const TrustedAppCard = memo(({ trustedApp, onDelete }: TrustedAppCardProps) => {
|
||||
const handleDelete = useCallback(() => onDelete(trustedApp.id), [onDelete, trustedApp.id]);
|
||||
const handleDelete = useCallback(() => onDelete(trustedApp), [onDelete, trustedApp]);
|
||||
|
||||
return (
|
||||
<ItemDetailsCard>
|
||||
|
@ -98,10 +102,13 @@ export const TrustedAppCard = memo(({ trustedApp, onDelete }: TrustedAppCardProp
|
|||
responsive
|
||||
/>
|
||||
|
||||
<ItemDetailsAction size="s" color="danger" onClick={handleDelete}>
|
||||
{i18n.translate('xpack.securitySolution.trustedapps.card.removeButtonLabel', {
|
||||
defaultMessage: 'Remove',
|
||||
})}
|
||||
<ItemDetailsAction
|
||||
size="s"
|
||||
color="danger"
|
||||
onClick={handleDelete}
|
||||
data-test-subj="trustedAppDeleteButton"
|
||||
>
|
||||
{CARD_DELETE_BUTTON_LABEL}
|
||||
</ItemDetailsAction>
|
||||
</ItemDetailsCard>
|
||||
);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import { KibanaContextProvider } from '../../../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import {
|
||||
createGlobalNoMiddlewareStore,
|
||||
createListFailedResourceState,
|
||||
createListLoadedResourceState,
|
||||
createListLoadingResourceState,
|
||||
createTrustedAppsListResourceStateChangedAction,
|
||||
} from '../../../test_utils';
|
||||
|
||||
import { TrustedAppsGrid } from '.';
|
||||
|
||||
const now = 111111;
|
||||
|
||||
const renderGrid = (store: ReturnType<typeof createGlobalNoMiddlewareStore>) => (
|
||||
<Provider store={store}>
|
||||
<KibanaContextProvider services={{ uiSettings: { get: () => 'MMM D, YYYY @ HH:mm:ss.SSS' } }}>
|
||||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
|
||||
<TrustedAppsGrid />
|
||||
</ThemeProvider>
|
||||
</KibanaContextProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
storiesOf('TrustedApps/TrustedAppsGrid', module)
|
||||
.add('default', () => {
|
||||
return renderGrid(createGlobalNoMiddlewareStore());
|
||||
})
|
||||
.add('loading', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(createListLoadingResourceState())
|
||||
);
|
||||
|
||||
return renderGrid(store);
|
||||
})
|
||||
.add('error', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListFailedResourceState('Intenal Server Error')
|
||||
)
|
||||
);
|
||||
|
||||
return renderGrid(store);
|
||||
})
|
||||
.add('loaded', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ pageSize: 10 }, now)
|
||||
)
|
||||
);
|
||||
|
||||
return renderGrid(store);
|
||||
})
|
||||
.add('loading second time', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadingResourceState(createListLoadedResourceState({ pageSize: 10 }, now))
|
||||
)
|
||||
);
|
||||
|
||||
return renderGrid(store);
|
||||
});
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import {
|
||||
createSampleTrustedApp,
|
||||
createListFailedResourceState,
|
||||
createListLoadedResourceState,
|
||||
createListLoadingResourceState,
|
||||
createTrustedAppsListResourceStateChangedAction,
|
||||
createUserChangedUrlAction,
|
||||
createGlobalNoMiddlewareStore,
|
||||
} from '../../../test_utils';
|
||||
|
||||
import { TrustedAppsGrid } from '.';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => 'mockId',
|
||||
}));
|
||||
|
||||
const now = 111111;
|
||||
|
||||
const renderList = (store: ReturnType<typeof createGlobalNoMiddlewareStore>) => {
|
||||
const Wrapper: React.FC = ({ children }) => (
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
return render(<TrustedAppsGrid />, { wrapper: Wrapper });
|
||||
};
|
||||
|
||||
describe('TrustedAppsGrid', () => {
|
||||
it('renders correctly initially', () => {
|
||||
expect(renderList(createGlobalNoMiddlewareStore()).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when loading data for the first time', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(createListLoadingResourceState())
|
||||
);
|
||||
|
||||
expect(renderList(store).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when failed loading data for the first time', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListFailedResourceState('Intenal Server Error')
|
||||
)
|
||||
);
|
||||
|
||||
expect(renderList(store).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when loaded data', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ pageSize: 10 }, now)
|
||||
)
|
||||
);
|
||||
|
||||
expect(renderList(store).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when new page and page size set (not loading yet)', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ pageSize: 10 }, now)
|
||||
)
|
||||
);
|
||||
store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50'));
|
||||
|
||||
expect(renderList(store).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when loading data for the second time', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadingResourceState(createListLoadedResourceState({ pageSize: 10 }, now))
|
||||
)
|
||||
);
|
||||
|
||||
expect(renderList(store).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when failed loading data for the second time', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListFailedResourceState(
|
||||
'Intenal Server Error',
|
||||
createListLoadedResourceState({ pageSize: 10 }, now)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(renderList(store).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('triggers deletion dialog when delete action clicked', async () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ pageSize: 10 }, now)
|
||||
)
|
||||
);
|
||||
store.dispatch = jest.fn();
|
||||
|
||||
(await renderList(store).findAllByTestId('trustedAppDeleteButton'))[0].click();
|
||||
|
||||
expect(store.dispatch).toBeCalledWith({
|
||||
type: 'trustedAppDeletionDialogStarted',
|
||||
payload: {
|
||||
entry: createSampleTrustedApp(0),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 React, { memo, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
EuiTablePagination,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiProgress,
|
||||
EuiIcon,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { Pagination } from '../../../state';
|
||||
|
||||
import {
|
||||
getListErrorMessage,
|
||||
getListItems,
|
||||
getListPagination,
|
||||
isListLoading,
|
||||
} from '../../../store/selectors';
|
||||
|
||||
import {
|
||||
useTrustedAppsNavigateCallback,
|
||||
useTrustedAppsSelector,
|
||||
useTrustedAppsStoreActionCallback,
|
||||
} from '../../hooks';
|
||||
|
||||
import { NO_RESULTS_MESSAGE } from '../../translations';
|
||||
|
||||
import { TrustedAppCard } from '../trusted_app_card';
|
||||
|
||||
export interface PaginationBarProps {
|
||||
pagination: Pagination;
|
||||
onChange: (pagination: { size: number; index: number }) => void;
|
||||
}
|
||||
|
||||
const PaginationBar = ({ pagination, onChange }: PaginationBarProps) => {
|
||||
const pageCount = Math.ceil(pagination.totalItemCount / pagination.pageSize);
|
||||
|
||||
useEffect(() => {
|
||||
if (pageCount > 0 && pageCount < pagination.pageIndex + 1) {
|
||||
onChange({ index: pageCount - 1, size: pagination.pageSize });
|
||||
}
|
||||
}, [pageCount, onChange, pagination]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiTablePagination
|
||||
activePage={pagination.pageIndex}
|
||||
itemsPerPage={pagination.pageSize}
|
||||
itemsPerPageOptions={pagination.pageSizeOptions}
|
||||
pageCount={pageCount}
|
||||
onChangeItemsPerPage={useCallback((size) => ({ index: 0, size }), [])}
|
||||
onChangePage={useCallback((index) => ({ index, size: pagination.pageSize }), [
|
||||
pagination.pageSize,
|
||||
])}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TrustedAppsGrid = memo(() => {
|
||||
const pagination = useTrustedAppsSelector(getListPagination);
|
||||
const listItems = useTrustedAppsSelector(getListItems);
|
||||
const isLoading = useTrustedAppsSelector(isListLoading);
|
||||
const error = useTrustedAppsSelector(getListErrorMessage);
|
||||
|
||||
const handleTrustedAppDelete = useTrustedAppsStoreActionCallback((trustedApp) => ({
|
||||
type: 'trustedAppDeletionDialogStarted',
|
||||
payload: { entry: trustedApp },
|
||||
}));
|
||||
const handlePaginationChange = useTrustedAppsNavigateCallback(({ index, size }) => ({
|
||||
page_index: index,
|
||||
page_size: size,
|
||||
}));
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
{isLoading && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiProgress size="xs" color="primary" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
{error && (
|
||||
<div className="euiTextAlign--center">
|
||||
<EuiIcon type="minusInCircle" color="danger" /> {error}
|
||||
</div>
|
||||
)}
|
||||
{!error && (
|
||||
<EuiFlexGroup direction="column">
|
||||
{listItems.map((item) => (
|
||||
<EuiFlexItem grow={false} key={item.id}>
|
||||
<TrustedAppCard trustedApp={item} onDelete={handleTrustedAppDelete} />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
{listItems.length === 0 && (
|
||||
<EuiText size="s" className="euiTextAlign--center">
|
||||
{NO_RESULTS_MESSAGE}
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{!error && pagination.totalItemCount > 0 && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<PaginationBar pagination={pagination} onChange={handlePaginationChange} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
TrustedAppsGrid.displayName = 'TrustedAppsGrid';
|
|
@ -994,6 +994,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = `
|
|||
>
|
||||
<button
|
||||
class="euiButton euiButton--danger euiButton--small"
|
||||
data-test-subj="trustedAppDeleteButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import { KibanaContextProvider } from '../../../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
import {
|
||||
createGlobalNoMiddlewareStore,
|
||||
createListFailedResourceState,
|
||||
createListLoadedResourceState,
|
||||
createListLoadingResourceState,
|
||||
createTrustedAppsListResourceStateChangedAction,
|
||||
} from '../../../test_utils';
|
||||
|
||||
import { TrustedAppsList } from '.';
|
||||
|
||||
const now = 111111;
|
||||
|
||||
const renderList = (store: ReturnType<typeof createGlobalNoMiddlewareStore>) => (
|
||||
<Provider store={store}>
|
||||
<KibanaContextProvider services={{ uiSettings: { get: () => 'MMM D, YYYY @ HH:mm:ss.SSS' } }}>
|
||||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
|
||||
<TrustedAppsList />
|
||||
</ThemeProvider>
|
||||
</KibanaContextProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
storiesOf('TrustedApps/TrustedAppsList', module)
|
||||
.add('default', () => {
|
||||
return renderList(createGlobalNoMiddlewareStore());
|
||||
})
|
||||
.add('loading', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(createListLoadingResourceState())
|
||||
);
|
||||
|
||||
return renderList(store);
|
||||
})
|
||||
.add('error', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListFailedResourceState('Intenal Server Error')
|
||||
)
|
||||
);
|
||||
|
||||
return renderList(store);
|
||||
})
|
||||
.add('loaded', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ pageSize: 10 }, now)
|
||||
)
|
||||
);
|
||||
|
||||
return renderList(store);
|
||||
})
|
||||
.add('loading second time', () => {
|
||||
const store = createGlobalNoMiddlewareStore();
|
||||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadingResourceState(createListLoadedResourceState({ pageSize: 10 }, now))
|
||||
)
|
||||
);
|
||||
|
||||
return renderList(store);
|
||||
});
|
|
@ -9,7 +9,6 @@ import { Provider } from 'react-redux';
|
|||
import { ThemeProvider } from 'styled-components';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import { TrustedAppsList } from './trusted_apps_list';
|
||||
import {
|
||||
createSampleTrustedApp,
|
||||
createListFailedResourceState,
|
||||
|
@ -18,7 +17,9 @@ import {
|
|||
createTrustedAppsListResourceStateChangedAction,
|
||||
createUserChangedUrlAction,
|
||||
createGlobalNoMiddlewareStore,
|
||||
} from '../test_utils';
|
||||
} from '../../../test_utils';
|
||||
|
||||
import { TrustedAppsList } from '.';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => 'mockId',
|
||||
|
@ -70,7 +71,7 @@ describe('TrustedAppsList', () => {
|
|||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
|
||||
createListLoadedResourceState({ pageSize: 20 }, now)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -82,7 +83,7 @@ describe('TrustedAppsList', () => {
|
|||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
|
||||
createListLoadedResourceState({ pageSize: 20 }, now)
|
||||
)
|
||||
);
|
||||
store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50'));
|
||||
|
@ -95,9 +96,7 @@ describe('TrustedAppsList', () => {
|
|||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadingResourceState(
|
||||
createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
|
||||
)
|
||||
createListLoadingResourceState(createListLoadedResourceState({ pageSize: 20 }, now))
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -111,7 +110,7 @@ describe('TrustedAppsList', () => {
|
|||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListFailedResourceState(
|
||||
'Intenal Server Error',
|
||||
createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
|
||||
createListLoadedResourceState({ pageSize: 20 }, now)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -124,7 +123,7 @@ describe('TrustedAppsList', () => {
|
|||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
|
||||
createListLoadedResourceState({ pageSize: 20 }, now)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -140,7 +139,7 @@ describe('TrustedAppsList', () => {
|
|||
|
||||
store.dispatch(
|
||||
createTrustedAppsListResourceStateChangedAction(
|
||||
createListLoadedResourceState({ index: 0, size: 20 }, 200, now)
|
||||
createListLoadedResourceState({ pageSize: 20 }, now)
|
||||
)
|
||||
);
|
||||
store.dispatch = jest.fn();
|
|
@ -5,9 +5,8 @@
|
|||
*/
|
||||
|
||||
import { Dispatch } from 'redux';
|
||||
import React, { memo, ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import React, { memo, ReactNode, useMemo, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
|
@ -16,26 +15,23 @@ import {
|
|||
RIGHT_ALIGNMENT,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { Immutable } from '../../../../../common/endpoint/types';
|
||||
import { AppAction } from '../../../../common/store/actions';
|
||||
import { TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
|
||||
import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants';
|
||||
import { getTrustedAppsListPath } from '../../../common/routing';
|
||||
import { Immutable } from '../../../../../../../common/endpoint/types';
|
||||
import { AppAction } from '../../../../../../common/store/actions';
|
||||
import { TrustedApp } from '../../../../../../../common/endpoint/types/trusted_apps';
|
||||
|
||||
import {
|
||||
getCurrentLocationPageIndex,
|
||||
getCurrentLocationPageSize,
|
||||
getListErrorMessage,
|
||||
getListItems,
|
||||
getListTotalItemsCount,
|
||||
getListPagination,
|
||||
isListLoading,
|
||||
} from '../store/selectors';
|
||||
} from '../../../store/selectors';
|
||||
|
||||
import { useTrustedAppsSelector } from './hooks';
|
||||
import { FormattedDate } from '../../../../../../common/components/formatted_date';
|
||||
|
||||
import { FormattedDate } from '../../../../common/components/formatted_date';
|
||||
import { ACTIONS_COLUMN_TITLE, LIST_ACTIONS, OS_TITLES, PROPERTY_TITLES } from './translations';
|
||||
import { TrustedAppCard } from './components/trusted_app_card';
|
||||
import { useTrustedAppsNavigateCallback, useTrustedAppsSelector } from '../../hooks';
|
||||
|
||||
import { ACTIONS_COLUMN_TITLE, LIST_ACTIONS, OS_TITLES, PROPERTY_TITLES } from '../../translations';
|
||||
import { TrustedAppCard } from '../trusted_app_card';
|
||||
|
||||
interface DetailsMap {
|
||||
[K: string]: ReactNode;
|
||||
|
@ -149,12 +145,9 @@ const getColumnDefinitions = (context: TrustedAppsListContext): ColumnsList => {
|
|||
|
||||
export const TrustedAppsList = memo(() => {
|
||||
const [detailsMap, setDetailsMap] = useState<DetailsMap>({});
|
||||
const pageIndex = useTrustedAppsSelector(getCurrentLocationPageIndex);
|
||||
const pageSize = useTrustedAppsSelector(getCurrentLocationPageSize);
|
||||
const totalItemCount = useTrustedAppsSelector(getListTotalItemsCount);
|
||||
const pagination = useTrustedAppsSelector(getListPagination);
|
||||
const listItems = useTrustedAppsSelector(getListItems);
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<EuiBasicTable
|
||||
|
@ -168,27 +161,11 @@ export const TrustedAppsList = memo(() => {
|
|||
itemId="id"
|
||||
itemIdToExpandedRowMap={detailsMap}
|
||||
isExpandable={true}
|
||||
pagination={useMemo(
|
||||
() => ({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount,
|
||||
hidePerPageOptions: false,
|
||||
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
|
||||
}),
|
||||
[pageIndex, pageSize, totalItemCount]
|
||||
)}
|
||||
onChange={useCallback(
|
||||
({ page }: { page: { index: number; size: number } }) => {
|
||||
history.push(
|
||||
getTrustedAppsListPath({
|
||||
page_index: page.index,
|
||||
page_size: page.size,
|
||||
})
|
||||
);
|
||||
},
|
||||
[history]
|
||||
)}
|
||||
pagination={pagination}
|
||||
onChange={useTrustedAppsNavigateCallback(({ page }) => ({
|
||||
page_index: page.index,
|
||||
page_size: page.size,
|
||||
}))}
|
||||
data-test-subj="trustedAppsList"
|
||||
/>
|
||||
);
|
|
@ -0,0 +1,47 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`view_type_toggle ViewTypeToggle should render grid selection correctly 1`] = `
|
||||
<EuiButtonGroup
|
||||
color="primary"
|
||||
data-test-subj="viewTypeToggleButton"
|
||||
idSelected="grid"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"iconType": "grid",
|
||||
"id": "grid",
|
||||
"label": "Grid view",
|
||||
},
|
||||
Object {
|
||||
"iconType": "list",
|
||||
"id": "list",
|
||||
"label": "List view",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`view_type_toggle ViewTypeToggle should render list selection correctly 1`] = `
|
||||
<EuiButtonGroup
|
||||
color="primary"
|
||||
data-test-subj="viewTypeToggleButton"
|
||||
idSelected="list"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"iconType": "grid",
|
||||
"id": "grid",
|
||||
"label": "Grid view",
|
||||
},
|
||||
Object {
|
||||
"iconType": "list",
|
||||
"id": "list",
|
||||
"label": "List view",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { storiesOf, addDecorator } from '@storybook/react';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import { ViewType } from '../../../state';
|
||||
import { ViewTypeToggle } from '.';
|
||||
|
||||
addDecorator((storyFn) => (
|
||||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>{storyFn()}</ThemeProvider>
|
||||
));
|
||||
|
||||
const useRenderStory = (viewType: ViewType) => {
|
||||
const [selectedOption, setSelectedOption] = useState<ViewType>(viewType);
|
||||
|
||||
return <ViewTypeToggle selectedOption={selectedOption} onToggle={setSelectedOption} />;
|
||||
};
|
||||
|
||||
storiesOf('TrustedApps/ViewTypeToggle', module)
|
||||
.add('grid selected', () => useRenderStory('grid'))
|
||||
.add('list selected', () => useRenderStory('list'));
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { ViewTypeToggle } from '.';
|
||||
|
||||
describe('view_type_toggle', () => {
|
||||
describe('ViewTypeToggle', () => {
|
||||
it('should render grid selection correctly', () => {
|
||||
const element = shallow(<ViewTypeToggle selectedOption="grid" onToggle={() => {}} />);
|
||||
|
||||
expect(element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render list selection correctly', () => {
|
||||
const element = shallow(<ViewTypeToggle selectedOption="list" onToggle={() => {}} />);
|
||||
|
||||
expect(element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should trigger onToggle', async () => {
|
||||
const onToggle = jest.fn();
|
||||
const element = render(<ViewTypeToggle selectedOption="list" onToggle={onToggle} />);
|
||||
|
||||
(await element.findAllByTestId('viewTypeToggleButton'))[0].click();
|
||||
|
||||
expect(onToggle).toBeCalledWith('grid');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 React, { memo, useCallback } from 'react';
|
||||
import { EuiButtonGroup } from '@elastic/eui';
|
||||
|
||||
import { ViewType } from '../../../state';
|
||||
import { GRID_VIEW_TOGGLE_LABEL, LIST_VIEW_TOGGLE_LABEL } from '../../translations';
|
||||
|
||||
export interface ViewTypeToggleProps {
|
||||
selectedOption: ViewType;
|
||||
onToggle: (type: ViewType) => void;
|
||||
}
|
||||
|
||||
export const ViewTypeToggle = memo(({ selectedOption, onToggle }: ViewTypeToggleProps) => {
|
||||
const handleChange = useCallback(
|
||||
(id) => {
|
||||
if (id === 'list' || id === 'grid') {
|
||||
onToggle(id);
|
||||
}
|
||||
},
|
||||
[onToggle]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiButtonGroup
|
||||
color="primary"
|
||||
idSelected={selectedOption}
|
||||
data-test-subj="viewTypeToggleButton"
|
||||
options={[
|
||||
{ id: 'grid', iconType: 'grid', label: GRID_VIEW_TOGGLE_LABEL },
|
||||
{ id: 'list', iconType: 'list', label: LIST_VIEW_TOGGLE_LABEL },
|
||||
]}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
ViewTypeToggle.displayName = 'ViewTypeToggle';
|
|
@ -4,7 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { State } from '../../../../common/store';
|
||||
|
||||
|
@ -13,10 +15,38 @@ import {
|
|||
MANAGEMENT_STORE_GLOBAL_NAMESPACE as GLOBAL_NS,
|
||||
} from '../../../common/constants';
|
||||
|
||||
import { TrustedAppsListPageState } from '../state';
|
||||
import { AppAction } from '../../../../common/store/actions';
|
||||
import { getTrustedAppsListPath } from '../../../common/routing';
|
||||
import { TrustedAppsListPageLocation, TrustedAppsListPageState } from '../state';
|
||||
import { getCurrentLocation } from '../store/selectors';
|
||||
|
||||
export function useTrustedAppsSelector<R>(selector: (state: TrustedAppsListPageState) => R): R {
|
||||
return useSelector((state: State) =>
|
||||
selector(state[GLOBAL_NS][TRUSTED_APPS_NS] as TrustedAppsListPageState)
|
||||
);
|
||||
}
|
||||
|
||||
export type NavigationCallback = (
|
||||
...args: Parameters<Parameters<typeof useCallback>[0]>
|
||||
) => Partial<TrustedAppsListPageLocation>;
|
||||
|
||||
export function useTrustedAppsNavigateCallback(callback: NavigationCallback) {
|
||||
const location = useTrustedAppsSelector(getCurrentLocation);
|
||||
const history = useHistory();
|
||||
|
||||
return useCallback(
|
||||
(...args) => history.push(getTrustedAppsListPath({ ...location, ...callback(...args) })),
|
||||
// TODO: needs more investigation, but if callback is in dependencies list memoization will never happen
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[history, location]
|
||||
);
|
||||
}
|
||||
|
||||
export function useTrustedAppsStoreActionCallback(
|
||||
callback: (...args: Parameters<Parameters<typeof useCallback>[0]>) => AppAction
|
||||
) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
return useCallback((...args) => dispatch(callback(...args)), [dispatch]);
|
||||
}
|
||||
|
|
|
@ -82,3 +82,28 @@ export const LIST_ACTIONS = {
|
|||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const CARD_DELETE_BUTTON_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.card.removeButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Remove',
|
||||
}
|
||||
);
|
||||
|
||||
export const GRID_VIEW_TOGGLE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.view.toggle.grid',
|
||||
{
|
||||
defaultMessage: 'Grid view',
|
||||
}
|
||||
);
|
||||
|
||||
export const LIST_VIEW_TOGGLE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.view.toggle.list',
|
||||
{
|
||||
defaultMessage: 'List view',
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_RESULTS_MESSAGE = i18n.translate('xpack.securitySolution.trustedapps.noResults', {
|
||||
defaultMessage: 'No items found',
|
||||
});
|
||||
|
|
|
@ -47,14 +47,6 @@ describe('When on the Trusted Apps Page', () => {
|
|||
window.scrollTo = jest.fn();
|
||||
});
|
||||
|
||||
it('should render expected set of list columns', () => {
|
||||
const { getByTestId } = render();
|
||||
const tableColumns = Array.from(
|
||||
getByTestId('trustedAppsList').querySelectorAll('table th')
|
||||
).map((th) => (th.textContent || '').trim());
|
||||
expect(tableColumns).toEqual(['Name', 'OS', 'Date Created', 'Created By', 'Actions']);
|
||||
});
|
||||
|
||||
it('should display subtitle info about trusted apps', async () => {
|
||||
const { getByTestId } = render();
|
||||
expect(getByTestId('header-panel-subtitle').textContent).toEqual(expectedAboutInfo);
|
||||
|
|
|
@ -3,39 +3,42 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ViewType } from '../state';
|
||||
import { getCurrentLocation, getListTotalItemsCount } from '../store/selectors';
|
||||
import { useTrustedAppsNavigateCallback, useTrustedAppsSelector } from './hooks';
|
||||
import { AdministrationListPage } from '../../../components/administration_list_page';
|
||||
import { TrustedAppsList } from './trusted_apps_list';
|
||||
import { CreateTrustedAppFlyout } from './components/create_trusted_app_flyout';
|
||||
import { ControlPanel } from './components/control_panel';
|
||||
import { TrustedAppsGrid } from './components/trusted_apps_grid';
|
||||
import { TrustedAppsList } from './components/trusted_apps_list';
|
||||
import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog';
|
||||
import { TrustedAppsNotifications } from './trusted_apps_notifications';
|
||||
import { CreateTrustedAppFlyout } from './components/create_trusted_app_flyout';
|
||||
import { getTrustedAppsListPath } from '../../../common/routing';
|
||||
import { useTrustedAppsSelector } from './hooks';
|
||||
import { getCurrentLocation } from '../store/selectors';
|
||||
import { TrustedAppsListPageRouteState } from '../../../../../common/endpoint/types';
|
||||
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
|
||||
import { ABOUT_TRUSTED_APPS } from './translations';
|
||||
|
||||
export const TrustedAppsPage = memo(() => {
|
||||
const history = useHistory();
|
||||
const { state: routeState } = useLocation<TrustedAppsListPageRouteState | undefined>();
|
||||
const location = useTrustedAppsSelector(getCurrentLocation);
|
||||
const handleAddButtonClick = useCallback(() => {
|
||||
history.push(
|
||||
getTrustedAppsListPath({
|
||||
...location,
|
||||
show: 'create',
|
||||
})
|
||||
);
|
||||
}, [history, location]);
|
||||
const handleAddFlyoutClose = useCallback(() => {
|
||||
const { show, ...paginationParamsOnly } = location;
|
||||
history.push(getTrustedAppsListPath(paginationParamsOnly));
|
||||
}, [history, location]);
|
||||
const totalItemsCount = useTrustedAppsSelector(getListTotalItemsCount);
|
||||
const handleAddButtonClick = useTrustedAppsNavigateCallback(() => ({ show: 'create' }));
|
||||
const handleAddFlyoutClose = useTrustedAppsNavigateCallback(() => ({ show: undefined }));
|
||||
const handleViewTypeChange = useTrustedAppsNavigateCallback((viewType: ViewType) => ({
|
||||
view_type: viewType,
|
||||
}));
|
||||
|
||||
const backButton = useMemo(() => {
|
||||
if (routeState && routeState.onBackButtonNavigateTo) {
|
||||
|
@ -84,7 +87,23 @@ export const TrustedAppsPage = memo(() => {
|
|||
data-test-subj="addTrustedAppFlyout"
|
||||
/>
|
||||
)}
|
||||
<TrustedAppsList />
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ControlPanel
|
||||
totalItemCount={totalItemsCount}
|
||||
currentViewType={location.view_type}
|
||||
onViewTypeChange={handleViewTypeChange}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
||||
{location.view_type === 'grid' && <TrustedAppsGrid />}
|
||||
{location.view_type === 'list' && <TrustedAppsList />}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</AdministrationListPage>
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue