From f340d713771ae160823bb243df20414df0413e7f Mon Sep 17 00:00:00 2001 From: Bohdan Tsymbala Date: Tue, 6 Oct 2020 17:18:30 +0200 Subject: [PATCH] [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. --- .../conditions_table/index.stories.tsx | 2 +- .../item_details_card/index.stories.tsx | 2 +- .../state/trusted_apps_list_page_state.ts | 13 +- .../trusted_apps/store/middleware.test.ts | 53 +- .../pages/trusted_apps/store/middleware.ts | 3 +- .../pages/trusted_apps/store/reducer.test.ts | 3 +- .../trusted_apps/store/selectors.test.ts | 27 +- .../pages/trusted_apps/store/selectors.ts | 17 +- .../pages/trusted_apps/test_utils/index.ts | 57 +- .../__snapshots__/index.test.tsx.snap | 105 + .../control_panel/index.stories.tsx | 38 + .../components/control_panel/index.test.tsx | 57 + .../view/components/control_panel/index.tsx | 41 + .../__snapshots__/index.test.tsx.snap | 2 + .../trusted_app_card/index.stories.tsx | 2 +- .../components/trusted_app_card/index.tsx | 23 +- .../__snapshots__/index.test.tsx.snap | 8584 +++++++++++++++++ .../trusted_apps_grid/index.stories.tsx | 81 + .../trusted_apps_grid/index.test.tsx | 140 + .../components/trusted_apps_grid/index.tsx | 118 + .../__snapshots__/index.test.tsx.snap} | 1 + .../trusted_apps_list/index.stories.tsx | 81 + .../trusted_apps_list/index.test.tsx} | 19 +- .../trusted_apps_list/index.tsx} | 57 +- .../__snapshots__/index.test.tsx.snap | 47 + .../view_type_toggle/index.stories.tsx | 26 + .../view_type_toggle/index.test.tsx | 35 + .../components/view_type_toggle/index.tsx | 42 + .../pages/trusted_apps/view/hooks.ts | 34 +- .../pages/trusted_apps/view/translations.ts | 25 + .../view/trusted_apps_page.test.tsx | 8 - .../trusted_apps/view/trusted_apps_page.tsx | 65 +- 32 files changed, 9631 insertions(+), 177 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.stories.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx rename x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/{__snapshots__/trusted_apps_list.test.tsx.snap => components/trusted_apps_list/__snapshots__/index.test.tsx.snap} (99%) create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/index.stories.tsx rename x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/{trusted_apps_list.test.tsx => components/trusted_apps_list/index.test.tsx} (88%) rename x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/{trusted_apps_list.tsx => components/trusted_apps_list/index.tsx} (71%) create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.stories.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/view_type_toggle/index.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx index b179dbb6a405..6cb65bac00ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/conditions_table/index.stories.tsx @@ -15,7 +15,7 @@ addDecorator((storyFn) => ( ({ eui: euiLightVars, darkMode: false })}>{storyFn()} )); -storiesOf('Components|ConditionsTable', module) +storiesOf('Components/ConditionsTable', module) .add('single item', () => { return ; }) diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx index b16f4be59886..e9d1825658be 100644 --- a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx @@ -14,7 +14,7 @@ addDecorator((storyFn) => ( ({ eui: euiLightVars, darkMode: false })}>{storyFn()} )); -storiesOf('Components|ItemDetailsCard', module).add('default', () => { +storiesOf('Components/ItemDetailsCard', module).add('default', () => { return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts index e58de80c354a..d0ff65e733fe 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts @@ -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 */ diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts index e19731f28d9b..160dedae0709 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts @@ -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) => { + const fullPagination = { ...createDefaultPagination(), ...pagination }; + + return { + data: createSampleTrustedApps(pagination), + page: fullPagination.pageIndex, + per_page: fullPagination.pageSize, + total: fullPagination.totalItemCount, + }; +}; const createTrustedAppsServiceMock = (): jest.Mocked => ({ 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 () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts index 9fa456dc5ffe..b55f63f9a60d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts @@ -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(), }, }) diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts index 1c1d609fb16e..3236999a88c8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts @@ -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( diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts index 8706333cb7bf..23f6a1190ed6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts @@ -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, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts index 62ffa364e4a6..589fbac03a7e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts @@ -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 { 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): 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 ): TrustedAppsListPageLocation => state.location; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts index c23b6ceae7b0..efc2717e10f1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts @@ -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): 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, 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, timestamp: number ): LoadedResourceState => ({ 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, timestamp: number ): LoadingResourceState => 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 = createDefaultPagination() ): TrustedAppsListPageState['listView'] => ({ - listResourceState: createListLoadedResourceState( - paginationInfo, - totalItemsCount, - freshDataTimestamp - ), + listResourceState: createListLoadedResourceState(pagination, freshDataTimestamp), freshDataTimestamp, }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000000..1cd4e96546f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/__snapshots__/index.test.tsx.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`control_panel ControlPanel should render grid selection correctly 1`] = ` + + + + 0 trusted applications + + + + + + +`; + +exports[`control_panel ControlPanel should render list selection correctly 1`] = ` + + + + 0 trusted applications + + + + + + +`; + +exports[`control_panel ControlPanel should render plural count correctly 1`] = ` + + + + 100 trusted applications + + + + + + +`; + +exports[`control_panel ControlPanel should render singular count correctly 1`] = ` + + + + 1 trusted application + + + + + + +`; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.stories.tsx new file mode 100644 index 000000000000..6c8766858e0f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.stories.tsx @@ -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) => ( + ({ eui: euiLightVars, darkMode: false })}>{storyFn()} +)); + +const useRenderStory = (props: Omit) => { + const [selectedOption, setSelectedOption] = useState(props.currentViewType); + + return ( + + ); +}; + +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' }); + }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.test.tsx new file mode 100644 index 000000000000..cf8c32104814 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.test.tsx @@ -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( + {}} /> + ); + + expect(element).toMatchSnapshot(); + }); + + it('should render list selection correctly', () => { + const element = shallow( + {}} /> + ); + + expect(element).toMatchSnapshot(); + }); + + it('should render singular count correctly', () => { + const element = shallow( + {}} /> + ); + + expect(element).toMatchSnapshot(); + }); + + it('should render plural count correctly', () => { + const element = shallow( + {}} /> + ); + + expect(element).toMatchSnapshot(); + }); + + it('should trigger onViewTypeChange', async () => { + const onToggle = jest.fn(); + const element = render( + + ); + + (await element.findAllByTestId('viewTypeToggleButton'))[0].click(); + + expect(onToggle).toBeCalledWith('grid'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.tsx new file mode 100644 index 000000000000..1dd70d766cd8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/control_panel/index.tsx @@ -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( + ({ totalItemCount, currentViewType, onViewTypeChange }) => { + return ( + + + + {i18n.translate('xpack.securitySolution.trustedapps.list.totalCount', { + defaultMessage: + '{totalItemCount, plural, one {# trusted application} other {# trusted applications}}', + values: { totalItemCount }, + })} + + + + + + + ); + } +); + +ControlPanel.displayName = 'ControlPanel'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap index 1d33a06c507a..c5a10c740ec7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap @@ -62,6 +62,7 @@ exports[`trusted_app_card TrustedAppCard should render correctly 1`] = ` /> @@ -132,6 +133,7 @@ exports[`trusted_app_card TrustedAppCard should trim long descriptions 1`] = ` /> diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx index 070647da48f4..d41266f5d439 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.stories.tsx @@ -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'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx index 95a9fd8a6b84..438331b706cc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx @@ -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 interface TrustedAppCardProps { trustedApp: Immutable; - onDelete: (id: string) => void; + onDelete: (trustedApp: Immutable) => 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 ( @@ -98,10 +102,13 @@ export const TrustedAppCard = memo(({ trustedApp, onDelete }: TrustedAppCardProp responsive /> - - {i18n.translate('xpack.securitySolution.trustedapps.card.removeButtonLabel', { - defaultMessage: 'Remove', - })} + + {CARD_DELETE_BUTTON_LABEL} ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000000..87fa82ecd4c0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -0,0 +1,8584 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TrustedAppsGrid renders correctly initially 1`] = ` +
+
+
+
+
+ No items found +
+
+
+
+
+`; + +exports[`TrustedAppsGrid renders correctly when failed loading data for the first time 1`] = ` +
+
+
+
+
+ + Intenal Server Error +
+
+
+
+`; + +exports[`TrustedAppsGrid renders correctly when failed loading data for the second time 1`] = ` +
+
+
+
+
+ + Intenal Server Error +
+
+
+
+`; + +exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +.c0 { + background-color: #f5f7fa; + padding: 16px; +} + +.c3 { + padding: 16px; +} + +.c1.c1.c1 { + width: 40%; +} + +.c2.c2.c2 { + width: 60%; +} + +
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 0 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 0 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 1 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 1 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 2 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 2 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 3 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 3 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 4 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 4 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 5 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 5 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 6 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 6 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 7 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 7 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 8 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 8 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 9 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 9 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+`; + +exports[`TrustedAppsGrid renders correctly when loading data for the first time 1`] = ` +
+
+
+
+
+
+
+
+ No items found +
+
+
+
+
+`; + +exports[`TrustedAppsGrid renders correctly when loading data for the second time 1`] = ` +.c0 { + background-color: #f5f7fa; + padding: 16px; +} + +.c3 { + padding: 16px; +} + +.c1.c1.c1 { + width: 40%; +} + +.c2.c2.c2 { + width: 60%; +} + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 0 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 0 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 1 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 1 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 2 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 2 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 3 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 3 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 4 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 4 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 5 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 5 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 6 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 6 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 7 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 7 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 8 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 8 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 9 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 9 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+`; + +exports[`TrustedAppsGrid renders correctly when new page and page size set (not loading yet) 1`] = ` +.c0 { + background-color: #f5f7fa; + padding: 16px; +} + +.c3 { + padding: 16px; +} + +.c1.c1.c1 { + width: 40%; +} + +.c2.c2.c2 { + width: 60%; +} + +
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 0 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 0 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 1 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 1 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 2 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 2 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 3 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 3 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 4 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 4 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 5 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 5 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 6 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 6 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 7 + +
+
+ OS +
+
+ + Mac OS + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 7 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 8 + +
+
+ OS +
+
+ + Linux + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 8 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name +
+
+ + trusted app 9 + +
+
+ OS +
+
+ + Windows + +
+
+ Date Created +
+
+ + 1 minute ago + +
+
+ Created By +
+
+ + someone + +
+
+ Description +
+
+ + Trusted App 9 + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+
+ + Field + +
+
+
+ + Operator + +
+
+
+ + Value + +
+
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+`; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx new file mode 100644 index 000000000000..fbe268a13220 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.stories.tsx @@ -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) => ( + + 'MMM D, YYYY @ HH:mm:ss.SSS' } }}> + ({ eui: euiLightVars, darkMode: false })}> + + + + +); + +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); + }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx new file mode 100644 index 000000000000..7eb78fec7349 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx @@ -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) => { + const Wrapper: React.FC = ({ children }) => ( + + ({ eui: euiLightVars, darkMode: false })}> + {children} + + + ); + + return render(, { 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), + }, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx new file mode 100644 index 000000000000..dd735f7d7515 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx @@ -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 ( +
+ ({ index: 0, size }), [])} + onChangePage={useCallback((index) => ({ index, size: pagination.pageSize }), [ + pagination.pageSize, + ])} + /> +
+ ); +}; + +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 ( + + {isLoading && ( + + + + )} + + {error && ( +
+ {error} +
+ )} + {!error && ( + + {listItems.map((item) => ( + + + + ))} + {listItems.length === 0 && ( + + {NO_RESULTS_MESSAGE} + + )} + + )} +
+ {!error && pagination.totalItemCount > 0 && ( + + + + )} +
+ ); +}); + +TrustedAppsGrid.displayName = 'TrustedAppsGrid'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap similarity index 99% rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap rename to x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap index bf5f5149b2ef..551032d88c5b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap @@ -994,6 +994,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` >