From 11886bf51c61ae0ec7fed4f54fb110d44b783c45 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 6 Oct 2020 17:17:41 +0200 Subject: [PATCH 01/33] [Lens] Add search to chart switcher (#77631) --- .../workspace_panel/chart_switch.scss | 4 ++ .../workspace_panel/chart_switch.tsx | 55 ++++++++++++++++--- x-pack/plugins/lens/public/types.ts | 16 ++++++ .../lens/public/xy_visualization/types.ts | 20 +++++-- .../xy_visualization/visualization.test.ts | 16 +++--- .../public/xy_visualization/visualization.tsx | 8 +-- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 8 files changed, 94 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss index f84191e1bfb1a..e0031d051df81 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.scss @@ -16,3 +16,7 @@ img.lnsChartSwitch__chartIcon { // sass-lint:disable-line no-qualifying-elements // The large icons aren't square so max out the width to fill the height width: 100%; } + +.lnsChartSwitch__search { + width: 4 * $euiSizeXXL; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index f4526cac39c8a..73ffbf56ff45a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -12,9 +12,14 @@ import { EuiPopoverTitle, EuiKeyPadMenu, EuiKeyPadMenuItem, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiSelectableMessage, } from '@elastic/eui'; import { flatten } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { Visualization, FramePublicAPI, Datasource } from '../../../types'; import { Action } from '../state_management'; import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_helpers'; @@ -173,6 +178,8 @@ export function ChartSwitch(props: Props) { }; } + const [searchTerm, setSearchTerm] = useState(''); + const visualizationTypes = useMemo( () => flyoutOpen && @@ -184,10 +191,17 @@ export function ChartSwitch(props: Props) { icon: t.icon, })) ) - ).map((visualizationType) => ({ - ...visualizationType, - selection: getSelection(visualizationType.visualizationId, visualizationType.id), - })), + ) + .filter( + (visualizationType) => + visualizationType.label.toLowerCase().includes(searchTerm.toLowerCase()) || + (visualizationType.fullLabel && + visualizationType.fullLabel.toLowerCase().includes(searchTerm.toLowerCase())) + ) + .map((visualizationType) => ({ + ...visualizationType, + selection: getSelection(visualizationType.visualizationId, visualizationType.id), + })), // eslint-disable-next-line react-hooks/exhaustive-deps [ flyoutOpen, @@ -195,6 +209,7 @@ export function ChartSwitch(props: Props) { props.framePublicAPI, props.visualizationId, props.visualizationState, + searchTerm, ] ); @@ -219,15 +234,30 @@ export function ChartSwitch(props: Props) { anchorPosition="downLeft" > - {i18n.translate('xpack.lens.configPanel.selectVisualization', { - defaultMessage: 'Select a visualization', - })} + + + {i18n.translate('xpack.lens.configPanel.chartType', { + defaultMessage: 'Chart type', + })} + + + setSearchTerm(e.target.value)} + /> + + {(visualizationTypes || []).map((v) => ( {v.label}} + title={v.fullLabel} role="menuitem" data-test-subj={`lnsChartSwitchPopover_${v.id}`} onClick={() => commitSelection(v.selection)} @@ -251,6 +281,17 @@ export function ChartSwitch(props: Props) { ))} + {searchTerm && (visualizationTypes || []).length === 0 && ( + + {searchTerm}, + }} + /> + + )} ); diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 6061f928bce41..2b9ca5a2425f8 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -430,10 +430,26 @@ export interface FramePublicAPI { removeLayers: (layerIds: string[]) => void; } +/** + * A visualization type advertised to the user in the chart switcher + */ export interface VisualizationType { + /** + * Unique id of the visualization type within the visualization defining it + */ id: string; + /** + * Icon used in the chart switcher + */ icon: IconType; + /** + * Visible label used in the chart switcher and above the workspace panel in collapsed state + */ label: string; + /** + * Optional label used in chart type search if chart switcher is expanded and for tooltips + */ + fullLabel?: string; } export interface Visualization { diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index cac982f852c7a..abee787888787 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -426,6 +426,9 @@ export const visualizationTypes: VisualizationType[] = [ id: 'bar_horizontal', icon: LensIconChartBarHorizontal, label: i18n.translate('xpack.lens.xyVisualization.barHorizontalLabel', { + defaultMessage: 'H. Bar', + }), + fullLabel: i18n.translate('xpack.lens.xyVisualization.barHorizontalFullLabel', { defaultMessage: 'Horizontal bar', }), }, @@ -440,22 +443,31 @@ export const visualizationTypes: VisualizationType[] = [ id: 'bar_percentage_stacked', icon: LensIconChartBarPercentage, label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarLabel', { - defaultMessage: 'Bar percentage', + defaultMessage: 'Percentage bar', }), }, { id: 'bar_horizontal_stacked', icon: LensIconChartBarHorizontalStacked, label: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalLabel', { - defaultMessage: 'Stacked horizontal bar', + defaultMessage: 'H. Stacked bar', + }), + fullLabel: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalFullLabel', { + defaultMessage: 'Horizontal stacked bar', }), }, { id: 'bar_horizontal_percentage_stacked', icon: LensIconChartBarHorizontalPercentage, label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageBarHorizontalLabel', { - defaultMessage: 'Horizontal bar percentage', + defaultMessage: 'H. Percentage bar', }), + fullLabel: i18n.translate( + 'xpack.lens.xyVisualization.stackedPercentageBarHorizontalFullLabel', + { + defaultMessage: 'Horizontal percentage bar', + } + ), }, { id: 'area', @@ -475,7 +487,7 @@ export const visualizationTypes: VisualizationType[] = [ id: 'area_percentage_stacked', icon: LensIconChartAreaPercentage, label: i18n.translate('xpack.lens.xyVisualization.stackedPercentageAreaLabel', { - defaultMessage: 'Area percentage', + defaultMessage: 'Percentage area', }), }, { diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 621fd082faf2d..d51b8c195c92c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -44,14 +44,14 @@ describe('xy_visualization', () => { it('should show mixed xy chart when multilple series types', () => { const desc = xyVisualization.getDescription(mixedState('bar', 'line')); - expect(desc.label).toEqual('Mixed XY chart'); + expect(desc.label).toEqual('Mixed XY'); }); it('should show the preferredSeriesType if there are no layers', () => { const desc = xyVisualization.getDescription(mixedState()); expect(desc.icon).toEqual(LensIconChartBar); - expect(desc.label).toEqual('Bar chart'); + expect(desc.label).toEqual('Bar'); }); it('should show mixed horizontal bar chart when multiple horizontal bar types', () => { @@ -59,23 +59,23 @@ describe('xy_visualization', () => { mixedState('bar_horizontal', 'bar_horizontal_stacked') ); - expect(desc.label).toEqual('Mixed horizontal bar chart'); + expect(desc.label).toEqual('Mixed H. bar'); }); it('should show bar chart when bar only', () => { const desc = xyVisualization.getDescription(mixedState('bar_horizontal', 'bar_horizontal')); - expect(desc.label).toEqual('Horizontal bar chart'); + expect(desc.label).toEqual('H. Bar'); }); it('should show the chart description if not mixed', () => { - expect(xyVisualization.getDescription(mixedState('area')).label).toEqual('Area chart'); - expect(xyVisualization.getDescription(mixedState('line')).label).toEqual('Line chart'); + expect(xyVisualization.getDescription(mixedState('area')).label).toEqual('Area'); + expect(xyVisualization.getDescription(mixedState('line')).label).toEqual('Line'); expect(xyVisualization.getDescription(mixedState('area_stacked')).label).toEqual( - 'Stacked area chart' + 'Stacked area' ); expect(xyVisualization.getDescription(mixedState('bar_horizontal_stacked')).label).toEqual( - 'Stacked horizontal bar chart' + 'H. Stacked bar' ); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 0b8f7e2ed0f11..76c5a51cb7168 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -53,7 +53,7 @@ function getDescription(state?: State) { return { icon: LensIconChartBarHorizontal, label: i18n.translate('xpack.lens.xyVisualization.mixedBarHorizontalLabel', { - defaultMessage: 'Mixed horizontal bar', + defaultMessage: 'Mixed H. bar', }), }; } @@ -118,14 +118,10 @@ export const xyVisualization: Visualization = { getDescription(state) { const { icon, label } = getDescription(state); - const chartLabel = i18n.translate('xpack.lens.xyVisualization.chartLabel', { - defaultMessage: '{label} chart', - values: { label }, - }); return { icon: icon || defaultIcon, - label: chartLabel, + label, }; }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 22b2bc059739c..d2f2cf9aa7d45 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9623,15 +9623,12 @@ "xpack.lens.xySuggestions.unstackedChartTitle": "スタックが解除されました", "xpack.lens.xySuggestions.yAxixConjunctionSign": " と ", "xpack.lens.xyVisualization.areaLabel": "エリア", - "xpack.lens.xyVisualization.barHorizontalLabel": "横棒", "xpack.lens.xyVisualization.barLabel": "バー", - "xpack.lens.xyVisualization.chartLabel": "{label} チャート", "xpack.lens.xyVisualization.lineLabel": "折れ線", "xpack.lens.xyVisualization.mixedBarHorizontalLabel": "ミックスされた横棒", "xpack.lens.xyVisualization.mixedLabel": "ミックスされた XY", "xpack.lens.xyVisualization.noDataLabel": "結果が見つかりませんでした", "xpack.lens.xyVisualization.stackedAreaLabel": "スタックされたエリア", - "xpack.lens.xyVisualization.stackedBarHorizontalLabel": "スタックされた横棒", "xpack.lens.xyVisualization.stackedBarLabel": "スタックされたバー", "xpack.lens.xyVisualization.xyLabel": "XY", "xpack.licenseMgmt.app.checkingPermissionsErrorMessage": "パーミッションの確認中にエラーが発生", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c968ad13de4b2..76769835f430e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9629,15 +9629,12 @@ "xpack.lens.xySuggestions.unstackedChartTitle": "非堆叠", "xpack.lens.xySuggestions.yAxixConjunctionSign": " & ", "xpack.lens.xyVisualization.areaLabel": "面积图", - "xpack.lens.xyVisualization.barHorizontalLabel": "水平条形图", "xpack.lens.xyVisualization.barLabel": "条形图", - "xpack.lens.xyVisualization.chartLabel": "{label} 图表", "xpack.lens.xyVisualization.lineLabel": "折线图", "xpack.lens.xyVisualization.mixedBarHorizontalLabel": "混合水平条形图", "xpack.lens.xyVisualization.mixedLabel": "混合 XY", "xpack.lens.xyVisualization.noDataLabel": "找不到结果", "xpack.lens.xyVisualization.stackedAreaLabel": "堆叠面积图", - "xpack.lens.xyVisualization.stackedBarHorizontalLabel": "堆叠水平条形图", "xpack.lens.xyVisualization.stackedBarLabel": "堆叠条形图", "xpack.lens.xyVisualization.xyLabel": "XY", "xpack.licenseMgmt.app.checkingPermissionsErrorMessage": "检查权限时出错", From f340d713771ae160823bb243df20414df0413e7f Mon Sep 17 00:00:00 2001 From: Bohdan Tsymbala Date: Tue, 6 Oct 2020 17:18:30 +0200 Subject: [PATCH 02/33] [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 b179dbb6a405e..6cb65bac00adf 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 b16f4be598866..e9d1825658bee 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 e58de80c354a8..d0ff65e733fef 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 e19731f28d9b9..160dedae07093 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 9fa456dc5ffe2..b55f63f9a60de 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 1c1d609fb16e8..3236999a88c84 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 8706333cb7bf6..23f6a1190ed61 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 62ffa364e4a6c..589fbac03a7e2 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 c23b6ceae7b07..efc2717e10f1d 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 0000000000000..1cd4e96546f96 --- /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 0000000000000..6c8766858e0fb --- /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 0000000000000..cf8c32104814b --- /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 0000000000000..1dd70d766cd85 --- /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 1d33a06c507a3..c5a10c740ec7e 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 070647da48f42..d41266f5d4397 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 95a9fd8a6b84d..438331b706cc3 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 0000000000000..87fa82ecd4c05 --- /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 0000000000000..fbe268a132202 --- /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 0000000000000..7eb78fec73490 --- /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 0000000000000..dd735f7d75152 --- /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 bf5f5149b2ef2..551032d88c5be 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`] = ` > -
-
- -
-
- -
- ", - "library_frame": false, - "line": Object { - "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", - "number": 307, - }, - } - } - > - -
- -
- - -
- -
- 305 - . -
-
- -
- 306 - . -
-
- -
- 307 - . -
-
- -
- 308 - . -
-
- -
- 309 - . -
-
-
-
- -
- - -
-                                  
-                                    
-                                          })
-                                    
-                                  
-                                
-
-
- - -
-                                  
-                                    
-                                      
-
-                                    
-                                  
-                                
-
-
- - -
-                                  
-                                    
-                                          client.query(
-                                      
-                                        'SELECT id FROM customers WHERE id=$1'
-                                      
-                                      , [req.body.customer_id], next())
-                                    
-                                  
-                                
-
-
- - -
-                                  
-                                    
-                                          req.body.lines.forEach(
-                                      
-                                        
-                                          function
-                                        
-                                         (
-                                        
-                                          line
-                                        
-                                        ) 
-                                      
-                                      {
-                                    
-                                  
-                                
-
-
- - -
-                                  
-                                    
-                                            client.query(
-                                      
-                                        'SELECT id FROM products WHERE id=$1'
-                                      
-                                      , [line.id], next())
-                                    
-                                  
-                                
-
-
-
-
-
-
- -
-
- -
-
- -
-
- - -`; diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/index.test.ts.snap deleted file mode 100644 index 6384253cb62a1..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,468 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Stacktrace/index getGroupedStackframes should collapse the library frames into a set of grouped stackframes 1`] = ` -Array [ - Object { - "excludeFromGrouping": false, - "isLibraryFrame": false, - "stackframes": Array [ - Object { - "abs_path": "/app/server/routes.js", - "context": Object { - "post": Array [ - " req.body.lines.forEach(function (line) {", - " client.query('SELECT id FROM products WHERE id=$1', [line.id], next())", - ], - "pre": Array [ - " })", - "", - ], - }, - "exclude_from_grouping": false, - "filename": "server/routes.js", - "function": "", - "library_frame": false, - "line": Object { - "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", - "number": 307, - }, - }, - ], - }, - Object { - "excludeFromGrouping": false, - "isLibraryFrame": true, - "stackframes": Array [ - Object { - "abs_path": "/app/node_modules/pg-pool/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/pg-pool/index.js", - "function": "BoundPool.", - "library_frame": true, - "line": Object { - "number": 137, - }, - }, - Object { - "abs_path": "/app/node_modules/generic-pool/lib/generic-pool.js", - "exclude_from_grouping": false, - "filename": "node_modules/generic-pool/lib/generic-pool.js", - "function": "dispense", - "library_frame": true, - "line": Object { - "number": 310, - }, - }, - Object { - "abs_path": "/app/node_modules/generic-pool/lib/generic-pool.js", - "exclude_from_grouping": false, - "filename": "node_modules/generic-pool/lib/generic-pool.js", - "function": "acquire", - "library_frame": true, - "line": Object { - "number": 391, - }, - }, - Object { - "abs_path": "/app/node_modules/pg-pool/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/pg-pool/index.js", - "function": "BoundPool.", - "library_frame": true, - "line": Object { - "number": 111, - }, - }, - Object { - "abs_path": "/app/node_modules/pg-pool/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/pg-pool/index.js", - "function": "Pool._promiseNoCallback", - "library_frame": true, - "line": Object { - "number": 75, - }, - }, - Object { - "abs_path": "/app/node_modules/pg-pool/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/pg-pool/index.js", - "function": "Pool.connect", - "library_frame": true, - "line": Object { - "number": 109, - }, - }, - ], - }, - Object { - "excludeFromGrouping": false, - "isLibraryFrame": false, - "stackframes": Array [ - Object { - "abs_path": "/app/server/db.js", - "context": Object { - "post": Array [ - "}", - "", - ], - "pre": Array [ - "", - "exports.client = function (cb) {", - ], - }, - "exclude_from_grouping": false, - "filename": "server/db.js", - "function": "exports.client", - "library_frame": false, - "line": Object { - "context": " pool.connect(cb)", - "number": 11, - }, - }, - Object { - "abs_path": "/app/server/routes.js", - "context": Object { - "post": Array [ - " if (err) return error(err, res)", - "", - ], - "pre": Array [ - " }", - "", - ], - }, - "exclude_from_grouping": false, - "filename": "server/routes.js", - "function": "", - "library_frame": false, - "line": Object { - "context": " db.client(function (err, client, done) {", - "number": 248, - }, - }, - ], - }, - Object { - "excludeFromGrouping": false, - "isLibraryFrame": true, - "stackframes": Array [ - Object { - "abs_path": "/app/node_modules/express/lib/router/layer.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/layer.js", - "function": "handle", - "library_frame": true, - "line": Object { - "number": 95, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/route.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/route.js", - "function": "next", - "library_frame": true, - "line": Object { - "number": 137, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/route.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/route.js", - "function": "dispatch", - "library_frame": true, - "line": Object { - "number": 112, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/layer.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/layer.js", - "function": "handle", - "library_frame": true, - "line": Object { - "number": 95, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "", - "library_frame": true, - "line": Object { - "number": 281, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "process_params", - "library_frame": true, - "line": Object { - "number": 335, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "next", - "library_frame": true, - "line": Object { - "number": 275, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "handle", - "library_frame": true, - "line": Object { - "number": 174, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "router", - "library_frame": true, - "line": Object { - "number": 47, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/layer.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/layer.js", - "function": "handle", - "library_frame": true, - "line": Object { - "number": 95, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "trim_prefix", - "library_frame": true, - "line": Object { - "number": 317, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "", - "library_frame": true, - "line": Object { - "number": 284, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "process_params", - "library_frame": true, - "line": Object { - "number": 335, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "next", - "library_frame": true, - "line": Object { - "number": 275, - }, - }, - ], - }, - Object { - "excludeFromGrouping": false, - "isLibraryFrame": false, - "stackframes": Array [ - Object { - "abs_path": "/app/server.js", - "context": Object { - "post": Array [ - " }", - "", - ], - "pre": Array [ - "app.use('/api', function (req, res, next) {", - " if (Math.random() > opbeansRedirectProbability) {", - ], - }, - "exclude_from_grouping": false, - "filename": "server.js", - "function": "", - "library_frame": false, - "line": Object { - "context": " return next()", - "number": 88, - }, - }, - ], - }, - Object { - "excludeFromGrouping": false, - "isLibraryFrame": true, - "stackframes": Array [ - Object { - "abs_path": "/app/node_modules/express/lib/router/layer.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/layer.js", - "function": "handle", - "library_frame": true, - "line": Object { - "number": 95, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "trim_prefix", - "library_frame": true, - "line": Object { - "number": 317, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "", - "library_frame": true, - "line": Object { - "number": 284, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "process_params", - "library_frame": true, - "line": Object { - "number": 335, - }, - }, - ], - }, - Object { - "excludeFromGrouping": true, - "isLibraryFrame": true, - "stackframes": Array [ - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": true, - "filename": "node_modules/express/lib/router/index.js", - "function": "next", - "library_frame": true, - "line": Object { - "number": 275, - }, - }, - ], - }, - Object { - "excludeFromGrouping": false, - "isLibraryFrame": true, - "stackframes": Array [ - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "", - "library_frame": true, - "line": Object { - "number": 635, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "next", - "library_frame": true, - "line": Object { - "number": 260, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "handle", - "library_frame": true, - "line": Object { - "number": 174, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "router", - "library_frame": true, - "line": Object { - "number": 47, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/layer.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/layer.js", - "function": "handle", - "library_frame": true, - "line": Object { - "number": 95, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "trim_prefix", - "library_frame": true, - "line": Object { - "number": 317, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "", - "library_frame": true, - "line": Object { - "number": 284, - }, - }, - Object { - "abs_path": "/app/node_modules/express/lib/router/index.js", - "exclude_from_grouping": false, - "filename": "node_modules/express/lib/router/index.js", - "function": "process_params", - "library_frame": true, - "line": Object { - "number": 335, - }, - }, - ], - }, -] -`; diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/index.test.ts b/x-pack/plugins/apm/public/components/shared/Stacktrace/stacktrace.test.ts similarity index 90% rename from x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/index.test.ts rename to x-pack/plugins/apm/public/components/shared/Stacktrace/stacktrace.test.ts index cd8bf90301836..9c76dad16d679 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/__test__/index.test.ts +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/stacktrace.test.ts @@ -4,15 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Stackframe } from '../../../../../typings/es_schemas/raw/fields/stackframe'; -import { getGroupedStackframes } from '../index'; -import stacktracesMock from './stacktraces.json'; +import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; +import { getGroupedStackframes } from './'; +import stacktracesMock from './__fixtures__/stacktraces.json'; -describe('Stacktrace/index', () => { +describe('Stacktrace', () => { describe('getGroupedStackframes', () => { - it('should collapse the library frames into a set of grouped stackframes', () => { - const result = getGroupedStackframes(stacktracesMock as Stackframe[]); - expect(result).toMatchSnapshot(); + it('collapses the library frames into a set of grouped stackframes', () => { + expect( + getGroupedStackframes(stacktracesMock as Stackframe[]).every( + (group) => group.stackframes.length > 0 + ) + ).toEqual(true); }); it('should group stackframes when `library_frame` is identical and `exclude_from_grouping` is false', () => { From f530dc8574528eb246b9e6a4224fe3473a616bc7 Mon Sep 17 00:00:00 2001 From: Constance Date: Tue, 6 Oct 2020 08:38:48 -0700 Subject: [PATCH 04/33] [proposal] Groups breadcrumb updates (#79551) + move chrome related settings to router pages + pull out i18n constants Co-authored-by: Scotty Bollinger --- .../components/layout/nav.tsx | 5 ++--- .../groups/components/group_overview.tsx | 6 ------ .../views/groups/components/group_sub_nav.tsx | 12 +++-------- .../views/groups/constants.ts | 20 ++++++++++++++++++ .../views/groups/group_router.tsx | 21 ++++++++++++++++--- .../workplace_search/views/groups/groups.tsx | 4 ---- .../views/groups/groups_router.tsx | 10 ++++++++- 7 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/constants.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx index 7070659a951ef..a454e3146f4d9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx @@ -13,6 +13,7 @@ import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { SideNav, SideNavLink } from '../../../shared/layout'; import { GroupSubNav } from '../../views/groups/components/group_sub_nav'; +import { NAV } from '../../views/groups/constants'; import { ORG_SOURCES_PATH, @@ -38,9 +39,7 @@ export const WorkplaceSearchNav: React.FC = () => { })} }> - {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups', { - defaultMessage: 'Groups', - })} + {NAV.GROUPS} {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index 983dede7bd4e8..1c7a01a1d9a46 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -22,9 +22,6 @@ import { EuiHorizontalRule, } from '@elastic/eui'; -import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; -import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../../shared/telemetry'; - import { AppLogic } from '../../../app_logic'; import { TruncatedContent } from '../../../../shared/truncate'; import { ContentSection } from '../../../components/shared/content_section'; @@ -260,9 +257,6 @@ export const GroupOverview: React.FC = () => { return ( <> - - - {sourcesSection} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx index db8d390acce51..a41cf6191eb64 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_sub_nav.tsx @@ -6,9 +6,9 @@ import React from 'react'; import { useValues } from 'kea'; -import { i18n } from '@kbn/i18n'; import { GroupLogic } from '../group_logic'; +import { NAV } from '../constants'; import { SideNavLink } from '../../../../shared/layout'; @@ -23,15 +23,9 @@ export const GroupSubNav: React.FC = () => { return ( <> - - {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups.groupOverview', { - defaultMessage: 'Overview', - })} - + {NAV.OVERVIEW} - {i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization', { - defaultMessage: 'Source Prioritization', - })} + {NAV.SOURCE_PRIORITIZATION} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/constants.ts new file mode 100644 index 0000000000000..7c3d160017138 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/constants.ts @@ -0,0 +1,20 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const NAV = { + GROUPS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups', { + defaultMessage: 'Groups', + }), + OVERVIEW: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups.groupOverview', { + defaultMessage: 'Overview', + }), + SOURCE_PRIORITIZATION: i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization', + { defaultMessage: 'Source Prioritization' } + ), +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx index e5779a96b4687..0a637497a5b05 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx @@ -10,7 +10,11 @@ import { useActions, useValues } from 'kea'; import { Route, Switch, useParams } from 'react-router-dom'; import { FlashMessages, FlashMessagesLogic } from '../../../shared/flash_messages'; +import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; + import { GROUP_SOURCE_PRIORITIZATION_PATH, GROUP_PATH } from '../../routes'; +import { NAV } from './constants'; import { GroupLogic } from './group_logic'; import { ManageUsersModal } from './components/manage_users_modal'; @@ -24,7 +28,11 @@ export const GroupRouter: React.FC = () => { const { messages } = useValues(FlashMessagesLogic); const { initializeGroup, resetGroup } = useActions(GroupLogic); - const { sharedSourcesModalModalVisible, manageUsersModalVisible } = useValues(GroupLogic); + const { + sharedSourcesModalModalVisible, + manageUsersModalVisible, + group: { name }, + } = useValues(GroupLogic); const hasMessages = messages.length > 0; @@ -37,8 +45,15 @@ export const GroupRouter: React.FC = () => { <> {hasMessages && } - - + + + + + + + + + {sharedSourcesModalModalVisible && } {manageUsersModalVisible && } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index ab5c6884d64df..34a66282a312d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -11,8 +11,6 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; import { EuiButton as EuiLinkButton } from '../../../shared/react_router_helpers'; -import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; -import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import { AppLogic } from '../../app_logic'; @@ -123,8 +121,6 @@ export const Groups: React.FC = () => { return ( <> - - { return ( - + + + + + ); From 6b80eb20ab58db4f68812dae2b7cfb817d3c4d97 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Tue, 6 Oct 2020 11:40:35 -0400 Subject: [PATCH 05/33] [Actions] write action executor errors to the Kibana log (#79474) resolves https://github.com/elastic/kibana/issues/72058 Finally, we're logging the action executor error responses to the Kibana log. We have not been doing this previously, out of an abundance of caution. The message written may contain data returned from a 3rd party service call, and we weren't sure this would be "safe" to log, in a PII sense. After several minor releases, and seeing these service messages in the event log for almost all the built-in action types, we've decided they are "safe" and it's time to add them, as they provide some **very** helpful diagnostic feedback to customers. --- .../server/lib/action_executor.test.ts | 87 ++++++++++++++++++- .../actions/server/lib/action_executor.ts | 7 +- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 65fd0646c639e..692d14e859b34 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -32,9 +32,10 @@ const executeParams = { }; const spacesMock = spacesServiceMock.createSetupContract(); +const loggerMock = loggingSystemMock.create().get(); const getActionsClientWithRequest = jest.fn(); actionExecutor.initialize({ - logger: loggingSystemMock.create().get(), + logger: loggerMock, spaces: spacesMock, getServices: () => services, getActionsClientWithRequest, @@ -321,3 +322,87 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o `"Unable to execute action due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"` ); }); + +test('does not log warning when alert executor succeeds', async () => { + const executorMock = setupActionExecutorMock(); + executorMock.mockResolvedValue({ + actionId: '1', + status: 'ok', + }); + await actionExecutor.execute(executeParams); + expect(loggerMock.warn).not.toBeCalled(); +}); + +test('logs a warning when alert executor has an error', async () => { + const executorMock = setupActionExecutorMock(); + executorMock.mockResolvedValue({ + actionId: '1', + status: 'error', + message: 'message for action execution error', + serviceMessage: 'serviceMessage for action execution error', + }); + await actionExecutor.execute(executeParams); + expect(loggerMock.warn).toBeCalledWith( + 'action execution failure: test:1: action-1: message for action execution error: serviceMessage for action execution error' + ); +}); + +test('logs a warning when alert executor throws an error', async () => { + const executorMock = setupActionExecutorMock(); + executorMock.mockRejectedValue(new Error('this action execution is intended to fail')); + await actionExecutor.execute(executeParams); + expect(loggerMock.warn).toBeCalledWith( + 'action execution failure: test:1: action-1: an error occurred while running the action executor: this action execution is intended to fail' + ); +}); + +test('logs a warning when alert executor returns invalid status', async () => { + const executorMock = setupActionExecutorMock(); + // object typed as any as it has an invalid status value, but we want to test that + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const alertExecutionStatus: any = { + actionId: '1', + status: 'invalid-status', + message: 'message for action execution error', + serviceMessage: 'serviceMessage for action execution error', + }; + executorMock.mockResolvedValue(alertExecutionStatus); + await actionExecutor.execute(executeParams); + expect(loggerMock.warn).toBeCalledWith( + 'action execution failure: test:1: action-1: returned unexpected result "invalid-status"' + ); +}); + +function setupActionExecutorMock() { + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + executor: jest.fn(), + }; + const actionSavedObject = { + id: '1', + type: 'action', + name: 'action-1', + attributes: { + actionTypeId: 'test', + config: { + bar: true, + }, + secrets: { + baz: true, + }, + }, + references: [], + }; + const actionResult = { + id: actionSavedObject.id, + name: actionSavedObject.name, + ...pick(actionSavedObject.attributes, 'actionTypeId', 'config'), + isPreconfigured: false, + }; + actionsClient.get.mockResolvedValueOnce(actionResult); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + actionTypeRegistry.get.mockReturnValueOnce(actionType); + return actionType.executor; +} diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 73434d5c1eaa2..0d4d6de3be1f9 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -79,6 +79,7 @@ export class ActionExecutor { } const { + logger, spaces, getServices, encryptedSavedObjectsClient, @@ -171,11 +172,15 @@ export class ActionExecutor { event.message = `action execution failure: ${actionLabel}`; event.error = event.error || {}; event.error.message = actionErrorToMessage(result); + logger.warn(`action execution failure: ${actionLabel}: ${event.error.message}`); } else { event.event.outcome = 'failure'; - event.message = `action execution returned unexpected result: ${actionLabel}`; + event.message = `action execution returned unexpected result: ${actionLabel}: "${result.status}"`; event.error = event.error || {}; event.error.message = 'action execution returned unexpected result'; + logger.warn( + `action execution failure: ${actionLabel}: returned unexpected result "${result.status}"` + ); } eventLogger.logEvent(event); From ed825398c6b3128b23b3e0639848a2f26532a9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 6 Oct 2020 16:41:40 +0100 Subject: [PATCH 06/33] [Telemetry Tools] Merge array of constraints (#79654) --- .../kbn-telemetry-tools/src/tools/serializer.test.ts | 11 +++++++++++ packages/kbn-telemetry-tools/src/tools/serializer.ts | 6 +++++- src/fixtures/telemetry_collectors/constants.ts | 5 +++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.test.ts b/packages/kbn-telemetry-tools/src/tools/serializer.test.ts index 85fb84c714e20..48c4d73c291fe 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.test.ts @@ -148,6 +148,17 @@ describe('getDescriptor', () => { }); }); + it('serializes RecordWithKnownAllProps', () => { + const usageInterface = usageInterfaces.get('RecordWithKnownAllProps')!; + const descriptor = getDescriptor(usageInterface, tsProgram); + expect(descriptor).toEqual({ + prop1: { kind: ts.SyntaxKind.NumberKeyword, type: 'NumberKeyword' }, + prop2: { kind: ts.SyntaxKind.NumberKeyword, type: 'NumberKeyword' }, + prop3: { kind: ts.SyntaxKind.NumberKeyword, type: 'NumberKeyword' }, + prop4: { kind: ts.SyntaxKind.NumberKeyword, type: 'NumberKeyword' }, + }); + }); + it('serializes IndexedAccessType', () => { const usageInterface = usageInterfaces.get('IndexedAccessType')!; const descriptor = getDescriptor(usageInterface, tsProgram); diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.ts b/packages/kbn-telemetry-tools/src/tools/serializer.ts index ea5f184008026..d380e17759e21 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.ts @@ -88,7 +88,11 @@ export function getConstraints(node: ts.Node, program: ts.Program): any { if (ts.isUnionTypeNode(node)) { const types = node.types.filter(discardNullOrUndefined); - return types.map((typeNode) => getConstraints(typeNode, program)); + return types.reduce((acc, typeNode) => { + const constraints = getConstraints(typeNode, program); + const contraintsArray = Array.isArray(constraints) ? constraints : [constraints]; + return [...acc, ...contraintsArray]; + }, []); } if (ts.isLiteralTypeNode(node) && ts.isLiteralExpression(node.literal)) { diff --git a/src/fixtures/telemetry_collectors/constants.ts b/src/fixtures/telemetry_collectors/constants.ts index 8896c294676c4..5b66161d07101 100644 --- a/src/fixtures/telemetry_collectors/constants.ts +++ b/src/fixtures/telemetry_collectors/constants.ts @@ -58,6 +58,10 @@ export type TypeAliasWithRecord = Usage & Record; export type MappedTypeProps = 'prop1' | 'prop2'; +export type MappedTypeExtraProps = 'prop3' | 'prop4'; + +export type MappedTypeAllProps = MappedTypeProps | MappedTypeExtraProps; + export interface MappedTypes { mappedTypeWithExternallyDefinedProps: { [key in MappedTypeProps]: number; @@ -68,5 +72,6 @@ export interface MappedTypes { } export type RecordWithKnownProps = Record; +export type RecordWithKnownAllProps = Record; export type IndexedAccessType = Pick; From 55af3285dc91c39f586e03528647f2b6d92efb84 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 6 Oct 2020 11:43:11 -0400 Subject: [PATCH 07/33] Remove backdrop blur: visual regression (#79668) --- x-pack/plugins/security_solution/public/resolver/view/styles.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx index 2577436f63096..e734d88fbd4ff 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx @@ -28,7 +28,6 @@ export const NodeSubMenu = styled(NodeSubMenuComponents)` contain: content; width: 12em; z-index: 2; - backdrop-filter: blur(5px); } &.options .item { From 2de2c70f75687ad7ec76e374ed1ff66381718dbb Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 6 Oct 2020 11:50:44 -0400 Subject: [PATCH 08/33] [Lens] Break long titles into multiple lines (#79580) --- .../editor_frame/config_panel/layer_panel.scss | 1 + .../dimension_panel/dimension_panel.tsx | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss index c77db2e65ce2d..54c922957d29b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss @@ -50,6 +50,7 @@ width: 100%; padding: $euiSizeS; min-height: $euiSizeXXL - 2; + word-break: break-word; &:focus { background-color: transparent !important; // sass-lint:disable-line no-important diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx index 12b8d91c35ade..ff6840bc16a59 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx @@ -229,6 +229,13 @@ export function onDrop(props: DatasourceDimensionDropHandlerProps - {uniqueLabel} + {formattedLabel} ); }; From 24e4536b99b9404b06eb3591528ed869d1366210 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 6 Oct 2020 18:52:24 +0300 Subject: [PATCH 09/33] hint that we export type only (#79635) --- src/plugins/dashboard/public/application/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/dashboard/public/application/index.ts b/src/plugins/dashboard/public/application/index.ts index fcd92da33aa5f..2558c49648b10 100644 --- a/src/plugins/dashboard/public/application/index.ts +++ b/src/plugins/dashboard/public/application/index.ts @@ -19,4 +19,4 @@ export * from './embeddable'; export * from './actions'; -export { RenderDeps } from './application'; +export type { RenderDeps } from './application'; From c355dfebab3cc46a218d3e64f194910deb1e21c9 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 6 Oct 2020 11:58:09 -0400 Subject: [PATCH 10/33] [Ingest Manager] Move config from xpack.ingestManager to xpack.fleet (#79406) --- docs/developer/plugin-list.asciidoc | 2 +- docs/settings/ingest-manager-settings.asciidoc | 14 +++++++------- x-pack/plugins/ingest_manager/README.md | 14 +++++++------- .../plugins/ingest_manager/common/types/index.ts | 2 +- x-pack/plugins/ingest_manager/kibana.json | 2 +- .../ingest_manager/hooks/use_fleet_status.tsx | 2 +- .../public/applications/ingest_manager/index.tsx | 5 +++-- .../ingest_manager/layouts/default.tsx | 4 ++-- .../components/agent_policy_delete_provider.tsx | 2 +- .../components/package_policy_delete_provider.tsx | 2 +- .../create_package_policy_page/index.tsx | 2 +- .../details_page/components/settings/index.tsx | 2 +- .../edit_package_policy_page/index.tsx | 2 +- .../sections/agent_policy/list_page/index.tsx | 2 +- .../ingest_manager/sections/fleet/index.tsx | 4 ++-- .../sections/fleet/setup_page/index.tsx | 2 +- .../server/collectors/config_collectors.ts | 2 +- x-pack/plugins/ingest_manager/server/index.ts | 12 ++++++++---- x-pack/plugins/ingest_manager/server/plugin.ts | 2 +- .../ingest_manager/server/routes/agent/index.ts | 2 +- .../server/routes/limited_concurrency.test.ts | 6 +++--- .../server/routes/limited_concurrency.ts | 2 +- .../ingest_manager/server/routes/setup/handlers.ts | 2 +- .../ingest_manager/server/routes/setup/index.ts | 2 +- .../services/agents/checkin/state_new_actions.ts | 7 +++---- .../ingest_manager/server/services/output.ts | 4 ++-- .../ingest_manager/server/services/settings.ts | 2 +- x-pack/test/api_integration/config.ts | 4 ++-- .../test/ingest_manager_api_integration/config.ts | 4 +--- x-pack/test/saved_objects_field_count/config.ts | 2 +- x-pack/test/security_solution_endpoint/config.ts | 2 +- .../security_solution_endpoint_api_int/registry.ts | 4 ++-- 32 files changed, 62 insertions(+), 60 deletions(-) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index d6d938bfc97de..1d4b80407cfc0 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -402,7 +402,7 @@ the infrastructure monitoring use-case within Kibana. |{kib-repo}blob/{branch}/x-pack/plugins/ingest_manager/README.md[ingestManager] -|Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.ingestManager.fleet.tlsCheckDisabled=false) +|Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) |{kib-repo}blob/{branch}/x-pack/plugins/ingest_pipelines/README.md[ingestPipelines] diff --git a/docs/settings/ingest-manager-settings.asciidoc b/docs/settings/ingest-manager-settings.asciidoc index 30e11f726c26b..9fa83fc242b4a 100644 --- a/docs/settings/ingest-manager-settings.asciidoc +++ b/docs/settings/ingest-manager-settings.asciidoc @@ -7,7 +7,7 @@ experimental[] -You can configure `xpack.ingestManager` settings in your `kibana.yml`. +You can configure `xpack.fleet` settings in your `kibana.yml`. By default, {ingest-manager} is enabled. To use {fleet}, you also need to configure {kib} and {es} hosts. See the {ingest-guide}/index.html[Ingest Management] docs for more information. @@ -17,9 +17,9 @@ See the {ingest-guide}/index.html[Ingest Management] docs for more information. [cols="2*<"] |=== -| `xpack.ingestManager.enabled` {ess-icon} +| `xpack.fleet.enabled` {ess-icon} | Set to `true` (default) to enable {ingest-manager}. -| `xpack.ingestManager.fleet.enabled` {ess-icon} +| `xpack.fleet.agents.enabled` {ess-icon} | Set to `true` (default) to enable {fleet}. |=== @@ -29,7 +29,7 @@ See the {ingest-guide}/index.html[Ingest Management] docs for more information. [cols="2*<"] |=== -| `xpack.ingestManager.registryUrl` +| `xpack.fleet.registryUrl` | The address to use to reach {package-manager} registry. |=== @@ -37,11 +37,11 @@ See the {ingest-guide}/index.html[Ingest Management] docs for more information. [cols="2*<"] |=== -| `xpack.ingestManager.fleet.kibana.host` +| `xpack.fleet.agents.kibana.host` | The hostname used by {agent} for accessing {kib}. -| `xpack.ingestManager.fleet.elasticsearch.host` +| `xpack.fleet.agents.elasticsearch.host` | The hostname used by {agent} for accessing {es}. -| `xpack.ingestManager.fleet.tlsCheckDisabled` +| `xpack.fleet.agents.tlsCheckDisabled` | Set to `true` to allow {fleet} to run on a {kib} instance without TLS enabled. |=== diff --git a/x-pack/plugins/ingest_manager/README.md b/x-pack/plugins/ingest_manager/README.md index 65df682c23659..ade5985782c89 100644 --- a/x-pack/plugins/ingest_manager/README.md +++ b/x-pack/plugins/ingest_manager/README.md @@ -3,23 +3,23 @@ ## Plugin - The plugin is enabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/common/types/index.ts#L9-L27) -- Adding `xpack.ingestManager.enabled=false` will disable the plugin including the EPM and Fleet features. It will also remove the `PACKAGE_POLICY_API_ROUTES` and `AGENT_POLICY_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) -- Adding `--xpack.ingestManager.fleet.enabled=false` will disable the Fleet API & UI +- Adding `xpack.fleet.enabled=false` will disable the plugin including the EPM and Fleet features. It will also remove the `PACKAGE_POLICY_API_ROUTES` and `AGENT_POLICY_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) +- Adding `--xpack.fleet.agents.enabled=false` will disable the Fleet API & UI - [code for adding the routes](https://github.com/elastic/kibana/blob/1f27d349533b1c2865c10c45b2cf705d7416fb36/x-pack/plugins/ingest_manager/server/plugin.ts#L115-L133) - [Integration tests](server/integration_tests/router.test.ts) - Both EPM and Fleet require `ingestManager` be enabled. They are not standalone features. -- For Enterprise license, a custom package registry URL can be used by setting `xpack.ingestManager.registryUrl=http://localhost:8080` +- For Enterprise license, a custom package registry URL can be used by setting `xpack.fleet.registryUrl=http://localhost:8080` - This property is currently only for internal Elastic development and is unsupported ## Fleet Requirements -Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag `--xpack.ingestManager.fleet.tlsCheckDisabled=false`) +Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag `--xpack.fleet.agents.tlsCheckDisabled=false`) Also you need to configure the hosts your agent is going to use to comunication with Elasticsearch and Kibana (Not needed if you use Elastic cloud). You can use the following flags: ``` ---xpack.ingestManager.fleet.elasticsearch.host=http://localhost:9200 ---xpack.ingestManager.fleet.kibana.host=http://localhost:5601 +--xpack.fleet.agents.elasticsearch.host=http://localhost:9200 +--xpack.fleet.agents.kibana.host=http://localhost:5601 ``` ## Development @@ -40,7 +40,7 @@ One common development workflow is: ``` - Start Kibana in another shell ``` - yarn start --xpack.ingestManager.enabled=true --no-base-path + yarn start --xpack.fleet.enabled=true --no-base-path ``` This plugin follows the `common`, `server`, `public` structure from the [Architecture Style Guide diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts index d62f4fbb023dc..49be48ef542f5 100644 --- a/x-pack/plugins/ingest_manager/common/types/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/index.ts @@ -9,7 +9,7 @@ export * from './rest_spec'; export interface IngestManagerConfigType { enabled: boolean; registryUrl?: string; - fleet: { + agents: { enabled: boolean; tlsCheckDisabled: boolean; pollingRequestTimeout: number; diff --git a/x-pack/plugins/ingest_manager/kibana.json b/x-pack/plugins/ingest_manager/kibana.json index 3a47da9fee01f..5ea6d21e1282e 100644 --- a/x-pack/plugins/ingest_manager/kibana.json +++ b/x-pack/plugins/ingest_manager/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": true, "ui": true, - "configPath": ["xpack", "ingestManager"], + "configPath": ["xpack", "fleet"], "requiredPlugins": ["licensing", "data", "encryptedSavedObjects"], "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home"], "extraPublicDirs": ["common"], diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx index 8290dfb8691cf..18bcb4539c740 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_fleet_status.tsx @@ -25,7 +25,7 @@ const FleetStatusContext = React.createContext(undefine export const FleetStatusProvider: React.FC = ({ children }) => { const config = useConfig(); const [state, setState] = useState({ - enabled: config.fleet.enabled, + enabled: config.agents.enabled, isLoading: false, isReady: false, }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx index 1262644382f37..563c4b4750c37 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx @@ -68,7 +68,8 @@ const ErrorLayout = ({ children }: { children: JSX.Element }) => ( const IngestManagerRoutes = memo<{ history: AppMountParameters['history']; basepath: string }>( ({ history, ...rest }) => { useBreadcrumbs('base'); - const { fleet } = useConfig(); + const { agents } = useConfig(); + const { notifications } = useCore(); const [isPermissionsLoading, setIsPermissionsLoading] = useState(false); @@ -209,7 +210,7 @@ const IngestManagerRoutes = memo<{ history: AppMountParameters['history']; basep - + diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx index 5de47ee4f410b..d71b90d16725d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx @@ -41,7 +41,7 @@ export const DefaultLayout: React.FunctionComponent = ({ children, }) => { const { getHref } = useLink(); - const { fleet } = useConfig(); + const { agents } = useConfig(); const [isSettingsFlyoutOpen, setIsSettingsFlyoutOpen] = React.useState(false); return ( @@ -80,7 +80,7 @@ export const DefaultLayout: React.FunctionComponent = ({ void; export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ children }) => { const { notifications } = useCore(); const { - fleet: { enabled: isFleetEnabled }, + agents: { enabled: isFleetEnabled }, } = useConfig(); const [agentPolicy, setAgentPolicy] = useState(); const [isModalOpen, setIsModalOpen] = useState(false); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx index 3a6852de873f8..9242c6eb86225 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx @@ -30,7 +30,7 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ }) => { const { notifications } = useCore(); const { - fleet: { enabled: isFleetEnabled }, + agents: { enabled: isFleetEnabled }, } = useConfig(); const [packagePolicies, setPackagePolicies] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx index 39f35fed56ef5..70e11d46aa7f6 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx @@ -59,7 +59,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { application: { navigateToApp }, } = useCore(); const { - fleet: { enabled: isFleetEnabled }, + agents: { enabled: isFleetEnabled }, } = useConfig(); const { params: { policyId, pkgkey }, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx index 58941c25c0fae..bc0457ffa0d43 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx @@ -35,7 +35,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( ({ agentPolicy: originalAgentPolicy }) => { const { notifications } = useCore(); const { - fleet: { enabled: isFleetEnabled }, + agents: { enabled: isFleetEnabled }, } = useConfig(); const history = useHistory(); const { getPath } = useLink(); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx index 816cf66d63413..af4ffab12a791 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/edit_package_policy_page/index.tsx @@ -45,7 +45,7 @@ import { StepDefinePackagePolicy } from '../create_package_policy_page/step_defi export const EditPackagePolicyPage: React.FunctionComponent = () => { const { notifications } = useCore(); const { - fleet: { enabled: isFleetEnabled }, + agents: { enabled: isFleetEnabled }, } = useConfig(); const { params: { policyId, packagePolicyId }, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx index fb963dc67ae1c..aa4b42986a4f5 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/index.tsx @@ -74,7 +74,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { const { getHref, getPath } = useLink(); const hasWriteCapabilites = useCapabilities().write; const { - fleet: { enabled: isFleetEnabled }, + agents: { enabled: isFleetEnabled }, } = useConfig(); // Table and search states diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx index 9bb77ca44b848..c0b765c4c3496 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/index.tsx @@ -18,11 +18,11 @@ import { ListLayout } from './components/list_layout'; export const FleetApp: React.FunctionComponent = () => { useBreadcrumbs('fleet'); const core = useCore(); - const { fleet } = useConfig(); + const { agents } = useConfig(); const fleetStatus = useFleetStatus(); - if (!fleet.enabled) return null; + if (!agents.enabled) return null; if (fleetStatus.isLoading) { return ; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx index 7f0a23caa5fa2..bac551818ec87 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx @@ -222,7 +222,7 @@ xpack.security.authc.api_key.enabled: true`} /> ), - tlsFlag: xpack.ingestManager.fleet.tlsCheckDisabled, + tlsFlag: xpack.fleet.agents.tlsCheckDisabled, true: true, }} /> diff --git a/x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts b/x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts index 514984f7f859d..c201d1d4dfa25 100644 --- a/x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts +++ b/x-pack/plugins/ingest_manager/server/collectors/config_collectors.ts @@ -7,5 +7,5 @@ import { IngestManagerConfigType } from '..'; export const getIsFleetEnabled = (config: IngestManagerConfigType) => { - return config.fleet.enabled; + return config.agents.enabled; }; diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts index f87ebb3d2c404..70685cf818b08 100644 --- a/x-pack/plugins/ingest_manager/server/index.ts +++ b/x-pack/plugins/ingest_manager/server/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema, TypeOf } from '@kbn/config-schema'; -import { PluginInitializerContext } from 'src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; import { IngestManagerPlugin } from './plugin'; import { AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS, @@ -19,15 +19,19 @@ export { ExternalCallback, } from './plugin'; -export const config = { +export const config: PluginConfigDescriptor = { exposeToBrowser: { epm: true, - fleet: true, + agents: true, }, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('xpack.ingestManager.fleet', 'xpack.fleet.agents'), + renameFromRoot('xpack.ingestManager', 'xpack.fleet'), + ], schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), registryUrl: schema.maybe(schema.uri()), - fleet: schema.object({ + agents: schema.object({ enabled: schema.boolean({ defaultValue: true }), tlsCheckDisabled: schema.boolean({ defaultValue: false }), pollingRequestTimeout: schema.number({ diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 6237b6d9ba357..e3757b46ed715 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -231,7 +231,7 @@ export class IngestManagerPlugin registerEPMRoutes(router); // Conditional config routes - if (config.fleet.enabled) { + if (config.agents.enabled) { const isESOUsingEphemeralEncryptionKey = deps.encryptedSavedObjects.usingEphemeralEncryptionKey; if (isESOUsingEphemeralEncryptionKey) { diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts index f7b0c536d1a0d..2f97a6bcde42c 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -119,7 +119,7 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) getAgentsHandler ); - const pollingRequestTimeout = config.fleet.pollingRequestTimeout; + const pollingRequestTimeout = config.agents.pollingRequestTimeout; // Agent checkin router.post( { diff --git a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts b/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts index e5b5a83743287..cc358c32528c9 100644 --- a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.test.ts @@ -15,7 +15,7 @@ import { IngestManagerConfigType } from '../index'; describe('registerLimitedConcurrencyRoutes', () => { test(`doesn't call registerOnPreAuth if maxConcurrentConnections is 0`, async () => { const mockSetup = coreMock.createSetup(); - const mockConfig = { fleet: { maxConcurrentConnections: 0 } } as IngestManagerConfigType; + const mockConfig = { agents: { maxConcurrentConnections: 0 } } as IngestManagerConfigType; registerLimitedConcurrencyRoutes(mockSetup, mockConfig); expect(mockSetup.http.registerOnPreAuth).not.toHaveBeenCalled(); @@ -23,7 +23,7 @@ describe('registerLimitedConcurrencyRoutes', () => { test(`calls registerOnPreAuth once if maxConcurrentConnections is 1`, async () => { const mockSetup = coreMock.createSetup(); - const mockConfig = { fleet: { maxConcurrentConnections: 1 } } as IngestManagerConfigType; + const mockConfig = { agents: { maxConcurrentConnections: 1 } } as IngestManagerConfigType; registerLimitedConcurrencyRoutes(mockSetup, mockConfig); expect(mockSetup.http.registerOnPreAuth).toHaveBeenCalledTimes(1); @@ -31,7 +31,7 @@ describe('registerLimitedConcurrencyRoutes', () => { test(`calls registerOnPreAuth once if maxConcurrentConnections is 1000`, async () => { const mockSetup = coreMock.createSetup(); - const mockConfig = { fleet: { maxConcurrentConnections: 1000 } } as IngestManagerConfigType; + const mockConfig = { agents: { maxConcurrentConnections: 1000 } } as IngestManagerConfigType; registerLimitedConcurrencyRoutes(mockSetup, mockConfig); expect(mockSetup.http.registerOnPreAuth).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts b/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts index 7ba8e151b726c..609428f5477f1 100644 --- a/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts +++ b/x-pack/plugins/ingest_manager/server/routes/limited_concurrency.ts @@ -75,7 +75,7 @@ export function createLimitedPreAuthHandler({ } export function registerLimitedConcurrencyRoutes(core: CoreSetup, config: IngestManagerConfigType) { - const max = config.fleet.maxConcurrentConnections; + const max = config.agents.maxConcurrentConnections; if (!max) return; core.http.registerOnPreAuth( diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts index ee7dab6ef1a8b..0bd7b4e875062 100644 --- a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts @@ -19,7 +19,7 @@ export const getFleetStatusHandler: RequestHandler = async (context, request, re const isTLSEnabled = appContextService.getHttpSetup().getServerInfo().protocol === 'https'; const isProductionMode = appContextService.getIsProductionMode(); const isCloud = appContextService.getCloud()?.isCloudEnabled ?? false; - const isTLSCheckDisabled = appContextService.getConfig()?.fleet?.tlsCheckDisabled ?? false; + const isTLSCheckDisabled = appContextService.getConfig()?.agents?.tlsCheckDisabled ?? false; const isUsingEphemeralEncryptionKey = appContextService.getEncryptedSavedObjectsSetup() .usingEphemeralEncryptionKey; diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts b/x-pack/plugins/ingest_manager/server/routes/setup/index.ts index b71e9983f494f..6672a7e8933a8 100644 --- a/x-pack/plugins/ingest_manager/server/routes/setup/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/setup/index.ts @@ -53,7 +53,7 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) // Ingest manager setup registerIngestManagerSetupRoute(router); - if (!config.fleet.enabled) { + if (!config.agents.enabled) { return; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts index 4862852e0cc71..1871cd2cb04f6 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts @@ -171,13 +171,12 @@ export async function createAgentActionFromPolicyAction( } function getPollingTimeoutMs() { - const pollingTimeoutMs = appContextService.getConfig()?.fleet.pollingRequestTimeout ?? 0; + const pollingTimeoutMs = appContextService.getConfig()?.agents.pollingRequestTimeout ?? 0; // If polling timeout is too short do not use margin if (pollingTimeoutMs <= AGENT_POLLING_REQUEST_TIMEOUT_MARGIN_MS) { return pollingTimeoutMs; } - // Set a timeout 20s before the real timeout to have a chance to respond an empty response before socket timeout return Math.max( pollingTimeoutMs - AGENT_POLLING_REQUEST_TIMEOUT_MARGIN_MS, @@ -193,10 +192,10 @@ export function agentCheckinStateNewActionsFactory() { const pollingTimeoutMs = getPollingTimeoutMs(); const rateLimiterIntervalMs = - appContextService.getConfig()?.fleet.agentPolicyRolloutRateLimitIntervalMs ?? + appContextService.getConfig()?.agents.agentPolicyRolloutRateLimitIntervalMs ?? AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS; const rateLimiterRequestPerInterval = - appContextService.getConfig()?.fleet.agentPolicyRolloutRateLimitRequestPerInterval ?? + appContextService.getConfig()?.agents.agentPolicyRolloutRateLimitRequestPerInterval ?? AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL; const rateLimiterMaxDelay = pollingTimeoutMs; diff --git a/x-pack/plugins/ingest_manager/server/services/output.ts b/x-pack/plugins/ingest_manager/server/services/output.ts index 1e5632719fb72..f780bd95faedc 100644 --- a/x-pack/plugins/ingest_manager/server/services/output.ts +++ b/x-pack/plugins/ingest_manager/server/services/output.ts @@ -27,7 +27,7 @@ class OutputService { const cloud = appContextService.getCloud(); const cloudId = cloud?.isCloudEnabled && cloud.cloudId; const cloudUrl = cloudId && decodeCloudId(cloudId)?.elasticsearchUrl; - const flagsUrl = appContextService.getConfig()!.fleet.elasticsearch.host; + const flagsUrl = appContextService.getConfig()!.agents.elasticsearch.host; const defaultUrl = 'http://localhost:9200'; const defaultOutputUrl = cloudUrl || flagsUrl || defaultUrl; @@ -35,7 +35,7 @@ class OutputService { const newDefaultOutput = { ...DEFAULT_OUTPUT, hosts: [defaultOutputUrl], - ca_sha256: appContextService.getConfig()!.fleet.elasticsearch.ca_sha256, + ca_sha256: appContextService.getConfig()!.agents.elasticsearch.ca_sha256, } as NewOutput; return await this.create(soClient, newDefaultOutput); diff --git a/x-pack/plugins/ingest_manager/server/services/settings.ts b/x-pack/plugins/ingest_manager/server/services/settings.ts index 25223fbc08535..44aece1c83a18 100644 --- a/x-pack/plugins/ingest_manager/server/services/settings.ts +++ b/x-pack/plugins/ingest_manager/server/services/settings.ts @@ -73,7 +73,7 @@ export function createDefaultSettings(): BaseSettings { const cloud = appContextService.getCloud(); const cloudId = cloud?.isCloudEnabled && cloud.cloudId; const cloudUrl = cloudId && decodeCloudId(cloudId)?.kibanaUrl; - const flagsUrl = appContextService.getConfig()?.fleet?.kibana?.host; + const flagsUrl = appContextService.getConfig()?.agents?.kibana?.host; const defaultUrl = url.format({ protocol: serverInfo.protocol, hostname: serverInfo.hostname, diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts index 3b4654fc357c0..97fd968ce7992 100644 --- a/x-pack/test/api_integration/config.ts +++ b/x-pack/test/api_integration/config.ts @@ -27,8 +27,8 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi ...xPackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), '--xpack.security.session.idleTimeout=3600000', // 1 hour '--telemetry.optIn=true', - '--xpack.ingestManager.enabled=true', - '--xpack.ingestManager.fleet.pollingRequestTimeout=5000', // 5 seconds + '--xpack.fleet.enabled=true', + '--xpack.fleet.agents.pollingRequestTimeout=5000', // 5 seconds ], }, esTestCluster: { diff --git a/x-pack/test/ingest_manager_api_integration/config.ts b/x-pack/test/ingest_manager_api_integration/config.ts index 193ac0d5974e6..15b68b8c8208d 100644 --- a/x-pack/test/ingest_manager_api_integration/config.ts +++ b/x-pack/test/ingest_manager_api_integration/config.ts @@ -63,9 +63,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xPackAPITestsConfig.get('kbnTestServer'), serverArgs: [ ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), - ...(registryPort - ? [`--xpack.ingestManager.registryUrl=http://localhost:${registryPort}`] - : []), + ...(registryPort ? [`--xpack.fleet.registryUrl=http://localhost:${registryPort}`] : []), ], }, }; diff --git a/x-pack/test/saved_objects_field_count/config.ts b/x-pack/test/saved_objects_field_count/config.ts index a0e9f7a62e2c9..f3f80426c2aae 100644 --- a/x-pack/test/saved_objects_field_count/config.ts +++ b/x-pack/test/saved_objects_field_count/config.ts @@ -29,7 +29,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...kibanaCommonTestsConfig.get('kbnTestServer.serverArgs'), // Enable plugins that are disabled by default to include their metrics // TODO: Find a way to automatically enable all discovered plugins - '--xpack.ingestManager.enabled=true', + '--xpack.fleet.enabled=true', '--xpack.lists.enabled=true', '--xpack.securitySolution.enabled=true', ], diff --git a/x-pack/test/security_solution_endpoint/config.ts b/x-pack/test/security_solution_endpoint/config.ts index 840862ab00560..9ee9e061edf4c 100644 --- a/x-pack/test/security_solution_endpoint/config.ts +++ b/x-pack/test/security_solution_endpoint/config.ts @@ -39,7 +39,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xpackFunctionalConfig.get('kbnTestServer'), serverArgs: [ ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), - '--xpack.ingestManager.enabled=true', + '--xpack.fleet.enabled=true', // if you return an empty string here the kibana server will not start properly but an empty array works ...getRegistryUrlAsArray(), ], diff --git a/x-pack/test/security_solution_endpoint_api_int/registry.ts b/x-pack/test/security_solution_endpoint_api_int/registry.ts index 9a9d184b9c297..9c5a8e98e3e2b 100644 --- a/x-pack/test/security_solution_endpoint_api_int/registry.ts +++ b/x-pack/test/security_solution_endpoint_api_int/registry.ts @@ -60,9 +60,9 @@ export function createEndpointDockerConfig( export function getRegistryUrlFromTestEnv(): string | undefined { let registryUrl: string | undefined; if (dockerRegistryPort !== undefined) { - registryUrl = `--xpack.ingestManager.registryUrl=http://localhost:${dockerRegistryPort}`; + registryUrl = `--xpack.fleet.registryUrl=http://localhost:${dockerRegistryPort}`; } else if (packageRegistryOverride !== undefined) { - registryUrl = `--xpack.ingestManager.registryUrl=${packageRegistryOverride}`; + registryUrl = `--xpack.fleet.registryUrl=${packageRegistryOverride}`; } return registryUrl; } From d9ca4c562e0357a5b5289fc90a0d4f09b96a31f5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 6 Oct 2020 10:09:58 -0600 Subject: [PATCH 11/33] [ML] avoid full page reload for links following CSV import (#79539) * [ML] avoid full page reload for links following CSV import * cleanup * review feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../results_links/results_links.tsx | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx index 5cba714afe47b..374d434708f4a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_links/results_links.tsx @@ -45,16 +45,12 @@ export const ResultsLinks: FC = ({ const [globalState, setGlobalState] = useState(); const [discoverLink, setDiscoverLink] = useState(''); - const { - services: { - http: { basePath }, - }, - } = useMlKibana(); const mlUrlGenerator = useMlUrlGenerator(); const navigateToPath = useNavigateToPath(); const { services: { + application: { navigateToApp, navigateToUrl }, share: { urlGenerators: { getUrlGenerator }, }, @@ -72,9 +68,19 @@ export const ResultsLinks: FC = ({ if (globalState?.time) { state.timeRange = globalState.time; } + + let discoverUrlGenerator; + try { + discoverUrlGenerator = getUrlGenerator(DISCOVER_APP_URL_GENERATOR); + } catch (error) { + // ignore error thrown when url generator is not available + } + + if (!discoverUrlGenerator) { + return; + } + const discoverUrl = await discoverUrlGenerator.createUrl(state); if (!unmounted) { - const discoverUrlGenerator = getUrlGenerator(DISCOVER_APP_URL_GENERATOR); - const discoverUrl = await discoverUrlGenerator.createUrl(state); setDiscoverLink(discoverUrl); } }; @@ -142,9 +148,26 @@ export const ResultsLinks: FC = ({ } } + function openInDiscover(e: React.MouseEvent) { + e.preventDefault(); + navigateToUrl(discoverLink); + } + + function openIndexManagement(e: React.MouseEvent) { + e.preventDefault(); + navigateToApp('management', { path: '/data/index_management/indices' }); + } + + function openIndexPatternManagement(e: React.MouseEvent) { + e.preventDefault(); + navigateToApp('management', { + path: `/kibana/indexPatterns${createIndexPattern ? `/patterns/${indexPatternId}` : ''}`, + }); + } + return ( - {createIndexPattern && ( + {createIndexPattern && discoverLink && ( } @@ -155,7 +178,7 @@ export const ResultsLinks: FC = ({ /> } description="" - href={discoverLink} + onClick={openInDiscover} /> )} @@ -205,7 +228,7 @@ export const ResultsLinks: FC = ({ /> } description="" - href={`${basePath.get()}/app/management/data/index_management/indices`} + onClick={openIndexManagement} /> @@ -219,9 +242,7 @@ export const ResultsLinks: FC = ({ /> } description="" - href={`${basePath.get()}/app/management/kibana/indexPatterns${ - createIndexPattern ? `/patterns/${indexPatternId}` : '' - }`} + onClick={openIndexPatternManagement} /> From 63b76f2cd5d1b37c9ee3d23982ed22c4f621d4f4 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Tue, 6 Oct 2020 09:13:31 -0700 Subject: [PATCH 12/33] [Core UI] Kibana Overview Page (#75827) * Added kibana landing page Created kivana_overview plugin Removed test from home plugin Added CSS Fixed page header links Added news feed Fixed spacers between news items [Core UI] Kibana Overview Page Style Tweaks (#76712) Fixed link to index management Added solution cards to kibana landing page Added solution links Fixed ts errors Using publishReplay() to support multiple consumers in newsfeed plugin Added createNewsFeed$ to newsfeed plugin start Added tests Removed unnecessary export Hides overview link when other Kibana apps are not available Added icon to overview plugin Removed question mark from news feed title Updated plugin-list Fixed i18n errors Revert snapshot Updated getting started page copy Hide news feed when no news feed results Disables Kibana overview page when kibana apps are unavailable Updated snapshots Refactor to use KibanaContextProvider Fixed security tests Fixed newsfeed api test Moved overview_footer and overview_header to kibana-react plugin [Core UI] Kibana Overview Page Style Fixes (#78677) * Fixed a11y issues * Made newsfeed optional dep of kibana overview plugin * Removed duplicate license copy * Fixed management security test * Added toast to change default route button * Updated snapshots * Simplified toast notification * Fixed i18n error * Assigned kibana_overview plugin to Core UI in CODEOWNERS * Updated snapshots * Fix import * [Core UI] Kibana Overview Page Style Fixes, Part 3 (#78970) * fix overview cards not stretching height equally * change var name for better specificity * [Core UI] Kibana Overview Page Style Fixes, Part 4 (#79136) * Adds support for all newsfeed plugin config settings in createNewsFeed$ * Fixed type * Updated kibana overview page route * Fixed imports in page_footer and page_header * Update Kibana overview graphics (#79534) * Updated snapshots * Updated snapshots * Changes newsfeed endpoint to kibana analytics in kibana_overview plugin * Renamed components * Fixed overview page footer and header component class names * Removed extraneous files * Fixed import * Replaced SVGs with optimized SVGs * Fixed header and footer in home and kibana overview pages * Updated snapshots * Changed url_forwarding plugin appRoute * Fixed aria-labelledby value * Updated snapshots * Added base paths Co-authored-by: Michael Marcialis Co-authored-by: Ryan Keairns --- .github/CODEOWNERS | 1 + .i18nrc.json | 1 + docs/developer/plugin-list.asciidoc | 4 + .../management_app/advanced_settings.tsx | 13 +- src/plugins/dashboard/public/plugin.tsx | 5 + .../discover/public/register_feature.ts | 5 + .../__snapshots__/home.test.js.snap | 1432 ++++------------- .../__snapshots__/synopsis.test.js.snap | 1 + .../application/components/_add_data.scss | 5 +- .../public/application/components/_home.scss | 33 +- .../public/application/components/_index.scss | 1 - .../components/_solutions_section.scss | 26 +- .../__snapshots__/add_data.test.tsx.snap | 29 +- .../components/add_data/add_data.test.tsx | 25 +- .../components/add_data/add_data.tsx | 29 +- .../public/application/components/home.js | 139 +- .../application/components/home.test.js | 13 +- .../public/application/components/home_app.js | 5 +- .../manage_data/manage_data.test.tsx | 4 - .../solution_panel.test.tsx.snap | 1 + .../solution_title.test.tsx.snap | 2 +- .../solutions_section.test.tsx.snap | 102 +- .../solutions_section/solution_panel.test.tsx | 3 +- .../solutions_section/solution_panel.tsx | 17 +- .../solutions_section/solution_title.tsx | 2 +- .../solutions_section.test.tsx | 58 +- .../solutions_section/solutions_section.tsx | 54 +- .../public/application/components/synopsis.js | 2 +- src/plugins/home/public/index.ts | 1 + src/plugins/home/public/plugin.test.ts | 17 - src/plugins/home/public/plugin.ts | 45 +- .../feature_catalogue_registry.mock.ts | 1 + .../feature_catalogue_registry.test.ts | 2 +- .../feature_catalogue_registry.ts | 16 +- src/plugins/kibana_overview/README.md | 9 + src/plugins/kibana_overview/common/index.ts | 23 + src/plugins/kibana_overview/kibana.json | 9 + .../kibana_overview/public/application.tsx | 62 + .../public/assets/kibana_canvas_dark.svg | 69 + .../public/assets/kibana_canvas_light.svg | 69 + .../public/assets/kibana_dashboard_dark.svg | 116 ++ .../public/assets/kibana_dashboard_light.svg | 116 ++ .../public/assets/kibana_discover_dark.svg | 145 ++ .../public/assets/kibana_discover_light.svg | 145 ++ .../public/assets/kibana_graph_dark.svg | 68 + .../public/assets/kibana_graph_light.svg | 68 + .../public/assets/kibana_maps_dark.svg | 57 + .../public/assets/kibana_maps_light.svg | 57 + .../public/assets/kibana_ml_dark.svg | 141 ++ .../public/assets/kibana_ml_light.svg | 141 ++ .../public/assets/kibana_montage_dark.svg | 178 ++ .../public/assets/kibana_montage_light.svg | 218 +++ .../solutions_enterprise_search_dark_2x.png | Bin 0 -> 2566 bytes .../solutions_enterprise_search_light_2x.png | Bin 0 -> 2503 bytes .../solutions_observability_dark_2x.png | Bin 0 -> 1484 bytes .../solutions_observability_light_2x.png | Bin 0 -> 1487 bytes .../solutions_security_solution_dark_2x.png | Bin 0 -> 1999 bytes .../solutions_security_solution_light_2x.png | Bin 0 -> 2041 bytes .../public/components/_index.scss | 1 + .../public/components/_overview.scss | 120 ++ .../__snapshots__/add_data.test.tsx.snap | 102 ++ .../components/add_data/add_data.test.tsx | 67 + .../public/components/add_data/add_data.tsx | 108 ++ .../public/components/add_data/index.ts} | 4 +- .../kibana_overview/public/components/app.tsx | 69 + .../getting_started.test.tsx.snap | 391 +++++ .../getting_started/getting_started.test.tsx | 118 ++ .../getting_started/getting_started.tsx | 126 ++ .../components/getting_started/index.ts | 20 + .../__snapshots__/manage_data.test.tsx.snap | 103 ++ .../public/components/manage_data/index.tsx | 20 + .../manage_data/manage_data.test.tsx | 83 + .../components/manage_data/manage_data.tsx | 95 ++ .../__snapshots__/news_feed.test.tsx.snap | 149 ++ .../public/components/news_feed/index.ts | 20 + .../components/news_feed/news_feed.test.tsx | 68 + .../public/components/news_feed/news_feed.tsx | 69 + .../__snapshots__/overview.test.tsx.snap | 1222 ++++++++++++++ .../public/components/overview/index.ts | 20 + .../components/overview/overview.test.tsx | 180 +++ .../public/components/overview/overview.tsx | 259 +++ .../__snapshots__/synopsis.test.js.snap | 72 + .../public/components/synopsis/index.js | 20 + .../public/components/synopsis/synopsis.js | 77 + .../components/synopsis/synopsis.test.js | 76 + src/plugins/kibana_overview/public/index.scss | 1 + src/plugins/kibana_overview/public/index.ts | 29 + src/plugins/kibana_overview/public/plugin.ts | 139 ++ src/plugins/kibana_overview/public/types.ts | 40 + src/plugins/kibana_react/public/index.ts | 1 + .../public/overview_page/index.ts | 21 + .../overview_page_footer.test.tsx.snap | 49 + .../overview_page_footer/index.ts | 20 + .../overview_page_footer.test.tsx | 52 + .../overview_page_footer.tsx | 114 ++ .../overview_page_header.test.tsx.snap | 68 + .../_overview_page_header.scss | 33 + .../overview_page_header/index.scss | 20 + .../overview_page_header/index.ts | 20 + .../overview_page_header.test.tsx | 49 + .../overview_page_header.tsx | 149 ++ src/plugins/newsfeed/common/constants.ts | 2 + src/plugins/newsfeed/public/index.ts | 16 +- src/plugins/newsfeed/public/lib/api.test.ts | 16 +- src/plugins/newsfeed/public/lib/api.ts | 33 +- src/plugins/newsfeed/public/plugin.tsx | 35 +- src/plugins/newsfeed/public/types.ts | 4 +- .../public/forward_app/forward_app.ts | 1 + .../canvas/public/feature_catalogue_entry.ts | 5 + .../enterprise_search/common/constants.ts | 5 +- .../enterprise_search/public/plugin.ts | 3 +- x-pack/plugins/graph/public/plugin.ts | 12 +- .../maps/public/feature_catalogue_entry.ts | 5 + x-pack/plugins/ml/public/register_feature.ts | 5 + x-pack/plugins/observability/public/plugin.ts | 8 +- .../security_solution/public/plugin.tsx | 6 +- .../plugins/security_solution/public/types.ts | 4 +- .../advanced_settings_security.ts | 2 +- .../feature_controls/canvas_security.ts | 4 +- .../feature_controls/dashboard_security.ts | 6 +- .../feature_controls/discover_security.ts | 6 +- .../graph/feature_controls/graph_security.ts | 4 +- .../index_patterns_security.ts | 2 +- .../feature_controls/management_security.ts | 2 +- .../maps/feature_controls/maps_security.ts | 4 +- .../feature_controls/timelion_security.ts | 4 +- .../feature_controls/visualize_security.ts | 6 +- 127 files changed, 6874 insertions(+), 1575 deletions(-) create mode 100644 src/plugins/kibana_overview/README.md create mode 100644 src/plugins/kibana_overview/common/index.ts create mode 100644 src/plugins/kibana_overview/kibana.json create mode 100644 src/plugins/kibana_overview/public/application.tsx create mode 100644 src/plugins/kibana_overview/public/assets/kibana_canvas_dark.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_canvas_light.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_discover_dark.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_discover_light.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_graph_dark.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_graph_light.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_maps_dark.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_maps_light.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_ml_dark.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_ml_light.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_montage_dark.svg create mode 100644 src/plugins/kibana_overview/public/assets/kibana_montage_light.svg create mode 100644 src/plugins/kibana_overview/public/assets/solutions_enterprise_search_dark_2x.png create mode 100644 src/plugins/kibana_overview/public/assets/solutions_enterprise_search_light_2x.png create mode 100644 src/plugins/kibana_overview/public/assets/solutions_observability_dark_2x.png create mode 100644 src/plugins/kibana_overview/public/assets/solutions_observability_light_2x.png create mode 100644 src/plugins/kibana_overview/public/assets/solutions_security_solution_dark_2x.png create mode 100644 src/plugins/kibana_overview/public/assets/solutions_security_solution_light_2x.png create mode 100644 src/plugins/kibana_overview/public/components/_index.scss create mode 100644 src/plugins/kibana_overview/public/components/_overview.scss create mode 100644 src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap create mode 100644 src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx create mode 100644 src/plugins/kibana_overview/public/components/add_data/add_data.tsx rename src/plugins/{home/public/application/components/_manage_data.scss => kibana_overview/public/components/add_data/index.ts} (90%) create mode 100644 src/plugins/kibana_overview/public/components/app.tsx create mode 100644 src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap create mode 100644 src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx create mode 100644 src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx create mode 100644 src/plugins/kibana_overview/public/components/getting_started/index.ts create mode 100644 src/plugins/kibana_overview/public/components/manage_data/__snapshots__/manage_data.test.tsx.snap create mode 100644 src/plugins/kibana_overview/public/components/manage_data/index.tsx create mode 100644 src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx create mode 100644 src/plugins/kibana_overview/public/components/manage_data/manage_data.tsx create mode 100644 src/plugins/kibana_overview/public/components/news_feed/__snapshots__/news_feed.test.tsx.snap create mode 100644 src/plugins/kibana_overview/public/components/news_feed/index.ts create mode 100644 src/plugins/kibana_overview/public/components/news_feed/news_feed.test.tsx create mode 100644 src/plugins/kibana_overview/public/components/news_feed/news_feed.tsx create mode 100644 src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap create mode 100644 src/plugins/kibana_overview/public/components/overview/index.ts create mode 100644 src/plugins/kibana_overview/public/components/overview/overview.test.tsx create mode 100644 src/plugins/kibana_overview/public/components/overview/overview.tsx create mode 100644 src/plugins/kibana_overview/public/components/synopsis/__snapshots__/synopsis.test.js.snap create mode 100644 src/plugins/kibana_overview/public/components/synopsis/index.js create mode 100644 src/plugins/kibana_overview/public/components/synopsis/synopsis.js create mode 100644 src/plugins/kibana_overview/public/components/synopsis/synopsis.test.js create mode 100644 src/plugins/kibana_overview/public/index.scss create mode 100644 src/plugins/kibana_overview/public/index.ts create mode 100644 src/plugins/kibana_overview/public/plugin.ts create mode 100644 src/plugins/kibana_overview/public/types.ts create mode 100644 src/plugins/kibana_react/public/overview_page/index.ts create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_footer/__snapshots__/overview_page_footer.test.tsx.snap create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_footer/index.ts create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.tsx create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/__snapshots__/overview_page_header.test.tsx.snap create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/_overview_page_header.scss create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/index.scss create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/index.ts create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/overview_page_header.test.tsx create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/overview_page_header.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d21d6ad81a0c6..d522be908b69c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -137,6 +137,7 @@ /src/plugins/home/public @elastic/kibana-core-ui /src/plugins/home/server/*.ts @elastic/kibana-core-ui /src/plugins/home/server/services/ @elastic/kibana-core-ui +/src/plugins/kibana_overview/ @elastic/kibana-core-ui /x-pack/plugins/global_search_bar/ @elastic/kibana-core-ui #CC# /src/legacy/core_plugins/newsfeed @elastic/kibana-core-ui #CC# /src/legacy/server/sample_data/ @elastic/kibana-core-ui diff --git a/.i18nrc.json b/.i18nrc.json index 7ecaa89f66604..e0281b0a5bc21 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -29,6 +29,7 @@ "indexPatternManagement": "src/plugins/index_pattern_management", "advancedSettings": "src/plugins/advanced_settings", "kibana_legacy": "src/plugins/kibana_legacy", + "kibanaOverview": "src/plugins/kibana_overview", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", "kibana_utils": "src/plugins/kibana_utils", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 1d4b80407cfc0..167c3fc70fdda 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -110,6 +110,10 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |This plugin contains several helpers and services to integrate pieces of the legacy Kibana app with the new Kibana platform. +|{kib-repo}blob/{branch}/src/plugins/kibana_overview/README.md[kibanaOverview] +|An overview page highlighting Kibana apps + + |{kib-repo}blob/{branch}/src/plugins/kibana_react/README.md[kibanaReact] |Tools for building React applications in Kibana. diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx index 8c9e3847844d9..afdd90959eabd 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx @@ -121,7 +121,18 @@ export class AdvancedSettingsComponent extends Component< setTimeout(() => { const id = hash.replace('#', ''); const element = document.getElementById(id); - const globalNavOffset = document.getElementById('globalHeaderBars')?.offsetHeight || 0; + + let globalNavOffset = 0; + + const globalNavBars = document + .getElementById('globalHeaderBars') + ?.getElementsByClassName('euiHeader'); + + if (globalNavBars) { + Array.from(globalNavBars).forEach((navBar) => { + globalNavOffset += (navBar as HTMLDivElement).offsetHeight; + }); + } if (element) { element.scrollIntoView(); diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 91f603dfc6c77..3325d193e56ed 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -395,6 +395,9 @@ export class DashboardPlugin title: i18n.translate('dashboard.featureCatalogue.dashboardTitle', { defaultMessage: 'Dashboard', }), + subtitle: i18n.translate('dashboard.featureCatalogue.dashboardSubtitle', { + defaultMessage: 'Analyze data in dashboards.', + }), description: i18n.translate('dashboard.featureCatalogue.dashboardDescription', { defaultMessage: 'Display and share a collection of visualizations and saved searches.', }), @@ -402,6 +405,8 @@ export class DashboardPlugin path: `/app/dashboards#${DashboardConstants.LANDING_PAGE_PATH}`, showOnHomePage: false, category: FeatureCatalogueCategory.DATA, + solutionId: 'kibana', + order: 100, }); } } diff --git a/src/plugins/discover/public/register_feature.ts b/src/plugins/discover/public/register_feature.ts index 5443bb261ab10..9a66936233692 100644 --- a/src/plugins/discover/public/register_feature.ts +++ b/src/plugins/discover/public/register_feature.ts @@ -25,6 +25,9 @@ export function registerFeature(home: HomePublicPluginSetup) { title: i18n.translate('discover.discoverTitle', { defaultMessage: 'Discover', }), + subtitle: i18n.translate('discover.discoverSubtitle', { + defaultMessage: 'Search and find insights.', + }), description: i18n.translate('discover.discoverDescription', { defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', }), @@ -32,5 +35,7 @@ export function registerFeature(home: HomePublicPluginSetup) { path: '/app/discover#/', showOnHomePage: false, category: FeatureCatalogueCategory.DATA, + solutionId: 'kibana', + order: 200, }); } diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap index bf1e8c8f0b401..e9b0494105e12 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap @@ -2,57 +2,26 @@ exports[`home change home route should render a link to change the default route in advanced settings if advanced settings is enabled 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -73,115 +42,36 @@ exports[`home change home route should render a link to change the default route aria-hidden="true" margin="xl" /> -
- - - - - - - - - - - - -
+
`; exports[`home directories should not render directory entry when showOnHomePage is false 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - - Manage - - - - -
-
-
+ + } + />
- 0 @@ -202,92 +92,36 @@ exports[`home directories should not render directory entry when showOnHomePage aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home directories should render ADMIN directory entry in "Manage your data" panel 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -320,92 +154,36 @@ exports[`home directories should render ADMIN directory entry in "Manage your da aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home directories should render DATA directory entry in "Ingest your data" panel 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -438,97 +216,43 @@ exports[`home directories should render DATA directory entry in "Ingest your dat aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home directories should render solutions in the "solution section" 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
`; exports[`home header render 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -700,102 +368,36 @@ exports[`home header render 1`] = ` aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home header should show "Dev tools" link if console is available 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - - Dev tools - - - - -
-
-
+ + } + />
- 0 @@ -828,103 +430,36 @@ exports[`home header should show "Dev tools" link if console is available 1`] = aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home header should show "Manage" link if stack management is available 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - - Manage - - - - -
-
-
+ + } + />
- 0 @@ -945,92 +480,36 @@ exports[`home header should show "Manage" link if stack management is available aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home isNewKibanaInstance should safely handle execeptions 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -1051,92 +530,36 @@ exports[`home isNewKibanaInstance should safely handle execeptions 1`] = ` aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when there are index patterns 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -1157,92 +580,36 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when there are no index patterns 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -1263,92 +630,36 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when th aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home should render home component 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -1369,92 +680,36 @@ exports[`home should render home component 1`] = ` aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home welcome should show the normal home page if loading fails 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -1475,92 +730,36 @@ exports[`home welcome should show the normal home page if loading fails 1`] = ` aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; exports[`home welcome should show the normal home page if welcome screen is disabled locally 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -1581,35 +780,10 @@ exports[`home welcome should show the normal home page if welcome screen is disa aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; @@ -1623,57 +797,26 @@ exports[`home welcome should show the welcome screen if enabled, and there are n exports[`home welcome stores skip welcome setting if skipped 1`] = `
-
-
- - - -

- -

-
-
- - - - - Add data - - - - -
-
-
+ + } + />
- 0 @@ -1694,35 +837,10 @@ exports[`home welcome stores skip welcome setting if skipped 1`] = ` aria-hidden="true" margin="xl" /> -
- - - - - - - - -
+
`; diff --git a/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap index 190985f70659d..ec192ce1eb32f 100644 --- a/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap @@ -9,6 +9,7 @@ exports[`props iconType 1`] = ` href="link_to_item" icon={ - - - +
+ + + +
= ({ addBasePath, features }) => (
- +

@@ -43,18 +43,21 @@ export const AddData: FC = ({ addBasePath, features }) => ( - - - - + +
+ + + +
diff --git a/src/plugins/home/public/application/components/home.js b/src/plugins/home/public/application/components/home.js index becafb2560217..054f5a5344df4 100644 --- a/src/plugins/home/public/application/components/home.js +++ b/src/plugins/home/public/application/components/home.js @@ -20,18 +20,16 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiButtonEmpty, - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { + OverviewPageFooter, + OverviewPageHeader, +} from '../../../../../../src/plugins/kibana_react/public'; +import { HOME_APP_BASE_PATH } from '../../../common/constants'; import { FeatureCatalogueCategory } from '../../services'; import { getServices } from '../kibana_services'; import { AddData } from './add_data'; -import { createAppNavigationHandler } from './app_navigation_handler'; import { ManageData } from './manage_data'; import { SolutionsSection } from './solutions_section'; import { Welcome } from './welcome'; @@ -121,12 +119,9 @@ export class Home extends Component { .sort((directoryA, directoryB) => directoryA.order - directoryB.order); renderNormal() { - const { addBasePath, solutions } = this.props; + const { addBasePath, solutions, directories } = this.props; const devTools = this.findDirectoryById('console'); - const stackManagement = this.findDirectoryById('stack-management'); - const advancedSettings = this.findDirectoryById('advanced_settings'); - const addDataFeatures = this.getFeaturesByCategory(FeatureCatalogueCategory.DATA); const manageDataFeatures = this.getFeaturesByCategory(FeatureCatalogueCategory.ADMIN); @@ -136,68 +131,27 @@ export class Home extends Component { } return ( -
-
-
- - - -

- -

-
-
- - - - - - {i18n.translate('home.pageHeader.addDataButtonLabel', { - defaultMessage: 'Add data', - })} - - - - {stackManagement ? ( - - - {i18n.translate('home.pageHeader.stackManagementButtonLabel', { - defaultMessage: 'Manage', - })} - - - ) : null} - - {devTools ? ( - - - {i18n.translate('home.pageHeader.devToolsButtonLabel', { - defaultMessage: 'Dev tools', - })} - - - ) : null} - - -
-
-
+
+ } + />
- {solutions.length && } + {solutions.length ? ( + + ) : null}
); @@ -294,12 +214,14 @@ Home.propTypes = { PropTypes.shape({ id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, + subtitle: PropTypes.string, description: PropTypes.string.isRequired, icon: PropTypes.string.isRequired, path: PropTypes.string.isRequired, showOnHomePage: PropTypes.bool.isRequired, category: PropTypes.string.isRequired, order: PropTypes.number, + solutionId: PropTypes.string, }) ), solutions: PropTypes.arrayOf( @@ -307,7 +229,8 @@ Home.propTypes = { id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, subtitle: PropTypes.string.isRequired, - descriptions: PropTypes.arrayOf(PropTypes.string).isRequired, + description: PropTypes.string, + appDescriptions: PropTypes.arrayOf(PropTypes.string).isRequired, icon: PropTypes.string.isRequired, path: PropTypes.string.isRequired, order: PropTypes.number, diff --git a/src/plugins/home/public/application/components/home.test.js b/src/plugins/home/public/application/components/home.test.js index 0d7596d92a5a1..9c73bbf9b75ba 100644 --- a/src/plugins/home/public/application/components/home.test.js +++ b/src/plugins/home/public/application/components/home.test.js @@ -35,6 +35,11 @@ jest.mock('../kibana_services', () => ({ }), })); +jest.mock('../../../../../../src/plugins/kibana_react/public', () => ({ + OverviewPageFooter: jest.fn().mockReturnValue(<>), + OverviewPageHeader: jest.fn().mockReturnValue(<>), +})); + describe('home', () => { let defaultProps; @@ -142,7 +147,7 @@ describe('home', () => { id: 'kibana', title: 'Kibana', subtitle: 'Visualize & analyze', - descriptions: ['Analyze data in dashboards'], + appDescriptions: ['Analyze data in dashboards'], icon: 'logoKibana', path: 'kibana_landing_page', order: 1, @@ -151,7 +156,7 @@ describe('home', () => { id: 'solution-2', title: 'Solution two', subtitle: 'Subtitle for solution two', - descriptions: ['Example use case'], + appDescriptions: ['Example use case'], icon: 'empty', path: 'path-to-solution-two', order: 2, @@ -160,7 +165,7 @@ describe('home', () => { id: 'solution-3', title: 'Solution three', subtitle: 'Subtitle for solution three', - descriptions: ['Example use case'], + appDescriptions: ['Example use case'], icon: 'empty', path: 'path-to-solution-three', order: 3, @@ -169,7 +174,7 @@ describe('home', () => { id: 'solution-4', title: 'Solution four', subtitle: 'Subtitle for solution four', - descriptions: ['Example use case'], + appDescriptions: ['Example use case'], icon: 'empty', path: 'path-to-solution-four', order: 4, diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index 69cd68d553d03..7fe4f4351c35b 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -104,12 +104,14 @@ HomeApp.propTypes = { PropTypes.shape({ id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, + subtitle: PropTypes.string, description: PropTypes.string.isRequired, icon: PropTypes.string.isRequired, path: PropTypes.string.isRequired, showOnHomePage: PropTypes.bool.isRequired, category: PropTypes.string.isRequired, order: PropTypes.number, + solutionId: PropTypes.string, }) ), solutions: PropTypes.arrayOf( @@ -117,7 +119,8 @@ HomeApp.propTypes = { id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, subtitle: PropTypes.string.isRequired, - descriptions: PropTypes.arrayOf(PropTypes.string).isRequired, + description: PropTypes.string, + appDescriptions: PropTypes.arrayOf(PropTypes.string).isRequired, icon: PropTypes.string.isRequired, path: PropTypes.string.isRequired, order: PropTypes.number, diff --git a/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx b/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx index 0e86bf7dd3d84..18a58e86eaa3f 100644 --- a/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx +++ b/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx @@ -37,7 +37,6 @@ const mockFeatures = [ { category: 'admin', description: 'Control who has access and what tasks they can perform.', - homePageSection: 'manage_data', icon: 'securityApp', id: 'security', order: 600, @@ -48,7 +47,6 @@ const mockFeatures = [ { category: 'admin', description: 'Track the real-time health and performance of your deployment.', - homePageSection: 'manage_data', icon: 'monitoringApp', id: 'monitoring', order: 610, @@ -60,7 +58,6 @@ const mockFeatures = [ category: 'admin', description: 'Save snapshots to a backup repository, and restore to recover index and cluster state.', - homePageSection: 'manage_data', icon: 'storage', id: 'snapshot_restore', order: 630, @@ -71,7 +68,6 @@ const mockFeatures = [ { category: 'admin', description: 'Define lifecycle policies to automatically perform operations as an index ages.', - homePageSection: 'manage_data', icon: 'indexSettings', id: 'index_lifecycle_management', order: 640, diff --git a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_panel.test.tsx.snap b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_panel.test.tsx.snap index ad92aac67d51b..726d3dda4e9cc 100644 --- a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_panel.test.tsx.snap +++ b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solution_panel.test.tsx.snap @@ -13,6 +13,7 @@ exports[`SolutionPanel renders the solution panel for the given solution 1`] = ` onClick={[Function]} >

diff --git a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap index 7015ebb40a71d..4052ffca9e56f 100644 --- a/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap +++ b/src/plugins/home/public/application/components/solutions_section/__snapshots__/solutions_section.test.tsx.snap @@ -7,19 +7,15 @@ exports[`SolutionsSection only renders a spacer if no solutions are available 1` className="homSolutions" > - -

- -

-
+ +

- -

- -

-
+ +

- -

- -

-
+ +
- -

- -

-
+ +
- descriptions.map(getDescriptionText).reduce(addSpacersBetweenElementsReducer, []); +const getDescriptions = (appDescriptions: string[]) => + appDescriptions + .map(getDescriptionText) + .reduce(addSpacersBetweenElementsReducer, []); interface Props { addBasePath: (path: string) => string; solution: FeatureCatalogueSolution; + apps?: FeatureCatalogueEntry[]; } -export const SolutionPanel: FC = ({ addBasePath, solution }) => ( +export const SolutionPanel: FC = ({ addBasePath, solution, apps = [] }) => ( = ({ addBasePath, solution }) => ( href={addBasePath(solution.path)} onClick={createAppNavigationHandler(solution.path)} > - + = ({ addBasePath, solution }) => ( - {getDescriptions(solution.descriptions)} + {getDescriptions( + apps.length ? apps.map(({ subtitle = '' }) => subtitle) : solution.appDescriptions + )} diff --git a/src/plugins/home/public/application/components/solutions_section/solution_title.tsx b/src/plugins/home/public/application/components/solutions_section/solution_title.tsx index fb833a40807d2..a9874ff7ddbe7 100644 --- a/src/plugins/home/public/application/components/solutions_section/solution_title.tsx +++ b/src/plugins/home/public/application/components/solutions_section/solution_title.tsx @@ -45,7 +45,7 @@ export const SolutionTitle: FC = ({ title, subtitle, iconType }) => ( className="homSolutionPanel__icon" /> - +

{title}

diff --git a/src/plugins/home/public/application/components/solutions_section/solutions_section.test.tsx b/src/plugins/home/public/application/components/solutions_section/solutions_section.test.tsx index 17d721cc96c02..9ec5bf695b35c 100644 --- a/src/plugins/home/public/application/components/solutions_section/solutions_section.test.tsx +++ b/src/plugins/home/public/application/components/solutions_section/solutions_section.test.tsx @@ -20,12 +20,13 @@ import React from 'react'; import { shallow } from 'enzyme'; import { SolutionsSection } from './solutions_section'; +import { FeatureCatalogueCategory } from '../../../services'; const solutionEntry1 = { id: 'kibana', title: 'Kibana', subtitle: 'Visualize & analyze', - descriptions: ['Analyze data in dashboards'], + appDescriptions: ['Analyze data in dashboards'], icon: 'logoKibana', path: 'kibana_landing_page', order: 1, @@ -34,7 +35,8 @@ const solutionEntry2 = { id: 'solution-2', title: 'Solution two', subtitle: 'Subtitle for solution two', - descriptions: ['Example use case'], + description: 'Description for solution two', + appDescriptions: ['Example use case'], icon: 'empty', path: 'path-to-solution-two', order: 2, @@ -43,7 +45,8 @@ const solutionEntry3 = { id: 'solution-3', title: 'Solution three', subtitle: 'Subtitle for solution three', - descriptions: ['Example use case'], + description: 'Description for solution three', + appDescriptions: ['Example use case'], icon: 'empty', path: 'path-to-solution-three', order: 3, @@ -52,23 +55,64 @@ const solutionEntry4 = { id: 'solution-4', title: 'Solution four', subtitle: 'Subtitle for solution four', - descriptions: ['Example use case'], + description: 'Description for solution four', + appDescriptions: ['Example use case'], icon: 'empty', path: 'path-to-solution-four', order: 4, }; +const mockDirectories = [ + { + id: 'dashboard', + title: 'Dashboard', + description: 'Description of dashboard', + icon: 'dashboardApp', + path: 'dashboard_landing_page', + showOnHomePage: false, + category: FeatureCatalogueCategory.DATA, + }, + { + id: 'discover', + title: 'Discover', + description: 'Description of discover', + icon: 'discoverApp', + path: 'discover_landing_page', + showOnHomePage: false, + category: FeatureCatalogueCategory.DATA, + }, + { + id: 'canvas', + title: 'Canvas', + description: 'Description of canvas', + icon: 'canvasApp', + path: 'canvas_landing_page', + showOnHomePage: false, + category: FeatureCatalogueCategory.DATA, + }, +]; + const addBasePathMock = (path: string) => (path ? path : 'path'); describe('SolutionsSection', () => { test('only renders a spacer if no solutions are available', () => { - const component = shallow(); + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); test('renders a single solution', () => { const component = shallow( - + ); expect(component).toMatchSnapshot(); }); @@ -78,6 +122,7 @@ describe('SolutionsSection', () => { ); expect(component).toMatchSnapshot(); @@ -87,6 +132,7 @@ describe('SolutionsSection', () => { ); expect(component).toMatchSnapshot(); diff --git a/src/plugins/home/public/application/components/solutions_section/solutions_section.tsx b/src/plugins/home/public/application/components/solutions_section/solutions_section.tsx index 1a78a6c71030a..13b70383147eb 100644 --- a/src/plugins/home/public/application/components/solutions_section/solutions_section.tsx +++ b/src/plugins/home/public/application/components/solutions_section/solutions_section.tsx @@ -19,16 +19,10 @@ import React, { FC } from 'react'; import PropTypes from 'prop-types'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiScreenReaderOnly, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { SolutionPanel } from './solution_panel'; -import { FeatureCatalogueSolution } from '../../../'; +import { FeatureCatalogueEntry, FeatureCatalogueSolution } from '../../../'; const sortByOrder = ( { order: orderA = 0 }: FeatureCatalogueSolution, @@ -38,25 +32,25 @@ const sortByOrder = ( interface Props { addBasePath: (path: string) => string; solutions: FeatureCatalogueSolution[]; + directories: FeatureCatalogueEntry[]; } -export const SolutionsSection: FC = ({ addBasePath, solutions }) => { +export const SolutionsSection: FC = ({ addBasePath, solutions, directories }) => { // Separate Kibana from other solutions const kibana = solutions.find(({ id }) => id === 'kibana'); + const kibanaApps = directories.filter(({ solutionId }) => solutionId === 'kibana'); solutions = solutions.sort(sortByOrder).filter(({ id }) => id !== 'kibana'); return ( <>
- -

- -

-
+

+ +

@@ -69,7 +63,13 @@ export const SolutionsSection: FC = ({ addBasePath, solutions }) => { ) : null} - {kibana ? : null} + {kibana ? ( + + ) : null}
@@ -79,12 +79,28 @@ export const SolutionsSection: FC = ({ addBasePath, solutions }) => { }; SolutionsSection.propTypes = { + addBasePath: PropTypes.func.isRequired, + directories: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + subtitle: PropTypes.string, + description: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + showOnHomePage: PropTypes.bool.isRequired, + category: PropTypes.string.isRequired, + order: PropTypes.number, + solutionId: PropTypes.string, + }) + ), solutions: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, subtitle: PropTypes.string.isRequired, - descriptions: PropTypes.arrayOf(PropTypes.string).isRequired, + description: PropTypes.string, + appDescriptions: PropTypes.arrayOf(PropTypes.string).isRequired, icon: PropTypes.string.isRequired, path: PropTypes.string.isRequired, order: PropTypes.number, diff --git a/src/plugins/home/public/application/components/synopsis.js b/src/plugins/home/public/application/components/synopsis.js index fbe3bb3ed6769..0777c0db7210e 100644 --- a/src/plugins/home/public/application/components/synopsis.js +++ b/src/plugins/home/public/application/components/synopsis.js @@ -38,7 +38,7 @@ export function Synopsis({ if (iconUrl) { optionalImg = ; } else if (iconType) { - optionalImg = ; + optionalImg = ; } const classes = classNames('homSynopsis__card', { diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts index 4459d187eead7..e4ca6fb1439e8 100644 --- a/src/plugins/home/public/index.ts +++ b/src/plugins/home/public/index.ts @@ -24,6 +24,7 @@ export { EnvironmentSetup, TutorialSetup, HomePublicPluginSetup, + HomePublicPluginStart, } from './plugin'; export { FeatureCatalogueEntry, diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts index 7b56c6ec89b77..f8ff7c95aae08 100644 --- a/src/plugins/home/public/plugin.test.ts +++ b/src/plugins/home/public/plugin.test.ts @@ -52,23 +52,6 @@ describe('HomePublicPlugin', () => { ); }); - test('registers kibana solution to feature catalogue', async () => { - const setup = await new HomePublicPlugin(mockInitializerContext).setup( - coreMock.createSetup() as any, - { - urlForwarding: urlForwardingPluginMock.createSetupContract(), - } - ); - expect(setup).toHaveProperty('featureCatalogue'); - expect(setup.featureCatalogue.registerSolution).toHaveBeenCalledTimes(1); - expect(setup.featureCatalogue.registerSolution).toHaveBeenCalledWith( - expect.objectContaining({ - icon: 'logoKibana', - id: 'kibana', - }) - ); - }); - test('wires up and returns registry', async () => { const setup = await new HomePublicPlugin(mockInitializerContext).setup( coreMock.createSetup() as any, diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index b62ceae3d0d37..90f2f939101cb 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -58,7 +58,12 @@ export interface HomePluginSetupDependencies { export class HomePublicPlugin implements - Plugin { + Plugin< + HomePublicPluginSetup, + HomePublicPluginStart, + HomePluginSetupDependencies, + HomePluginStartDependencies + > { private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); private readonly environmentService = new EnvironmentService(); private readonly tutorialService = new TutorialService(); @@ -128,39 +133,6 @@ export class HomePublicPlugin order: 500, }); - featureCatalogue.registerSolution({ - id: 'kibana', - title: i18n.translate('home.kibana.featureCatalogue.title', { - defaultMessage: 'Kibana', - }), - subtitle: i18n.translate('home.kibana.featureCatalogue.subtitle', { - defaultMessage: 'Visualize & analyze', - }), - descriptions: [ - i18n.translate('home.kibana.featureCatalogueDescription1', { - defaultMessage: 'Analyze data in dashboards.', - }), - i18n.translate('home.kibana.featureCatalogueDescription2', { - defaultMessage: 'Search and find insights.', - }), - i18n.translate('home.kibana.featureCatalogueDescription3', { - defaultMessage: 'Design pixel-perfect reports.', - }), - i18n.translate('home.kibana.featureCatalogueDescription4', { - defaultMessage: 'Plot geographic data.', - }), - i18n.translate('home.kibana.featureCatalogueDescription5', { - defaultMessage: 'Model, predict, and detect.', - }), - i18n.translate('home.kibana.featureCatalogueDescription6', { - defaultMessage: 'Reveal patterns and relationships.', - }), - ], - icon: 'logoKibana', - path: '/app/dashboards', - order: 400, - }); - return { featureCatalogue, environment: { ...this.environmentService.setup() }, @@ -188,6 +160,8 @@ export class HomePublicPlugin } }); } + + return { featureCatalogue: this.featuresCatalogueRegistry }; } } @@ -212,3 +186,6 @@ export interface HomePublicPluginSetup { environment: EnvironmentSetup; } +export interface HomePublicPluginStart { + featureCatalogue: FeatureCatalogueRegistry; +} diff --git a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts index 23f36cef89ee6..e1a415ba2d571 100644 --- a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts +++ b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts @@ -36,6 +36,7 @@ const createMock = (): jest.Mocked> => start: jest.fn(), get: jest.fn(() => []), getSolutions: jest.fn(() => []), + removeFeature: jest.fn(), }; service.setup.mockImplementation(createSetupMock); return service; diff --git a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.test.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.test.ts index b9902e0b10fb1..b009041bbf15b 100644 --- a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.test.ts +++ b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.test.ts @@ -38,7 +38,7 @@ const KIBANA_SOLUTION: FeatureCatalogueSolution = { id: 'kibana', title: 'Kibana', subtitle: 'Visualize & analyze', - descriptions: ['Analyze data in dashboards.', 'Search and find insights.'], + appDescriptions: ['Analyze data in dashboards.', 'Search and find insights.'], icon: 'kibanaApp', path: `/app/home`, }; diff --git a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts index d965042b65cef..845070da0db9f 100644 --- a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts +++ b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.ts @@ -35,6 +35,8 @@ export interface FeatureCatalogueEntry { readonly title: string; /** {@link FeatureCatalogueCategory} to display this feature in. */ readonly category: FeatureCatalogueCategory; + /** A tagline of feature displayed to the user. */ + readonly subtitle?: string; /** One-line description of feature displayed to the user. */ readonly description: string; /** EUI `IconType` for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or ReactElement. */ @@ -47,6 +49,8 @@ export interface FeatureCatalogueEntry { readonly order?: number; /** Optional function to control visibility of this feature. */ readonly visible?: () => boolean; + /** Unique string identifier of the solution this feature belongs to */ + readonly solutionId?: string; } /** @public */ @@ -57,8 +61,10 @@ export interface FeatureCatalogueSolution { readonly title: string; /** The tagline of the solution displayed to the user. */ readonly subtitle: string; + /** One-line description of the solution displayed to the user. */ + readonly description?: string; /** A list of use cases for this solution displayed to the user. */ - readonly descriptions: string[]; + readonly appDescriptions: string[]; /** EUI `IconType` for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or ReactElement. */ readonly icon: IconType; /** URL path to link to this future. Should not include the basePath. */ @@ -99,7 +105,7 @@ export class FeatureCatalogueRegistry { this.capabilities = capabilities; } - public get(): readonly FeatureCatalogueEntry[] { + public get(): FeatureCatalogueEntry[] { if (this.capabilities === null) { throw new Error('Catalogue entries are only available after start phase'); } @@ -112,7 +118,7 @@ export class FeatureCatalogueRegistry { .sort(compareByKey('title')); } - public getSolutions(): readonly FeatureCatalogueSolution[] { + public getSolutions(): FeatureCatalogueSolution[] { if (this.capabilities === null) { throw new Error('Catalogue entries are only available after start phase'); } @@ -121,6 +127,10 @@ export class FeatureCatalogueRegistry { .filter((solution) => capabilities.catalogue[solution.id] !== false) .sort(compareByKey('title')); } + + public removeFeature(appId: string) { + this.features.delete(appId); + } } export type FeatureCatalogueRegistrySetup = ReturnType; diff --git a/src/plugins/kibana_overview/README.md b/src/plugins/kibana_overview/README.md new file mode 100644 index 0000000000000..ad0cbfdf7013b --- /dev/null +++ b/src/plugins/kibana_overview/README.md @@ -0,0 +1,9 @@ +# kibana-overview + +> An overview page highlighting Kibana apps + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/kibana_overview/common/index.ts b/src/plugins/kibana_overview/common/index.ts new file mode 100644 index 0000000000000..3bdfbee1081ad --- /dev/null +++ b/src/plugins/kibana_overview/common/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const PLUGIN_ID = 'kibanaOverview'; +export const PLUGIN_NAME = 'Overview'; +export const PLUGIN_PATH = `/app/kibana_overview`; +export const PLUGIN_ICON = 'logoKibana'; diff --git a/src/plugins/kibana_overview/kibana.json b/src/plugins/kibana_overview/kibana.json new file mode 100644 index 0000000000000..9ddcaabdaed6b --- /dev/null +++ b/src/plugins/kibana_overview/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "kibanaOverview", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["navigation", "data", "home"], + "optionalPlugins": ["newsfeed"], + "requiredBundles": ["kibanaReact", "newsfeed"] +} diff --git a/src/plugins/kibana_overview/public/application.tsx b/src/plugins/kibana_overview/public/application.tsx new file mode 100644 index 0000000000000..1bf3fe07c36a8 --- /dev/null +++ b/src/plugins/kibana_overview/public/application.tsx @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; +import { NewsfeedApiEndpoint } from '../../../../src/plugins/newsfeed/public'; +import { AppMountParameters, CoreStart } from '../../../../src/core/public'; +import { AppPluginStartDependencies } from './types'; +import { KibanaOverviewApp } from './components/app'; + +export const renderApp = ( + core: CoreStart, + deps: AppPluginStartDependencies, + { appBasePath, element }: AppMountParameters +) => { + const { notifications, http } = core; + const { newsfeed, home, navigation } = deps; + const newsfeed$ = newsfeed?.createNewsFeed$(NewsfeedApiEndpoint.KIBANA_ANALYTICS); + const navLinks = core.chrome.navLinks.getAll(); + const solutions = home.featureCatalogue + .getSolutions() + .filter(({ id }) => id !== 'kibana') + .filter(({ id }) => navLinks.find(({ category, hidden }) => !hidden && category?.id === id)); + const features = home.featureCatalogue.get(); + + ReactDOM.render( + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/src/plugins/kibana_overview/public/assets/kibana_canvas_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_canvas_dark.svg new file mode 100644 index 0000000000000..c86a3a89924c8 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_canvas_dark.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_canvas_light.svg b/src/plugins/kibana_overview/public/assets/kibana_canvas_light.svg new file mode 100644 index 0000000000000..d51560cb915c9 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_canvas_light.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg new file mode 100644 index 0000000000000..834dd98d60e4c --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg b/src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg new file mode 100644 index 0000000000000..958d25362c439 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_discover_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_discover_dark.svg new file mode 100644 index 0000000000000..cf3116ae7f36a --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_discover_dark.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_discover_light.svg b/src/plugins/kibana_overview/public/assets/kibana_discover_light.svg new file mode 100644 index 0000000000000..6e039d03bef89 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_discover_light.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_graph_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_graph_dark.svg new file mode 100644 index 0000000000000..ea43adf3390d0 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_graph_dark.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_graph_light.svg b/src/plugins/kibana_overview/public/assets/kibana_graph_light.svg new file mode 100644 index 0000000000000..c4505209a20bd --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_graph_light.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_maps_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_maps_dark.svg new file mode 100644 index 0000000000000..a6c53012a5e90 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_maps_dark.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_maps_light.svg b/src/plugins/kibana_overview/public/assets/kibana_maps_light.svg new file mode 100644 index 0000000000000..06a3ee5177eb9 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_maps_light.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_ml_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_ml_dark.svg new file mode 100644 index 0000000000000..574760e523f51 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_ml_dark.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_ml_light.svg b/src/plugins/kibana_overview/public/assets/kibana_ml_light.svg new file mode 100644 index 0000000000000..2375d78ce61f3 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_ml_light.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_montage_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_montage_dark.svg new file mode 100644 index 0000000000000..7a476e4b19ae1 --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_montage_dark.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/kibana_montage_light.svg b/src/plugins/kibana_overview/public/assets/kibana_montage_light.svg new file mode 100644 index 0000000000000..83f48a0d31a0c --- /dev/null +++ b/src/plugins/kibana_overview/public/assets/kibana_montage_light.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/kibana_overview/public/assets/solutions_enterprise_search_dark_2x.png b/src/plugins/kibana_overview/public/assets/solutions_enterprise_search_dark_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..86ac827f06a77ccd3f7949c22620882aee6494c0 GIT binary patch literal 2566 zcmeHJdr;GN65sGr9+pZ<5~x*&Ll_Ww9S=cKd_@>A7AU8@Bto$Q0U-#=BM|gzu>`@# z1py%~v53V>Ktl=P6)T~KR0T6B0)`;rA;tiPBpL!qf6mR^U-#eXfA`0CzOy^KpV^(= z{p?=+l5lXHnXMTBtP2j>9}ZxE13F#2ZCp*;XC<>o8LjS+{M;Qf2PI*XEvWn^@}&2KL@CcC_dNIGLG5Y5B$ohKCNmTdWE~yy)LcqLb%T?$#{1=HC&2!&GfaAxnMv4!Y}~48fTv9=jXc2FqUpr>$4rG4>00ZP43A%3v^@H z9(sN!CVZcCS^OP2KQoG_;sJb@G&oC;ofwo!c3)FDym#<;cOR2oow+o>>fpmNhYVcZ z^wgc@-hR0tGAcIC8i@J;+0c#Bfz-<=STiy5L|5l4l$*n|_#K-#t))v#OXOd2)BYLK zE+^@Xs1zj$O>>(}Nc5??#_XF;hUW6RX|ztp<@z#02W}>tL36x`CGC{pK%{W;EZWA( z5Y!?03dQgv$JNT(uqUj>aykoB8qUsQ`!>!`>IH(Xeh0T#_EPC+p#?m{%5PGLj9Fzw z24`tRyJ^*@?UT>(beY?6#i@ z-Ms3ouy2of3CSY4j}r;fYjQOR*e~!>DHWR6?mEgO|+RYsj{i) zj-dTvWC-}wW{>;zvbT-Nadww+;7&Mi+;>-2ME{oZZvh{-IZ8#d$VGwF>m}h~_b~f# zn$n0GW~)RO#O3W`rTiTQOs_>FrZS<%5olMv*TUxcTc22{3_+@lD7w(W`2za-D*#G9 z_i`r<8Brg2k%&qc8f!L@1^jZ4lf=;c$L@dB@`37M>*c;YUgGQq#2Y5&wB^354U#eT z789U7B^G<6E%&r;u>#z!`v!@-=KQ)|lHM3mTWsy!FZl>dyWf^PuQ~?&lk2Q-lv`L) zZPrc{kuzEBVHxW7>UBcWIV4Eyb+qLZ_xd@??BVyO*98X5?xB9pHq#JfikWm%+9NW) ztkfYx8$`+LRjdVC%mV-A-onDW$POSkmhCQH|X>f66_q@xK{L8%ysH=-pd~QeAwl?gMvD<=uIb{|I(z8wF zW;E?Zfnr8obNo$voSn2mtJ5Oo8bpTtX=u-=ti8(q4!!AacgI-wt0y}}5q3=1GVU`A zP#?@+((SW(m}tkmAV^3{HUf1&vF-MF)8))JlEHV;Z5K;rs-L;O)}S7kC=R1+^-Ld# zjs^b4JUYLz`x0#|J&sPt1JNzX)iU>{Ho)fIJ>lJ>`6&pF=$;9_RXphA3Pg6mm{qX% zjsS6}8vz+UNnO&pA+JMKwTw!p6ln&v1cA1E!_jKcUi zJ(`L-k^t0AqzZnc@OzCWs_OA2GsrkwU)&;2&CKD|#v+We)e4uLka>Egp5rz{-v?B& zA(OCmRMW|+X>R_^01D=Vi^CkPy&Y1HhK}A&HipF?a`KeJ${)wmqaD$rul>ax8|J%1RI|3AvdTA{7RP{J!%`o;c6S7Bt|LxkKu#>O7T!1ln( zQ{!}oxS~foB0G{4mG$7e5I?RNorUEc&)=YSoTes;O2hmabS6EtTq9+C-y@qM@PbS|wr6`|2tp zYuQvOTBQvtyi-3aNN0i)sa2c)5I<&KA}mZK^{d{m{nx)||Ls1{eeON?p7XiqIrluD zbFLin{c^48dQ$*cOWyBE1wi8fP|hY8W(ICDx_oSddMxaPY$_pp#a4k7$0B;_*p@)6ggXIKhoi0)>b2^qbU1Ij_nJr?`xBGPDj9C>A6e) z@K%}~+t)%cfC`G?z`cn@+0m&3ycW-Jv&dvZZnqTC0 z_09EeG~_9|R+6@`u;5ZKu)M5a$9`!DxIErLd=sBHH&N2(d)EkX`8-2uEsOPgr|j;} z>BMa|kXkYmtJk!)4%yoB9}mQ89=C@Mq8Nv6#C@f2JE~?D&z1_}M2Z~au%#ec-T|Fu z`a{*s=W;m$m0Gg+(!y(|nONA6#1O}1)Lw6z8Pn*0YV8W^$ucWO*!@7ca#jbxEz#bx?DF{$$tYOf+bZP88mkoj=aGl<*0*_HZr{ zRVcshaPfJ(lf^pxLNCGf5}G7v6_}o=IC?*uG9E4$Mq~jgwzavnwt+s!e9001n%O{x z!rI-|mgP0M&kv^ug%&x0_DF=erXIu?MX{ORY_*Qf9eMJc4A-H~}Ag+!wzYN%;ifQ^||(=_sg> z`!(|7++C4-k9na%Xu}Q^D%m5>b_CxFAg2A>iCwzFy=r`q`UIo%;Y%838=gwmO z?#4<_G%WR!8A$5fw|&r*@W34nNzcfP>YKp+lZO+=PD>~OKMvFT3~ z*LKkppb z;~$&W0Ir;e@n)N?x#SFtcIO(;xXJ?B24w%ldHbV#qkk|)a3TW@i=gBVE9&YRm2!&n z9w!uV@?s1^cN3kjh}qOFfDcp}df)t4-$b_ON-EO|&M-FUj&)^Zu7Sa*Fm#cxQ&a$f zKVlArUX0#vPwRX>l=V_gVCp-8k@wAj-s|@=)8>DOG=izr|COosC6%hSu9wa#dbZj7 zW)}kV-*U_IM}hgTTF1YhcUOnK|3+Em+DJPL$>puLJ-bGWMF&=`$iDkc2T{q<)qzt`diyQ;a{&ti}0@Z@AM)!&~QpNI`S&#ukKR9 exs#?q>i(_S&5>C~rl@8C8DuYC&x$V?U;iJgWu?ae literal 0 HcmV?d00001 diff --git a/src/plugins/kibana_overview/public/assets/solutions_observability_dark_2x.png b/src/plugins/kibana_overview/public/assets/solutions_observability_dark_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c9dd85ee07f352f2dac633d6c094eb3627890747 GIT binary patch literal 1484 zcmeAS@N?(olHy`uVBq!ia0y~yV3J^9VASJa1B#sS&$b0poCO|{#S9E$svykh8Km+7 zD9BhGOy_gwATrb)e260R} zb}Dwaig}9i5(A-QYZM~xC~!VMaR2brzfXVvO_pY8IJXT*%s9ZnP{hQ{aDtPEK|$Ss zpOy_gvV94>a=3|S_) zr_udO*SuqHTLgJ_J!6ad;~I0XIdl2HGcxn!}PwR`UWcaMMOZhO*118ZpFcEW}NeV5Q4xBp%`_vxn=&!<0R zXJT%M-L+=^=FZo*O3iNVRgP1kQP}25GTd1B`*GzDYdu;!; gZRTTeNdLV6h6JZbQB6gE9{mbvI?y;QA!7l4X}VvK780s=BvlpzC>L2-Q-#r}<$;C3yUXNmh13 zL?|@>==um>NOeeT2taG4?H>oN0A5fzm!T_t4Pmj`uY_*HWstE!D1>hR1Jl!Tp!qoh&%T<$=4B2FN7Xa9Th? z@FfGlLy`jy5-Di>wJiAVKW)ibG~CVT5J1Zg$p&U*?vq~7oVzgX#n-W5RyGnqISH3@ zas(jl+Mlx6+114tLJM)VPUI-WRCq;GqUu*SH!;vLkjpN{5a6$Gze zv}G3g!jhhY1gzds(Hn8kTDqU~HaZo;z~@)k4V_^e7^s|KH|$XzNJ)-`;LX>qnXJM> zi45umW26EN4wN(#2p{ek;X>NJ9Bq6?$7?9+GW4E>aPG06v3o2w4H#OUR{kwm|bjRhUvT>D}?U`1;&6uR?K6v{;oQ-*zTXIU+&>3 z@=BSt0yztogl|Zr!;LxU|EkMQY4pt^6g25Y7GaZKY>9dLZI*cF?PR+8vtEB!djV8+ z+%{}Qvr2WlHvXd6jS?$V%KyHHxNIAEy%_BaPip=I5~)r>vQ}(>&;Ppw{Dc ztz#Jym{=l>UZWXD5d2tc$Tds%kHkoRADJ(V2?~(QYwSy=Na(DSP)Gdb^1?D6OstW{ z03=zVvucY|WNyv-rVzCB;7~$AXt?oa(bx9yPHJ?)Pxme$^YHnq z6n7a+49DE^%CBI`xv&yKlYxH4h}Bs$4%h*?HRFzxhg!FV6(O?0s8HE`XNnI9Qg#~EltNn}mGT z317XRSt4JR@2f;?%h|8k{klwVRp~lVzIZ&JMqi9poAL1<; zkZA{7%a|W#9zLq(LR%QktAFyn1H!lpMVPPkt}0{h(JzwE-mc6(q9QTcNnJJZH8<#4^{W1+og=n{|u~Vt)iXkdS(_JcwWYPT* zm5bc8J0dK43F%HaZPT@F#4sXhD#0*qX;POmEh5r)nNI)oU(;-gamSQG%^YsyB;K=Rw62%P=#nZ*FM9)^cVNOfO8BT<|h7YUj{ zr&)X<^CA)>0J`dV>p8{%CUptX5ou+_Z}&50j&a)#4_}P0Dvc6dx<=}Y|LEKP%P3J` zo{|vY_4SccbH8>^JPEP``I)6e0!(cv2tUIBOp$D`^rAr?q#)or zhYx}z2|y9c2#)@zkhv*EQHTotN<&LXNOK+U+n`{EsMx>H7f04O~ih$7k z0Qxz^_ws}3R##jsUtDEc&JIeQxG;3Ym(|=V95a}NO3mLMx_Di51Wbuxigz6o>)NF=3&LdZmecqHKsnujFzOi8G$CxPmzL^>8Xn` zj`a}1o)5ldZ~8^H&eUkV9w61$l^Pv6sf;2(MCt+$ zT-PEkU$JO`tZ3*9iP3cd6nfEzrb>d5Va1p={XSxuHt}lbyYp8*$s1X|P_eO|K|t0Z zSNDSYt#TzFCVwJzJVTVTVlmQRB%7Dih&LH@rV)3|(uvzxey*4C?mXK|C0xi7lU9l?_LOK#l zwJxA55kjn}9Naaji@~&pB#Ah_R{7}B8+Qu=jG-Y`A|A^BqFnqP_RFW1>7i8wW6e=| zx2U=%=D#o1K99r4nm#r+Cqv%j0$Q_MYOv(8{YC28C_XW3PVE0u%chzGUgHZfCZtJRMXy$LYH(=n1Mw=2jiDBQ_ajM^>E&V z)f|QTw>iorZ2W7Uba1+GdgD+=!0ALn-e9U6Y)`-3e}b~SW@?{rl@)jQ(p u+%(Tv{?B{-S#|%ty0WE(CLV+jt`^?j5L@RsOMk{cH6+9&MQb7@m45*)c4w{t literal 0 HcmV?d00001 diff --git a/src/plugins/kibana_overview/public/components/_index.scss b/src/plugins/kibana_overview/public/components/_index.scss new file mode 100644 index 0000000000000..b8857d171728f --- /dev/null +++ b/src/plugins/kibana_overview/public/components/_index.scss @@ -0,0 +1 @@ +@import 'overview'; diff --git a/src/plugins/kibana_overview/public/components/_overview.scss b/src/plugins/kibana_overview/public/components/_overview.scss new file mode 100644 index 0000000000000..74a58122d4851 --- /dev/null +++ b/src/plugins/kibana_overview/public/components/_overview.scss @@ -0,0 +1,120 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.kbnOverviewWrapper { + background-color: $euiColorEmptyShade; + display: flex; + flex-direction: column; + min-height: calc(100vh - #{$euiHeaderHeightCompensation}); +} + +.kbnOverviewContent { + margin: 0 auto; + max-width: 1200px; + padding: $euiSizeXL $euiSize; + width: 100%; + + // Ensure card heights are stretched equally when wrapped with this element + .kbnRedirectCrossAppLinks { + align-items: flex-start; + display: flex; + flex: 1; + flex-direction: column; + } +} + +.kbnOverviewApps__item { + .kbnOverviewApps__group--primary & { + @include euiBreakpoint('m', 'l', 'xl') { + max-width: calc(50% - #{$euiSizeM * 2}); + } + } + + .kbnOverviewApps__group--secondary & { + @include euiBreakpoint('m', 'l', 'xl') { + max-width: calc(25% - #{$euiSizeM * 2}); + } + } +} + +.kbnOverviewNews__content article { + & + article { + margin-top: $euiSizeL; + } + + &, + header { + & > * + * { + margin-top: $euiSizeXS; + } + } + + h3 { + font-weight: inherit; + } +} + +.kbnOverviewMore__item { + @include euiBreakpoint('m', 'l', 'xl') { + max-width: calc(33.333333333333333% - #{$euiSizeM * 2}); + } +} + +.kbnOverviewSolution__icon { + background-color: $euiColorEmptyShade !important; + box-shadow: none !important; + height: $euiSizeL * 2; + padding: $euiSizeM; + width: $euiSizeL * 2; +} + +.kbnOverviewSupplements--noNews .kbnOverviewMore { + h2 { + @include euiBreakpoint('m', 'l', 'xl') { + text-align: center; + } + } + + .kbnOverviewMore__content { + @include euiBreakpoint('m', 'l', 'xl') { + justify-content: center; + } + } +} + +.kbnOverviewData--expanded { + flex-direction: column; + + &, + & > * { + margin-bottom: 0 !important; + margin-top: 0 !important; + } +} + +// Accounting for no `flush="both"` prop on EuiButtonEmpty +.kbnOverviewDataAdd__actionButton { + margin-right: 0; +} + +.kbnOverviewDataManage__item:not(:only-child) { + @include euiBreakpoint('m', 'l', 'xl') { + flex: 0 0 calc(50% - #{$euiSizeM * 2}); + } +} diff --git a/src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap b/src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap new file mode 100644 index 0000000000000..42623abd79ac0 --- /dev/null +++ b/src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap @@ -0,0 +1,102 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AddData render 1`] = ` +
+ + + +

+ +

+
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+`; diff --git a/src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx b/src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx new file mode 100644 index 0000000000000..f5cdbd9e27e28 --- /dev/null +++ b/src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { AddData } from './add_data'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { FeatureCatalogueCategory } from 'src/plugins/home/public'; + +const mockFeatures = [ + { + category: FeatureCatalogueCategory.DATA, + description: 'Ingest data from popular apps and services.', + showOnHomePage: true, + icon: 'indexOpen', + id: 'home_tutorial_directory', + order: 500, + path: '/app/home#/tutorial_directory', + title: 'Ingest data', + }, + { + category: FeatureCatalogueCategory.ADMIN, + description: 'Add and manage your fleet of Elastic Agents and integrations.', + showOnHomePage: true, + icon: 'indexManagementApp', + id: 'ingestManager', + order: 510, + path: '/app/ingestManager', + title: 'Add Elastic Agent', + }, + { + category: FeatureCatalogueCategory.DATA, + description: 'Import your own CSV, NDJSON, or log file', + showOnHomePage: true, + icon: 'document', + id: 'ml_file_data_visualizer', + order: 520, + path: '/app/ml#/filedatavisualizer', + title: 'Upload a file', + }, +]; + +const addBasePathMock = jest.fn((path: string) => (path ? path : 'path')); + +describe('AddData', () => { + test('render', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/kibana_overview/public/components/add_data/add_data.tsx b/src/plugins/kibana_overview/public/components/add_data/add_data.tsx new file mode 100644 index 0000000000000..e29c2a08395cf --- /dev/null +++ b/src/plugins/kibana_overview/public/components/add_data/add_data.tsx @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CoreStart } from 'kibana/public'; +import { RedirectAppLinks, useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { FeatureCatalogueEntry } from '../../../../../../src/plugins/home/public'; +// @ts-expect-error untyped component +import { Synopsis } from '../synopsis'; + +interface Props { + addBasePath: (path: string) => string; + features: FeatureCatalogueEntry[]; +} + +export const AddData: FC = ({ addBasePath, features }) => { + const { + services: { application }, + } = useKibana(); + + return ( +
+ + + +

+ +

+
+
+ + +
+ + + +
+
+
+ + + + + {features.map((feature) => ( + + + + + + ))} + +
+ ); +}; + +AddData.propTypes = { + addBasePath: PropTypes.func.isRequired, + features: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + showOnHomePage: PropTypes.bool.isRequired, + category: PropTypes.string.isRequired, + order: PropTypes.number, + }) + ), +}; diff --git a/src/plugins/home/public/application/components/_manage_data.scss b/src/plugins/kibana_overview/public/components/add_data/index.ts similarity index 90% rename from src/plugins/home/public/application/components/_manage_data.scss rename to src/plugins/kibana_overview/public/components/add_data/index.ts index 389d8c8b3bf0f..a7d465d177636 100644 --- a/src/plugins/home/public/application/components/_manage_data.scss +++ b/src/plugins/kibana_overview/public/components/add_data/index.ts @@ -17,6 +17,4 @@ * under the License. */ -.homDataManage__content .euiIcon__fillSecondary { - fill: $euiColorDarkestShade; -} +export * from './add_data'; diff --git a/src/plugins/kibana_overview/public/components/app.tsx b/src/plugins/kibana_overview/public/components/app.tsx new file mode 100644 index 0000000000000..bf9211f49a18e --- /dev/null +++ b/src/plugins/kibana_overview/public/components/app.tsx @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useState } from 'react'; +import { Observable } from 'rxjs'; +import { I18nProvider } from '@kbn/i18n/react'; +import { HashRouter as Router, Switch, Route } from 'react-router-dom'; +import { CoreStart } from 'src/core/public'; +import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; +import { FetchResult } from 'src/plugins/newsfeed/public'; +import { FeatureCatalogueEntry, FeatureCatalogueSolution } from 'src/plugins/home/public'; +import { Overview } from './overview'; + +interface KibanaOverviewAppDeps { + basename: string; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + navigation: NavigationPublicPluginStart; + newsfeed$?: Observable; + solutions: FeatureCatalogueSolution[]; + features: FeatureCatalogueEntry[]; +} + +export const KibanaOverviewApp = ({ + basename, + newsfeed$, + solutions, + features, +}: KibanaOverviewAppDeps) => { + const [newsFetchResult, setNewsFetchResult] = useState(null); + + useEffect(() => { + if (newsfeed$) { + const subscription = newsfeed$.subscribe((res: FetchResult | void | null) => { + setNewsFetchResult(res); + }); + + return () => subscription.unsubscribe(); + } + }, [newsfeed$]); + + return ( + + + + + + + + + + ); +}; diff --git a/src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap b/src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap new file mode 100644 index 0000000000000..374715a277ebc --- /dev/null +++ b/src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap @@ -0,0 +1,391 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GettingStarted dark mode on 1`] = ` +
+ + +
+ +

+ +

+
+ + +

+ +

+
+ + + + + } + layout="horizontal" + paddingSize="none" + title="Dashboard" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Discover" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Canvas" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Maps" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Machine Learning" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Graph" + titleElement="h3" + titleSize="xs" + /> + + + + + + + + +
+
+ + + +
+
+`; + +exports[`GettingStarted render 1`] = ` +
+ + +
+ +

+ +

+
+ + +

+ +

+
+ + + + + } + layout="horizontal" + paddingSize="none" + title="Dashboard" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Discover" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Canvas" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Maps" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Machine Learning" + titleElement="h3" + titleSize="xs" + /> + + + + } + layout="horizontal" + paddingSize="none" + title="Graph" + titleElement="h3" + titleSize="xs" + /> + + + + + + + + +
+
+ + + +
+
+`; diff --git a/src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx b/src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx new file mode 100644 index 0000000000000..7d40c4174f39b --- /dev/null +++ b/src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx @@ -0,0 +1,118 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { GettingStarted } from './getting_started'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { FeatureCatalogueCategory } from 'src/plugins/home/public'; + +const addBasePathMock = jest.fn((path: string) => (path ? path : 'path')); + +const mockApps = [ + { + category: FeatureCatalogueCategory.DATA, + description: 'Display and share a collection of visualizations and saved searches.', + icon: 'dashboardApp', + id: 'dashboard', + order: 100, + path: 'path-to-dashboard', + showOnHomePage: false, + solutionId: 'kibana', + subtitle: 'Analyze data in dashboards.', + title: 'Dashboard', + }, + { + category: FeatureCatalogueCategory.DATA, + description: 'Interactively explore your data by querying and filtering raw documents.', + icon: 'discoverApp', + id: 'discover', + order: 200, + path: 'path-to-discover', + + showOnHomePage: false, + solutionId: 'kibana', + subtitle: 'Search and find insights.', + title: 'Discover', + }, + { + category: FeatureCatalogueCategory.DATA, + description: 'Showcase your data in a pixel-perfect way.', + icon: 'canvasApp', + id: 'canvas', + order: 300, + path: 'path-to-canvas', + + showOnHomePage: false, + solutionId: 'kibana', + subtitle: 'Design pixel-perfect reports.', + title: 'Canvas', + }, + { + category: FeatureCatalogueCategory.DATA, + description: 'Explore geospatial data from Elasticsearch and the Elastic Maps Service.', + icon: 'gisApp', + id: 'maps', + order: 400, + path: 'path-to-maps', + showOnHomePage: false, + solutionId: 'kibana', + subtitle: 'Plot geographic data.', + title: 'Maps', + }, + { + category: FeatureCatalogueCategory.DATA, + description: + 'Automatically model the normal behavior of your time series data to detect anomalies.', + icon: 'machineLearningApp', + id: 'ml', + order: 500, + path: 'path-to-ml', + showOnHomePage: false, + solutionId: 'kibana', + subtitle: 'Model, predict, and detect.', + title: 'Machine Learning', + }, + { + category: FeatureCatalogueCategory.DATA, + description: 'Surface and analyze relevant relationships in your Elasticsearch data.', + icon: 'graphApp', + id: 'graph', + order: 600, + path: 'path-to-graph', + showOnHomePage: false, + solutionId: 'kibana', + subtitle: 'Reveal patterns and relationships.', + title: 'Graph', + }, +]; + +describe('GettingStarted', () => { + test('render', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchSnapshot(); + }); + test('dark mode on', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx b/src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx new file mode 100644 index 0000000000000..9f2d714f43a53 --- /dev/null +++ b/src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx @@ -0,0 +1,126 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FC } from 'react'; +import { + EuiButton, + EuiCard, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiImage, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CoreStart } from 'kibana/public'; +import { RedirectAppLinks, useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { FeatureCatalogueEntry } from '../../../../../../src/plugins/home/public'; +import { PLUGIN_ID } from '../../../common'; + +interface Props { + addBasePath: (path: string) => string; + isDarkTheme: boolean; + apps: FeatureCatalogueEntry[]; +} + +export const GettingStarted: FC = ({ addBasePath, isDarkTheme, apps }) => { + const { + services: { application }, + } = useKibana(); + const gettingStartedGraphicURL = `/plugins/${PLUGIN_ID}/assets/kibana_montage_${ + isDarkTheme ? 'dark' : 'light' + }.svg`; + + return ( +
+ + +
+ +

+ +

+
+ + + + +

+ +

+
+ + + + + {apps.map(({ subtitle = '', icon, title }) => ( + + } + layout="horizontal" + paddingSize="none" + title={title} + titleElement="h3" + titleSize="xs" + /> + + ))} + + + + + + + + + +
+
+ + + + +
+
+ ); +}; diff --git a/src/plugins/kibana_overview/public/components/getting_started/index.ts b/src/plugins/kibana_overview/public/components/getting_started/index.ts new file mode 100644 index 0000000000000..6a0df12de2d2d --- /dev/null +++ b/src/plugins/kibana_overview/public/components/getting_started/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './getting_started'; diff --git a/src/plugins/kibana_overview/public/components/manage_data/__snapshots__/manage_data.test.tsx.snap b/src/plugins/kibana_overview/public/components/manage_data/__snapshots__/manage_data.test.tsx.snap new file mode 100644 index 0000000000000..4be9e4df6b736 --- /dev/null +++ b/src/plugins/kibana_overview/public/components/manage_data/__snapshots__/manage_data.test.tsx.snap @@ -0,0 +1,103 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManageData render 1`] = ` + + +`; + +exports[`ManageData render empty without any features 1`] = ``; diff --git a/src/plugins/kibana_overview/public/components/manage_data/index.tsx b/src/plugins/kibana_overview/public/components/manage_data/index.tsx new file mode 100644 index 0000000000000..2845e3bd12023 --- /dev/null +++ b/src/plugins/kibana_overview/public/components/manage_data/index.tsx @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './manage_data'; diff --git a/src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx b/src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx new file mode 100644 index 0000000000000..3ce2364c96083 --- /dev/null +++ b/src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { ManageData } from './manage_data'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { FeatureCatalogueCategory } from 'src/plugins/home/public'; + +const mockFeatures = [ + { + category: FeatureCatalogueCategory.ADMIN, + description: 'Control who has access and what tasks they can perform.', + icon: 'securityApp', + id: 'security', + order: 600, + path: 'path-to-security-roles', + title: 'Protect your data', + showOnHomePage: true, + }, + { + category: FeatureCatalogueCategory.ADMIN, + description: 'Track the real-time health and performance of your deployment.', + icon: 'monitoringApp', + id: 'monitoring', + order: 610, + path: 'path-to-monitoring', + title: 'Monitor the stack', + showOnHomePage: true, + }, + { + category: FeatureCatalogueCategory.ADMIN, + description: + 'Save snapshots to a backup repository, and restore to recover index and cluster state.', + icon: 'storage', + id: 'snapshot_restore', + order: 630, + path: 'path-to-snapshot-restore', + title: 'Store & recover backups', + showOnHomePage: true, + }, + { + category: FeatureCatalogueCategory.ADMIN, + description: 'Define lifecycle policies to automatically perform operations as an index ages.', + icon: 'indexSettings', + id: 'index_lifecycle_management', + order: 640, + path: 'path-to-index-lifecycle-management', + title: 'Manage index lifecycles', + showOnHomePage: true, + }, +]; + +const addBasePathMock = jest.fn((path: string) => (path ? path : 'path')); + +describe('ManageData', () => { + test('render', () => { + const component = shallowWithIntl( + + ); + expect(component).toMatchSnapshot(); + }); + + test('render empty without any features', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/kibana_overview/public/components/manage_data/manage_data.tsx b/src/plugins/kibana_overview/public/components/manage_data/manage_data.tsx new file mode 100644 index 0000000000000..f7a40b9370efd --- /dev/null +++ b/src/plugins/kibana_overview/public/components/manage_data/manage_data.tsx @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FC } from 'react'; +import PropTypes from 'prop-types'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CoreStart } from 'kibana/public'; +import { RedirectAppLinks, useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { FeatureCatalogueEntry } from '../../../../../../src/plugins/home/public'; +// @ts-expect-error untyped component +import { Synopsis } from '../synopsis'; + +interface Props { + addBasePath: (path: string) => string; + features: FeatureCatalogueEntry[]; +} + +export const ManageData: FC = ({ addBasePath, features }) => { + const { + services: { application }, + } = useKibana(); + return ( + <> + {features.length > 1 ?