diff --git a/src/plugins/inspector/public/views/requests/components/request_selector.tsx b/src/plugins/inspector/public/views/requests/components/request_selector.tsx
index 2d94c7ff5bb18..04fac0bd93b7e 100644
--- a/src/plugins/inspector/public/views/requests/components/request_selector.tsx
+++ b/src/plugins/inspector/public/views/requests/components/request_selector.tsx
@@ -13,118 +13,73 @@ import { i18n } from '@kbn/i18n';
import {
EuiBadge,
- EuiButtonEmpty,
- EuiContextMenuPanel,
- EuiContextMenuItem,
+ EuiComboBox,
+ EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
- EuiPopover,
- EuiTextColor,
EuiToolTip,
} from '@elastic/eui';
import { RequestStatus } from '../../../../common/adapters';
import { Request } from '../../../../common/adapters/request/types';
-interface RequestSelectorState {
- isPopoverOpen: boolean;
-}
-
interface RequestSelectorProps {
requests: Request[];
selectedRequest: Request;
- onRequestChanged: Function;
+ onRequestChanged: (request: Request) => void;
}
-export class RequestSelector extends Component {
+export class RequestSelector extends Component {
static propTypes = {
requests: PropTypes.array.isRequired,
selectedRequest: PropTypes.object.isRequired,
onRequestChanged: PropTypes.func,
};
- state = {
- isPopoverOpen: false,
- };
+ handleSelected = (selectedOptions: Array>) => {
+ const selectedOption = this.props.requests.find(
+ (request) => request.id === selectedOptions[0].value
+ );
- togglePopover = () => {
- this.setState((prevState: RequestSelectorState) => ({
- isPopoverOpen: !prevState.isPopoverOpen,
- }));
+ if (selectedOption) {
+ this.props.onRequestChanged(selectedOption);
+ }
};
- closePopover = () => {
- this.setState({
- isPopoverOpen: false,
+ renderRequestCombobox() {
+ const options = this.props.requests.map((item) => {
+ const hasFailed = item.status === RequestStatus.ERROR;
+ const testLabel = item.name.replace(/\s+/, '_');
+
+ return {
+ 'data-test-subj': `inspectorRequestChooser${testLabel}`,
+ label: hasFailed
+ ? `${item.name} ${i18n.translate('inspector.requests.failedLabel', {
+ defaultMessage: ' (failed)',
+ })}`
+ : item.name,
+ value: item.id,
+ };
});
- };
-
- renderRequestDropdownItem = (request: Request, index: number) => {
- const hasFailed = request.status === RequestStatus.ERROR;
- const inProgress = request.status === RequestStatus.PENDING;
return (
- {
- this.props.onRequestChanged(request);
- this.closePopover();
- }}
- toolTipContent={request.description}
- toolTipPosition="left"
- data-test-subj={`inspectorRequestChooser${request.name}`}
- >
-
- {request.name}
-
- {hasFailed && (
-
- )}
-
- {inProgress && (
-
- )}
-
-
- );
- };
-
- renderRequestDropdown() {
- const button = (
-
- {this.props.selectedRequest.name}
-
- );
-
- return (
-
-
-
+ isClearable={false}
+ onChange={this.handleSelected}
+ options={options}
+ prepend="Request"
+ selectedOptions={[
+ {
+ label: this.props.selectedRequest.name,
+ value: this.props.selectedRequest.id,
+ },
+ ]}
+ singleSelection={{ asPlainText: true }}
+ />
);
}
@@ -132,23 +87,8 @@ export class RequestSelector extends Component
-
-
-
-
-
-
- {requests.length <= 1 && (
-
- {selectedRequest.name}
-
- )}
- {requests.length > 1 && this.renderRequestDropdown()}
-
+
+ {requests.length && this.renderRequestCombobox()}
{selectedRequest.status !== RequestStatus.PENDING && (
{
});
it('should sort rows by column and pass the sorted rows for consumers', () => {
+ (createTableVisCell as jest.Mock).mockClear();
const uiStateProps = {
...props.uiStateProps,
sort: {
@@ -96,7 +97,7 @@ describe('TableVisBasic', () => {
visConfig={{ ...props.visConfig, showToolbar: true }}
/>
);
- expect(createTableVisCell).toHaveBeenCalledWith(sortedRows, table.formattedColumns);
+ expect(createTableVisCell).toHaveBeenCalledWith(sortedRows, table.formattedColumns, undefined);
expect(createGridColumns).toHaveBeenCalledWith(
table.columns,
sortedRows,
diff --git a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx
index e627b9e7f92f2..cfe1ce5d40a1e 100644
--- a/src/plugins/vis_types/table/public/components/table_vis_basic.tsx
+++ b/src/plugins/vis_types/table/public/components/table_vis_basic.tsx
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React, { memo, useCallback, useMemo } from 'react';
+import React, { memo, useCallback, useMemo, useEffect, useState, useRef } from 'react';
import { EuiDataGrid, EuiDataGridProps, EuiDataGridSorting, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { orderBy } from 'lodash';
@@ -47,8 +47,16 @@ export const TableVisBasic = memo(
// renderCellValue is a component which renders a cell based on column and row indexes
const renderCellValue = useMemo(
- () => createTableVisCell(sortedRows, formattedColumns),
- [formattedColumns, sortedRows]
+ () => createTableVisCell(sortedRows, formattedColumns, visConfig.autoFitRowToContent),
+ [formattedColumns, sortedRows, visConfig.autoFitRowToContent]
+ );
+
+ const rowHeightsOptions = useMemo(
+ () =>
+ visConfig.autoFitRowToContent
+ ? ({ defaultHeight: 'auto' } as unknown as EuiDataGridProps['rowHeightsOptions'])
+ : undefined,
+ [visConfig.autoFitRowToContent]
);
// Columns config
@@ -103,6 +111,26 @@ export const TableVisBasic = memo(
[columns, setColumnsWidth]
);
+ const firstRender = useRef(true);
+ const [dataGridUpdateCounter, setDataGridUpdateCounter] = useState(0);
+
+ // key was added as temporary solution to force re-render if we change autoFitRowToContent or we get new data
+ // cause we have problem with correct updating height cache in EUI datagrid when we use auto-height
+ // will be removed as soon as fix problem on EUI side
+ useEffect(() => {
+ // skip first render
+ if (firstRender.current) {
+ firstRender.current = false;
+ return;
+ }
+ // skip if auto height was turned off
+ if (!visConfig.autoFitRowToContent) {
+ return;
+ }
+ // update counter to remount grid from scratch
+ setDataGridUpdateCounter((counter) => counter + 1);
+ }, [visConfig.autoFitRowToContent, table, sort, pagination, columnsWidth]);
+
return (
<>
{title && (
@@ -111,12 +139,14 @@ export const TableVisBasic = memo(
)}
id),
diff --git a/src/plugins/vis_types/table/public/components/table_vis_cell.tsx b/src/plugins/vis_types/table/public/components/table_vis_cell.tsx
index 9749cdcb5740c..7d7af447db489 100644
--- a/src/plugins/vis_types/table/public/components/table_vis_cell.tsx
+++ b/src/plugins/vis_types/table/public/components/table_vis_cell.tsx
@@ -13,7 +13,7 @@ import { DatatableRow } from 'src/plugins/expressions';
import { FormattedColumns } from '../types';
export const createTableVisCell =
- (rows: DatatableRow[], formattedColumns: FormattedColumns) =>
+ (rows: DatatableRow[], formattedColumns: FormattedColumns, autoFitRowToContent?: boolean) =>
({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => {
const rowValue = rows[rowIndex][columnId];
const column = formattedColumns[columnId];
@@ -28,7 +28,7 @@ export const createTableVisCell =
*/
dangerouslySetInnerHTML={{ __html: content }} // eslint-disable-line react/no-danger
data-test-subj="tbvChartCellContent"
- className="tbvChartCellContent"
+ className={autoFitRowToContent ? '' : 'tbvChartCellContent'}
/>
);
diff --git a/src/plugins/vis_types/table/public/components/table_vis_options.tsx b/src/plugins/vis_types/table/public/components/table_vis_options.tsx
index 8a6b8586fce7d..698aca6034a6b 100644
--- a/src/plugins/vis_types/table/public/components/table_vis_options.tsx
+++ b/src/plugins/vis_types/table/public/components/table_vis_options.tsx
@@ -93,6 +93,16 @@ function TableOptions({
data-test-subj="showMetricsAtAllLevels"
/>
+
+
{
splitColumn: undefined,
splitRow: undefined,
showMetricsAtAllLevels: false,
+ autoFitRowToContent: false,
sort: {
columnIndex: null,
direction: null,
diff --git a/src/plugins/vis_types/table/public/table_vis_fn.ts b/src/plugins/vis_types/table/public/table_vis_fn.ts
index ebddb0b4b7fef..861923ef5086e 100644
--- a/src/plugins/vis_types/table/public/table_vis_fn.ts
+++ b/src/plugins/vis_types/table/public/table_vis_fn.ts
@@ -118,6 +118,11 @@ export const createTableVisFn = (): TableExpressionFunctionDefinition => ({
defaultMessage: 'Specifies calculating function for the total row. Possible options are: ',
}),
},
+ autoFitRowToContent: {
+ types: ['boolean'],
+ help: '',
+ default: false,
+ },
},
fn(input, args, handlers) {
const convertedData = tableVisResponseHandler(input, args);
diff --git a/src/plugins/vis_types/table/public/table_vis_type.ts b/src/plugins/vis_types/table/public/table_vis_type.ts
index 4664e87cea79b..a641224e23f52 100644
--- a/src/plugins/vis_types/table/public/table_vis_type.ts
+++ b/src/plugins/vis_types/table/public/table_vis_type.ts
@@ -35,6 +35,7 @@ export const tableVisTypeDefinition: VisTypeDefinition = {
showToolbar: false,
totalFunc: 'sum',
percentageCol: '',
+ autoFitRowToContent: false,
},
},
editorConfig: {
diff --git a/src/plugins/vis_types/table/public/to_ast.ts b/src/plugins/vis_types/table/public/to_ast.ts
index 8e1c92c8dde4f..0268708f22dfe 100644
--- a/src/plugins/vis_types/table/public/to_ast.ts
+++ b/src/plugins/vis_types/table/public/to_ast.ts
@@ -64,6 +64,7 @@ export const toExpressionAst: VisToExpressionAst = (vis, params)
showMetricsAtAllLevels: vis.params.showMetricsAtAllLevels,
showToolbar: vis.params.showToolbar,
showTotal: vis.params.showTotal,
+ autoFitRowToContent: vis.params.autoFitRowToContent,
totalFunc: vis.params.totalFunc,
title: vis.title,
metrics: metrics.map(prepareDimension),
diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts
index 9b2d6bfe25b32..48f850539c20c 100644
--- a/src/plugins/visualizations/public/mocks.ts
+++ b/src/plugins/visualizations/public/mocks.ts
@@ -33,9 +33,6 @@ const createStartContract = (): VisualizationsStart => ({
getAliases: jest.fn(),
getByGroup: jest.fn(),
unRegisterAlias: jest.fn(),
- savedVisualizationsLoader: {
- get: jest.fn(),
- } as any,
getSavedVisualization: jest.fn(),
saveVisualization: jest.fn(),
findListItems: jest.fn(),
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index 87095f5c389ed..60c50d018252b 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -18,7 +18,6 @@ import {
setUsageCollector,
setExpressions,
setUiActions,
- setSavedVisualizationsLoader,
setTimeFilter,
setAggs,
setChrome,
@@ -39,7 +38,6 @@ import { visDimension as visDimensionExpressionFunction } from '../common/expres
import { xyDimension as xyDimensionExpressionFunction } from '../common/expression_functions/xy_dimension';
import { createStartServicesGetter, StartServicesGetter } from '../../kibana_utils/public';
-import { createSavedVisLoader, SavedVisualizationsLoader } from './saved_visualizations';
import type { SerializedVis, Vis } from './vis';
import { showNewVisModal } from './wizard';
@@ -83,7 +81,6 @@ import type { VisSavedObject, SaveVisOptions, GetVisOptions } from './types';
export type VisualizationsSetup = TypesSetup;
export interface VisualizationsStart extends TypesStart {
- savedVisualizationsLoader: SavedVisualizationsLoader;
createVis: (visType: string, visState: SerializedVis) => Promise;
convertToSerializedVis: typeof convertToSerializedVis;
convertFromSerializedVis: typeof convertFromSerializedVis;
@@ -194,14 +191,6 @@ export class VisualizationsPlugin
setSpaces(spaces);
}
- const savedVisualizationsLoader = createSavedVisLoader({
- savedObjectsClient: core.savedObjects.client,
- indexPatterns: data.indexPatterns,
- savedObjects,
- visualizationTypes: types,
- });
- setSavedVisualizationsLoader(savedVisualizationsLoader);
-
return {
...types,
showNewVisModal,
@@ -236,7 +225,6 @@ export class VisualizationsPlugin
await createVisAsync(visType, visState),
convertToSerializedVis,
convertFromSerializedVis,
- savedVisualizationsLoader,
__LEGACY: {
createVisEmbeddableFromObject: createVisEmbeddableFromObject({
start: this.getStartServicesOrDie!,
diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
deleted file mode 100644
index 9107805185fe3..0000000000000
--- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-/**
- * @name SavedVis
- *
- * @extends SavedObject.
- *
- * NOTE: It's a type of SavedObject, but specific to visualizations.
- */
-import type { SavedObjectsStart, SavedObject } from '../../../../plugins/saved_objects/public';
-// @ts-ignore
-import { updateOldState } from '../legacy/vis_update_state';
-import { extractReferences, injectReferences } from '../utils/saved_visualization_references';
-import type { SavedObjectsClientContract } from '../../../../core/public';
-import type { IndexPatternsContract } from '../../../../plugins/data/public';
-import type { ISavedVis } from '../types';
-
-export interface SavedVisServices {
- savedObjectsClient: SavedObjectsClientContract;
- savedObjects: SavedObjectsStart;
- indexPatterns: IndexPatternsContract;
-}
-
-/** @deprecated **/
-export function createSavedVisClass(services: SavedVisServices) {
- class SavedVis extends services.savedObjects.SavedObjectClass {
- public static type: string = 'visualization';
- public static mapping: Record = {
- title: 'text',
- visState: 'json',
- uiStateJSON: 'text',
- description: 'text',
- savedSearchId: 'keyword',
- version: 'integer',
- };
- // Order these fields to the top, the rest are alphabetical
- public static fieldOrder = ['title', 'description'];
-
- constructor(opts: Record | string = {}) {
- if (typeof opts !== 'object') {
- opts = { id: opts };
- }
- const visState = !opts.type ? null : { type: opts.type };
- // Gives our SavedWorkspace the properties of a SavedObject
- super({
- type: SavedVis.type,
- mapping: SavedVis.mapping,
- extractReferences,
- injectReferences,
- id: (opts.id as string) || '',
- indexPattern: opts.indexPattern,
- defaults: {
- title: '',
- visState,
- uiStateJSON: '{}',
- description: '',
- savedSearchId: opts.savedSearchId,
- version: 1,
- },
- afterESResp: async (savedObject: SavedObject) => {
- const savedVis = savedObject as any as ISavedVis;
- savedVis.visState = await updateOldState(savedVis.visState);
- if (savedVis.searchSourceFields?.index) {
- await services.indexPatterns.get(savedVis.searchSourceFields.index as any);
- }
- return savedVis as any as SavedObject;
- },
- });
- this.showInRecentlyAccessed = true;
- this.getFullPath = () => {
- return `/app/visualize#/edit/${this.id}`;
- };
- }
- }
-
- return SavedVis as unknown as new (opts: Record | string) => SavedObject;
-}
diff --git a/src/plugins/visualizations/public/saved_visualizations/find_list_items.test.ts b/src/plugins/visualizations/public/saved_visualizations/find_list_items.test.ts
deleted file mode 100644
index 229f5a4ffd05c..0000000000000
--- a/src/plugins/visualizations/public/saved_visualizations/find_list_items.test.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { findListItems } from './find_list_items';
-import { coreMock } from '../../../../core/public/mocks';
-import { SavedObjectsClientContract } from '../../../../core/public';
-import { VisTypeAlias } from '../vis_types';
-
-describe('saved_visualizations', () => {
- function testProps() {
- const savedObjects = coreMock.createStart().savedObjects
- .client as jest.Mocked;
- (savedObjects.find as jest.Mock).mockImplementation(() => ({
- total: 0,
- savedObjects: [],
- }));
- return {
- visTypes: [],
- search: '',
- size: 10,
- savedObjectsClient: savedObjects,
- mapSavedObjectApiHits: jest.fn(),
- };
- }
-
- it('searches visualization title and description', async () => {
- const props = testProps();
- const { find } = props.savedObjectsClient;
- await findListItems(props);
- expect(find.mock.calls).toMatchObject([
- [
- {
- type: ['visualization'],
- searchFields: ['title^3', 'description'],
- },
- ],
- ]);
- });
-
- it('searches searchFields and types specified by app extensions', async () => {
- const props = {
- ...testProps(),
- visTypes: [
- {
- appExtensions: {
- visualizations: {
- docTypes: ['bazdoc', 'etc'],
- searchFields: ['baz', 'bing'],
- },
- },
- } as VisTypeAlias,
- ],
- };
- const { find } = props.savedObjectsClient;
- await findListItems(props);
- expect(find.mock.calls).toMatchObject([
- [
- {
- type: ['bazdoc', 'etc', 'visualization'],
- searchFields: ['baz', 'bing', 'title^3', 'description'],
- },
- ],
- ]);
- });
-
- it('deduplicates types and search fields', async () => {
- const props = {
- ...testProps(),
- visTypes: [
- {
- appExtensions: {
- visualizations: {
- docTypes: ['bazdoc', 'bar'],
- searchFields: ['baz', 'bing', 'barfield'],
- },
- },
- } as VisTypeAlias,
- {
- appExtensions: {
- visualizations: {
- docTypes: ['visualization', 'foo', 'bazdoc'],
- searchFields: ['baz', 'bing', 'foofield'],
- },
- },
- } as VisTypeAlias,
- ],
- };
- const { find } = props.savedObjectsClient;
- await findListItems(props);
- expect(find.mock.calls).toMatchObject([
- [
- {
- type: ['bazdoc', 'bar', 'visualization', 'foo'],
- searchFields: ['baz', 'bing', 'barfield', 'foofield', 'title^3', 'description'],
- },
- ],
- ]);
- });
-
- it('searches the search term prefix', async () => {
- const props = {
- ...testProps(),
- search: 'ahoythere',
- };
- const { find } = props.savedObjectsClient;
- await findListItems(props);
- expect(find.mock.calls).toMatchObject([
- [
- {
- search: 'ahoythere*',
- },
- ],
- ]);
- });
-
- it('searches with references', async () => {
- const props = {
- ...testProps(),
- references: [
- { type: 'foo', id: 'hello' },
- { type: 'bar', id: 'dolly' },
- ],
- };
- const { find } = props.savedObjectsClient;
- await findListItems(props);
- expect(find.mock.calls).toMatchObject([
- [
- {
- hasReference: [
- { type: 'foo', id: 'hello' },
- { type: 'bar', id: 'dolly' },
- ],
- },
- ],
- ]);
- });
-
- it('uses type-specific toListItem function, if available', async () => {
- const props = {
- ...testProps(),
- mapSavedObjectApiHits(savedObject: {
- id: string;
- type: string;
- attributes: { title: string };
- }) {
- return {
- id: savedObject.id,
- title: `DEFAULT ${savedObject.attributes.title}`,
- };
- },
- visTypes: [
- {
- appExtensions: {
- visualizations: {
- docTypes: ['wizard'],
- toListItem(savedObject) {
- return {
- id: savedObject.id,
- title: `${(savedObject.attributes as { label: string }).label} THE GRAY`,
- };
- },
- },
- },
- } as VisTypeAlias,
- ],
- };
-
- (props.savedObjectsClient.find as jest.Mock).mockImplementationOnce(async () => ({
- total: 2,
- savedObjects: [
- {
- id: 'lotr',
- type: 'wizard',
- attributes: { label: 'Gandalf' },
- },
- {
- id: 'wat',
- type: 'visualization',
- attributes: { title: 'WATEVER' },
- },
- ],
- }));
-
- const items = await findListItems(props);
- expect(items).toEqual({
- total: 2,
- hits: [
- {
- id: 'lotr',
- title: 'Gandalf THE GRAY',
- },
- {
- id: 'wat',
- title: 'DEFAULT WATEVER',
- },
- ],
- });
- });
-});
diff --git a/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts b/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts
deleted file mode 100644
index f000b18413ce3..0000000000000
--- a/src/plugins/visualizations/public/saved_visualizations/find_list_items.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import _ from 'lodash';
-import {
- SavedObjectAttributes,
- SavedObjectsClientContract,
- SavedObjectsFindOptionsReference,
- SavedObjectsFindOptions,
-} from '../../../../core/public';
-import { SavedObjectLoader } from '../../../../plugins/saved_objects/public';
-import type { VisTypeAlias } from '../vis_types';
-import { VisualizationsAppExtension } from '../vis_types/vis_type_alias_registry';
-
-/**
- * Search for visualizations and convert them into a list display-friendly format.
- */
-export async function findListItems({
- visTypes,
- search,
- size,
- savedObjectsClient,
- mapSavedObjectApiHits,
- references,
-}: {
- search: string;
- size: number;
- visTypes: VisTypeAlias[];
- savedObjectsClient: SavedObjectsClientContract;
- mapSavedObjectApiHits: SavedObjectLoader['mapSavedObjectApiHits'];
- references?: SavedObjectsFindOptionsReference[];
-}) {
- const extensions = visTypes
- .map((v) => v.appExtensions?.visualizations)
- .filter(Boolean) as VisualizationsAppExtension[];
- const extensionByType = extensions.reduce((acc, m) => {
- return m!.docTypes.reduce((_acc, type) => {
- acc[type] = m;
- return acc;
- }, acc);
- }, {} as { [visType: string]: VisualizationsAppExtension });
- const searchOption = (field: string, ...defaults: string[]) =>
- _(extensions).map(field).concat(defaults).compact().flatten().uniq().value() as string[];
- const searchOptions: SavedObjectsFindOptions = {
- type: searchOption('docTypes', 'visualization'),
- searchFields: searchOption('searchFields', 'title^3', 'description'),
- search: search ? `${search}*` : undefined,
- perPage: size,
- page: 1,
- defaultSearchOperator: 'AND' as 'AND',
- hasReference: references,
- };
-
- const { total, savedObjects } = await savedObjectsClient.find(
- searchOptions
- );
-
- return {
- total,
- hits: savedObjects.map((savedObject) => {
- const config = extensionByType[savedObject.type];
-
- if (config) {
- return {
- ...config.toListItem(savedObject),
- references: savedObject.references,
- };
- } else {
- return mapSavedObjectApiHits(savedObject);
- }
- }),
- };
-}
diff --git a/src/plugins/visualizations/public/saved_visualizations/index.ts b/src/plugins/visualizations/public/saved_visualizations/index.ts
deleted file mode 100644
index e42348bc0b434..0000000000000
--- a/src/plugins/visualizations/public/saved_visualizations/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-export * from './saved_visualizations';
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts
deleted file mode 100644
index cec65b8f988b3..0000000000000
--- a/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import type { SavedObjectReference, SavedObjectsFindOptionsReference } from 'kibana/public';
-import { SavedObjectLoader } from '../../../../plugins/saved_objects/public';
-import { findListItems } from './find_list_items';
-import { createSavedVisClass, SavedVisServices } from './_saved_vis';
-import type { TypesStart } from '../vis_types';
-
-export interface SavedVisServicesWithVisualizations extends SavedVisServices {
- visualizationTypes: TypesStart;
-}
-export type SavedVisualizationsLoader = ReturnType;
-
-export interface FindListItemsOptions {
- size?: number;
- references?: SavedObjectsFindOptionsReference[];
-}
-
-/** @deprecated **/
-export function createSavedVisLoader(services: SavedVisServicesWithVisualizations) {
- const { savedObjectsClient, visualizationTypes } = services;
-
- class SavedObjectLoaderVisualize extends SavedObjectLoader {
- mapHitSource = (
- source: Record,
- id: string,
- references: SavedObjectReference[] = []
- ) => {
- const visTypes = visualizationTypes;
- source.id = id;
- source.references = references;
- source.url = this.urlFor(id);
-
- let typeName = source.typeName;
- if (source.visState) {
- try {
- typeName = JSON.parse(String(source.visState)).type;
- } catch (e) {
- /* missing typename handled below */
- }
- }
-
- if (!typeName || !visTypes.get(typeName)) {
- source.error = 'Unknown visualization type';
- return source;
- }
-
- source.type = visTypes.get(typeName);
- source.savedObjectType = 'visualization';
- source.icon = source.type.icon;
- source.image = source.type.image;
- source.typeTitle = source.type.title;
- source.editUrl = `/edit/${id}`;
-
- return source;
- };
- urlFor(id: string) {
- return `#/edit/${encodeURIComponent(id)}`;
- }
- // This behaves similarly to find, except it returns visualizations that are
- // defined as appExtensions and which may not conform to type: visualization
- findListItems(search: string = '', sizeOrOptions: number | FindListItemsOptions = 100) {
- const { size = 100, references = undefined } =
- typeof sizeOrOptions === 'number'
- ? {
- size: sizeOrOptions,
- }
- : sizeOrOptions;
- return findListItems({
- search,
- size,
- references,
- mapSavedObjectApiHits: this.mapSavedObjectApiHits.bind(this),
- savedObjectsClient,
- visTypes: visualizationTypes.getAliases(),
- });
- }
- }
- const SavedVis = createSavedVisClass(services);
- return new SavedObjectLoaderVisualize(SavedVis, savedObjectsClient) as SavedObjectLoader & {
- findListItems: (search: string, sizeOrOptions?: number | FindListItemsOptions) => any;
- };
-}
diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts
index ed18884d9dc83..95f5fa02c09a8 100644
--- a/src/plugins/visualizations/public/services.ts
+++ b/src/plugins/visualizations/public/services.ts
@@ -18,13 +18,11 @@ import type {
} from '../../../core/public';
import type { TypesStart } from './vis_types';
import { createGetterSetter } from '../../../plugins/kibana_utils/public';
-import type { DataPublicPluginStart, TimefilterContract } from '../../../plugins/data/public';
-import type { UsageCollectionSetup } from '../../../plugins/usage_collection/public';
-import type { ExpressionsStart } from '../../../plugins/expressions/public';
-import type { UiActionsStart } from '../../../plugins/ui_actions/public';
-import type { SavedVisualizationsLoader } from './saved_visualizations';
-import type { EmbeddableStart } from '../../embeddable/public';
-
+import { DataPublicPluginStart, TimefilterContract } from '../../../plugins/data/public';
+import { UsageCollectionSetup } from '../../../plugins/usage_collection/public';
+import { ExpressionsStart } from '../../../plugins/expressions/public';
+import { UiActionsStart } from '../../../plugins/ui_actions/public';
+import { EmbeddableStart } from '../../embeddable/public';
import type { SpacesPluginStart } from '../../../../x-pack/plugins/spaces/public';
export const [getUISettings, setUISettings] = createGetterSetter('UISettings');
@@ -57,9 +55,6 @@ export const [getExpressions, setExpressions] = createGetterSetter('UiActions');
-export const [getSavedVisualizationsLoader, setSavedVisualizationsLoader] =
- createGetterSetter('SavedVisualisationsLoader');
-
export const [getAggs, setAggs] =
createGetterSetter('AggConfigs');
diff --git a/src/plugins/visualizations/server/saved_objects/visualization.ts b/src/plugins/visualizations/server/saved_objects/visualization.ts
index 53027d5d5046c..0793893f1d3d5 100644
--- a/src/plugins/visualizations/server/saved_objects/visualization.ts
+++ b/src/plugins/visualizations/server/saved_objects/visualization.ts
@@ -12,7 +12,8 @@ import { visualizationSavedObjectTypeMigrations } from '../migrations/visualizat
export const visualizationSavedObjectType: SavedObjectsType = {
name: 'visualization',
hidden: false,
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
+ convertToMultiNamespaceTypeVersion: '8.0.0',
management: {
icon: 'visualizeApp',
defaultSearchField: 'title',
diff --git a/test/api_integration/apis/saved_objects/find.ts b/test/api_integration/apis/saved_objects/find.ts
index 9b2b3a96cba5b..c8623f08e6f97 100644
--- a/test/api_integration/apis/saved_objects/find.ts
+++ b/test/api_integration/apis/saved_objects/find.ts
@@ -14,6 +14,9 @@ export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
const SPACE_ID = 'ftr-so-find';
+ const UUID_PATTERN = new RegExp(
+ /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
+ );
describe('find', () => {
before(async () => {
@@ -25,7 +28,7 @@ export default function ({ getService }: FtrProviderContext) {
await kibanaServer.spaces.create({ id: `${SPACE_ID}-foo`, name: `${SPACE_ID}-foo` });
await kibanaServer.importExport.load(
- 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic/foo-ns.json',
+ 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json',
{
space: `${SPACE_ID}-foo`,
}
@@ -128,22 +131,25 @@ export default function ({ getService }: FtrProviderContext) {
describe('wildcard namespace', () => {
it('should return 200 with individual responses from the all namespaces', async () =>
await supertest
- .get(`/api/saved_objects/_find?type=visualization&fields=title&namespaces=*`)
+ .get(
+ `/api/saved_objects/_find?type=visualization&fields=title&fields=originId&namespaces=*`
+ )
.expect(200)
.then((resp) => {
const knownDocuments = resp.body.saved_objects.filter((so: { namespaces: string[] }) =>
so.namespaces.some((ns) => [SPACE_ID, `${SPACE_ID}-foo`].includes(ns))
);
+ const [obj1, obj2] = knownDocuments.map(
+ ({ id, originId, namespaces }: SavedObject) => ({ id, originId, namespaces })
+ );
- expect(
- knownDocuments.map((so: { id: string; namespaces: string[] }) => ({
- id: so.id,
- namespaces: so.namespaces,
- }))
- ).to.eql([
- { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', namespaces: [SPACE_ID] },
- { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', namespaces: [`${SPACE_ID}-foo`] },
- ]);
+ expect(obj1.id).to.equal('dd7caf20-9efd-11e7-acb3-3dab96693fab');
+ expect(obj1.originId).to.equal(undefined);
+ expect(obj1.namespaces).to.eql([SPACE_ID]);
+
+ expect(obj2.id).to.match(UUID_PATTERN); // this was imported to the second space and hit an unresolvable conflict, so the object ID was regenerated silently
+ expect(obj2.originId).to.equal('dd7caf20-9efd-11e7-acb3-3dab96693fab');
+ expect(obj2.namespaces).to.eql([`${SPACE_ID}-foo`]);
}));
});
diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts
index 79d8a645d3ba7..bb1840d6d4e87 100644
--- a/test/api_integration/apis/saved_objects_management/find.ts
+++ b/test/api_integration/apis/saved_objects_management/find.ts
@@ -220,7 +220,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
});
expect(resp.body.saved_objects[1].meta).to.eql({
icon: 'visualizeApp',
@@ -230,7 +230,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
});
}));
diff --git a/test/api_integration/apis/saved_objects_management/relationships.ts b/test/api_integration/apis/saved_objects_management/relationships.ts
index 47a0bedd7d77b..cab323ca028ae 100644
--- a/test/api_integration/apis/saved_objects_management/relationships.ts
+++ b/test/api_integration/apis/saved_objects_management/relationships.ts
@@ -107,7 +107,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
},
},
@@ -149,7 +149,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
},
relationship: 'parent',
@@ -192,7 +192,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
},
},
@@ -207,7 +207,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
},
},
@@ -230,7 +230,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
},
relationship: 'child',
@@ -245,7 +245,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
},
relationship: 'child',
@@ -386,7 +386,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
},
},
@@ -456,7 +456,7 @@ export default function ({ getService }: FtrProviderContext) {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
},
- namespaceType: 'single',
+ namespaceType: 'multiple-isolated',
hiddenType: false,
title: 'Visualization',
},
diff --git a/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json b/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json
index 4f343b81cd402..09651172e56a3 100644
--- a/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json
+++ b/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json
@@ -94,4 +94,4 @@
"type": "dashboard",
"updated_at": "2017-09-21T18:57:40.826Z",
"version": "WzExLDJd"
-}
\ No newline at end of file
+}
diff --git a/test/api_integration/fixtures/kbn_archiver/saved_objects/basic/foo-ns.json b/test/api_integration/fixtures/kbn_archiver/saved_objects/basic/foo-ns.json
deleted file mode 100644
index 736abf331d314..0000000000000
--- a/test/api_integration/fixtures/kbn_archiver/saved_objects/basic/foo-ns.json
+++ /dev/null
@@ -1,97 +0,0 @@
-{
- "attributes": {
- "buildNum": 8467,
- "defaultIndex": "91200a00-9efd-11e7-acb3-3dab96693fab"
- },
- "coreMigrationVersion": "7.14.0",
- "id": "7.0.0-alpha1",
- "migrationVersion": {
- "config": "7.13.0"
- },
- "references": [],
- "type": "config",
- "updated_at": "2017-09-21T18:49:16.302Z",
- "version": "WzEzLDJd"
-}
-
-{
- "attributes": {
- "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]",
- "timeFieldName": "@timestamp",
- "title": "logstash-*"
- },
- "coreMigrationVersion": "7.14.0",
- "id": "91200a00-9efd-11e7-acb3-3dab96693fab",
- "migrationVersion": {
- "index-pattern": "7.11.0"
- },
- "references": [],
- "type": "index-pattern",
- "updated_at": "2017-09-21T18:49:16.270Z",
- "version": "WzEyLDJd"
-}
-
-{
- "attributes": {
- "description": "",
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"
- },
- "title": "Count of requests",
- "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}",
- "version": 1,
- "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100,\"filter\":true},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false,\"palette\":{\"type\":\"palette\",\"name\":\"kibana_palette\"},\"isVislibVis\":true,\"detailedTooltip\":true,\"fittingFunction\":\"zero\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}"
- },
- "coreMigrationVersion": "7.14.0",
- "id": "dd7caf20-9efd-11e7-acb3-3dab96693fab",
- "migrationVersion": {
- "visualization": "7.13.0"
- },
- "references": [
- {
- "id": "91200a00-9efd-11e7-acb3-3dab96693fab",
- "name": "kibanaSavedObjectMeta.searchSourceJSON.index",
- "type": "index-pattern"
- }
- ],
- "type": "visualization",
- "updated_at": "2017-09-21T18:51:23.794Z",
- "version": "WzE0LDJd"
-}
-
-{
- "attributes": {
- "description": "",
- "hits": 0,
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
- },
- "optionsJSON": "{\"darkTheme\":false}",
- "panelsJSON": "[{\"version\":\"7.3.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":12,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"}]",
- "refreshInterval": {
- "display": "Off",
- "pause": false,
- "value": 0
- },
- "timeFrom": "Wed Sep 16 2015 22:52:17 GMT-0700",
- "timeRestore": true,
- "timeTo": "Fri Sep 18 2015 12:24:38 GMT-0700",
- "title": "Requests",
- "version": 1
- },
- "coreMigrationVersion": "7.14.0",
- "id": "be3733a0-9efe-11e7-acb3-3dab96693fab",
- "migrationVersion": {
- "dashboard": "7.11.0"
- },
- "references": [
- {
- "id": "dd7caf20-9efd-11e7-acb3-3dab96693fab",
- "name": "1:panel_1",
- "type": "visualization"
- }
- ],
- "type": "dashboard",
- "updated_at": "2017-09-21T18:57:40.826Z",
- "version": "WzE1LDJd"
-}
\ No newline at end of file
diff --git a/test/functional/apps/discover/_inspector.ts b/test/functional/apps/discover/_inspector.ts
index 9ff425be2739b..10402703875d6 100644
--- a/test/functional/apps/discover/_inspector.ts
+++ b/test/functional/apps/discover/_inspector.ts
@@ -53,9 +53,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await inspector.open();
await testSubjects.click('inspectorRequestChooser');
let foundZero = false;
- for (const subj of ['Documents', 'Total hits', 'Charts']) {
+ for (const subj of ['Documents', 'Chart_data']) {
await testSubjects.click(`inspectorRequestChooser${subj}`);
- if (testSubjects.exists('inspectorRequestDetailStatistics', { timeout: 500 })) {
+ if (await testSubjects.exists('inspectorRequestDetailStatistics', { timeout: 500 })) {
await testSubjects.click(`inspectorRequestDetailStatistics`);
const requestStatsTotalHits = getHitCount(await inspector.getTableData());
if (requestStatsTotalHits === '0') {
diff --git a/test/functional/apps/visualize/_vega_chart.ts b/test/functional/apps/visualize/_vega_chart.ts
index b2692c2a00d78..6640b37b4a28a 100644
--- a/test/functional/apps/visualize/_vega_chart.ts
+++ b/test/functional/apps/visualize/_vega_chart.ts
@@ -131,9 +131,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
it('should set the default query name if not given in the schema', async () => {
- const requests = await inspector.getRequestNames();
+ const singleExampleRequest = await inspector.hasSingleRequest();
+ const selectedExampleRequest = await inspector.getSelectedOption();
- expect(requests).to.be('Unnamed request #0');
+ expect(singleExampleRequest).to.be(true);
+ expect(selectedExampleRequest).to.equal('Unnamed request #0');
});
it('should log the request statistic', async () => {
diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts
index 5364dbebe904c..753d9b7b0b85e 100644
--- a/test/functional/services/inspector.ts
+++ b/test/functional/services/inspector.ts
@@ -16,6 +16,7 @@ export class InspectorService extends FtrService {
private readonly flyout = this.ctx.getService('flyout');
private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly find = this.ctx.getService('find');
+ private readonly comboBox = this.ctx.getService('comboBox');
private async getIsEnabled(): Promise {
const ariaDisabled = await this.testSubjects.getAttribute('openInspectorButton', 'disabled');
@@ -206,20 +207,29 @@ export class InspectorService extends FtrService {
}
/**
- * Returns request name as the comma-separated string
+ * Returns the selected option value from combobox
*/
- public async getRequestNames(): Promise {
+ public async getSelectedOption(): Promise {
await this.openInspectorRequestsView();
- const requestChooserExists = await this.testSubjects.exists('inspectorRequestChooser');
- if (requestChooserExists) {
- await this.testSubjects.click('inspectorRequestChooser');
- const menu = await this.testSubjects.find('inspectorRequestChooserMenuPanel');
- const requestNames = await menu.getVisibleText();
- return requestNames.trim().split('\n').join(',');
+ const selectedOption = await this.comboBox.getComboBoxSelectedOptions(
+ 'inspectorRequestChooser'
+ );
+
+ if (selectedOption.length !== 1) {
+ return 'Combobox has multiple options';
}
- const singleRequest = await this.testSubjects.find('inspectorRequestName');
- return await singleRequest.getVisibleText();
+ return selectedOption[0];
+ }
+
+ /**
+ * Returns request name as the comma-separated string from combobox
+ */
+ public async getRequestNames(): Promise {
+ await this.openInspectorRequestsView();
+
+ const comboBoxOptions = await this.comboBox.getOptionsList('inspectorRequestChooser');
+ return comboBoxOptions.trim().split('\n').join(',');
}
public getOpenRequestStatisticButton() {
@@ -233,4 +243,17 @@ export class InspectorService extends FtrService {
public getOpenRequestDetailResponseButton() {
return this.testSubjects.find('inspectorRequestDetailResponse');
}
+
+ /**
+ * Returns true if the value equals the combobox options list
+ * @param value default combobox single option text
+ */
+ public async hasSingleRequest(
+ value: string = "You've selected all available options"
+ ): Promise {
+ await this.openInspectorRequestsView();
+ const comboBoxOptions = await this.comboBox.getOptionsList('inspectorRequestChooser');
+
+ return value === comboBoxOptions;
+ }
}
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 9de81f68110c1..18c0ad38f4601 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -31,7 +31,8 @@
"lib": [
"esnext",
// includes support for browser APIs
- "dom"
+ "dom",
+ "DOM.Iterable"
],
// Node 8 should support everything output by esnext, we override this
// in webpack with loader-level compiler options
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
index 2cee5bbbec80b..944d8315452b0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
@@ -8,6 +8,7 @@
import '../../../../__mocks__/shallow_useeffect.mock';
import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
import { mockUseParams } from '../../../../__mocks__/react_router';
+
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
@@ -27,6 +28,7 @@ import { CurationLogic } from './curation_logic';
import { DeleteCurationButton } from './delete_curation_button';
import { PromotedDocuments, OrganicDocuments } from './documents';
+import { History } from './history';
describe('AutomatedCuration', () => {
const values = {
@@ -39,6 +41,7 @@ describe('AutomatedCuration', () => {
suggestion: {
status: 'applied',
},
+ queries: ['foo'],
},
activeQuery: 'query A',
isAutomated: true,
@@ -61,20 +64,46 @@ describe('AutomatedCuration', () => {
expect(wrapper.is(AppSearchPageTemplate));
expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
+ expect(wrapper.find(History)).toHaveLength(0);
});
- it('includes a static tab group', () => {
+ it('includes tabs', () => {
const wrapper = shallow();
- const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
+ let tabs = getPageHeaderTabs(wrapper).find(EuiTab);
- expect(tabs).toHaveLength(2);
+ expect(tabs).toHaveLength(3);
- expect(tabs.at(0).prop('onClick')).toBeUndefined();
expect(tabs.at(0).prop('isSelected')).toBe(true);
expect(tabs.at(1).prop('onClick')).toBeUndefined();
expect(tabs.at(1).prop('isSelected')).toBe(false);
expect(tabs.at(1).prop('disabled')).toBe(true);
+
+ expect(tabs.at(2).prop('isSelected')).toBe(false);
+
+ // Clicking on the History tab shows the history view
+ tabs.at(2).simulate('click');
+
+ tabs = getPageHeaderTabs(wrapper).find(EuiTab);
+
+ expect(tabs.at(0).prop('isSelected')).toBe(false);
+ expect(tabs.at(2).prop('isSelected')).toBe(true);
+
+ expect(wrapper.find(PromotedDocuments)).toHaveLength(0);
+ expect(wrapper.find(OrganicDocuments)).toHaveLength(0);
+ expect(wrapper.find(History)).toHaveLength(1);
+
+ // Clicking back to the Promoted tab shows promoted documents
+ tabs.at(0).simulate('click');
+
+ tabs = getPageHeaderTabs(wrapper).find(EuiTab);
+
+ expect(tabs.at(0).prop('isSelected')).toBe(true);
+ expect(tabs.at(2).prop('isSelected')).toBe(false);
+
+ expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
+ expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
+ expect(wrapper.find(History)).toHaveLength(0);
});
it('initializes CurationLogic with a curationId prop from URL param', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
index fa34fa071b855..276b40ba88677 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
@@ -5,15 +5,18 @@
* 2.0.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useValues, useActions } from 'kea';
import { EuiButton, EuiBadge, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { EngineLogic } from '../../engine';
import { AppSearchPageTemplate } from '../../layout';
import { AutomatedIcon } from '../components/automated_icon';
+
import {
AUTOMATED_LABEL,
COVERT_TO_MANUAL_BUTTON_LABEL,
@@ -26,19 +29,25 @@ import { HIDDEN_DOCUMENTS_TITLE, PROMOTED_DOCUMENTS_TITLE } from './constants';
import { CurationLogic } from './curation_logic';
import { DeleteCurationButton } from './delete_curation_button';
import { PromotedDocuments, OrganicDocuments } from './documents';
+import { History } from './history';
+
+const PROMOTED = 'promoted';
+const HISTORY = 'history';
export const AutomatedCuration: React.FC = () => {
const { curationId } = useParams<{ curationId: string }>();
const logic = CurationLogic({ curationId });
const { convertToManual } = useActions(logic);
const { activeQuery, dataLoading, queries, curation } = useValues(logic);
+ const { engineName } = useValues(EngineLogic);
+ const [selectedPageTab, setSelectedPageTab] = useState(PROMOTED);
- // This tab group is meant to visually mirror the dynamic group of tags in the ManualCuration component
const pageTabs = [
{
label: PROMOTED_DOCUMENTS_TITLE,
append: {curation.promoted.length},
- isSelected: true,
+ isSelected: selectedPageTab === PROMOTED,
+ onClick: () => setSelectedPageTab(PROMOTED),
},
{
label: HIDDEN_DOCUMENTS_TITLE,
@@ -46,6 +55,16 @@ export const AutomatedCuration: React.FC = () => {
isSelected: false,
disabled: true,
},
+ {
+ label: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyButtonLabel',
+ {
+ defaultMessage: 'History',
+ }
+ ),
+ isSelected: selectedPageTab === HISTORY,
+ onClick: () => setSelectedPageTab(HISTORY),
+ },
];
return (
@@ -83,8 +102,11 @@ export const AutomatedCuration: React.FC = () => {
}}
isLoading={dataLoading}
>
-
-
+ {selectedPageTab === PROMOTED && }
+ {selectedPageTab === PROMOTED && }
+ {selectedPageTab === HISTORY && (
+
+ )}
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.test.tsx
new file mode 100644
index 0000000000000..a7f83fb0c61d9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.test.tsx
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EntSearchLogStream } from '../../../../shared/log_stream';
+
+import { History } from './history';
+
+describe('History', () => {
+ it('renders', () => {
+ const wrapper = shallow();
+ expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual(
+ 'appsearch.search_relevance_suggestions.query: some text and event.kind: event and event.dataset: search-relevance-suggestions and appsearch.search_relevance_suggestions.engine: foo and event.action: curation_suggestion'
+ );
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.tsx
new file mode 100644
index 0000000000000..744141372469c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/history.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { i18n } from '@kbn/i18n';
+
+import { EntSearchLogStream } from '../../../../shared/log_stream';
+import { DataPanel } from '../../data_panel';
+
+interface Props {
+ query: string;
+ engineName: string;
+}
+
+export const History: React.FC = ({ query, engineName }) => {
+ const filters = [
+ `appsearch.search_relevance_suggestions.query: ${query}`,
+ 'event.kind: event',
+ 'event.dataset: search-relevance-suggestions',
+ `appsearch.search_relevance_suggestions.engine: ${engineName}`,
+ 'event.action: curation_suggestion',
+ ];
+
+ return (
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableTitle',
+ {
+ defaultMessage: 'Automated curation changes',
+ }
+ )}
+
+ }
+ subtitle={i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableDescription',
+ {
+ defaultMessage: 'A detailed log of recent changes to your automated curation.',
+ }
+ )}
+ hasBorder
+ >
+
+
+ );
+};
diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts
index eaac2a8113231..6f107ae44bfa7 100644
--- a/x-pack/plugins/fleet/common/types/models/epm.ts
+++ b/x-pack/plugins/fleet/common/types/models/epm.ts
@@ -126,6 +126,11 @@ interface RegistryAdditionalProperties {
readme?: string;
internal?: boolean; // Registry addition[0] and EPM uses it[1] [0]: https://github.com/elastic/package-registry/blob/dd7b021893aa8d66a5a5fde963d8ff2792a9b8fa/util/package.go#L63 [1]
data_streams?: RegistryDataStream[]; // Registry addition [0] [0]: https://github.com/elastic/package-registry/blob/dd7b021893aa8d66a5a5fde963d8ff2792a9b8fa/util/package.go#L65
+ elasticsearch?: {
+ privileges?: {
+ cluster?: string[];
+ };
+ };
}
interface RegistryOverridePropertyValue {
icons?: RegistryImage[];
diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts
index aca537ae31b52..df484646ef66b 100644
--- a/x-pack/plugins/fleet/common/types/models/package_policy.ts
+++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts
@@ -65,6 +65,11 @@ export interface NewPackagePolicy {
package?: PackagePolicyPackage;
inputs: NewPackagePolicyInput[];
vars?: PackagePolicyConfigRecord;
+ elasticsearch?: {
+ privileges?: {
+ cluster?: string[];
+ };
+ };
}
export interface UpdatePackagePolicy extends NewPackagePolicy {
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
index 881fc566c932d..6e3eba19c52e3 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
@@ -452,7 +452,7 @@ export function Detail() {
name: (
),
isSelected: panel === 'policies',
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
index d6dc7e8440dae..69487454dcb94 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
@@ -211,7 +211,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
{
field: 'packagePolicy.name',
name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', {
- defaultMessage: 'Integration',
+ defaultMessage: 'Integration Policy',
}),
render(_, { packagePolicy }) {
return ;
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx
index 2acd5634b1e5f..b5a8394fa2cb2 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx
@@ -154,6 +154,7 @@ export const UpdateButton: React.FunctionComponent = ({
return;
}
+ setIsUpdateModalVisible(false);
setIsUpgradingPackagePolicies(true);
await installPackage({ name, version, title });
@@ -166,7 +167,6 @@ export const UpdateButton: React.FunctionComponent = ({
);
setIsUpgradingPackagePolicies(false);
- setIsUpdateModalVisible(false);
notifications.toasts.addSuccess({
title: toMountPoint(
@@ -285,15 +285,14 @@ export const UpdateButton: React.FunctionComponent = ({
setIsUpdateModalVisible(true) : handleClickUpdate
}
>
diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts
index 0e7b335da6775..c7f6b6fefc414 100644
--- a/x-pack/plugins/fleet/server/mocks/index.ts
+++ b/x-pack/plugins/fleet/server/mocks/index.ts
@@ -65,7 +65,7 @@ export const xpackMocks = {
export const createPackagePolicyServiceMock = (): jest.Mocked => {
return {
- compilePackagePolicyInputs: jest.fn(),
+ _compilePackagePolicyInputs: jest.fn(),
buildPackagePolicyFromPackage: jest.fn(),
bulkCreate: jest.fn(),
create: jest.fn(),
diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
index 729417fa96060..5441af0af686a 100644
--- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
+++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts
@@ -35,7 +35,7 @@ jest.mock(
} => {
return {
packagePolicyService: {
- compilePackagePolicyInputs: jest.fn((packageInfo, vars, dataInputs) =>
+ _compilePackagePolicyInputs: jest.fn((registryPkgInfo, packageInfo, vars, dataInputs) =>
Promise.resolve(dataInputs)
),
buildPackagePolicyFromPackage: jest.fn(),
diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts
index ac5ca401da000..f0b51b19dda33 100644
--- a/x-pack/plugins/fleet/server/saved_objects/index.ts
+++ b/x-pack/plugins/fleet/server/saved_objects/index.ts
@@ -236,6 +236,16 @@ const getSavedObjectTypes = (
version: { type: 'keyword' },
},
},
+ elasticsearch: {
+ enabled: false,
+ properties: {
+ privileges: {
+ properties: {
+ cluster: { type: 'keyword' },
+ },
+ },
+ },
+ },
vars: { type: 'flattened' },
inputs: {
type: 'nested',
diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts
index 2ce68b46387c9..72566a18bd66e 100644
--- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts
+++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts
@@ -279,6 +279,104 @@ describe('storedPackagePoliciesToAgentPermissions()', () => {
});
});
+ it('Returns the cluster privileges if there is one in the package policy', async () => {
+ getPackageInfoMock.mockResolvedValueOnce({
+ name: 'test-package',
+ version: '0.0.0',
+ latestVersion: '0.0.0',
+ release: 'experimental',
+ format_version: '1.0.0',
+ title: 'Test Package',
+ description: '',
+ icons: [],
+ owner: { github: '' },
+ status: 'not_installed',
+ assets: {
+ kibana: {
+ dashboard: [],
+ visualization: [],
+ search: [],
+ index_pattern: [],
+ map: [],
+ lens: [],
+ security_rule: [],
+ ml_module: [],
+ tag: [],
+ },
+ elasticsearch: {
+ component_template: [],
+ ingest_pipeline: [],
+ ilm_policy: [],
+ transform: [],
+ index_template: [],
+ data_stream_ilm_policy: [],
+ ml_model: [],
+ },
+ },
+ data_streams: [
+ {
+ type: 'logs',
+ dataset: 'some-logs',
+ title: '',
+ release: '',
+ package: 'test-package',
+ path: '',
+ ingest_pipeline: '',
+ streams: [{ input: 'test-logs', title: 'Test Logs', template_path: '' }],
+ },
+ ],
+ });
+
+ const packagePolicies: PackagePolicy[] = [
+ {
+ id: '12345',
+ name: 'test-policy',
+ namespace: 'test',
+ enabled: true,
+ package: { name: 'test-package', version: '0.0.0', title: 'Test Package' },
+ elasticsearch: {
+ privileges: {
+ cluster: ['monitor'],
+ },
+ },
+ inputs: [
+ {
+ type: 'test-logs',
+ enabled: true,
+ streams: [
+ {
+ id: 'test-logs',
+ enabled: true,
+ data_stream: { type: 'logs', dataset: 'some-logs' },
+ compiled_stream: { data_stream: { dataset: 'compiled' } },
+ },
+ ],
+ },
+ ],
+ created_at: '',
+ updated_at: '',
+ created_by: '',
+ updated_by: '',
+ revision: 1,
+ policy_id: '',
+ output_id: '',
+ },
+ ];
+
+ const permissions = await storedPackagePoliciesToAgentPermissions(soClient, packagePolicies);
+ expect(permissions).toMatchObject({
+ 'test-policy': {
+ indices: [
+ {
+ names: ['logs-compiled-test'],
+ privileges: ['auto_configure', 'create_doc'],
+ },
+ ],
+ cluster: ['monitor'],
+ },
+ });
+ });
+
it('Returns the dataset for osquery_manager package', async () => {
getPackageInfoMock.mockResolvedValueOnce({
format_version: '1.0.0',
diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts
index 22dcb8ac7b4cb..383747fe126c0 100644
--- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts
+++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts
@@ -121,12 +121,21 @@ export async function storedPackagePoliciesToAgentPermissions(
});
}
+ let clusterRoleDescriptor = {};
+ const cluster = packagePolicy?.elasticsearch?.privileges?.cluster ?? [];
+ if (cluster.length > 0) {
+ clusterRoleDescriptor = {
+ cluster,
+ };
+ }
+
return [
packagePolicy.name,
{
indices: dataStreamsForPermissions.map((ds) =>
getDataStreamPrivileges(ds, packagePolicy.namespace)
),
+ ...clusterRoleDescriptor,
},
];
}
diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts
index 0b6b3579f7b87..9dc05ee2cb4ba 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.test.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts
@@ -33,6 +33,7 @@ import type {
InputsOverride,
NewPackagePolicy,
NewPackagePolicyInput,
+ RegistryPackage,
} from '../../common';
import { IngestManagerError } from '../errors';
@@ -43,6 +44,7 @@ import {
_applyIndexPrivileges,
} from './package_policy';
import { appContextService } from './app_context';
+import { fetchInfo } from './epm/registry';
async function mockedGetAssetsData(_a: any, _b: any, dataset: string) {
if (dataset === 'dataset1') {
@@ -88,6 +90,10 @@ hosts:
];
}
+function mockedRegistryInfo(): RegistryPackage {
+ return {} as RegistryPackage;
+}
+
jest.mock('./epm/packages/assets', () => {
return {
getAssetsData: mockedGetAssetsData,
@@ -100,11 +106,7 @@ jest.mock('./epm/packages', () => {
};
});
-jest.mock('./epm/registry', () => {
- return {
- fetchInfo: () => ({}),
- };
-});
+jest.mock('./epm/registry');
jest.mock('./agent_policy', () => {
return {
@@ -126,12 +128,18 @@ jest.mock('./agent_policy', () => {
};
});
+const mockedFetchInfo = fetchInfo as jest.Mock>;
+
type CombinedExternalCallback = PutPackagePolicyUpdateCallback | PostPackagePolicyCreateCallback;
describe('Package policy service', () => {
- describe('compilePackagePolicyInputs', () => {
+ beforeEach(() => {
+ mockedFetchInfo.mockResolvedValue({} as RegistryPackage);
+ });
+ describe('_compilePackagePolicyInputs', () => {
it('should work with config variables from the stream', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -194,7 +202,8 @@ describe('Package policy service', () => {
});
it('should work with a two level dataset name', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -246,7 +255,8 @@ describe('Package policy service', () => {
});
it('should work with config variables at the input level', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -309,7 +319,8 @@ describe('Package policy service', () => {
});
it('should work with config variables at the package level', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -377,7 +388,8 @@ describe('Package policy service', () => {
});
it('should work with an input with a template and no streams', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [],
policy_templates: [
@@ -419,7 +431,8 @@ describe('Package policy service', () => {
});
it('should work with an input with a template and streams', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
data_streams: [
{
@@ -524,7 +537,8 @@ describe('Package policy service', () => {
});
it('should work with a package without input', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
policy_templates: [
{
@@ -540,7 +554,8 @@ describe('Package policy service', () => {
});
it('should work with a package with a empty inputs array', async () => {
- const inputs = await packagePolicyService.compilePackagePolicyInputs(
+ const inputs = await packagePolicyService._compilePackagePolicyInputs(
+ mockedRegistryInfo(),
{
policy_templates: [
{
@@ -834,6 +849,59 @@ describe('Package policy service', () => {
expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['north', 'south']));
expect(modifiedStream.vars!.period.value).toEqual('12mo');
});
+
+ it('should update elasticsearch.priviles.cluster when updating', async () => {
+ const savedObjectsClient = savedObjectsClientMock.create();
+ const mockPackagePolicy = createPackagePolicyMock();
+
+ const attributes = {
+ ...mockPackagePolicy,
+ inputs: [],
+ };
+
+ mockedFetchInfo.mockResolvedValue({
+ elasticsearch: {
+ privileges: {
+ cluster: ['monitor'],
+ },
+ },
+ } as RegistryPackage);
+
+ savedObjectsClient.get.mockResolvedValue({
+ id: 'test',
+ type: 'abcd',
+ references: [],
+ version: 'test',
+ attributes,
+ });
+
+ savedObjectsClient.update.mockImplementation(
+ async (
+ type: string,
+ id: string,
+ attrs: any
+ ): Promise> => {
+ savedObjectsClient.get.mockResolvedValue({
+ id: 'test',
+ type: 'abcd',
+ references: [],
+ version: 'test',
+ attributes: attrs,
+ });
+ return attrs;
+ }
+ );
+ const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
+
+ const result = await packagePolicyService.update(
+ savedObjectsClient,
+ elasticsearchClient,
+ 'the-package-policy-id',
+ { ...mockPackagePolicy, inputs: [] }
+ );
+
+ expect(result.elasticsearch).toMatchObject({ privileges: { cluster: ['monitor'] } });
+ });
});
describe('runDeleteExternalCallbacks', () => {
diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts
index b7772892f542a..b0c0b9499c68e 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.ts
@@ -114,7 +114,7 @@ class PackagePolicyService {
'There is already a package with the same name on this agent policy'
);
}
-
+ let elasticsearch: PackagePolicy['elasticsearch'];
// Add ids to stream
const packagePolicyId = options?.id || uuid.v4();
let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) =>
@@ -155,7 +155,15 @@ class PackagePolicyService {
}
}
- inputs = await this.compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs);
+ const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version);
+ inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
+ pkgInfo,
+ packagePolicy.vars || {},
+ inputs
+ );
+
+ elasticsearch = registryPkgInfo.elasticsearch;
}
const isoDate = new Date().toISOString();
@@ -164,6 +172,7 @@ class PackagePolicyService {
{
...packagePolicy,
inputs,
+ elasticsearch,
revision: 1,
created_at: isoDate,
created_by: options?.user?.username ?? 'system',
@@ -375,15 +384,21 @@ class PackagePolicyService {
);
inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs);
-
+ let elasticsearch: PackagePolicy['elasticsearch'];
if (packagePolicy.package?.name) {
const pkgInfo = await getPackageInfo({
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
pkgVersion: packagePolicy.package.version,
});
-
- inputs = await this.compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs);
+ const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version);
+ inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
+ pkgInfo,
+ packagePolicy.vars || {},
+ inputs
+ );
+ elasticsearch = registryPkgInfo.elasticsearch;
}
await soClient.update(
@@ -392,6 +407,7 @@ class PackagePolicyService {
{
...restOfPackagePolicy,
inputs,
+ elasticsearch,
revision: oldPackagePolicy.revision + 1,
updated_at: new Date().toISOString(),
updated_by: options?.user?.username ?? 'system',
@@ -563,12 +579,14 @@ class PackagePolicyService {
packageInfo,
packageToPackagePolicyInputs(packageInfo) as InputsOverride[]
);
-
- updatePackagePolicy.inputs = await this.compilePackagePolicyInputs(
+ const registryPkgInfo = await Registry.fetchInfo(packageInfo.name, packageInfo.version);
+ updatePackagePolicy.inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
packageInfo,
updatePackagePolicy.vars || {},
updatePackagePolicy.inputs as PackagePolicyInput[]
);
+ updatePackagePolicy.elasticsearch = registryPkgInfo.elasticsearch;
await this.update(soClient, esClient, id, updatePackagePolicy, options);
result.push({
@@ -618,12 +636,14 @@ class PackagePolicyService {
packageToPackagePolicyInputs(packageInfo) as InputsOverride[],
true
);
-
- updatedPackagePolicy.inputs = await this.compilePackagePolicyInputs(
+ const registryPkgInfo = await Registry.fetchInfo(packageInfo.name, packageInfo.version);
+ updatedPackagePolicy.inputs = await this._compilePackagePolicyInputs(
+ registryPkgInfo,
packageInfo,
updatedPackagePolicy.vars || {},
updatedPackagePolicy.inputs as PackagePolicyInput[]
);
+ updatedPackagePolicy.elasticsearch = registryPkgInfo.elasticsearch;
const hasErrors = 'errors' in updatedPackagePolicy;
@@ -663,12 +683,12 @@ class PackagePolicyService {
}
}
- public async compilePackagePolicyInputs(
+ public async _compilePackagePolicyInputs(
+ registryPkgInfo: RegistryPackage,
pkgInfo: PackageInfo,
vars: PackagePolicy['vars'],
inputs: PackagePolicyInput[]
): Promise {
- const registryPkgInfo = await Registry.fetchInfo(pkgInfo.name, pkgInfo.version);
const inputsPromises = inputs.map(async (input) => {
const compiledInput = await _compilePackagePolicyInput(registryPkgInfo, pkgInfo, vars, input);
const compiledStreams = await _compilePackageStreams(registryPkgInfo, pkgInfo, vars, input);
diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json
index 4f8e1c0bdbae4..bc0cf47181585 100644
--- a/x-pack/plugins/monitoring/kibana.json
+++ b/x-pack/plugins/monitoring/kibana.json
@@ -25,7 +25,6 @@
"home",
"alerting",
"kibanaReact",
- "licenseManagement",
"kibanaLegacy"
]
}
diff --git a/x-pack/plugins/monitoring/public/alerts/badge.tsx b/x-pack/plugins/monitoring/public/alerts/badge.tsx
index 6b1c8c5085565..22bffb5d62b19 100644
--- a/x-pack/plugins/monitoring/public/alerts/badge.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/badge.tsx
@@ -73,7 +73,7 @@ export const AlertsBadge: React.FC = (props: Props) => {
const groupByType = GROUP_BY_NODE;
const panels = showByNode
? getAlertPanelsByNode(PANEL_TITLE, alerts, stateFilter)
- : getAlertPanelsByCategory(PANEL_TITLE, inSetupMode, alerts, stateFilter);
+ : getAlertPanelsByCategory(PANEL_TITLE, !!inSetupMode, alerts, stateFilter);
if (panels.length && !inSetupMode && panels[0].items) {
panels[0].items.push(
...[
diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts
deleted file mode 100644
index 6ded0bce51d4b..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/app_modules.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import angular, { IWindowService } from 'angular';
-import '../views/all';
-// required for `ngSanitize` angular module
-import 'angular-sanitize';
-import 'angular-route';
-import '../index.scss';
-import { upperFirst } from 'lodash';
-import { CoreStart } from 'kibana/public';
-import { i18nDirective, i18nFilter, I18nProvider } from './angular_i18n';
-import { Storage } from '../../../../../src/plugins/kibana_utils/public';
-import { createTopNavDirective, createTopNavHelper } from './top_nav';
-import { MonitoringStartPluginDependencies } from '../types';
-import { GlobalState } from '../url_state';
-import { getSafeForExternalLink } from '../lib/get_safe_for_external_link';
-
-// @ts-ignore
-import { formatMetric, formatNumber } from '../lib/format_number';
-// @ts-ignore
-import { extractIp } from '../lib/extract_ip';
-// @ts-ignore
-import { PrivateProvider } from './providers/private';
-// @ts-ignore
-import { breadcrumbsProvider } from '../services/breadcrumbs';
-// @ts-ignore
-import { monitoringClustersProvider } from '../services/clusters';
-// @ts-ignore
-import { executorProvider } from '../services/executor';
-// @ts-ignore
-import { featuresProvider } from '../services/features';
-// @ts-ignore
-import { licenseProvider } from '../services/license';
-// @ts-ignore
-import { titleProvider } from '../services/title';
-// @ts-ignore
-import { enableAlertsModalProvider } from '../services/enable_alerts_modal';
-// @ts-ignore
-import { monitoringMlListingProvider } from '../directives/elasticsearch/ml_job_listing';
-// @ts-ignore
-import { monitoringMainProvider } from '../directives/main';
-
-export const appModuleName = 'monitoring';
-
-type IPrivate = (provider: (...injectable: unknown[]) => T) => T;
-
-const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react'];
-
-export const localAppModule = ({
- core,
- data: { query },
- navigation,
- externalConfig,
-}: MonitoringStartPluginDependencies) => {
- createLocalI18nModule();
- createLocalPrivateModule();
- createLocalStorage();
- createLocalConfigModule(core);
- createLocalStateModule(query, core.notifications.toasts);
- createLocalTopNavModule(navigation);
- createHrefModule(core);
- createMonitoringAppServices();
- createMonitoringAppDirectives();
- createMonitoringAppConfigConstants(externalConfig);
- createMonitoringAppFilters();
-
- const appModule = angular.module(appModuleName, [
- ...thirdPartyAngularDependencies,
- 'monitoring/I18n',
- 'monitoring/Private',
- 'monitoring/Storage',
- 'monitoring/Config',
- 'monitoring/State',
- 'monitoring/TopNav',
- 'monitoring/href',
- 'monitoring/constants',
- 'monitoring/services',
- 'monitoring/filters',
- 'monitoring/directives',
- ]);
- return appModule;
-};
-
-function createMonitoringAppConfigConstants(
- keys: MonitoringStartPluginDependencies['externalConfig']
-) {
- let constantsModule = angular.module('monitoring/constants', []);
- keys.map(([key, value]) => (constantsModule = constantsModule.constant(key as string, value)));
-}
-
-function createLocalStateModule(
- query: MonitoringStartPluginDependencies['data']['query'],
- toasts: MonitoringStartPluginDependencies['core']['notifications']['toasts']
-) {
- angular
- .module('monitoring/State', ['monitoring/Private'])
- .service(
- 'globalState',
- function (
- Private: IPrivate,
- $rootScope: ng.IRootScopeService,
- $location: ng.ILocationService
- ) {
- function GlobalStateProvider(this: any) {
- const state = new GlobalState(query, toasts, $rootScope, $location, this);
- const initialState: any = state.getState();
- for (const key in initialState) {
- if (!initialState.hasOwnProperty(key)) {
- continue;
- }
- this[key] = initialState[key];
- }
- this.save = () => {
- const newState = { ...this };
- delete newState.save;
- state.setState(newState);
- };
- }
- return Private(GlobalStateProvider);
- }
- );
-}
-
-function createMonitoringAppServices() {
- angular
- .module('monitoring/services', ['monitoring/Private'])
- .service('breadcrumbs', function (Private: IPrivate) {
- return Private(breadcrumbsProvider);
- })
- .service('monitoringClusters', function (Private: IPrivate) {
- return Private(monitoringClustersProvider);
- })
- .service('$executor', function (Private: IPrivate) {
- return Private(executorProvider);
- })
- .service('features', function (Private: IPrivate) {
- return Private(featuresProvider);
- })
- .service('enableAlertsModal', function (Private: IPrivate) {
- return Private(enableAlertsModalProvider);
- })
- .service('license', function (Private: IPrivate) {
- return Private(licenseProvider);
- })
- .service('title', function (Private: IPrivate) {
- return Private(titleProvider);
- });
-}
-
-function createMonitoringAppDirectives() {
- angular
- .module('monitoring/directives', [])
- .directive('monitoringMlListing', monitoringMlListingProvider)
- .directive('monitoringMain', monitoringMainProvider);
-}
-
-function createMonitoringAppFilters() {
- angular
- .module('monitoring/filters', [])
- .filter('capitalize', function () {
- return function (input: string) {
- return upperFirst(input?.toLowerCase());
- };
- })
- .filter('formatNumber', function () {
- return formatNumber;
- })
- .filter('formatMetric', function () {
- return formatMetric;
- })
- .filter('extractIp', function () {
- return extractIp;
- });
-}
-
-function createLocalConfigModule(core: MonitoringStartPluginDependencies['core']) {
- angular.module('monitoring/Config', []).provider('config', function () {
- return {
- $get: () => ({
- get: (key: string) => core.uiSettings?.get(key),
- }),
- };
- });
-}
-
-function createLocalStorage() {
- angular
- .module('monitoring/Storage', [])
- .service('localStorage', function ($window: IWindowService) {
- return new Storage($window.localStorage);
- })
- .service('sessionStorage', function ($window: IWindowService) {
- return new Storage($window.sessionStorage);
- })
- .service('sessionTimeout', function () {
- return {};
- });
-}
-
-function createLocalPrivateModule() {
- angular.module('monitoring/Private', []).provider('Private', PrivateProvider);
-}
-
-function createLocalTopNavModule({ ui }: MonitoringStartPluginDependencies['navigation']) {
- angular
- .module('monitoring/TopNav', ['react'])
- .directive('kbnTopNav', createTopNavDirective)
- .directive('kbnTopNavHelper', createTopNavHelper(ui));
-}
-
-function createLocalI18nModule() {
- angular
- .module('monitoring/I18n', [])
- .provider('i18n', I18nProvider)
- .filter('i18n', i18nFilter)
- .directive('i18nId', i18nDirective);
-}
-
-function createHrefModule(core: CoreStart) {
- const name: string = 'kbnHref';
- angular.module('monitoring/href', []).directive(name, function () {
- return {
- restrict: 'A',
- link: {
- pre: (_$scope, _$el, $attr) => {
- $attr.$observe(name, (val) => {
- if (val) {
- const url = getSafeForExternalLink(val as string);
- $attr.$set('href', core.http.basePath.prepend(url));
- }
- });
-
- _$scope.$on('$locationChangeSuccess', () => {
- const url = getSafeForExternalLink($attr.href as string);
- $attr.$set('href', core.http.basePath.prepend(url));
- });
- },
- },
- };
- });
-}
diff --git a/x-pack/plugins/monitoring/public/angular/helpers/routes.ts b/x-pack/plugins/monitoring/public/angular/helpers/routes.ts
deleted file mode 100644
index 2579e522882a2..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/helpers/routes.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-type RouteObject = [string, { reloadOnSearch: boolean }];
-interface Redirect {
- redirectTo: string;
-}
-
-class Routes {
- private routes: RouteObject[] = [];
- public redirect?: Redirect = { redirectTo: '/no-data' };
-
- public when = (...args: RouteObject) => {
- const [, routeOptions] = args;
- routeOptions.reloadOnSearch = false;
- this.routes.push(args);
- return this;
- };
-
- public otherwise = (redirect: Redirect) => {
- this.redirect = redirect;
- return this;
- };
-
- public addToProvider = ($routeProvider: any) => {
- this.routes.forEach((args) => {
- $routeProvider.when.apply(this, args);
- });
-
- if (this.redirect) {
- $routeProvider.otherwise(this.redirect);
- }
- };
-}
-export const uiRoutes = new Routes();
diff --git a/x-pack/plugins/monitoring/public/angular/helpers/utils.ts b/x-pack/plugins/monitoring/public/angular/helpers/utils.ts
deleted file mode 100644
index 32184ad71ed8d..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/helpers/utils.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { IScope } from 'angular';
-import * as Rx from 'rxjs';
-
-/**
- * Subscribe to an observable at a $scope, ensuring that the digest cycle
- * is run for subscriber hooks and routing errors to fatalError if not handled.
- */
-export const subscribeWithScope = (
- $scope: IScope,
- observable: Rx.Observable,
- observer?: Rx.PartialObserver
-) => {
- return observable.subscribe({
- next(value) {
- if (observer && observer.next) {
- $scope.$applyAsync(() => observer.next!(value));
- }
- },
- error(error) {
- $scope.$applyAsync(() => {
- if (observer && observer.error) {
- observer.error(error);
- } else {
- throw new Error(
- `Uncaught error in subscribeWithScope(): ${
- error ? error.stack || error.message : error
- }`
- );
- }
- });
- },
- complete() {
- if (observer && observer.complete) {
- $scope.$applyAsync(() => observer.complete!());
- }
- },
- });
-};
diff --git a/x-pack/plugins/monitoring/public/angular/index.ts b/x-pack/plugins/monitoring/public/angular/index.ts
deleted file mode 100644
index 1a655fc1ee256..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/index.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import angular, { IModule } from 'angular';
-import { uiRoutes } from './helpers/routes';
-import { Legacy } from '../legacy_shims';
-import { configureAppAngularModule } from '../angular/top_nav';
-import { localAppModule, appModuleName } from './app_modules';
-import { APP_WRAPPER_CLASS } from '../../../../../src/core/public';
-
-import { MonitoringStartPluginDependencies } from '../types';
-
-export class AngularApp {
- private injector?: angular.auto.IInjectorService;
-
- constructor(deps: MonitoringStartPluginDependencies) {
- const {
- core,
- element,
- data,
- navigation,
- isCloud,
- pluginInitializerContext,
- externalConfig,
- triggersActionsUi,
- usageCollection,
- appMountParameters,
- } = deps;
- const app: IModule = localAppModule(deps);
- app.run(($injector: angular.auto.IInjectorService) => {
- this.injector = $injector;
- Legacy.init(
- {
- core,
- element,
- data,
- navigation,
- isCloud,
- pluginInitializerContext,
- externalConfig,
- triggersActionsUi,
- usageCollection,
- appMountParameters,
- },
- this.injector
- );
- });
-
- app.config(($routeProvider: unknown) => uiRoutes.addToProvider($routeProvider));
-
- const np = { core, env: pluginInitializerContext.env };
- configureAppAngularModule(app, np, true);
- const appElement = document.createElement('div');
- appElement.setAttribute('style', 'height: 100%');
- appElement.innerHTML = '';
-
- if (!element.classList.contains(APP_WRAPPER_CLASS)) {
- element.classList.add(APP_WRAPPER_CLASS);
- }
-
- angular.bootstrap(appElement, [appModuleName]);
- angular.element(element).append(appElement);
- }
-
- public destroy = () => {
- if (this.injector) {
- this.injector.get('$rootScope').$destroy();
- }
- };
-
- public applyScope = () => {
- if (!this.injector) {
- return;
- }
-
- const rootScope = this.injector.get('$rootScope');
- rootScope.$applyAsync();
- };
-}
diff --git a/x-pack/plugins/monitoring/public/angular/providers/private.js b/x-pack/plugins/monitoring/public/angular/providers/private.js
deleted file mode 100644
index 018e2d7d41840..0000000000000
--- a/x-pack/plugins/monitoring/public/angular/providers/private.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * # `Private()`
- * Private module loader, used to merge angular and require js dependency styles
- * by allowing a require.js module to export a single provider function that will
- * create a value used within an angular application. This provider can declare
- * angular dependencies by listing them as arguments, and can be require additional
- * Private modules.
- *
- * ## Define a private module provider:
- * ```js
- * export default function PingProvider($http) {
- * this.ping = function () {
- * return $http.head('/health-check');
- * };
- * };
- * ```
- *
- * ## Require a private module:
- * ```js
- * export default function ServerHealthProvider(Private, Promise) {
- * let ping = Private(require('ui/ping'));
- * return {
- * check: Promise.method(function () {
- * let attempts = 0;
- * return (function attempt() {
- * attempts += 1;
- * return ping.ping()
- * .catch(function (err) {
- * if (attempts < 3) return attempt();
- * })
- * }())
- * .then(function () {
- * return true;
- * })
- * .catch(function () {
- * return false;
- * });
- * })
- * }
- * };
- * ```
- *
- * # `Private.stub(provider, newInstance)`
- * `Private.stub()` replaces the instance of a module with another value. This is all we have needed until now.
- *
- * ```js
- * beforeEach(inject(function ($injector, Private) {
- * Private.stub(
- * // since this module just exports a function, we need to change
- * // what Private returns in order to modify it's behavior
- * require('ui/agg_response/hierarchical/_build_split'),
- * sinon.stub().returns(fakeSplit)
- * );
- * }));
- * ```
- *
- * # `Private.swap(oldProvider, newProvider)`
- * This new method does an 1-for-1 swap of module providers, unlike `stub()` which replaces a modules instance.
- * Pass the module you want to swap out, and the one it should be replaced with, then profit.
- *
- * Note: even though this example shows `swap()` being called in a config
- * function, it can be called from anywhere. It is particularly useful
- * in this scenario though.
- *
- * ```js
- * beforeEach(module('kibana', function (PrivateProvider) {
- * PrivateProvider.swap(
- * function StubbedRedirectProvider($decorate) {
- * // $decorate is a function that will instantiate the original module when called
- * return sinon.spy($decorate());
- * }
- * );
- * }));
- * ```
- *
- * @param {[type]} prov [description]
- */
-import { partial, uniqueId, isObject } from 'lodash';
-
-const nextId = partial(uniqueId, 'privateProvider#');
-
-function name(fn) {
- return fn.name || fn.toString().split('\n').shift();
-}
-
-export function PrivateProvider() {
- const provider = this;
-
- // one cache/swaps per Provider
- const cache = {};
- const swaps = {};
-
- // return the uniq id for this function
- function identify(fn) {
- if (typeof fn !== 'function') {
- throw new TypeError('Expected private module "' + fn + '" to be a function');
- }
-
- if (fn.$$id) return fn.$$id;
- else return (fn.$$id = nextId());
- }
-
- provider.stub = function (fn, instance) {
- cache[identify(fn)] = instance;
- return instance;
- };
-
- provider.swap = function (fn, prov) {
- const id = identify(fn);
- swaps[id] = prov;
- };
-
- provider.$get = [
- '$injector',
- function PrivateFactory($injector) {
- // prevent circular deps by tracking where we came from
- const privPath = [];
- const pathToString = function () {
- return privPath.map(name).join(' -> ');
- };
-
- // call a private provider and return the instance it creates
- function instantiate(prov, locals) {
- if (~privPath.indexOf(prov)) {
- throw new Error(
- 'Circular reference to "' +
- name(prov) +
- '"' +
- ' found while resolving private deps: ' +
- pathToString()
- );
- }
-
- privPath.push(prov);
-
- const context = {};
- let instance = $injector.invoke(prov, context, locals);
- if (!isObject(instance)) instance = context;
-
- privPath.pop();
- return instance;
- }
-
- // retrieve an instance from cache or create and store on
- function get(id, prov, $delegateId, $delegateProv) {
- if (cache[id]) return cache[id];
-
- let instance;
-
- if ($delegateId != null && $delegateProv != null) {
- instance = instantiate(prov, {
- $decorate: partial(get, $delegateId, $delegateProv),
- });
- } else {
- instance = instantiate(prov);
- }
-
- return (cache[id] = instance);
- }
-
- // main api, get the appropriate instance for a provider
- function Private(prov) {
- let id = identify(prov);
- let $delegateId;
- let $delegateProv;
-
- if (swaps[id]) {
- $delegateId = id;
- $delegateProv = prov;
-
- prov = swaps[$delegateId];
- id = identify(prov);
- }
-
- return get(id, prov, $delegateId, $delegateProv);
- }
-
- Private.stub = provider.stub;
- Private.swap = provider.swap;
-
- return Private;
- },
- ];
-
- return provider;
-}
diff --git a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx
index c2dfe1c0dae7d..2a2de0a716cea 100644
--- a/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx
+++ b/x-pack/plugins/monitoring/public/application/pages/logstash/pipelines.tsx
@@ -34,12 +34,12 @@ export const LogStashPipelinesPage: React.FC = ({ clusters }) =>
const { getPaginationTableProps, getPaginationRouteOptions, updateTotalItemCount } =
useTable('logstash.pipelines');
- const title = i18n.translate('xpack.monitoring.logstash.overview.title', {
- defaultMessage: 'Logstash',
+ const title = i18n.translate('xpack.monitoring.logstash.pipelines.routeTitle', {
+ defaultMessage: 'Logstash Pipelines',
});
- const pageTitle = i18n.translate('xpack.monitoring.logstash.overview.pageTitle', {
- defaultMessage: 'Logstash overview',
+ const pageTitle = i18n.translate('xpack.monitoring.logstash.pipelines.pageTitle', {
+ defaultMessage: 'Logstash pipelines',
});
const getPageData = useCallback(async () => {
diff --git a/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx b/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx
index e798e7d74ad38..e767074aea42b 100644
--- a/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx
+++ b/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx
@@ -17,7 +17,7 @@ import { CODE_PATH_LICENSE, STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../
import { Legacy } from '../../../legacy_shims';
import { Enabler } from './enabler';
import { BreadcrumbContainer } from '../../hooks/use_breadcrumbs';
-import { initSetupModeState } from '../../setup_mode/setup_mode';
+import { initSetupModeState } from '../../../lib/setup_mode';
import { GlobalStateContext } from '../../contexts/global_state_context';
import { useRequestErrorHandler } from '../../hooks/use_request_error_handler';
diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
index 23eeb2c034a80..c0030cfcfe55c 100644
--- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
+++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx
@@ -17,7 +17,7 @@ import {
getSetupModeState,
isSetupModeFeatureEnabled,
updateSetupModeData,
-} from '../setup_mode/setup_mode';
+} from '../../lib/setup_mode';
import { SetupModeFeature } from '../../../common/enums';
import { AlertsDropdown } from '../../alerts/alerts_dropdown';
import { ActionMenu } from '../../components/action_menu';
diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/index.ts b/x-pack/plugins/monitoring/public/application/setup_mode/index.ts
index 1bcdcdef09c28..57d734fc6d056 100644
--- a/x-pack/plugins/monitoring/public/application/setup_mode/index.ts
+++ b/x-pack/plugins/monitoring/public/application/setup_mode/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export * from './setup_mode';
+export * from '../../lib/setup_mode';
diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx
deleted file mode 100644
index 828d5a2d20ae6..0000000000000
--- a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { render } from 'react-dom';
-import { get, includes } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { HttpStart, IHttpFetchError } from 'kibana/public';
-import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
-import { Legacy } from '../../legacy_shims';
-import { SetupModeEnterButton } from '../../components/setup_mode/enter_button';
-import { SetupModeFeature } from '../../../common/enums';
-import { ISetupModeContext } from '../../components/setup_mode/setup_mode_context';
-import { State as GlobalState } from '../contexts/global_state_context';
-
-function isOnPage(hash: string) {
- return includes(window.location.hash, hash);
-}
-
-let globalState: GlobalState;
-let httpService: HttpStart;
-let errorHandler: (error: IHttpFetchError) => void;
-
-interface ISetupModeState {
- enabled: boolean;
- data: any;
- callback?: (() => void) | null;
- hideBottomBar: boolean;
-}
-const setupModeState: ISetupModeState = {
- enabled: false,
- data: null,
- callback: null,
- hideBottomBar: false,
-};
-
-export const getSetupModeState = () => setupModeState;
-
-export const setNewlyDiscoveredClusterUuid = (clusterUuid: string) => {
- globalState.cluster_uuid = clusterUuid;
- globalState.save?.();
-};
-
-export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid = false) => {
- const clusterUuid = globalState.cluster_uuid;
- const ccs = globalState.ccs;
-
- let url = '../api/monitoring/v1/setup/collection';
- if (uuid) {
- url += `/node/${uuid}`;
- } else if (!fetchWithoutClusterUuid && clusterUuid) {
- url += `/cluster/${clusterUuid}`;
- } else {
- url += '/cluster';
- }
-
- try {
- const response = await httpService.post(url, {
- body: JSON.stringify({
- ccs,
- }),
- });
- return response;
- } catch (err) {
- errorHandler(err);
- throw err;
- }
-};
-
-const notifySetupModeDataChange = () => setupModeState.callback && setupModeState.callback();
-
-export const updateSetupModeData = async (uuid?: string, fetchWithoutClusterUuid = false) => {
- const data = await fetchCollectionData(uuid, fetchWithoutClusterUuid);
- setupModeState.data = data;
- const hasPermissions = get(data, '_meta.hasPermissions', false);
- if (!hasPermissions) {
- let text: string = '';
- if (!hasPermissions) {
- text = i18n.translate('xpack.monitoring.setupMode.notAvailablePermissions', {
- defaultMessage: 'You do not have the necessary permissions to do this.',
- });
- }
-
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', {
- defaultMessage: 'Setup mode is not available',
- }),
- text,
- });
- return toggleSetupMode(false);
- }
- notifySetupModeDataChange();
-
- const clusterUuid = globalState.cluster_uuid;
- if (!clusterUuid) {
- const liveClusterUuid: string = get(data, '_meta.liveClusterUuid');
- const migratedEsNodes = Object.values(get(data, 'elasticsearch.byUuid', {})).filter(
- (node: any) => node.isPartiallyMigrated || node.isFullyMigrated
- );
- if (liveClusterUuid && migratedEsNodes.length > 0) {
- setNewlyDiscoveredClusterUuid(liveClusterUuid);
- }
- }
-};
-
-export const hideBottomBar = () => {
- setupModeState.hideBottomBar = true;
- notifySetupModeDataChange();
-};
-export const showBottomBar = () => {
- setupModeState.hideBottomBar = false;
- notifySetupModeDataChange();
-};
-
-export const disableElasticsearchInternalCollection = async () => {
- const clusterUuid = globalState.cluster_uuid;
- const url = `../api/monitoring/v1/setup/collection/${clusterUuid}/disable_internal_collection`;
- try {
- const response = await httpService.post(url);
- return response;
- } catch (err) {
- errorHandler(err);
- throw err;
- }
-};
-
-export const toggleSetupMode = (inSetupMode: boolean) => {
- setupModeState.enabled = inSetupMode;
- globalState.inSetupMode = inSetupMode;
- globalState.save?.();
- setSetupModeMenuItem();
- notifySetupModeDataChange();
-
- if (inSetupMode) {
- // Intentionally do not await this so we don't block UI operations
- updateSetupModeData();
- }
-};
-
-export const setSetupModeMenuItem = () => {
- if (isOnPage('no-data')) {
- return;
- }
-
- const enabled = !globalState.inSetupMode;
- const I18nContext = Legacy.shims.I18nContext;
-
- render(
-
-
-
-
- ,
- document.getElementById('setupModeNav')
- );
-};
-
-export const initSetupModeState = async (
- state: GlobalState,
- http: HttpStart,
- handleErrors: (error: IHttpFetchError) => void,
- callback?: () => void
-) => {
- globalState = state;
- httpService = http;
- errorHandler = handleErrors;
- if (callback) {
- setupModeState.callback = callback;
- }
-
- if (globalState.inSetupMode) {
- toggleSetupMode(true);
- }
-};
-
-export const isInSetupMode = (context?: ISetupModeContext, gState: GlobalState = globalState) => {
- if (context?.setupModeSupported === false) {
- return false;
- }
- if (setupModeState.enabled) {
- return true;
- }
-
- return gState.inSetupMode;
-};
-
-export const isSetupModeFeatureEnabled = (feature: SetupModeFeature) => {
- if (!setupModeState.enabled) {
- return false;
- }
-
- if (feature === SetupModeFeature.MetricbeatMigration) {
- if (Legacy.shims.isCloud) {
- return false;
- }
- }
-
- return true;
-};
diff --git a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js
index a9ee2464cd423..df524fa99ae53 100644
--- a/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js
+++ b/x-pack/plugins/monitoring/public/application/setup_mode/setup_mode_renderer.js
@@ -13,7 +13,7 @@ import {
disableElasticsearchInternalCollection,
toggleSetupMode,
setSetupModeMenuItem,
-} from './setup_mode';
+} from '../../lib/setup_mode';
import { Flyout } from '../../components/metricbeat_migration/flyout';
import {
EuiBottomBar,
diff --git a/x-pack/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js b/x-pack/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js
deleted file mode 100644
index 69579cb831c06..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/elasticsearch/ml_job_listing/index.js
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { capitalize } from 'lodash';
-import numeral from '@elastic/numeral';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { EuiMonitoringTable } from '../../../components/table';
-import { MachineLearningJobStatusIcon } from '../../../components/elasticsearch/ml_job_listing/status_icon';
-import { LARGE_ABBREVIATED, LARGE_BYTES } from '../../../../common/formatting';
-import { EuiLink, EuiPage, EuiPageContent, EuiPageBody, EuiPanel, EuiSpacer } from '@elastic/eui';
-import { ClusterStatus } from '../../../components/elasticsearch/cluster_status';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
-
-const getColumns = () => [
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.jobIdTitle', {
- defaultMessage: 'Job ID',
- }),
- field: 'job_id',
- sortable: true,
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.stateTitle', {
- defaultMessage: 'State',
- }),
- field: 'state',
- sortable: true,
- render: (state) => (
-
-
-
- {capitalize(state)}
-
- ),
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.processedRecordsTitle', {
- defaultMessage: 'Processed Records',
- }),
- field: 'data_counts.processed_record_count',
- sortable: true,
- render: (value) => {numeral(value).format(LARGE_ABBREVIATED)},
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.modelSizeTitle', {
- defaultMessage: 'Model Size',
- }),
- field: 'model_size_stats.model_bytes',
- sortable: true,
- render: (value) => {numeral(value).format(LARGE_BYTES)},
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.forecastsTitle', {
- defaultMessage: 'Forecasts',
- }),
- field: 'forecasts_stats.total',
- sortable: true,
- render: (value) => {numeral(value).format(LARGE_ABBREVIATED)},
- },
- {
- name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.nodeTitle', {
- defaultMessage: 'Node',
- }),
- field: 'node.name',
- sortable: true,
- render: (name, node) => {
- if (node) {
- return (
-
- {name}
-
- );
- }
-
- return (
-
- );
- },
- },
-];
-
-//monitoringMlListing
-export function monitoringMlListingProvider() {
- return {
- restrict: 'E',
- scope: {
- jobs: '=',
- paginationSettings: '=',
- sorting: '=',
- onTableChange: '=',
- status: '=',
- },
- link(scope, $el) {
- scope.$on('$destroy', () => $el && $el[0] && unmountComponentAtNode($el[0]));
- const columns = getColumns();
-
- const filterJobsPlaceholder = i18n.translate(
- 'xpack.monitoring.elasticsearch.mlJobListing.filterJobsPlaceholder',
- {
- defaultMessage: 'Filter Jobs…',
- }
- );
-
- scope.$watch('jobs', (_jobs = []) => {
- const jobs = _jobs.map((job) => {
- if (job.ml) {
- return {
- ...job.ml.job,
- node: job.node,
- job_id: job.ml.job.id,
- };
- }
- return job;
- });
- const mlTable = (
-
-
-
-
-
-
-
-
-
-
-
- );
- render(mlTable, $el[0]);
- });
- },
- };
-}
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.html b/x-pack/plugins/monitoring/public/directives/main/index.html
deleted file mode 100644
index fd14120e1db2f..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/index.html
+++ /dev/null
@@ -1,323 +0,0 @@
-
-
-
-
-
-
-
-
{{pageTitle || monitoringMain.instance}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.js b/x-pack/plugins/monitoring/public/directives/main/index.js
deleted file mode 100644
index 0e464f0a356c4..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/index.js
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { EuiSelect, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import template from './index.html';
-import { Legacy } from '../../legacy_shims';
-import { shortenPipelineHash } from '../../../common/formatting';
-import {
- getSetupModeState,
- initSetupModeState,
- isSetupModeFeatureEnabled,
-} from '../../lib/setup_mode';
-import { Subscription } from 'rxjs';
-import { getSafeForExternalLink } from '../../lib/get_safe_for_external_link';
-import { SetupModeFeature } from '../../../common/enums';
-import './index.scss';
-
-const setOptions = (controller) => {
- if (
- !controller.pipelineVersions ||
- !controller.pipelineVersions.length ||
- !controller.pipelineDropdownElement
- ) {
- return;
- }
-
- render(
-
-
- {
- return {
- text: i18n.translate(
- 'xpack.monitoring.logstashNavigation.pipelineVersionDescription',
- {
- defaultMessage:
- 'Version active {relativeLastSeen} and first seen {relativeFirstSeen}',
- values: {
- relativeLastSeen: option.relativeLastSeen,
- relativeFirstSeen: option.relativeFirstSeen,
- },
- }
- ),
- value: option.hash,
- };
- })}
- onChange={controller.onChangePipelineHash}
- />
-
- ,
- controller.pipelineDropdownElement
- );
-};
-
-/*
- * Manage data and provide helper methods for the "main" directive's template
- */
-export class MonitoringMainController {
- // called internally by Angular
- constructor() {
- this.inListing = false;
- this.inAlerts = false;
- this.inOverview = false;
- this.inElasticsearch = false;
- this.inKibana = false;
- this.inLogstash = false;
- this.inBeats = false;
- this.inApm = false;
- }
-
- addTimerangeObservers = () => {
- const timefilter = Legacy.shims.timefilter;
- this.subscriptions = new Subscription();
-
- const refreshIntervalUpdated = () => {
- const { value: refreshInterval, pause: isPaused } = timefilter.getRefreshInterval();
- this.datePicker.onRefreshChange({ refreshInterval, isPaused }, true);
- };
-
- const timeUpdated = () => {
- this.datePicker.onTimeUpdate({ dateRange: timefilter.getTime() }, true);
- };
-
- this.subscriptions.add(
- timefilter.getRefreshIntervalUpdate$().subscribe(refreshIntervalUpdated)
- );
- this.subscriptions.add(timefilter.getTimeUpdate$().subscribe(timeUpdated));
- };
-
- dropdownLoadedHandler() {
- this.pipelineDropdownElement = document.querySelector('#dropdown-elm');
- setOptions(this);
- }
-
- // kick things off from the directive link function
- setup(options) {
- const timefilter = Legacy.shims.timefilter;
- this._licenseService = options.licenseService;
- this._breadcrumbsService = options.breadcrumbsService;
- this._executorService = options.executorService;
-
- Object.assign(this, options.attributes);
-
- this.navName = `${this.name}-nav`;
-
- // set the section we're navigated in
- if (this.product) {
- this.inElasticsearch = this.product === 'elasticsearch';
- this.inKibana = this.product === 'kibana';
- this.inLogstash = this.product === 'logstash';
- this.inBeats = this.product === 'beats';
- this.inApm = this.product === 'apm';
- } else {
- this.inOverview = this.name === 'overview';
- this.inAlerts = this.name === 'alerts';
- this.inListing = this.name === 'listing'; // || this.name === 'no-data';
- }
-
- if (!this.inListing) {
- // no breadcrumbs in cluster listing page
- this.breadcrumbs = this._breadcrumbsService(options.clusterName, this);
- }
-
- if (this.pipelineHash) {
- this.pipelineHashShort = shortenPipelineHash(this.pipelineHash);
- this.onChangePipelineHash = () => {
- window.location.hash = getSafeForExternalLink(
- `#/logstash/pipelines/${this.pipelineId}/${this.pipelineHash}`
- );
- };
- }
-
- this.datePicker = {
- enableTimeFilter: timefilter.isTimeRangeSelectorEnabled(),
- timeRange: timefilter.getTime(),
- refreshInterval: timefilter.getRefreshInterval(),
- onRefreshChange: ({ isPaused, refreshInterval }, skipSet = false) => {
- this.datePicker.refreshInterval = {
- pause: isPaused,
- value: refreshInterval,
- };
- if (!skipSet) {
- timefilter.setRefreshInterval({
- pause: isPaused,
- value: refreshInterval ? refreshInterval : this.datePicker.refreshInterval.value,
- });
- }
- },
- onTimeUpdate: ({ dateRange }, skipSet = false) => {
- this.datePicker.timeRange = {
- ...dateRange,
- };
- if (!skipSet) {
- timefilter.setTime(dateRange);
- }
- this._executorService.cancel();
- this._executorService.run();
- },
- };
- }
-
- // check whether to "highlight" a tab
- isActiveTab(testPath) {
- return this.name === testPath;
- }
-
- // check whether to show ML tab
- isMlSupported() {
- return this._licenseService.mlIsSupported();
- }
-
- isDisabledTab(product) {
- const setupMode = getSetupModeState();
- if (!isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) {
- return false;
- }
-
- if (!setupMode.data) {
- return false;
- }
-
- const data = setupMode.data[product] || {};
- if (data.totalUniqueInstanceCount === 0) {
- return true;
- }
- if (
- data.totalUniqueInternallyCollectedCount === 0 &&
- data.totalUniqueFullyMigratedCount === 0 &&
- data.totalUniquePartiallyMigratedCount === 0
- ) {
- return true;
- }
- return false;
- }
-}
-
-export function monitoringMainProvider(breadcrumbs, license, $injector) {
- const $executor = $injector.get('$executor');
- const $parse = $injector.get('$parse');
-
- return {
- restrict: 'E',
- transclude: true,
- template,
- controller: MonitoringMainController,
- controllerAs: 'monitoringMain',
- bindToController: true,
- link(scope, _element, attributes, controller) {
- scope.$applyAsync(() => {
- controller.addTimerangeObservers();
- const setupObj = getSetupObj();
- controller.setup(setupObj);
- Object.keys(setupObj.attributes).forEach((key) => {
- attributes.$observe(key, () => controller.setup(getSetupObj()));
- });
- if (attributes.onLoaded) {
- const onLoaded = $parse(attributes.onLoaded)(scope);
- onLoaded();
- }
- });
-
- initSetupModeState(scope, $injector, () => {
- controller.setup(getSetupObj());
- });
- if (!scope.cluster) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- scope.cluster = ($route.current.locals.clusters || []).find(
- (cluster) => cluster.cluster_uuid === globalState.cluster_uuid
- );
- }
-
- function getSetupObj() {
- return {
- licenseService: license,
- breadcrumbsService: breadcrumbs,
- executorService: $executor,
- attributes: {
- name: attributes.name,
- product: attributes.product,
- instance: attributes.instance,
- resolver: attributes.resolver,
- page: attributes.page,
- tabIconClass: attributes.tabIconClass,
- tabIconLabel: attributes.tabIconLabel,
- pipelineId: attributes.pipelineId,
- pipelineHash: attributes.pipelineHash,
- pipelineVersions: get(scope, 'pageData.versions'),
- isCcrEnabled: attributes.isCcrEnabled === 'true' || attributes.isCcrEnabled === true,
- },
- clusterName: get(scope, 'cluster.cluster_name'),
- };
- }
-
- scope.$on('$destroy', () => {
- controller.pipelineDropdownElement &&
- unmountComponentAtNode(controller.pipelineDropdownElement);
- controller.subscriptions && controller.subscriptions.unsubscribe();
- });
- scope.$watch('pageData.versions', (versions) => {
- controller.pipelineVersions = versions;
- setOptions(controller);
- });
- },
- };
-}
diff --git a/x-pack/plugins/monitoring/public/directives/main/index.scss b/x-pack/plugins/monitoring/public/directives/main/index.scss
deleted file mode 100644
index db5d2b72ab07b..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/index.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.monTopNavSecondItem {
- padding-left: $euiSizeM;
-}
diff --git a/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js b/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
deleted file mode 100644
index 195e11cee6112..0000000000000
--- a/x-pack/plugins/monitoring/public/directives/main/monitoring_main_controller.test.js
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { noop } from 'lodash';
-import expect from '@kbn/expect';
-import { Legacy } from '../../legacy_shims';
-import { MonitoringMainController } from './';
-
-const getMockLicenseService = (options) => ({ mlIsSupported: () => options.mlIsSupported });
-const getMockBreadcrumbsService = () => noop; // breadcrumb service has its own test
-
-describe('Monitoring Main Directive Controller', () => {
- const core = {
- notifications: {},
- application: {},
- i18n: {},
- chrome: {},
- };
- const data = {
- query: {
- timefilter: {
- timefilter: {
- isTimeRangeSelectorEnabled: () => true,
- getTime: () => 1,
- getRefreshInterval: () => 1,
- },
- },
- },
- };
- const isCloud = false;
- const triggersActionsUi = {};
-
- beforeAll(() => {
- Legacy.init({
- core,
- data,
- isCloud,
- triggersActionsUi,
- });
- });
-
- /*
- * Simulates calling the monitoringMain directive the way Cluster Listing
- * does:
- *
- * ...
- */
- it('in Cluster Listing', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'listing',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(true);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('listing');
- expect(controller.product).to.be(undefined);
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Simulates calling the monitoringMain directive the way Cluster Alerts
- * Listing does:
- *
- * ...
- */
- it('in Cluster Alerts', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'alerts',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(true);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('alerts');
- expect(controller.product).to.be(undefined);
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Simulates calling the monitoringMain directive the way Cluster Overview
- * does:
- *
- * ...
- */
- it('in Cluster Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'overview',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(true);
-
- // attributes
- expect(controller.name).to.be('overview');
- expect(controller.product).to.be(undefined);
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Simulates calling the monitoringMain directive the way that Elasticsearch
- * Node / Advanced does:
- *
- * ...
- */
- it('in ES Node - Advanced', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- product: 'elasticsearch',
- name: 'nodes',
- instance: 'es-node-name-01',
- resolver: 'es-node-resolver-01',
- page: 'advanced',
- tabIconClass: 'fa star',
- tabIconLabel: 'Master Node',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('nodes');
- expect(controller.product).to.be('elasticsearch');
- expect(controller.instance).to.be('es-node-name-01');
- expect(controller.resolver).to.be('es-node-resolver-01');
- expect(controller.page).to.be('advanced');
- expect(controller.tabIconClass).to.be('fa star');
- expect(controller.tabIconLabel).to.be('Master Node');
- });
-
- /**
- *
- */
- it('in Kibana Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- product: 'kibana',
- name: 'overview',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('overview');
- expect(controller.product).to.be('kibana');
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /**
- *
- */
- it('in Logstash Listing', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService(),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- product: 'logstash',
- name: 'listing',
- },
- });
-
- // derived properties
- expect(controller.inListing).to.be(false);
- expect(controller.inAlerts).to.be(false);
- expect(controller.inOverview).to.be(false);
-
- // attributes
- expect(controller.name).to.be('listing');
- expect(controller.product).to.be('logstash');
- expect(controller.instance).to.be(undefined);
- expect(controller.resolver).to.be(undefined);
- expect(controller.page).to.be(undefined);
- expect(controller.tabIconClass).to.be(undefined);
- expect(controller.tabIconLabel).to.be(undefined);
- });
-
- /*
- * Test `controller.isMlSupported` function
- */
- describe('Checking support for ML', () => {
- it('license supports ML', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService({ mlIsSupported: true }),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'listing',
- },
- });
-
- expect(controller.isMlSupported()).to.be(true);
- });
- it('license does not support ML', () => {
- getMockLicenseService({ mlIsSupported: false });
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: getMockLicenseService({ mlIsSupported: false }),
- breadcrumbsService: getMockBreadcrumbsService(),
- attributes: {
- name: 'listing',
- },
- });
-
- expect(controller.isMlSupported()).to.be(false);
- });
- });
-});
diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.test.js b/x-pack/plugins/monitoring/public/lib/setup_mode.test.js
index 6dad6effeecc1..47cae9c4f0851 100644
--- a/x-pack/plugins/monitoring/public/lib/setup_mode.test.js
+++ b/x-pack/plugins/monitoring/public/lib/setup_mode.test.js
@@ -83,7 +83,7 @@ function waitForSetupModeData() {
return new Promise((resolve) => process.nextTick(resolve));
}
-describe('setup_mode', () => {
+xdescribe('setup_mode', () => {
beforeEach(async () => {
setModulesAndMocks();
});
diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx
index fca7f94731bc5..e582f4aa40812 100644
--- a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx
+++ b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx
@@ -9,37 +9,21 @@ import React from 'react';
import { render } from 'react-dom';
import { get, includes } from 'lodash';
import { i18n } from '@kbn/i18n';
+import { HttpStart, IHttpFetchError } from 'kibana/public';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { Legacy } from '../legacy_shims';
-import { ajaxErrorHandlersProvider } from './ajax_error_handler';
import { SetupModeEnterButton } from '../components/setup_mode/enter_button';
import { SetupModeFeature } from '../../common/enums';
import { ISetupModeContext } from '../components/setup_mode/setup_mode_context';
-import * as setupModeReact from '../application/setup_mode/setup_mode';
-import { isReactMigrationEnabled } from '../external_config';
+import { State as GlobalState } from '../application/contexts/global_state_context';
function isOnPage(hash: string) {
return includes(window.location.hash, hash);
}
-interface IAngularState {
- injector: any;
- scope: any;
-}
-
-const angularState: IAngularState = {
- injector: null,
- scope: null,
-};
-
-const checkAngularState = () => {
- if (!angularState.injector || !angularState.scope) {
- throw new Error(
- 'Unable to interact with setup mode because the angular injector was not previously set.' +
- ' This needs to be set by calling `initSetupModeState`.'
- );
- }
-};
+let globalState: GlobalState;
+let httpService: HttpStart;
+let errorHandler: (error: IHttpFetchError) => void;
interface ISetupModeState {
enabled: boolean;
@@ -57,20 +41,11 @@ const setupModeState: ISetupModeState = {
export const getSetupModeState = () => setupModeState;
export const setNewlyDiscoveredClusterUuid = (clusterUuid: string) => {
- const globalState = angularState.injector.get('globalState');
- const executor = angularState.injector.get('$executor');
- angularState.scope.$apply(() => {
- globalState.cluster_uuid = clusterUuid;
- globalState.save();
- });
- executor.run();
+ globalState.cluster_uuid = clusterUuid;
+ globalState.save?.();
};
export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid = false) => {
- checkAngularState();
-
- const http = angularState.injector.get('$http');
- const globalState = angularState.injector.get('globalState');
const clusterUuid = globalState.cluster_uuid;
const ccs = globalState.ccs;
@@ -84,12 +59,15 @@ export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid
}
try {
- const response = await http.post(url, { ccs });
- return response.data;
+ const response = await httpService.post(url, {
+ body: JSON.stringify({
+ ccs,
+ }),
+ });
+ return response;
} catch (err) {
- const Private = angularState.injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
+ errorHandler(err);
+ throw err;
}
};
@@ -107,19 +85,16 @@ export const updateSetupModeData = async (uuid?: string, fetchWithoutClusterUuid
});
}
- angularState.scope.$evalAsync(() => {
- Legacy.shims.toastNotifications.addDanger({
- title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', {
- defaultMessage: 'Setup mode is not available',
- }),
- text,
- });
+ Legacy.shims.toastNotifications.addDanger({
+ title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', {
+ defaultMessage: 'Setup mode is not available',
+ }),
+ text,
});
return toggleSetupMode(false);
}
notifySetupModeDataChange();
- const globalState = angularState.injector.get('globalState');
const clusterUuid = globalState.cluster_uuid;
if (!clusterUuid) {
const liveClusterUuid: string = get(data, '_meta.liveClusterUuid');
@@ -142,31 +117,21 @@ export const showBottomBar = () => {
};
export const disableElasticsearchInternalCollection = async () => {
- checkAngularState();
-
- const http = angularState.injector.get('$http');
- const globalState = angularState.injector.get('globalState');
const clusterUuid = globalState.cluster_uuid;
const url = `../api/monitoring/v1/setup/collection/${clusterUuid}/disable_internal_collection`;
try {
- const response = await http.post(url);
- return response.data;
+ const response = await httpService.post(url);
+ return response;
} catch (err) {
- const Private = angularState.injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
+ errorHandler(err);
+ throw err;
}
};
export const toggleSetupMode = (inSetupMode: boolean) => {
- if (isReactMigrationEnabled()) return setupModeReact.toggleSetupMode(inSetupMode);
-
- checkAngularState();
-
- const globalState = angularState.injector.get('globalState');
setupModeState.enabled = inSetupMode;
globalState.inSetupMode = inSetupMode;
- globalState.save();
+ globalState.save?.();
setSetupModeMenuItem();
notifySetupModeDataChange();
@@ -177,13 +142,10 @@ export const toggleSetupMode = (inSetupMode: boolean) => {
};
export const setSetupModeMenuItem = () => {
- checkAngularState();
-
if (isOnPage('no-data')) {
return;
}
- const globalState = angularState.injector.get('globalState');
const enabled = !globalState.inSetupMode;
const I18nContext = Legacy.shims.I18nContext;
@@ -197,23 +159,25 @@ export const setSetupModeMenuItem = () => {
);
};
-export const addSetupModeCallback = (callback: () => void) => (setupModeState.callback = callback);
-
-export const initSetupModeState = async ($scope: any, $injector: any, callback?: () => void) => {
- angularState.scope = $scope;
- angularState.injector = $injector;
+export const initSetupModeState = async (
+ state: GlobalState,
+ http: HttpStart,
+ handleErrors: (error: IHttpFetchError) => void,
+ callback?: () => void
+) => {
+ globalState = state;
+ httpService = http;
+ errorHandler = handleErrors;
if (callback) {
setupModeState.callback = callback;
}
- const globalState = $injector.get('globalState');
if (globalState.inSetupMode) {
toggleSetupMode(true);
}
};
-export const isInSetupMode = (context?: ISetupModeContext) => {
- if (isReactMigrationEnabled()) return setupModeReact.isInSetupMode(context);
+export const isInSetupMode = (context?: ISetupModeContext, gState: GlobalState = globalState) => {
if (context?.setupModeSupported === false) {
return false;
}
@@ -221,20 +185,19 @@ export const isInSetupMode = (context?: ISetupModeContext) => {
return true;
}
- const $injector = angularState.injector || Legacy.shims.getAngularInjector();
- const globalState = $injector.get('globalState');
- return globalState.inSetupMode;
+ return gState.inSetupMode;
};
export const isSetupModeFeatureEnabled = (feature: SetupModeFeature) => {
- if (isReactMigrationEnabled()) return setupModeReact.isSetupModeFeatureEnabled(feature);
if (!setupModeState.enabled) {
return false;
}
+
if (feature === SetupModeFeature.MetricbeatMigration) {
if (Legacy.shims.isCloud) {
return false;
}
}
+
return true;
};
diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts
index 82e49fec5a8d4..f75b76871f58c 100644
--- a/x-pack/plugins/monitoring/public/plugin.ts
+++ b/x-pack/plugins/monitoring/public/plugin.ts
@@ -36,9 +36,6 @@ interface MonitoringSetupPluginDependencies {
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
usageCollection: UsageCollectionSetup;
}
-
-const HASH_CHANGE = 'hashchange';
-
export class MonitoringPlugin
implements
Plugin
@@ -88,7 +85,6 @@ export class MonitoringPlugin
category: DEFAULT_APP_CATEGORIES.management,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
- const { AngularApp } = await import('./angular');
const externalConfig = this.getExternalConfig();
const deps: MonitoringStartPluginDependencies = {
navigation: pluginsStart.navigation,
@@ -118,26 +114,8 @@ export class MonitoringPlugin
const config = Object.fromEntries(externalConfig);
setConfig(config);
- if (config.renderReactApp) {
- const { renderApp } = await import('./application');
- return renderApp(coreStart, pluginsStart, params, config);
- } else {
- const monitoringApp = new AngularApp(deps);
- const removeHistoryListener = params.history.listen((location) => {
- if (location.pathname === '' && location.hash === '') {
- monitoringApp.applyScope();
- }
- });
-
- const removeHashChange = this.setInitialTimefilter(deps);
- return () => {
- if (removeHashChange) {
- removeHashChange();
- }
- removeHistoryListener();
- monitoringApp.destroy();
- };
- }
+ const { renderApp } = await import('./application');
+ return renderApp(coreStart, pluginsStart, params, config);
},
};
@@ -148,28 +126,6 @@ export class MonitoringPlugin
public stop() {}
- private setInitialTimefilter({ data }: MonitoringStartPluginDependencies) {
- const { timefilter } = data.query.timefilter;
- const { pause: pauseByDefault } = timefilter.getRefreshIntervalDefaults();
- if (pauseByDefault) {
- return;
- }
- /**
- * We can't use timefilter.getRefreshIntervalUpdate$ last value,
- * since it's not a BehaviorSubject. This means we need to wait for
- * hash change because of angular's applyAsync
- */
- const onHashChange = () => {
- const { value, pause } = timefilter.getRefreshInterval();
- if (!value && pause) {
- window.removeEventListener(HASH_CHANGE, onHashChange);
- timefilter.setRefreshInterval({ value: 10000, pause: false });
- }
- };
- window.addEventListener(HASH_CHANGE, onHashChange, false);
- return () => window.removeEventListener(HASH_CHANGE, onHashChange);
- }
-
private getExternalConfig() {
const monitoring = this.initializerContext.config.get();
return [
diff --git a/x-pack/plugins/monitoring/public/services/breadcrumbs.js b/x-pack/plugins/monitoring/public/services/breadcrumbs.js
deleted file mode 100644
index 54ff46f4bf0ab..0000000000000
--- a/x-pack/plugins/monitoring/public/services/breadcrumbs.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Legacy } from '../legacy_shims';
-import { i18n } from '@kbn/i18n';
-
-// Helper for making objects to use in a link element
-const createCrumb = (url, label, testSubj, ignoreGlobalState = false) => {
- const crumb = { url, label, ignoreGlobalState };
- if (testSubj) {
- crumb.testSubj = testSubj;
- }
- return crumb;
-};
-
-// generate Elasticsearch breadcrumbs
-function getElasticsearchBreadcrumbs(mainInstance) {
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/elasticsearch', 'Elasticsearch'));
- if (mainInstance.name === 'indices') {
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/indices',
- i18n.translate('xpack.monitoring.breadcrumbs.es.indicesLabel', {
- defaultMessage: 'Indices',
- }),
- 'breadcrumbEsIndices'
- )
- );
- } else if (mainInstance.name === 'nodes') {
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/nodes',
- i18n.translate('xpack.monitoring.breadcrumbs.es.nodesLabel', { defaultMessage: 'Nodes' }),
- 'breadcrumbEsNodes'
- )
- );
- } else if (mainInstance.name === 'ml') {
- // ML Instance (for user later)
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/ml_jobs',
- i18n.translate('xpack.monitoring.breadcrumbs.es.jobsLabel', {
- defaultMessage: 'Machine learning jobs',
- })
- )
- );
- } else if (mainInstance.name === 'ccr_shard') {
- breadcrumbs.push(
- createCrumb(
- '#/elasticsearch/ccr',
- i18n.translate('xpack.monitoring.breadcrumbs.es.ccrLabel', { defaultMessage: 'CCR' })
- )
- );
- }
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, 'Elasticsearch'));
- }
- return breadcrumbs;
-}
-
-// generate Kibana breadcrumbs
-function getKibanaBreadcrumbs(mainInstance) {
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/kibana', 'Kibana'));
- breadcrumbs.push(
- createCrumb(
- '#/kibana/instances',
- i18n.translate('xpack.monitoring.breadcrumbs.kibana.instancesLabel', {
- defaultMessage: 'Instances',
- })
- )
- );
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, 'Kibana'));
- }
- return breadcrumbs;
-}
-
-// generate Logstash breadcrumbs
-function getLogstashBreadcrumbs(mainInstance) {
- const logstashLabel = i18n.translate('xpack.monitoring.breadcrumbs.logstashLabel', {
- defaultMessage: 'Logstash',
- });
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/logstash', logstashLabel));
- if (mainInstance.name === 'nodes') {
- breadcrumbs.push(
- createCrumb(
- '#/logstash/nodes',
- i18n.translate('xpack.monitoring.breadcrumbs.logstash.nodesLabel', {
- defaultMessage: 'Nodes',
- })
- )
- );
- }
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else if (mainInstance.page === 'pipeline') {
- breadcrumbs.push(createCrumb('#/logstash', logstashLabel));
- breadcrumbs.push(
- createCrumb(
- '#/logstash/pipelines',
- i18n.translate('xpack.monitoring.breadcrumbs.logstash.pipelinesLabel', {
- defaultMessage: 'Pipelines',
- })
- )
- );
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, logstashLabel));
- }
-
- return breadcrumbs;
-}
-
-// generate Beats breadcrumbs
-function getBeatsBreadcrumbs(mainInstance) {
- const beatsLabel = i18n.translate('xpack.monitoring.breadcrumbs.beatsLabel', {
- defaultMessage: 'Beats',
- });
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/beats', beatsLabel));
- breadcrumbs.push(
- createCrumb(
- '#/beats/beats',
- i18n.translate('xpack.monitoring.breadcrumbs.beats.instancesLabel', {
- defaultMessage: 'Instances',
- })
- )
- );
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- breadcrumbs.push(createCrumb(null, beatsLabel));
- }
-
- return breadcrumbs;
-}
-
-// generate Apm breadcrumbs
-function getApmBreadcrumbs(mainInstance) {
- const apmLabel = i18n.translate('xpack.monitoring.breadcrumbs.apmLabel', {
- defaultMessage: 'APM server',
- });
- const breadcrumbs = [];
- if (mainInstance.instance) {
- breadcrumbs.push(createCrumb('#/apm', apmLabel));
- breadcrumbs.push(
- createCrumb(
- '#/apm/instances',
- i18n.translate('xpack.monitoring.breadcrumbs.apm.instancesLabel', {
- defaultMessage: 'Instances',
- })
- )
- );
- breadcrumbs.push(createCrumb(null, mainInstance.instance));
- } else {
- // don't link to Overview when we're possibly on Overview or its sibling tabs
- breadcrumbs.push(createCrumb(null, apmLabel));
- }
- return breadcrumbs;
-}
-
-export function breadcrumbsProvider() {
- return function createBreadcrumbs(clusterName, mainInstance) {
- const homeCrumb = i18n.translate('xpack.monitoring.breadcrumbs.clustersLabel', {
- defaultMessage: 'Clusters',
- });
-
- let breadcrumbs = [createCrumb('#/home', homeCrumb, 'breadcrumbClusters', true)];
-
- if (!mainInstance.inOverview && clusterName) {
- breadcrumbs.push(createCrumb('#/overview', clusterName));
- }
-
- if (mainInstance.inElasticsearch) {
- breadcrumbs = breadcrumbs.concat(getElasticsearchBreadcrumbs(mainInstance));
- }
- if (mainInstance.inKibana) {
- breadcrumbs = breadcrumbs.concat(getKibanaBreadcrumbs(mainInstance));
- }
- if (mainInstance.inLogstash) {
- breadcrumbs = breadcrumbs.concat(getLogstashBreadcrumbs(mainInstance));
- }
- if (mainInstance.inBeats) {
- breadcrumbs = breadcrumbs.concat(getBeatsBreadcrumbs(mainInstance));
- }
- if (mainInstance.inApm) {
- breadcrumbs = breadcrumbs.concat(getApmBreadcrumbs(mainInstance));
- }
-
- Legacy.shims.breadcrumbs.set(
- breadcrumbs.map((b) => ({
- text: b.label,
- href: b.url,
- 'data-test-subj': b.testSubj,
- ignoreGlobalState: b.ignoreGlobalState,
- }))
- );
-
- return breadcrumbs;
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js b/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
deleted file mode 100644
index 0af5d59e54555..0000000000000
--- a/x-pack/plugins/monitoring/public/services/breadcrumbs.test.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import expect from '@kbn/expect';
-import { breadcrumbsProvider } from './breadcrumbs';
-import { MonitoringMainController } from '../directives/main';
-import { Legacy } from '../legacy_shims';
-
-describe('Monitoring Breadcrumbs Service', () => {
- const core = {
- notifications: {},
- application: {},
- i18n: {},
- chrome: {},
- };
- const data = {
- query: {
- timefilter: {
- timefilter: {
- isTimeRangeSelectorEnabled: () => true,
- getTime: () => 1,
- getRefreshInterval: () => 1,
- },
- },
- },
- };
- const isCloud = false;
- const triggersActionsUi = {};
-
- beforeAll(() => {
- Legacy.init({
- core,
- data,
- isCloud,
- triggersActionsUi,
- });
- });
-
- it('in Cluster Alerts', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- name: 'alerts',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- ]);
- });
-
- it('in Cluster Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- name: 'overview',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- ]);
- });
-
- it('in ES Node - Advanced', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'elasticsearch',
- name: 'nodes',
- instance: 'es-node-name-01',
- resolver: 'es-node-resolver-01',
- page: 'advanced',
- tabIconClass: 'fa star',
- tabIconLabel: 'Master Node',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: '#/elasticsearch', label: 'Elasticsearch', ignoreGlobalState: false },
- {
- url: '#/elasticsearch/nodes',
- label: 'Nodes',
- testSubj: 'breadcrumbEsNodes',
- ignoreGlobalState: false,
- },
- { url: null, label: 'es-node-name-01', ignoreGlobalState: false },
- ]);
- });
-
- it('in Kibana Overview', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'kibana',
- name: 'overview',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: null, label: 'Kibana', ignoreGlobalState: false },
- ]);
- });
-
- /**
- *
- */
- it('in Logstash Listing', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'logstash',
- name: 'listing',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: null, label: 'Logstash', ignoreGlobalState: false },
- ]);
- });
-
- /**
- *
- */
- it('in Logstash Pipeline Viewer', () => {
- const controller = new MonitoringMainController();
- controller.setup({
- clusterName: 'test-cluster-foo',
- licenseService: {},
- breadcrumbsService: breadcrumbsProvider(),
- attributes: {
- product: 'logstash',
- page: 'pipeline',
- pipelineId: 'main',
- pipelineHash: '42ee890af9...',
- },
- });
- expect(controller.breadcrumbs).to.eql([
- { url: '#/home', label: 'Clusters', testSubj: 'breadcrumbClusters', ignoreGlobalState: true },
- { url: '#/overview', label: 'test-cluster-foo', ignoreGlobalState: false },
- { url: '#/logstash', label: 'Logstash', ignoreGlobalState: false },
- { url: '#/logstash/pipelines', label: 'Pipelines', ignoreGlobalState: false },
- ]);
- });
-});
diff --git a/x-pack/plugins/monitoring/public/services/clusters.js b/x-pack/plugins/monitoring/public/services/clusters.js
deleted file mode 100644
index b19d0ea56765f..0000000000000
--- a/x-pack/plugins/monitoring/public/services/clusters.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../lib/ajax_error_handler';
-import { Legacy } from '../legacy_shims';
-import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../common/constants';
-
-function formatClusters(clusters) {
- return clusters.map(formatCluster);
-}
-
-function formatCluster(cluster) {
- if (cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID) {
- cluster.cluster_name = 'Standalone Cluster';
- }
- return cluster;
-}
-
-export function monitoringClustersProvider($injector) {
- return async (clusterUuid, ccs, codePaths) => {
- const { min, max } = Legacy.shims.timefilter.getBounds();
-
- // append clusterUuid if the parameter is given
- let url = '../api/monitoring/v1/clusters';
- if (clusterUuid) {
- url += `/${clusterUuid}`;
- }
-
- const $http = $injector.get('$http');
-
- async function getClusters() {
- try {
- const response = await $http.post(
- url,
- {
- ccs,
- timeRange: {
- min: min.toISOString(),
- max: max.toISOString(),
- },
- codePaths,
- },
- { headers: { 'kbn-system-request': 'true' } }
- );
- return formatClusters(response.data);
- } catch (err) {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- }
- }
-
- return await getClusters();
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
deleted file mode 100644
index 438c5ab83f5e3..0000000000000
--- a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-import { ajaxErrorHandlersProvider } from '../lib/ajax_error_handler';
-import { showAlertsToast } from '../alerts/lib/alerts_toast';
-
-export function enableAlertsModalProvider($http, $window, $injector) {
- function shouldShowAlertsModal(alerts) {
- const modalHasBeenShown = $window.sessionStorage.getItem('ALERTS_MODAL_HAS_BEEN_SHOWN');
- const decisionMade = $window.localStorage.getItem('ALERTS_MODAL_DECISION_MADE');
-
- if (Object.keys(alerts).length > 0) {
- $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true);
- return false;
- } else if (!modalHasBeenShown && !decisionMade) {
- return true;
- }
-
- return false;
- }
-
- async function enableAlerts() {
- try {
- const { data } = await $http.post('../api/monitoring/v1/alerts/enable', {});
- $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true);
- showAlertsToast(data);
- } catch (err) {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- }
- }
-
- function notAskAgain() {
- $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true);
- }
-
- function hideModalForSession() {
- $window.sessionStorage.setItem('ALERTS_MODAL_HAS_BEEN_SHOWN', true);
- }
-
- return {
- shouldShowAlertsModal,
- enableAlerts,
- notAskAgain,
- hideModalForSession,
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/executor.js b/x-pack/plugins/monitoring/public/services/executor.js
deleted file mode 100644
index 60b2c171eac32..0000000000000
--- a/x-pack/plugins/monitoring/public/services/executor.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Legacy } from '../legacy_shims';
-import { subscribeWithScope } from '../angular/helpers/utils';
-import { Subscription } from 'rxjs';
-
-export function executorProvider($timeout, $q) {
- const queue = [];
- const subscriptions = new Subscription();
- let executionTimer;
- let ignorePaused = false;
-
- /**
- * Resets the timer to start again
- * @returns {void}
- */
- function reset() {
- cancel();
- start();
- }
-
- function killTimer() {
- if (executionTimer) {
- $timeout.cancel(executionTimer);
- }
- }
-
- /**
- * Cancels the execution timer
- * @returns {void}
- */
- function cancel() {
- killTimer();
- }
-
- /**
- * Registers a service with the executor
- * @param {object} service The service to register
- * @returns {void}
- */
- function register(service) {
- queue.push(service);
- }
-
- /**
- * Stops the executor and empties the service queue
- * @returns {void}
- */
- function destroy() {
- subscriptions.unsubscribe();
- cancel();
- ignorePaused = false;
- queue.splice(0, queue.length);
- }
-
- /**
- * Runs the queue (all at once)
- * @returns {Promise} a promise of all the services
- */
- function run() {
- const noop = () => $q.resolve();
- return $q
- .all(
- queue.map((service) => {
- return service
- .execute()
- .then(service.handleResponse || noop)
- .catch(service.handleError || noop);
- })
- )
- .finally(reset);
- }
-
- function reFetch() {
- cancel();
- run();
- }
-
- function killIfPaused() {
- if (Legacy.shims.timefilter.getRefreshInterval().pause) {
- killTimer();
- }
- }
-
- /**
- * Starts the executor service if the timefilter is not paused
- * @returns {void}
- */
- function start() {
- const timefilter = Legacy.shims.timefilter;
- if (
- (ignorePaused || timefilter.getRefreshInterval().pause === false) &&
- timefilter.getRefreshInterval().value > 0
- ) {
- executionTimer = $timeout(run, timefilter.getRefreshInterval().value);
- }
- }
-
- /**
- * Expose the methods
- */
- return {
- register,
- start($scope) {
- $scope.$applyAsync(() => {
- const timefilter = Legacy.shims.timefilter;
- subscriptions.add(
- subscribeWithScope($scope, timefilter.getFetch$(), {
- next: reFetch,
- })
- );
- subscriptions.add(
- subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), {
- next: killIfPaused,
- })
- );
- start();
- });
- },
- run,
- destroy,
- reset,
- cancel,
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/features.js b/x-pack/plugins/monitoring/public/services/features.js
deleted file mode 100644
index 34564f79c9247..0000000000000
--- a/x-pack/plugins/monitoring/public/services/features.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { has, isUndefined } from 'lodash';
-
-export function featuresProvider($window) {
- function getData() {
- let returnData = {};
- const monitoringData = $window.localStorage.getItem('xpack.monitoring.data');
-
- try {
- returnData = (monitoringData && JSON.parse(monitoringData)) || {};
- } catch (e) {
- console.error('Monitoring UI: error parsing locally stored monitoring data', e);
- }
-
- return returnData;
- }
-
- function update(featureName, value) {
- const monitoringDataObj = getData();
- monitoringDataObj[featureName] = value;
- $window.localStorage.setItem('xpack.monitoring.data', JSON.stringify(monitoringDataObj));
- }
-
- function isEnabled(featureName, defaultSetting) {
- const monitoringDataObj = getData();
- if (has(monitoringDataObj, featureName)) {
- return monitoringDataObj[featureName];
- }
-
- if (isUndefined(defaultSetting)) {
- return false;
- }
-
- return defaultSetting;
- }
-
- return {
- isEnabled,
- update,
- };
-}
diff --git a/x-pack/plugins/monitoring/public/services/license.js b/x-pack/plugins/monitoring/public/services/license.js
deleted file mode 100644
index cab5ad01cf58a..0000000000000
--- a/x-pack/plugins/monitoring/public/services/license.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { includes } from 'lodash';
-import { ML_SUPPORTED_LICENSES } from '../../common/constants';
-
-export function licenseProvider() {
- return new (class LicenseService {
- constructor() {
- // do not initialize with usable state
- this.license = {
- type: null,
- expiry_date_in_millis: -Infinity,
- };
- }
-
- // we're required to call this initially
- setLicense(license) {
- this.license = license;
- }
-
- isBasic() {
- return this.license.type === 'basic';
- }
-
- mlIsSupported() {
- return includes(ML_SUPPORTED_LICENSES, this.license.type);
- }
-
- doesExpire() {
- const { expiry_date_in_millis: expiryDateInMillis } = this.license;
- return expiryDateInMillis !== undefined;
- }
-
- isActive() {
- const { expiry_date_in_millis: expiryDateInMillis } = this.license;
- return new Date().getTime() < expiryDateInMillis;
- }
-
- isExpired() {
- if (this.doesExpire()) {
- const { expiry_date_in_millis: expiryDateInMillis } = this.license;
- return new Date().getTime() >= expiryDateInMillis;
- }
- return false;
- }
- })();
-}
diff --git a/x-pack/plugins/monitoring/public/services/title.js b/x-pack/plugins/monitoring/public/services/title.js
deleted file mode 100644
index e12d4936584fa..0000000000000
--- a/x-pack/plugins/monitoring/public/services/title.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { get } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { Legacy } from '../legacy_shims';
-
-export function titleProvider($rootScope) {
- return function changeTitle(cluster, suffix) {
- let clusterName = get(cluster, 'cluster_name');
- clusterName = clusterName ? `- ${clusterName}` : '';
- suffix = suffix ? `- ${suffix}` : '';
- $rootScope.$applyAsync(() => {
- Legacy.shims.docTitle.change(
- i18n.translate('xpack.monitoring.stackMonitoringDocTitle', {
- defaultMessage: 'Stack Monitoring {clusterName} {suffix}',
- values: { clusterName, suffix },
- })
- );
- });
- };
-}
diff --git a/x-pack/plugins/monitoring/public/views/access_denied/index.html b/x-pack/plugins/monitoring/public/views/access_denied/index.html
deleted file mode 100644
index 24863559212f7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/access_denied/index.html
+++ /dev/null
@@ -1,44 +0,0 @@
-
diff --git a/x-pack/plugins/monitoring/public/views/access_denied/index.js b/x-pack/plugins/monitoring/public/views/access_denied/index.js
deleted file mode 100644
index e52df61dd8966..0000000000000
--- a/x-pack/plugins/monitoring/public/views/access_denied/index.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../angular/helpers/routes';
-import template from './index.html';
-
-const tryPrivilege = ($http) => {
- return $http
- .get('../api/monitoring/v1/check_access')
- .then(() => window.history.replaceState(null, null, '#/home'))
- .catch(() => true);
-};
-
-uiRoutes.when('/access-denied', {
- template,
- resolve: {
- /*
- * The user may have been granted privileges in between leaving Monitoring
- * and before coming back to Monitoring. That means, they just be on this
- * page because Kibana remembers the "last app URL". We check for the
- * privilege one time up front (doing it in the resolve makes it happen
- * before the template renders), and then keep retrying every 5 seconds.
- */
- initialCheck($http) {
- return tryPrivilege($http);
- },
- },
- controllerAs: 'accessDenied',
- controller: function ($scope, $injector) {
- const $http = $injector.get('$http');
- const $interval = $injector.get('$interval');
-
- // The template's "Back to Kibana" button click handler
- this.goToKibanaURL = '/app/home';
-
- // keep trying to load data in the background
- const accessPoller = $interval(() => tryPrivilege($http), 5 * 1000); // every 5 seconds
- $scope.$on('$destroy', () => $interval.cancel(accessPoller));
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/all.js b/x-pack/plugins/monitoring/public/views/all.js
deleted file mode 100644
index 3af0c85d95687..0000000000000
--- a/x-pack/plugins/monitoring/public/views/all.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import './no_data';
-import './access_denied';
-import './license';
-import './cluster/listing';
-import './cluster/overview';
-import './elasticsearch/overview';
-import './elasticsearch/indices';
-import './elasticsearch/index';
-import './elasticsearch/index/advanced';
-import './elasticsearch/nodes';
-import './elasticsearch/node';
-import './elasticsearch/node/advanced';
-import './elasticsearch/ccr';
-import './elasticsearch/ccr/shard';
-import './elasticsearch/ml_jobs';
-import './kibana/overview';
-import './kibana/instances';
-import './kibana/instance';
-import './logstash/overview';
-import './logstash/nodes';
-import './logstash/node';
-import './logstash/node/advanced';
-import './logstash/node/pipelines';
-import './logstash/pipelines';
-import './logstash/pipeline';
-import './beats/overview';
-import './beats/listing';
-import './beats/beat';
-import './apm/overview';
-import './apm/instances';
-import './apm/instance';
-import './loading';
diff --git a/x-pack/plugins/monitoring/public/views/apm/instance/index.html b/x-pack/plugins/monitoring/public/views/apm/instance/index.html
deleted file mode 100644
index 79579990eb649..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instance/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/apm/instance/index.js b/x-pack/plugins/monitoring/public/views/apm/instance/index.js
deleted file mode 100644
index 0d733036bb266..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instance/index.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find, get } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { ApmServerInstance } from '../../../components/apm/instance';
-import { CODE_PATH_APM } from '../../../../common/constants';
-
-uiRoutes.when('/apm/instances/:uuid', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_APM] });
- },
- },
-
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const title = $injector.get('title');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
- super({
- title: i18n.translate('xpack.monitoring.apm.instance.routeTitle', {
- defaultMessage: '{apm} - Instance',
- values: {
- apm: 'APM server',
- },
- }),
- telemetryPageViewTitle: 'apm_server_instance',
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/apm/${$route.current.params.uuid}`,
- defaultData: {},
- reactNodeId: 'apmInstanceReact',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.setPageTitle(
- i18n.translate('xpack.monitoring.apm.instance.pageTitle', {
- defaultMessage: 'APM server instance: {instanceName}',
- values: {
- instanceName: get(data, 'apmSummary.name'),
- },
- })
- );
- title($scope.cluster, `APM server - ${get(data, 'apmSummary.name')}`);
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/apm/instances/index.html b/x-pack/plugins/monitoring/public/views/apm/instances/index.html
deleted file mode 100644
index fd8029e277d78..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instances/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/apm/instances/index.js b/x-pack/plugins/monitoring/public/views/apm/instances/index.js
deleted file mode 100644
index f9747ec176e86..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/instances/index.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { ApmServerInstances } from '../../../components/apm/instances';
-import { MonitoringViewBaseEuiTableController } from '../..';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { APM_SYSTEM_ID, CODE_PATH_APM } from '../../../../common/constants';
-
-uiRoutes.when('/apm/instances', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_APM] });
- },
- },
- controller: class extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.apm.instances.routeTitle', {
- defaultMessage: '{apm} - Instances',
- values: {
- apm: 'APM server',
- },
- }),
- pageTitle: i18n.translate('xpack.monitoring.apm.instances.pageTitle', {
- defaultMessage: 'APM server instances',
- }),
- storageKey: 'apm.instances',
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/apm/instances`,
- defaultData: {},
- reactNodeId: 'apmInstancesReact',
- $scope,
- $injector,
- });
-
- this.scope = $scope;
- this.injector = $injector;
- this.onTableChangeRender = this.renderComponent;
-
- $scope.$watch(
- () => this.data,
- () => this.renderComponent()
- );
- }
-
- renderComponent() {
- const { pagination, sorting, onTableChange } = this;
-
- const component = (
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- this.renderReact(component);
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/apm/overview/index.html b/x-pack/plugins/monitoring/public/views/apm/overview/index.html
deleted file mode 100644
index 0cf804e377476..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/overview/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/apm/overview/index.js b/x-pack/plugins/monitoring/public/views/apm/overview/index.js
deleted file mode 100644
index bef17bf4a2fad..0000000000000
--- a/x-pack/plugins/monitoring/public/views/apm/overview/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { ApmOverview } from '../../../components/apm/overview';
-import { CODE_PATH_APM } from '../../../../common/constants';
-
-uiRoutes.when('/apm', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_APM] });
- },
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.apm.overview.routeTitle', {
- defaultMessage: 'APM server',
- }),
- pageTitle: i18n.translate('xpack.monitoring.apm.overview.pageTitle', {
- defaultMessage: 'APM server overview',
- }),
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/apm`,
- defaultData: {},
- reactNodeId: 'apmOverviewReact',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js
deleted file mode 100644
index dd9898a6e195c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/base_controller.js
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import moment from 'moment';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { getPageData } from '../lib/get_page_data';
-import { PageLoading } from '../components';
-import { Legacy } from '../legacy_shims';
-import { PromiseWithCancel } from '../../common/cancel_promise';
-import { SetupModeFeature } from '../../common/enums';
-import { updateSetupModeData, isSetupModeFeatureEnabled } from '../lib/setup_mode';
-import { AlertsContext } from '../alerts/context';
-import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
-import { AlertsDropdown } from '../alerts/alerts_dropdown';
-import { HeaderMenuPortal } from '../../../observability/public';
-
-/**
- * Given a timezone, this function will calculate the offset in milliseconds
- * from UTC time.
- *
- * @param {string} timezone
- */
-const getOffsetInMS = (timezone) => {
- if (timezone === 'Browser') {
- return 0;
- }
- const offsetInMinutes = moment.tz(timezone).utcOffset();
- const offsetInMS = offsetInMinutes * 1 * 60 * 1000;
- return offsetInMS;
-};
-
-/**
- * Class to manage common instantiation behaviors in a view controller
- *
- * This is expected to be extended, and behavior enabled using super();
- *
- * Example:
- * uiRoutes.when('/myRoute', {
- * template: importedTemplate,
- * controllerAs: 'myView',
- * controller: class MyView extends MonitoringViewBaseController {
- * constructor($injector, $scope) {
- * super({
- * title: 'Hello World',
- * api: '../api/v1/monitoring/foo/bar',
- * defaultData,
- * reactNodeId,
- * $scope,
- * $injector,
- * options: {
- * enableTimeFilter: false // this will have just the page auto-refresh control show
- * }
- * });
- * }
- * }
- * });
- */
-export class MonitoringViewBaseController {
- /**
- * Create a view controller
- * @param {String} title - Title of the page
- * @param {String} api - Back-end API endpoint to poll for getting the page
- * data using POST and time range data in the body. Whenever possible, use
- * this method for data polling rather than supply the getPageData param.
- * @param {Function} apiUrlFn - Function that returns a string for the back-end
- * API endpoint, in case the string has dynamic query parameters (e.g.
- * show_system_indices) rather than supply the getPageData param.
- * @param {Function} getPageData - (Optional) Function to fetch page data, if
- * simply passing the API string isn't workable.
- * @param {Object} defaultData - Initial model data to populate
- * @param {String} reactNodeId - DOM element ID of the element for mounting
- * the view's main React component
- * @param {Service} $injector - Angular dependency injection service
- * @param {Service} $scope - Angular view data binding service
- * @param {Boolean} options.enableTimeFilter - Whether to show the time filter
- * @param {Boolean} options.enableAutoRefresh - Whether to show the auto
- * refresh control
- */
- constructor({
- title = '',
- pageTitle = '',
- api = '',
- apiUrlFn,
- getPageData: _getPageData = getPageData,
- defaultData,
- reactNodeId = null, // WIP: https://github.com/elastic/x-pack-kibana/issues/5198
- $scope,
- $injector,
- options = {},
- alerts = { shouldFetch: false, options: {} },
- fetchDataImmediately = true,
- telemetryPageViewTitle = '',
- }) {
- const titleService = $injector.get('title');
- const $executor = $injector.get('$executor');
- const $window = $injector.get('$window');
- const config = $injector.get('config');
-
- titleService($scope.cluster, title);
-
- $scope.pageTitle = pageTitle;
- this.setPageTitle = (title) => ($scope.pageTitle = title);
- $scope.pageData = this.data = { ...defaultData };
- this._isDataInitialized = false;
- this.reactNodeId = reactNodeId;
- this.telemetryPageViewTitle = telemetryPageViewTitle || title;
-
- let deferTimer;
- let zoomInLevel = 0;
-
- const popstateHandler = () => zoomInLevel > 0 && --zoomInLevel;
- const removePopstateHandler = () => $window.removeEventListener('popstate', popstateHandler);
- const addPopstateHandler = () => $window.addEventListener('popstate', popstateHandler);
-
- this.zoomInfo = {
- zoomOutHandler: () => $window.history.back(),
- showZoomOutBtn: () => zoomInLevel > 0,
- };
-
- const { enableTimeFilter = true, enableAutoRefresh = true } = options;
-
- async function fetchAlerts() {
- const globalState = $injector.get('globalState');
- const bounds = Legacy.shims.timefilter.getBounds();
- const min = bounds.min?.valueOf();
- const max = bounds.max?.valueOf();
- const options = alerts.options || {};
- try {
- return await Legacy.shims.http.post(
- `/api/monitoring/v1/alert/${globalState.cluster_uuid}/status`,
- {
- body: JSON.stringify({
- alertTypeIds: options.alertTypeIds,
- filters: options.filters,
- timeRange: {
- min,
- max,
- },
- }),
- }
- );
- } catch (err) {
- Legacy.shims.toastNotifications.addDanger({
- title: 'Error fetching alert status',
- text: err.message,
- });
- }
- }
-
- this.updateData = () => {
- if (this.updateDataPromise) {
- // Do not sent another request if one is inflight
- // See https://github.com/elastic/kibana/issues/24082
- this.updateDataPromise.cancel();
- this.updateDataPromise = null;
- }
- const _api = apiUrlFn ? apiUrlFn() : api;
- const promises = [_getPageData($injector, _api, this.getPaginationRouteOptions())];
- if (alerts.shouldFetch) {
- promises.push(fetchAlerts());
- }
- if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) {
- promises.push(updateSetupModeData());
- }
- this.updateDataPromise = new PromiseWithCancel(Promise.allSettled(promises));
- return this.updateDataPromise.promise().then(([pageData, alerts]) => {
- $scope.$apply(() => {
- this._isDataInitialized = true; // render will replace loading screen with the react component
- $scope.pageData = this.data = pageData.value; // update the view's data with the fetch result
- $scope.alerts = this.alerts = alerts && alerts.value ? alerts.value : {};
- });
- });
- };
-
- $scope.$applyAsync(() => {
- const timefilter = Legacy.shims.timefilter;
-
- if (enableTimeFilter === false) {
- timefilter.disableTimeRangeSelector();
- } else {
- timefilter.enableTimeRangeSelector();
- }
-
- if (enableAutoRefresh === false) {
- timefilter.disableAutoRefreshSelector();
- } else {
- timefilter.enableAutoRefreshSelector();
- }
-
- // needed for chart pages
- this.onBrush = ({ xaxis }) => {
- removePopstateHandler();
- const { to, from } = xaxis;
- const timezone = config.get('dateFormat:tz');
- const offset = getOffsetInMS(timezone);
- timefilter.setTime({
- from: moment(from - offset),
- to: moment(to - offset),
- mode: 'absolute',
- });
- $executor.cancel();
- $executor.run();
- ++zoomInLevel;
- clearTimeout(deferTimer);
- /*
- Needed to defer 'popstate' event, so it does not fire immediately after it's added.
- 10ms is to make sure the event is not added with the same code digest
- */
- deferTimer = setTimeout(() => addPopstateHandler(), 10);
- };
-
- // Render loading state
- this.renderReact(null, true);
- fetchDataImmediately && this.updateData();
- });
-
- $executor.register({
- execute: () => this.updateData(),
- });
- $executor.start($scope);
- $scope.$on('$destroy', () => {
- clearTimeout(deferTimer);
- removePopstateHandler();
- const targetElement = document.getElementById(this.reactNodeId);
- if (targetElement) {
- // WIP https://github.com/elastic/x-pack-kibana/issues/5198
- unmountComponentAtNode(targetElement);
- }
- $executor.destroy();
- });
-
- this.setTitle = (title) => titleService($scope.cluster, title);
- }
-
- renderReact(component, trackPageView = false) {
- const renderElement = document.getElementById(this.reactNodeId);
- if (!renderElement) {
- console.warn(`"#${this.reactNodeId}" element has not been added to the DOM yet`);
- return;
- }
- const I18nContext = Legacy.shims.I18nContext;
- const wrappedComponent = (
-
-
-
-
-
-
- {!this._isDataInitialized ? (
-
- ) : (
- component
- )}
-
-
-
- );
- render(wrappedComponent, renderElement);
- }
-
- getPaginationRouteOptions() {
- return {};
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/base_eui_table_controller.js b/x-pack/plugins/monitoring/public/views/base_eui_table_controller.js
deleted file mode 100644
index 0520ce3f10de5..0000000000000
--- a/x-pack/plugins/monitoring/public/views/base_eui_table_controller.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { MonitoringViewBaseController } from './';
-import { euiTableStorageGetter, euiTableStorageSetter } from '../components/table';
-import { EUI_SORT_ASCENDING } from '../../common/constants';
-
-const PAGE_SIZE_OPTIONS = [5, 10, 20, 50];
-
-/**
- * Class to manage common instantiation behaviors in a view controller
- * And add persistent state to a table:
- * - page index: in table pagination, which page are we looking at
- * - filter text: what filter was entered in the table's filter bar
- * - sortKey: which column field of table data is used for sorting
- * - sortOrder: is sorting ordered ascending or descending
- *
- * This is expected to be extended, and behavior enabled using super();
- */
-export class MonitoringViewBaseEuiTableController extends MonitoringViewBaseController {
- /**
- * Create a table view controller
- * - used by parent class:
- * @param {String} title - Title of the page
- * @param {Function} getPageData - Function to fetch page data
- * @param {Service} $injector - Angular dependency injection service
- * @param {Service} $scope - Angular view data binding service
- * @param {Boolean} options.enableTimeFilter - Whether to show the time filter
- * @param {Boolean} options.enableAutoRefresh - Whether to show the auto refresh control
- * - specific to this class:
- * @param {String} storageKey - the namespace that will be used to keep the state data in the Monitoring localStorage object
- *
- */
- constructor(args) {
- super(args);
- const { storageKey, $injector } = args;
- const storage = $injector.get('localStorage');
-
- const getLocalStorageData = euiTableStorageGetter(storageKey);
- const setLocalStorageData = euiTableStorageSetter(storageKey);
- const { page, sort } = getLocalStorageData(storage);
-
- this.pagination = {
- pageSize: 20,
- initialPageSize: 20,
- pageIndex: 0,
- initialPageIndex: 0,
- pageSizeOptions: PAGE_SIZE_OPTIONS,
- };
-
- if (page) {
- if (!PAGE_SIZE_OPTIONS.includes(page.size)) {
- page.size = 20;
- }
- this.setPagination(page);
- }
-
- this.setSorting(sort);
-
- this.onTableChange = ({ page, sort }) => {
- this.setPagination(page);
- this.setSorting({ sort });
- setLocalStorageData(storage, {
- page,
- sort: {
- sort,
- },
- });
- if (this.onTableChangeRender) {
- this.onTableChangeRender();
- }
- };
-
- // For pages where we do not fetch immediately, we want to fetch after pagination is applied
- args.fetchDataImmediately === false && this.updateData();
- }
-
- setPagination(page) {
- this.pagination = {
- initialPageSize: page.size,
- pageSize: page.size,
- initialPageIndex: page.index,
- pageIndex: page.index,
- pageSizeOptions: PAGE_SIZE_OPTIONS,
- };
- }
-
- setSorting(sort) {
- this.sorting = sort || { sort: {} };
-
- if (!this.sorting.sort.field) {
- this.sorting.sort.field = 'name';
- }
- if (!this.sorting.sort.direction) {
- this.sorting.sort.direction = EUI_SORT_ASCENDING;
- }
- }
-
- setQueryText(queryText) {
- this.queryText = queryText;
- }
-
- getPaginationRouteOptions() {
- if (!this.pagination || !this.sorting) {
- return {};
- }
-
- return {
- pagination: {
- size: this.pagination.pageSize,
- index: this.pagination.pageIndex,
- },
- ...this.sorting,
- queryText: this.queryText,
- };
- }
-
- getPaginationTableProps(pagination) {
- return {
- sorting: this.sorting,
- pagination: pagination,
- onTableChange: this.onTableChange,
- fetchMoreData: async ({ page, sort, queryText }) => {
- this.setPagination(page);
- this.setSorting(sort);
- this.setQueryText(queryText);
- await this.updateData();
- },
- };
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/base_table_controller.js b/x-pack/plugins/monitoring/public/views/base_table_controller.js
deleted file mode 100644
index a066a91e48c8b..0000000000000
--- a/x-pack/plugins/monitoring/public/views/base_table_controller.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { MonitoringViewBaseController } from './';
-import { tableStorageGetter, tableStorageSetter } from '../components/table';
-
-/**
- * Class to manage common instantiation behaviors in a view controller
- * And add persistent state to a table:
- * - page index: in table pagination, which page are we looking at
- * - filter text: what filter was entered in the table's filter bar
- * - sortKey: which column field of table data is used for sorting
- * - sortOrder: is sorting ordered ascending or descending
- *
- * This is expected to be extended, and behavior enabled using super();
- */
-export class MonitoringViewBaseTableController extends MonitoringViewBaseController {
- /**
- * Create a table view controller
- * - used by parent class:
- * @param {String} title - Title of the page
- * @param {Function} getPageData - Function to fetch page data
- * @param {Service} $injector - Angular dependency injection service
- * @param {Service} $scope - Angular view data binding service
- * @param {Boolean} options.enableTimeFilter - Whether to show the time filter
- * @param {Boolean} options.enableAutoRefresh - Whether to show the auto refresh control
- * - specific to this class:
- * @param {String} storageKey - the namespace that will be used to keep the state data in the Monitoring localStorage object
- *
- */
- constructor(args) {
- super(args);
- const { storageKey, $injector } = args;
- const storage = $injector.get('localStorage');
-
- const getLocalStorageData = tableStorageGetter(storageKey);
- const setLocalStorageData = tableStorageSetter(storageKey);
- const { pageIndex, filterText, sortKey, sortOrder } = getLocalStorageData(storage);
-
- this.pageIndex = pageIndex;
- this.filterText = filterText;
- this.sortKey = sortKey;
- this.sortOrder = sortOrder;
-
- this.onNewState = (newState) => {
- setLocalStorageData(storage, newState);
- };
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js
deleted file mode 100644
index 7f87fa413d8ca..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/beat/get_page_data.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats/beat/${$route.current.params.beatUuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/index.html b/x-pack/plugins/monitoring/public/views/beats/beat/index.html
deleted file mode 100644
index 6ae727e31cbeb..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/beat/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/beats/beat/index.js b/x-pack/plugins/monitoring/public/views/beats/beat/index.js
deleted file mode 100644
index f1a171a19cd89..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/beat/index.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { CODE_PATH_BEATS } from '../../../../common/constants';
-import { Beat } from '../../../components/beats/beat';
-
-uiRoutes.when('/beats/beat/:beatUuid', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_BEATS] });
- },
- pageData: getPageData,
- },
- controllerAs: 'beat',
- controller: class BeatDetail extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- const pageData = $route.current.locals.pageData;
- super({
- title: i18n.translate('xpack.monitoring.beats.instance.routeTitle', {
- defaultMessage: 'Beats - {instanceName} - Overview',
- values: {
- instanceName: pageData.summary.name,
- },
- }),
- pageTitle: i18n.translate('xpack.monitoring.beats.instance.pageTitle', {
- defaultMessage: 'Beat instance: {beatName}',
- values: {
- beatName: pageData.summary.name,
- },
- }),
- telemetryPageViewTitle: 'beats_instance',
- getPageData,
- $scope,
- $injector,
- reactNodeId: 'monitoringBeatsInstanceApp',
- });
-
- this.data = pageData;
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js
deleted file mode 100644
index 99366f05f3ad4..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/listing/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats/beats`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/index.html b/x-pack/plugins/monitoring/public/views/beats/listing/index.html
deleted file mode 100644
index 0ce66a6848dfd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/listing/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/x-pack/plugins/monitoring/public/views/beats/listing/index.js b/x-pack/plugins/monitoring/public/views/beats/listing/index.js
deleted file mode 100644
index eae74d8a08b9e..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/listing/index.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import React from 'react';
-import { Listing } from '../../../components/beats/listing/listing';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { CODE_PATH_BEATS, BEATS_SYSTEM_ID } from '../../../../common/constants';
-
-uiRoutes.when('/beats/beats', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_BEATS] });
- },
- pageData: getPageData,
- },
- controllerAs: 'beats',
- controller: class BeatsListing extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.beats.routeTitle', { defaultMessage: 'Beats' }),
- pageTitle: i18n.translate('xpack.monitoring.beats.listing.pageTitle', {
- defaultMessage: 'Beats listing',
- }),
- telemetryPageViewTitle: 'beats_listing',
- storageKey: 'beats.beats',
- getPageData,
- reactNodeId: 'monitoringBeatsInstancesApp',
- $scope,
- $injector,
- });
-
- this.data = $route.current.locals.pageData;
- this.scope = $scope;
- this.injector = $injector;
- this.onTableChangeRender = this.renderComponent;
-
- $scope.$watch(
- () => this.data,
- () => this.renderComponent()
- );
- }
-
- renderComponent() {
- const { sorting, pagination, onTableChange } = this.scope.beats;
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js b/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js
deleted file mode 100644
index 497ed8cdb0e74..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/overview/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/beats`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/index.html b/x-pack/plugins/monitoring/public/views/beats/overview/index.html
deleted file mode 100644
index 0b827c96f68fd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/overview/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/beats/overview/index.js b/x-pack/plugins/monitoring/public/views/beats/overview/index.js
deleted file mode 100644
index 475a63d440c76..0000000000000
--- a/x-pack/plugins/monitoring/public/views/beats/overview/index.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { CODE_PATH_BEATS } from '../../../../common/constants';
-import { BeatsOverview } from '../../../components/beats/overview';
-
-uiRoutes.when('/beats', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_BEATS] });
- },
- pageData: getPageData,
- },
- controllerAs: 'beats',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: i18n.translate('xpack.monitoring.beats.overview.routeTitle', {
- defaultMessage: 'Beats - Overview',
- }),
- pageTitle: i18n.translate('xpack.monitoring.beats.overview.pageTitle', {
- defaultMessage: 'Beats overview',
- }),
- getPageData,
- $scope,
- $injector,
- reactNodeId: 'monitoringBeatsOverviewApp',
- });
-
- this.data = $route.current.locals.pageData;
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.html b/x-pack/plugins/monitoring/public/views/cluster/listing/index.html
deleted file mode 100644
index 713ca8fb1ffc9..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
deleted file mode 100644
index 8b365292aeb13..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import template from './index.html';
-import { Listing } from '../../../components/cluster/listing';
-import { CODE_PATH_ALL } from '../../../../common/constants';
-import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal.tsx';
-
-const CODE_PATHS = [CODE_PATH_ALL];
-
-const getPageData = ($injector) => {
- const monitoringClusters = $injector.get('monitoringClusters');
- return monitoringClusters(undefined, undefined, CODE_PATHS);
-};
-
-const getAlerts = (clusters) => {
- return clusters.reduce((alerts, cluster) => ({ ...alerts, ...cluster.alerts.list }), {});
-};
-
-uiRoutes
- .when('/home', {
- template,
- resolve: {
- clusters: (Private) => {
- const routeInit = Private(routeInitProvider);
- return routeInit({
- codePaths: CODE_PATHS,
- fetchAllClusters: true,
- unsetGlobalState: true,
- }).then((clusters) => {
- if (!clusters || !clusters.length) {
- window.location.hash = '#/no-data';
- return Promise.reject();
- }
- if (clusters.length === 1) {
- // Bypass the cluster listing if there is just 1 cluster
- window.history.replaceState(null, null, '#/overview');
- return Promise.reject();
- }
- return clusters;
- });
- },
- },
- controllerAs: 'clusters',
- controller: class ClustersList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- storageKey: 'clusters',
- pageTitle: i18n.translate('xpack.monitoring.cluster.listing.pageTitle', {
- defaultMessage: 'Cluster listing',
- }),
- getPageData,
- $scope,
- $injector,
- reactNodeId: 'monitoringClusterListingApp',
- telemetryPageViewTitle: 'cluster_listing',
- });
-
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const storage = $injector.get('localStorage');
- const showLicenseExpiration = $injector.get('showLicenseExpiration');
-
- this.data = $route.current.locals.clusters;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
- <>
-
-
- >
- );
- }
- );
- }
- },
- })
- .otherwise({ redirectTo: '/loading' });
diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.html b/x-pack/plugins/monitoring/public/views/cluster/overview/index.html
deleted file mode 100644
index 1762ee1c2a282..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
deleted file mode 100644
index 20e694ad8548f..0000000000000
--- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { isEmpty } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../';
-import { Overview } from '../../../components/cluster/overview';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { CODE_PATH_ALL } from '../../../../common/constants';
-import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal.tsx';
-
-const CODE_PATHS = [CODE_PATH_ALL];
-
-uiRoutes.when('/overview', {
- template,
- resolve: {
- clusters(Private) {
- // checks license info of all monitored clusters for multi-cluster monitoring usage and capability
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: CODE_PATHS });
- },
- },
- controllerAs: 'monitoringClusterOverview',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const monitoringClusters = $injector.get('monitoringClusters');
- const globalState = $injector.get('globalState');
- const showLicenseExpiration = $injector.get('showLicenseExpiration');
-
- super({
- title: i18n.translate('xpack.monitoring.cluster.overviewTitle', {
- defaultMessage: 'Overview',
- }),
- pageTitle: i18n.translate('xpack.monitoring.cluster.overview.pageTitle', {
- defaultMessage: 'Cluster overview',
- }),
- defaultData: {},
- getPageData: async () => {
- const clusters = await monitoringClusters(
- globalState.cluster_uuid,
- globalState.ccs,
- CODE_PATHS
- );
- return clusters[0];
- },
- reactNodeId: 'monitoringClusterOverviewApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- },
- telemetryPageViewTitle: 'cluster_overview',
- });
-
- this.init = () => this.renderReact(null);
-
- $scope.$watch(
- () => this.data,
- async (data) => {
- if (isEmpty(data)) {
- return;
- }
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js
deleted file mode 100644
index 4f45038986332..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html
deleted file mode 100644
index ca0b036ae39e1..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
deleted file mode 100644
index 91cc9c8782b22..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { getPageData } from './get_page_data';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Ccr } from '../../../components/elasticsearch/ccr';
-import { MonitoringViewBaseController } from '../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CCR_READ_EXCEPTIONS,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../common/constants';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/ccr', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'elasticsearchCcr',
- controller: class ElasticsearchCcrController extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.ccr.routeTitle', {
- defaultMessage: 'Elasticsearch - Ccr',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.ccr.pageTitle', {
- defaultMessage: 'Elasticsearch Ccr',
- }),
- reactNodeId: 'elasticsearchCcrReact',
- getPageData,
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_CCR_READ_EXCEPTIONS],
- },
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js
deleted file mode 100644
index ca1aad39e3610..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { Legacy } from '../../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr/${$route.current.params.index}/shard/${$route.current.params.shardId}`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html
deleted file mode 100644
index 76469e5d9add5..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
deleted file mode 100644
index 767fb18685633..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { getPageData } from './get_page_data';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import { CcrShard } from '../../../../components/elasticsearch/ccr_shard';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CCR_READ_EXCEPTIONS,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../../common/constants';
-import { SetupModeRenderer } from '../../../../components/renderers';
-import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'elasticsearchCcr',
- controller: class ElasticsearchCcrController extends MonitoringViewBaseController {
- constructor($injector, $scope, pageData) {
- const $route = $injector.get('$route');
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.routeTitle', {
- defaultMessage: 'Elasticsearch - Ccr - Shard',
- }),
- reactNodeId: 'elasticsearchCcrShardReact',
- getPageData,
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_CCR_READ_EXCEPTIONS],
- filters: [
- {
- shardId: $route.current.pathParams.shardId,
- },
- ],
- },
- },
- });
-
- $scope.instance = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.instanceTitle', {
- defaultMessage: 'Index: {followerIndex} Shard: {shardId}',
- values: {
- followerIndex: get(pageData, 'stat.follower_index'),
- shardId: get(pageData, 'stat.shard_id'),
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.pageTitle', {
- defaultMessage: 'Elasticsearch Ccr Shard - Index: {followerIndex} Shard: {shardId}',
- values: {
- followerIndex: get(
- pageData,
- 'stat.follower.index',
- get(pageData, 'stat.follower_index')
- ),
- shardId: get(
- pageData,
- 'stat.follower.shard.number',
- get(pageData, 'stat.shard_id')
- ),
- },
- })
- );
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.html
deleted file mode 100644
index 159376148d173..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js
deleted file mode 100644
index 9276527951612..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for Advanced Index Detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { AdvancedIndex } from '../../../../components/elasticsearch/index/advanced';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_LARGE_SHARD_SIZE,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../../common/constants';
-import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context';
-import { SetupModeRenderer } from '../../../../components/renderers';
-
-function getPageData($injector) {
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/indices/${$route.current.params.index}`;
- const $http = $injector.get('$http');
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: true,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/elasticsearch/indices/:index/advanced', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringElasticsearchAdvancedIndexApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const indexName = $route.current.params.index;
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.indices.advanced.routeTitle', {
- defaultMessage: 'Elasticsearch - Indices - {indexName} - Advanced',
- values: {
- indexName,
- },
- }),
- telemetryPageViewTitle: 'elasticsearch_index_advanced',
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchAdvancedIndexApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LARGE_SHARD_SIZE],
- filters: [
- {
- shardIndex: $route.current.pathParams.index,
- },
- ],
- },
- },
- });
-
- this.indexName = indexName;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.html
deleted file mode 100644
index 84d90f184358d..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js
deleted file mode 100644
index c9efb622ff9d1..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for single index detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
-import { indicesByNodes } from '../../../components/elasticsearch/shard_allocation/transformers/indices_by_nodes';
-import { Index } from '../../../components/elasticsearch/index/index';
-import { MonitoringViewBaseController } from '../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_LARGE_SHARD_SIZE,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../common/constants';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import { SetupModeRenderer } from '../../../components/renderers';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/indices/${$route.current.params.index}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: false,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/elasticsearch/indices/:index', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringElasticsearchIndexApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const indexName = $route.current.params.index;
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.indices.overview.routeTitle', {
- defaultMessage: 'Elasticsearch - Indices - {indexName} - Overview',
- values: {
- indexName,
- },
- }),
- telemetryPageViewTitle: 'elasticsearch_index',
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.indices.overview.pageTitle', {
- defaultMessage: 'Index: {indexName}',
- values: {
- indexName,
- },
- }),
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchIndexApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LARGE_SHARD_SIZE],
- filters: [
- {
- shardIndex: $route.current.pathParams.index,
- },
- ],
- },
- },
- });
-
- this.indexName = indexName;
- const transformer = indicesByNodes();
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.shards) {
- return;
- }
-
- const shards = data.shards;
- $scope.totalCount = shards.length;
- $scope.showing = transformer(shards, data.nodes);
- $scope.labels = labels.node;
- if (shards.some((shard) => shard.state === 'UNASSIGNED')) {
- $scope.labels = labels.indexWithUnassigned;
- } else {
- $scope.labels = labels.index;
- }
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.html
deleted file mode 100644
index 84013078e0ef1..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js
deleted file mode 100644
index 5acff8be20dcf..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { ElasticsearchIndices } from '../../../components';
-import template from './index.html';
-import {
- CODE_PATH_ELASTICSEARCH,
- ELASTICSEARCH_SYSTEM_ID,
- RULE_LARGE_SHARD_SIZE,
-} from '../../../../common/constants';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/indices', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- },
- controllerAs: 'elasticsearchIndices',
- controller: class ElasticsearchIndicesController extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const features = $injector.get('features');
-
- const { cluster_uuid: clusterUuid } = globalState;
- $scope.cluster = find($route.current.locals.clusters, { cluster_uuid: clusterUuid });
-
- let showSystemIndices = features.isEnabled('showSystemIndices', false);
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.indices.routeTitle', {
- defaultMessage: 'Elasticsearch - Indices',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.indices.pageTitle', {
- defaultMessage: 'Elasticsearch indices',
- }),
- storageKey: 'elasticsearch.indices',
- apiUrlFn: () =>
- `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/indices?show_system_indices=${showSystemIndices}`,
- reactNodeId: 'elasticsearchIndicesReact',
- defaultData: {},
- $scope,
- $injector,
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LARGE_SHARD_SIZE],
- },
- },
- });
-
- this.isCcrEnabled = $scope.cluster.isCcrEnabled;
-
- // for binding
- const toggleShowSystemIndices = (isChecked) => {
- // flip the boolean
- showSystemIndices = isChecked;
- // preserve setting in localStorage
- features.update('showSystemIndices', isChecked);
- // update the page (resets pagination and sorting)
- this.updateData();
- };
-
- const renderComponent = () => {
- const { clusterStatus, indices } = this.data;
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- };
-
- this.onTableChangeRender = renderComponent;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
- renderComponent();
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js
deleted file mode 100644
index 39bd2686069de..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/get_page_data.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ml_jobs`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.html
deleted file mode 100644
index 6fdae46b6b6ed..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js
deleted file mode 100644
index d44b782f3994b..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ml_jobs/index.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { CODE_PATH_ELASTICSEARCH, CODE_PATH_ML } from '../../../../common/constants';
-
-uiRoutes.when('/elasticsearch/ml_jobs', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH, CODE_PATH_ML] });
- },
- pageData: getPageData,
- },
- controllerAs: 'mlJobs',
- controller: class MlJobsList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.mlJobs.routeTitle', {
- defaultMessage: 'Elasticsearch - Machine Learning Jobs',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.mlJobs.pageTitle', {
- defaultMessage: 'Elasticsearch machine learning jobs',
- }),
- storageKey: 'elasticsearch.mlJobs',
- getPageData,
- $scope,
- $injector,
- });
-
- const $route = $injector.get('$route');
- this.data = $route.current.locals.pageData;
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
- this.isCcrEnabled = Boolean($scope.cluster && $scope.cluster.isCcrEnabled);
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.html
deleted file mode 100644
index c79c4eed46bb7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js
deleted file mode 100644
index dc0456178fbff..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for Advanced Node Detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { AdvancedNode } from '../../../../components/elasticsearch/node/advanced';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CPU_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MISSING_MONITORING_DATA,
- RULE_DISK_USAGE,
- RULE_MEMORY_USAGE,
-} from '../../../../../common/constants';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const timeBounds = Legacy.shims.timefilter.getBounds();
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/nodes/${$route.current.params.node}`;
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: true,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/elasticsearch/nodes/:node/advanced', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const nodeName = $route.current.params.node;
-
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchAdvancedNodeApp',
- telemetryPageViewTitle: 'elasticsearch_node_advanced',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [
- RULE_CPU_USAGE,
- RULE_DISK_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MEMORY_USAGE,
- RULE_MISSING_MONITORING_DATA,
- ],
- filters: [
- {
- nodeUuid: nodeName,
- },
- ],
- },
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.advanced.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes - {nodeSummaryName} - Advanced',
- values: {
- nodeSummaryName: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.overview.pageTitle', {
- defaultMessage: 'Elasticsearch node: {node}',
- values: {
- node: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js
deleted file mode 100644
index 1d8bc3f3efa32..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/get_page_data.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/nodes/${$route.current.params.node}`;
- const features = $injector.get('features');
- const showSystemIndices = features.isEnabled('showSystemIndices', false);
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- showSystemIndices,
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: false,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.html
deleted file mode 100644
index 1c3b32728cecd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js
deleted file mode 100644
index 3ec10aa9d4a4c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for Node Detail
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get, partial } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { Node } from '../../../components/elasticsearch/node/node';
-import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels';
-import { nodesByIndices } from '../../../components/elasticsearch/shard_allocation/transformers/nodes_by_indices';
-import { MonitoringViewBaseController } from '../../base_controller';
-import {
- CODE_PATH_ELASTICSEARCH,
- RULE_CPU_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MISSING_MONITORING_DATA,
- RULE_DISK_USAGE,
- RULE_MEMORY_USAGE,
- ELASTICSEARCH_SYSTEM_ID,
-} from '../../../../common/constants';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/nodes/:node', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringElasticsearchNodeApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const nodeName = $route.current.params.node;
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.node.overview.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes - {nodeName} - Overview',
- values: {
- nodeName,
- },
- }),
- telemetryPageViewTitle: 'elasticsearch_node',
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringElasticsearchNodeApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [
- RULE_CPU_USAGE,
- RULE_DISK_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MEMORY_USAGE,
- RULE_MISSING_MONITORING_DATA,
- ],
- filters: [
- {
- nodeUuid: nodeName,
- },
- ],
- },
- },
- });
-
- this.nodeName = nodeName;
-
- const features = $injector.get('features');
- const callPageData = partial(getPageData, $injector);
- // show/hide system indices in shard allocation view
- $scope.showSystemIndices = features.isEnabled('showSystemIndices', false);
- $scope.toggleShowSystemIndices = (isChecked) => {
- $scope.showSystemIndices = isChecked;
- // preserve setting in localStorage
- features.update('showSystemIndices', isChecked);
- // update the page
- callPageData().then((data) => (this.data = data));
- };
-
- const transformer = nodesByIndices();
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.shards) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.overview.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes - {nodeName} - Overview',
- values: {
- nodeName: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.elasticsearch.node.overview.pageTitle', {
- defaultMessage: 'Elasticsearch node: {node}',
- values: {
- node: get(data, 'nodeSummary.name'),
- },
- })
- );
-
- const shards = data.shards;
- $scope.totalCount = shards.length;
- $scope.showing = transformer(shards, data.nodes);
- $scope.labels = labels.node;
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.html
deleted file mode 100644
index 95a483a59f20c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js
deleted file mode 100644
index 5bc546e8590ad..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { Legacy } from '../../../legacy_shims';
-import template from './index.html';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { ElasticsearchNodes } from '../../../components';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { SetupModeRenderer } from '../../../components/renderers';
-import {
- ELASTICSEARCH_SYSTEM_ID,
- CODE_PATH_ELASTICSEARCH,
- RULE_CPU_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MISSING_MONITORING_DATA,
- RULE_DISK_USAGE,
- RULE_MEMORY_USAGE,
-} from '../../../../common/constants';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-
-uiRoutes.when('/elasticsearch/nodes', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- },
- controllerAs: 'elasticsearchNodes',
- controller: class ElasticsearchNodesController extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const showCgroupMetricsElasticsearch = $injector.get('showCgroupMetricsElasticsearch');
-
- $scope.cluster =
- find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- }) || {};
-
- const getPageData = ($injector, _api = undefined, routeOptions = {}) => {
- _api; // to fix eslint
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- const getNodes = (clusterUuid = globalState.cluster_uuid) =>
- $http.post(`../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/nodes`, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- ...routeOptions,
- });
-
- const promise = globalState.cluster_uuid
- ? getNodes()
- : new Promise((resolve) => resolve({ data: {} }));
- return promise
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
- };
-
- super({
- title: i18n.translate('xpack.monitoring.elasticsearch.nodes.routeTitle', {
- defaultMessage: 'Elasticsearch - Nodes',
- }),
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.nodes.pageTitle', {
- defaultMessage: 'Elasticsearch nodes',
- }),
- storageKey: 'elasticsearch.nodes',
- reactNodeId: 'elasticsearchNodesReact',
- defaultData: {},
- getPageData,
- $scope,
- $injector,
- fetchDataImmediately: false, // We want to apply pagination before sending the first request,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [
- RULE_CPU_USAGE,
- RULE_DISK_USAGE,
- RULE_THREAD_POOL_SEARCH_REJECTIONS,
- RULE_THREAD_POOL_WRITE_REJECTIONS,
- RULE_MEMORY_USAGE,
- RULE_MISSING_MONITORING_DATA,
- ],
- },
- },
- });
-
- this.isCcrEnabled = $scope.cluster.isCcrEnabled;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
-
- const { clusterStatus, nodes, totalNodeCount } = data;
- const pagination = {
- ...this.pagination,
- totalItemCount: totalNodeCount,
- };
-
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js
deleted file mode 100644
index f39033fe7014d..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/controller.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { MonitoringViewBaseController } from '../../';
-import { ElasticsearchOverview } from '../../../components';
-
-export class ElasticsearchOverviewController extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- // breadcrumbs + page title
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- super({
- title: 'Elasticsearch',
- pageTitle: i18n.translate('xpack.monitoring.elasticsearch.overview.pageTitle', {
- defaultMessage: 'Elasticsearch overview',
- }),
- api: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch`,
- defaultData: {
- clusterStatus: { status: '' },
- metrics: null,
- shardActivity: null,
- },
- reactNodeId: 'elasticsearchOverviewReact',
- $scope,
- $injector,
- });
-
- this.isCcrEnabled = $scope.cluster.isCcrEnabled;
- this.showShardActivityHistory = false;
- this.toggleShardActivityHistory = () => {
- this.showShardActivityHistory = !this.showShardActivityHistory;
- $scope.$evalAsync(() => {
- this.renderReact(this.data, $scope.cluster);
- });
- };
-
- this.initScope($scope);
- }
-
- initScope($scope) {
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(data, $scope.cluster);
- }
- );
-
- // HACK to force table to re-render even if data hasn't changed. This
- // happens when the data remains empty after turning on showHistory. The
- // button toggle needs to update the "no data" message based on the value of showHistory
- $scope.$watch(
- () => this.showShardActivityHistory,
- () => {
- const { data } = this;
- const dataWithShardActivityLoading = { ...data, shardActivity: null };
- // force shard activity to rerender by manipulating and then re-setting its data prop
- this.renderReact(dataWithShardActivityLoading, $scope.cluster);
- this.renderReact(data, $scope.cluster);
- }
- );
- }
-
- filterShardActivityData(shardActivity) {
- return shardActivity.filter((row) => {
- return this.showShardActivityHistory || row.stage !== 'DONE';
- });
- }
-
- renderReact(data, cluster) {
- // All data needs to originate in this view, and get passed as a prop to the components, for statelessness
- const { clusterStatus, metrics, shardActivity, logs } = data || {};
- const shardActivityData = shardActivity && this.filterShardActivityData(shardActivity); // no filter on data = null
- const component = (
-
- );
-
- super.renderReact(component);
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.html
deleted file mode 100644
index 127c48add5e8d..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.js
deleted file mode 100644
index cc507934dd767..0000000000000
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/overview/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { ElasticsearchOverviewController } from './controller';
-import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants';
-
-uiRoutes.when('/elasticsearch', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_ELASTICSEARCH] });
- },
- },
- controllerAs: 'elasticsearchOverview',
- controller: ElasticsearchOverviewController,
-});
diff --git a/x-pack/plugins/monitoring/public/views/index.js b/x-pack/plugins/monitoring/public/views/index.js
deleted file mode 100644
index 8cfb8f35e68ba..0000000000000
--- a/x-pack/plugins/monitoring/public/views/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export { MonitoringViewBaseController } from './base_controller';
-export { MonitoringViewBaseTableController } from './base_table_controller';
-export { MonitoringViewBaseEuiTableController } from './base_eui_table_controller';
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instance/index.html b/x-pack/plugins/monitoring/public/views/kibana/instance/index.html
deleted file mode 100644
index 8bb17839683a8..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instance/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js b/x-pack/plugins/monitoring/public/views/kibana/instance/index.js
deleted file mode 100644
index a71289b084516..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instance/index.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Kibana Instance
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiSpacer,
- EuiFlexGrid,
- EuiFlexItem,
- EuiPanel,
-} from '@elastic/eui';
-import { MonitoringTimeseriesContainer } from '../../../components/chart';
-import { DetailStatus } from '../../../components/kibana/detail_status';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_KIBANA, RULE_KIBANA_VERSION_MISMATCH } from '../../../../common/constants';
-import { AlertsCallout } from '../../../alerts/callout';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/kibana/${$route.current.params.uuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/kibana/instances/:uuid', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_KIBANA] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringKibanaInstanceApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: `Kibana - ${get($scope.pageData, 'kibanaSummary.name')}`,
- telemetryPageViewTitle: 'kibana_instance',
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringKibanaInstanceApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_KIBANA_VERSION_MISMATCH],
- },
- },
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.metrics) {
- return;
- }
- this.setTitle(`Kibana - ${get(data, 'kibanaSummary.name')}`);
- this.setPageTitle(
- i18n.translate('xpack.monitoring.kibana.instance.pageTitle', {
- defaultMessage: 'Kibana instance: {instance}',
- values: {
- instance: get($scope.pageData, 'kibanaSummary.name'),
- },
- })
- );
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js b/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js
deleted file mode 100644
index 82c49ee0ebb13..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instances/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/kibana/instances`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.html b/x-pack/plugins/monitoring/public/views/kibana/instances/index.html
deleted file mode 100644
index 8e1639a2323a5..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js
deleted file mode 100644
index 2601a366e6843..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { KibanaInstances } from '../../../components/kibana/instances';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import {
- KIBANA_SYSTEM_ID,
- CODE_PATH_KIBANA,
- RULE_KIBANA_VERSION_MISMATCH,
-} from '../../../../common/constants';
-
-uiRoutes.when('/kibana/instances', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_KIBANA] });
- },
- pageData: getPageData,
- },
- controllerAs: 'kibanas',
- controller: class KibanaInstancesList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.kibana.instances.routeTitle', {
- defaultMessage: 'Kibana - Instances',
- }),
- pageTitle: i18n.translate('xpack.monitoring.kibana.instances.pageTitle', {
- defaultMessage: 'Kibana instances',
- }),
- storageKey: 'kibana.instances',
- getPageData,
- reactNodeId: 'monitoringKibanaInstancesApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_KIBANA_VERSION_MISMATCH],
- },
- },
- });
-
- const renderReact = () => {
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- };
-
- this.onTableChangeRender = renderReact;
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data) {
- return;
- }
-
- renderReact();
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/kibana/overview/index.html b/x-pack/plugins/monitoring/public/views/kibana/overview/index.html
deleted file mode 100644
index 5b131e113dfa4..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/overview/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/kibana/overview/index.js b/x-pack/plugins/monitoring/public/views/kibana/overview/index.js
deleted file mode 100644
index ad59265a98531..0000000000000
--- a/x-pack/plugins/monitoring/public/views/kibana/overview/index.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Kibana Overview
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { MonitoringTimeseriesContainer } from '../../../components/chart';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPanel,
- EuiSpacer,
- EuiFlexGroup,
- EuiFlexItem,
-} from '@elastic/eui';
-import { ClusterStatus } from '../../../components/kibana/cluster_status';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_KIBANA } from '../../../../common/constants';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/kibana`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/kibana', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_KIBANA] });
- },
- pageData: getPageData,
- },
- controllerAs: 'monitoringKibanaOverviewApp',
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: `Kibana`,
- pageTitle: i18n.translate('xpack.monitoring.kibana.overview.pageTitle', {
- defaultMessage: 'Kibana overview',
- }),
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringKibanaOverviewApp',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.clusterStatus) {
- return;
- }
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/license/controller.js b/x-pack/plugins/monitoring/public/views/license/controller.js
deleted file mode 100644
index 297edf6481a55..0000000000000
--- a/x-pack/plugins/monitoring/public/views/license/controller.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { get, find } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { Legacy } from '../../legacy_shims';
-import { formatDateTimeLocal } from '../../../common/formatting';
-import { BASE_PATH as MANAGEMENT_BASE_PATH } from '../../../../../plugins/license_management/common/constants';
-import { License } from '../../components';
-
-const REACT_NODE_ID = 'licenseReact';
-
-export class LicenseViewController {
- constructor($injector, $scope) {
- Legacy.shims.timefilter.disableTimeRangeSelector();
- Legacy.shims.timefilter.disableAutoRefreshSelector();
-
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(document.getElementById(REACT_NODE_ID));
- });
-
- this.init($injector, $scope, i18n);
- }
-
- init($injector, $scope) {
- const globalState = $injector.get('globalState');
- const title = $injector.get('title');
- const $route = $injector.get('$route');
-
- const cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
- $scope.cluster = cluster;
- const routeTitle = i18n.translate('xpack.monitoring.license.licenseRouteTitle', {
- defaultMessage: 'License',
- });
- title($scope.cluster, routeTitle);
-
- this.license = cluster.license;
- this.isExpired = Date.now() > get(cluster, 'license.expiry_date_in_millis');
- this.isPrimaryCluster = cluster.isPrimary;
-
- const basePath = Legacy.shims.getBasePath();
- this.uploadLicensePath = basePath + '/app/kibana#' + MANAGEMENT_BASE_PATH + 'upload_license';
-
- this.renderReact($scope);
- }
-
- renderReact($scope) {
- const injector = Legacy.shims.getAngularInjector();
- const timezone = injector.get('config').get('dateFormat:tz');
- $scope.$evalAsync(() => {
- const { isPrimaryCluster, license, isExpired, uploadLicensePath } = this;
- let expiryDate = license.expiry_date_in_millis;
- if (license.expiry_date_in_millis !== undefined) {
- expiryDate = formatDateTimeLocal(license.expiry_date_in_millis, timezone);
- }
-
- // Mount the React component to the template
- render(
- ,
- document.getElementById(REACT_NODE_ID)
- );
- });
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/license/index.html b/x-pack/plugins/monitoring/public/views/license/index.html
deleted file mode 100644
index 7fb9c69941004..0000000000000
--- a/x-pack/plugins/monitoring/public/views/license/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/license/index.js b/x-pack/plugins/monitoring/public/views/license/index.js
deleted file mode 100644
index 0ffb953268690..0000000000000
--- a/x-pack/plugins/monitoring/public/views/license/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../angular/helpers/routes';
-import { routeInitProvider } from '../../lib/route_init';
-import template from './index.html';
-import { LicenseViewController } from './controller';
-import { CODE_PATH_LICENSE } from '../../../common/constants';
-
-uiRoutes.when('/license', {
- template,
- resolve: {
- clusters: (Private) => {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LICENSE] });
- },
- },
- controllerAs: 'licenseView',
- controller: LicenseViewController,
-});
diff --git a/x-pack/plugins/monitoring/public/views/loading/index.html b/x-pack/plugins/monitoring/public/views/loading/index.html
deleted file mode 100644
index 9a5971a65bc39..0000000000000
--- a/x-pack/plugins/monitoring/public/views/loading/index.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/loading/index.js b/x-pack/plugins/monitoring/public/views/loading/index.js
deleted file mode 100644
index 6406b9e6364f0..0000000000000
--- a/x-pack/plugins/monitoring/public/views/loading/index.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Controller for single index detail
- */
-import React from 'react';
-import { render } from 'react-dom';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../angular/helpers/routes';
-import { routeInitProvider } from '../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../legacy_shims';
-import { CODE_PATH_ELASTICSEARCH } from '../../../common/constants';
-import { PageLoading } from '../../components';
-import { ajaxErrorHandlersProvider } from '../../lib/ajax_error_handler';
-
-const CODE_PATHS = [CODE_PATH_ELASTICSEARCH];
-uiRoutes.when('/loading', {
- template,
- controllerAs: 'monitoringLoading',
- controller: class {
- constructor($injector, $scope) {
- const Private = $injector.get('Private');
- const titleService = $injector.get('title');
- titleService(
- $scope.cluster,
- i18n.translate('xpack.monitoring.loading.pageTitle', {
- defaultMessage: 'Loading',
- })
- );
-
- this.init = () => {
- const reactNodeId = 'monitoringLoadingReact';
- const renderElement = document.getElementById(reactNodeId);
- if (!renderElement) {
- console.warn(`"#${reactNodeId}" element has not been added to the DOM yet`);
- return;
- }
- const I18nContext = Legacy.shims.I18nContext;
- render(
-
-
- ,
- renderElement
- );
- };
-
- const routeInit = Private(routeInitProvider);
- routeInit({ codePaths: CODE_PATHS, fetchAllClusters: true, unsetGlobalState: true })
- .then((clusters) => {
- if (!clusters || !clusters.length) {
- window.location.hash = '#/no-data';
- $scope.$apply();
- return;
- }
- if (clusters.length === 1) {
- // Bypass the cluster listing if there is just 1 cluster
- window.history.replaceState(null, null, '#/overview');
- $scope.$apply();
- return;
- }
-
- window.history.replaceState(null, null, '#/home');
- $scope.$apply();
- })
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return $scope.$apply(() => ajaxErrorHandlers(err));
- });
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.html b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.html
deleted file mode 100644
index 63f51809fd7e7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js
deleted file mode 100644
index 9acfd81d186fd..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/advanced/index.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node Advanced View
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { MonitoringViewBaseController } from '../../../base_controller';
-import { DetailStatus } from '../../../../components/logstash/detail_status';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPanel,
- EuiSpacer,
- EuiFlexGrid,
- EuiFlexItem,
-} from '@elastic/eui';
-import { MonitoringTimeseriesContainer } from '../../../../components/chart';
-import {
- CODE_PATH_LOGSTASH,
- RULE_LOGSTASH_VERSION_MISMATCH,
-} from '../../../../../common/constants';
-import { AlertsCallout } from '../../../../alerts/callout';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const $route = $injector.get('$route');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/node/${$route.current.params.uuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: true,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash/node/:uuid/advanced', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringLogstashNodeAdvancedApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LOGSTASH_VERSION_MISMATCH],
- },
- },
- telemetryPageViewTitle: 'logstash_node_advanced',
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.logstash.node.advanced.routeTitle', {
- defaultMessage: 'Logstash - {nodeName} - Advanced',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.node.advanced.pageTitle', {
- defaultMessage: 'Logstash node: {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- const metricsToShow = [
- data.metrics.logstash_node_cpu_utilization,
- data.metrics.logstash_queue_events_count,
- data.metrics.logstash_node_cgroup_cpu,
- data.metrics.logstash_pipeline_queue_size,
- data.metrics.logstash_node_cgroup_stats,
- ];
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
- {metricsToShow.map((metric, index) => (
-
-
-
-
- ))}
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/index.html b/x-pack/plugins/monitoring/public/views/logstash/node/index.html
deleted file mode 100644
index 062c830dd8b7a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/index.js
deleted file mode 100644
index b23875ba1a3bb..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/index.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { DetailStatus } from '../../../components/logstash/detail_status';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPanel,
- EuiSpacer,
- EuiFlexGrid,
- EuiFlexItem,
-} from '@elastic/eui';
-import { MonitoringTimeseriesContainer } from '../../../components/chart';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_LOGSTASH, RULE_LOGSTASH_VERSION_MISMATCH } from '../../../../common/constants';
-import { AlertsCallout } from '../../../alerts/callout';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const $route = $injector.get('$route');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/node/${$route.current.params.uuid}`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- is_advanced: false,
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash/node/:uuid', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringLogstashNodeApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LOGSTASH_VERSION_MISMATCH],
- },
- },
- telemetryPageViewTitle: 'logstash_node',
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.logstash.node.routeTitle', {
- defaultMessage: 'Logstash - {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.node.pageTitle', {
- defaultMessage: 'Logstash node: {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- const metricsToShow = [
- data.metrics.logstash_events_input_rate,
- data.metrics.logstash_jvm_usage,
- data.metrics.logstash_events_output_rate,
- data.metrics.logstash_node_cpu_metric,
- data.metrics.logstash_events_latency,
- data.metrics.logstash_os_load,
- ];
-
- this.renderReact(
-
-
-
-
-
-
-
-
-
- {metricsToShow.map((metric, index) => (
-
-
-
-
- ))}
-
-
-
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.html b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.html
deleted file mode 100644
index cae3a169bfd5a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js
deleted file mode 100644
index 0d5105696102a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/node/pipelines/index.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node Pipelines Listing
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../../lib/route_init';
-import { isPipelineMonitoringSupportedInVersion } from '../../../../lib/logstash/pipelines';
-import template from './index.html';
-import { Legacy } from '../../../../legacy_shims';
-import { MonitoringViewBaseEuiTableController } from '../../../';
-import { PipelineListing } from '../../../../components/logstash/pipeline_listing/pipeline_listing';
-import { DetailStatus } from '../../../../components/logstash/detail_status';
-import { CODE_PATH_LOGSTASH } from '../../../../../common/constants';
-
-const getPageData = ($injector, _api = undefined, routeOptions = {}) => {
- _api; // fixing eslint
- const $route = $injector.get('$route');
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const Private = $injector.get('Private');
-
- const logstashUuid = $route.current.params.uuid;
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/node/${logstashUuid}/pipelines`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- ...routeOptions,
- })
- .then((response) => response.data)
- .catch((err) => {
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-};
-
-function makeUpgradeMessage(logstashVersion) {
- if (isPipelineMonitoringSupportedInVersion(logstashVersion)) {
- return null;
- }
-
- return i18n.translate('xpack.monitoring.logstash.node.pipelines.notAvailableDescription', {
- defaultMessage:
- 'Pipeline monitoring is only available in Logstash version 6.0.0 or higher. This node is running version {logstashVersion}.',
- values: {
- logstashVersion,
- },
- });
-}
-
-uiRoutes.when('/logstash/node/:uuid/pipelines', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- },
- controller: class extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- const config = $injector.get('config');
-
- super({
- defaultData: {},
- getPageData,
- reactNodeId: 'monitoringLogstashNodePipelinesApp',
- $scope,
- $injector,
- fetchDataImmediately: false, // We want to apply pagination before sending the first request
- telemetryPageViewTitle: 'logstash_node_pipelines',
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.nodeSummary) {
- return;
- }
-
- this.setTitle(
- i18n.translate('xpack.monitoring.logstash.node.pipelines.routeTitle', {
- defaultMessage: 'Logstash - {nodeName} - Pipelines',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.node.pipelines.pageTitle', {
- defaultMessage: 'Logstash node pipelines: {nodeName}',
- values: {
- nodeName: data.nodeSummary.name,
- },
- })
- );
-
- const pagination = {
- ...this.pagination,
- totalItemCount: data.totalPipelineCount,
- };
-
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js
deleted file mode 100644
index 4c9167a47b0d7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/nodes/get_page_data.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { Legacy } from '../../../legacy_shims';
-
-export function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/nodes`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.html b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.html
deleted file mode 100644
index 6da00b1c771b8..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js b/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js
deleted file mode 100644
index 56b5d0ec6c82a..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/nodes/index.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { routeInitProvider } from '../../../lib/route_init';
-import { MonitoringViewBaseEuiTableController } from '../../';
-import { getPageData } from './get_page_data';
-import template from './index.html';
-import { Listing } from '../../../components/logstash/listing';
-import { SetupModeRenderer } from '../../../components/renderers';
-import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
-import {
- CODE_PATH_LOGSTASH,
- LOGSTASH_SYSTEM_ID,
- RULE_LOGSTASH_VERSION_MISMATCH,
-} from '../../../../common/constants';
-
-uiRoutes.when('/logstash/nodes', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controllerAs: 'lsNodes',
- controller: class LsNodesList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.logstash.nodes.routeTitle', {
- defaultMessage: 'Logstash - Nodes',
- }),
- pageTitle: i18n.translate('xpack.monitoring.logstash.nodes.pageTitle', {
- defaultMessage: 'Logstash nodes',
- }),
- storageKey: 'logstash.nodes',
- getPageData,
- reactNodeId: 'monitoringLogstashNodesApp',
- $scope,
- $injector,
- alerts: {
- shouldFetch: true,
- options: {
- alertTypeIds: [RULE_LOGSTASH_VERSION_MISMATCH],
- },
- },
- });
-
- const renderComponent = () => {
- this.renderReact(
- (
-
- {flyoutComponent}
-
- {bottomBarComponent}
-
- )}
- />
- );
- };
-
- this.onTableChangeRender = renderComponent;
-
- $scope.$watch(
- () => this.data,
- () => renderComponent()
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/overview/index.html b/x-pack/plugins/monitoring/public/views/logstash/overview/index.html
deleted file mode 100644
index 088aa35892bbe..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/overview/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/overview/index.js b/x-pack/plugins/monitoring/public/views/logstash/overview/index.js
deleted file mode 100644
index b5e8ecbefc532..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/overview/index.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/**
- * Logstash Overview
- */
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { Overview } from '../../../components/logstash/overview';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_LOGSTASH } from '../../../../common/constants';
-
-function getPageData($injector) {
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- })
- .then((response) => response.data)
- .catch((err) => {
- const Private = $injector.get('Private');
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash', {
- template,
- resolve: {
- clusters: function (Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- super({
- title: 'Logstash',
- pageTitle: i18n.translate('xpack.monitoring.logstash.overview.pageTitle', {
- defaultMessage: 'Logstash overview',
- }),
- getPageData,
- reactNodeId: 'monitoringLogstashOverviewApp',
- $scope,
- $injector,
- });
-
- $scope.$watch(
- () => this.data,
- (data) => {
- this.renderReact(
-
- );
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.html b/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.html
deleted file mode 100644
index afd1d994f1e9c..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.js b/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.js
deleted file mode 100644
index dd7bcc8436358..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipeline/index.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Logstash Node Pipeline View
- */
-import React from 'react';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import moment from 'moment';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import { CALCULATE_DURATION_SINCE, CODE_PATH_LOGSTASH } from '../../../../common/constants';
-import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration';
-import template from './index.html';
-import { i18n } from '@kbn/i18n';
-import { List } from '../../../components/logstash/pipeline_viewer/models/list';
-import { PipelineState } from '../../../components/logstash/pipeline_viewer/models/pipeline_state';
-import { PipelineViewer } from '../../../components/logstash/pipeline_viewer';
-import { Pipeline } from '../../../components/logstash/pipeline_viewer/models/pipeline';
-import { vertexFactory } from '../../../components/logstash/pipeline_viewer/models/graph/vertex_factory';
-import { MonitoringViewBaseController } from '../../base_controller';
-import { EuiPageBody, EuiPage, EuiPageContent } from '@elastic/eui';
-
-let previousPipelineHash = undefined;
-let detailVertexId = undefined;
-
-function getPageData($injector) {
- const $route = $injector.get('$route');
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const minIntervalSeconds = $injector.get('minIntervalSeconds');
- const Private = $injector.get('Private');
-
- const { ccs, cluster_uuid: clusterUuid } = globalState;
- const pipelineId = $route.current.params.id;
- const pipelineHash = $route.current.params.hash || '';
-
- // Pipeline version was changed, so clear out detailVertexId since that vertex won't
- // exist in the updated pipeline version
- if (pipelineHash !== previousPipelineHash) {
- previousPipelineHash = pipelineHash;
- detailVertexId = undefined;
- }
-
- const url = pipelineHash
- ? `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipeline/${pipelineId}/${pipelineHash}`
- : `../api/monitoring/v1/clusters/${clusterUuid}/logstash/pipeline/${pipelineId}`;
- return $http
- .post(url, {
- ccs,
- detailVertexId,
- })
- .then((response) => response.data)
- .then((data) => {
- data.versions = data.versions.map((version) => {
- const relativeFirstSeen = formatTimestampToDuration(
- version.firstSeen,
- CALCULATE_DURATION_SINCE
- );
- const relativeLastSeen = formatTimestampToDuration(
- version.lastSeen,
- CALCULATE_DURATION_SINCE
- );
-
- const fudgeFactorSeconds = 2 * minIntervalSeconds;
- const isLastSeenCloseToNow = Date.now() - version.lastSeen <= fudgeFactorSeconds * 1000;
-
- return {
- ...version,
- relativeFirstSeen: i18n.translate(
- 'xpack.monitoring.logstash.pipeline.relativeFirstSeenAgoLabel',
- {
- defaultMessage: '{relativeFirstSeen} ago',
- values: { relativeFirstSeen },
- }
- ),
- relativeLastSeen: isLastSeenCloseToNow
- ? i18n.translate('xpack.monitoring.logstash.pipeline.relativeLastSeenNowLabel', {
- defaultMessage: 'now',
- })
- : i18n.translate('xpack.monitoring.logstash.pipeline.relativeLastSeenAgoLabel', {
- defaultMessage: 'until {relativeLastSeen} ago',
- values: { relativeLastSeen },
- }),
- };
- });
-
- return data;
- })
- .catch((err) => {
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-}
-
-uiRoutes.when('/logstash/pipelines/:id/:hash?', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- pageData: getPageData,
- },
- controller: class extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- const config = $injector.get('config');
- const dateFormat = config.get('dateFormat');
-
- super({
- title: i18n.translate('xpack.monitoring.logstash.pipeline.routeTitle', {
- defaultMessage: 'Logstash - Pipeline',
- }),
- storageKey: 'logstash.pipelines',
- getPageData,
- reactNodeId: 'monitoringLogstashPipelineApp',
- $scope,
- options: {
- enableTimeFilter: false,
- },
- $injector,
- });
-
- const timeseriesTooltipXValueFormatter = (xValue) => moment(xValue).format(dateFormat);
-
- const setDetailVertexId = (vertex) => {
- if (!vertex) {
- detailVertexId = undefined;
- } else {
- detailVertexId = vertex.id;
- }
-
- return this.updateData();
- };
-
- $scope.$watch(
- () => this.data,
- (data) => {
- if (!data || !data.pipeline) {
- return;
- }
- this.setPageTitle(
- i18n.translate('xpack.monitoring.logstash.pipeline.pageTitle', {
- defaultMessage: 'Logstash pipeline: {pipeline}',
- values: {
- pipeline: data.pipeline.id,
- },
- })
- );
- this.pipelineState = new PipelineState(data.pipeline);
- this.detailVertex = data.vertex ? vertexFactory(null, data.vertex) : null;
- this.renderReact(
-
-
-
-
-
-
-
- );
- }
- );
-
- $scope.$on('$destroy', () => {
- previousPipelineHash = undefined;
- detailVertexId = undefined;
- });
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.html b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.html
deleted file mode 100644
index bef8a7a4737f3..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js b/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js
deleted file mode 100644
index f3121687f17db..0000000000000
--- a/x-pack/plugins/monitoring/public/views/logstash/pipelines/index.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { find } from 'lodash';
-import { uiRoutes } from '../../../angular/helpers/routes';
-import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
-import { routeInitProvider } from '../../../lib/route_init';
-import { isPipelineMonitoringSupportedInVersion } from '../../../lib/logstash/pipelines';
-import template from './index.html';
-import { Legacy } from '../../../legacy_shims';
-import { PipelineListing } from '../../../components/logstash/pipeline_listing/pipeline_listing';
-import { MonitoringViewBaseEuiTableController } from '../..';
-import { CODE_PATH_LOGSTASH } from '../../../../common/constants';
-
-/*
- * Logstash Pipelines Listing page
- */
-
-const getPageData = ($injector, _api = undefined, routeOptions = {}) => {
- _api; // to fix eslint
- const $http = $injector.get('$http');
- const globalState = $injector.get('globalState');
- const Private = $injector.get('Private');
-
- const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/logstash/pipelines`;
- const timeBounds = Legacy.shims.timefilter.getBounds();
-
- return $http
- .post(url, {
- ccs: globalState.ccs,
- timeRange: {
- min: timeBounds.min.toISOString(),
- max: timeBounds.max.toISOString(),
- },
- ...routeOptions,
- })
- .then((response) => response.data)
- .catch((err) => {
- const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
- return ajaxErrorHandlers(err);
- });
-};
-
-function makeUpgradeMessage(logstashVersions) {
- if (
- !Array.isArray(logstashVersions) ||
- logstashVersions.length === 0 ||
- logstashVersions.some(isPipelineMonitoringSupportedInVersion)
- ) {
- return null;
- }
-
- return 'Pipeline monitoring is only available in Logstash version 6.0.0 or higher.';
-}
-
-uiRoutes.when('/logstash/pipelines', {
- template,
- resolve: {
- clusters(Private) {
- const routeInit = Private(routeInitProvider);
- return routeInit({ codePaths: [CODE_PATH_LOGSTASH] });
- },
- },
- controller: class LogstashPipelinesList extends MonitoringViewBaseEuiTableController {
- constructor($injector, $scope) {
- super({
- title: i18n.translate('xpack.monitoring.logstash.pipelines.routeTitle', {
- defaultMessage: 'Logstash Pipelines',
- }),
- pageTitle: i18n.translate('xpack.monitoring.logstash.pipelines.pageTitle', {
- defaultMessage: 'Logstash pipelines',
- }),
- storageKey: 'logstash.pipelines',
- getPageData,
- reactNodeId: 'monitoringLogstashPipelinesApp',
- $scope,
- $injector,
- fetchDataImmediately: false, // We want to apply pagination before sending the first request
- });
-
- const $route = $injector.get('$route');
- const config = $injector.get('config');
- this.data = $route.current.locals.pageData;
- const globalState = $injector.get('globalState');
- $scope.cluster = find($route.current.locals.clusters, {
- cluster_uuid: globalState.cluster_uuid,
- });
-
- const renderReact = (pageData) => {
- if (!pageData) {
- return;
- }
-
- const upgradeMessage = pageData
- ? makeUpgradeMessage(pageData.clusterStatus.versions, i18n)
- : null;
-
- const pagination = {
- ...this.pagination,
- totalItemCount: pageData.totalPipelineCount,
- };
-
- super.renderReact(
- this.onBrush({ xaxis })}
- stats={pageData.clusterStatus}
- data={pageData.pipelines}
- {...this.getPaginationTableProps(pagination)}
- upgradeMessage={upgradeMessage}
- dateFormat={config.get('dateFormat')}
- />
- );
- };
-
- $scope.$watch(
- () => this.data,
- (pageData) => {
- renderReact(pageData);
- }
- );
- }
- },
-});
diff --git a/x-pack/plugins/monitoring/public/views/no_data/controller.js b/x-pack/plugins/monitoring/public/views/no_data/controller.js
deleted file mode 100644
index 4a6a73dfb2010..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/controller.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import {
- ClusterSettingsChecker,
- NodeSettingsChecker,
- Enabler,
- startChecks,
-} from '../../lib/elasticsearch_settings';
-import { ModelUpdater } from './model_updater';
-import { NoData } from '../../components';
-import { CODE_PATH_LICENSE } from '../../../common/constants';
-import { MonitoringViewBaseController } from '../base_controller';
-import { i18n } from '@kbn/i18n';
-import { Legacy } from '../../legacy_shims';
-
-export class NoDataController extends MonitoringViewBaseController {
- constructor($injector, $scope) {
- window.injectorThree = $injector;
- const monitoringClusters = $injector.get('monitoringClusters');
- const $http = $injector.get('$http');
- const checkers = [new ClusterSettingsChecker($http), new NodeSettingsChecker($http)];
-
- const getData = async () => {
- let catchReason;
- try {
- const monitoringClustersData = await monitoringClusters(undefined, undefined, [
- CODE_PATH_LICENSE,
- ]);
- if (monitoringClustersData && monitoringClustersData.length) {
- window.history.replaceState(null, null, '#/home');
- return monitoringClustersData;
- }
- } catch (err) {
- if (err && err.status === 503) {
- catchReason = {
- property: 'custom',
- message: err.data.message,
- };
- }
- }
-
- this.errors.length = 0;
- if (catchReason) {
- this.reason = catchReason;
- } else if (!this.isCollectionEnabledUpdating && !this.isCollectionIntervalUpdating) {
- /**
- * `no-use-before-define` is fine here, since getData is an async function.
- * Needs to be done this way, since there is no `this` before super is executed
- * */
- await startChecks(checkers, updateModel); // eslint-disable-line no-use-before-define
- }
- };
-
- super({
- title: i18n.translate('xpack.monitoring.noData.routeTitle', {
- defaultMessage: 'Setup Monitoring',
- }),
- getPageData: async () => await getData(),
- reactNodeId: 'noDataReact',
- $scope,
- $injector,
- });
- Object.assign(this, this.getDefaultModel());
-
- //Need to set updateModel after super since there is no `this` otherwise
- const { updateModel } = new ModelUpdater($scope, this);
- const enabler = new Enabler($http, updateModel);
- $scope.$watch(
- () => this,
- () => {
- if (this.isCollectionEnabledUpdated && !this.reason) {
- return;
- }
- this.render(enabler);
- },
- true
- );
- }
-
- getDefaultModel() {
- return {
- errors: [], // errors can happen from trying to check or set ES settings
- checkMessage: null, // message to show while waiting for api response
- isLoading: true, // flag for in-progress state of checking for no data reason
- isCollectionEnabledUpdating: false, // flags to indicate whether to show a spinner while waiting for ajax
- isCollectionEnabledUpdated: false,
- isCollectionIntervalUpdating: false,
- isCollectionIntervalUpdated: false,
- };
- }
-
- render(enabler) {
- const props = this;
- this.renderReact();
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/no_data/index.html b/x-pack/plugins/monitoring/public/views/no_data/index.html
deleted file mode 100644
index c6fc97b639f42..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/index.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/x-pack/plugins/monitoring/public/views/no_data/index.js b/x-pack/plugins/monitoring/public/views/no_data/index.js
deleted file mode 100644
index 4bbc490ce29ed..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { uiRoutes } from '../../angular/helpers/routes';
-import template from './index.html';
-import { NoDataController } from './controller';
-
-uiRoutes.when('/no-data', {
- template,
- controller: NoDataController,
-});
diff --git a/x-pack/plugins/monitoring/public/views/no_data/model_updater.js b/x-pack/plugins/monitoring/public/views/no_data/model_updater.js
deleted file mode 100644
index 115dc782162a7..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/model_updater.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-/*
- * Class for handling model updates of an Angular controller
- * Some properties are simple primitives like strings or booleans,
- * but sometimes we need a property in the model to be an Array. For example,
- * there may be multiple errors that happen in a flow.
- *
- * I use 1 method to handling property values that are either primitives or
- * arrays, because it allows the callers to be a little more dumb. All they
- * have to know is the property name, rather than the type as well.
- */
-export class ModelUpdater {
- constructor($scope, model) {
- this.$scope = $scope;
- this.model = model;
- this.updateModel = this.updateModel.bind(this);
- }
-
- updateModel(properties) {
- const { $scope, model } = this;
- const keys = Object.keys(properties);
- $scope.$evalAsync(() => {
- keys.forEach((key) => {
- if (Array.isArray(model[key])) {
- model[key].push(properties[key]);
- } else {
- model[key] = properties[key];
- }
- });
- });
- }
-}
diff --git a/x-pack/plugins/monitoring/public/views/no_data/model_updater.test.js b/x-pack/plugins/monitoring/public/views/no_data/model_updater.test.js
deleted file mode 100644
index b286bfb10a9e4..0000000000000
--- a/x-pack/plugins/monitoring/public/views/no_data/model_updater.test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ModelUpdater } from './model_updater';
-
-describe('Model Updater for Angular Controller with React Components', () => {
- let $scope;
- let model;
- let updater;
-
- beforeEach(() => {
- $scope = {};
- $scope.$evalAsync = (cb) => cb();
-
- model = {};
-
- updater = new ModelUpdater($scope, model);
- jest.spyOn(updater, 'updateModel');
- });
-
- test('should successfully construct an object', () => {
- expect(typeof updater).toBe('object');
- expect(updater.updateModel).not.toHaveBeenCalled();
- });
-
- test('updateModel method should add properties to the model', () => {
- expect(typeof updater).toBe('object');
- updater.updateModel({
- foo: 'bar',
- bar: 'baz',
- error: 'monkeywrench',
- });
- expect(model).toEqual({
- foo: 'bar',
- bar: 'baz',
- error: 'monkeywrench',
- });
- });
-
- test('updateModel method should push properties to the model if property is originally an array', () => {
- model.errors = ['first'];
- updater.updateModel({
- errors: 'second',
- primitive: 'hello',
- });
- expect(model).toEqual({
- errors: ['first', 'second'],
- primitive: 'hello',
- });
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/check_browser_open.ts b/x-pack/plugins/reporting/server/lib/screenshots/check_browser_open.ts
deleted file mode 100644
index 95bfa7af870fe..0000000000000
--- a/x-pack/plugins/reporting/server/lib/screenshots/check_browser_open.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { HeadlessChromiumDriver } from '../../browsers';
-import { getChromiumDisconnectedError } from '../../browsers/chromium';
-
-/*
- * Call this function within error-handling `catch` blocks while setup and wait
- * for the Kibana URL to be ready for screenshot. This detects if a block of
- * code threw an exception because the page is closed or crashed.
- *
- * Also call once after `setup$` fires in the screenshot pipeline
- */
-export const checkPageIsOpen = (browser: HeadlessChromiumDriver) => {
- if (!browser.isPageOpen()) {
- throw getChromiumDisconnectedError();
- }
-};
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
index 0ca622d67283c..f160fcb8b27ad 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts
@@ -6,6 +6,7 @@
*/
import { set } from 'lodash';
+import { durationToNumber } from '../../../common/schema_utils';
import { HeadlessChromiumDriver } from '../../browsers';
import {
createMockBrowserDriverFactory,
@@ -25,6 +26,7 @@ describe('getNumberOfItems', () => {
let layout: LayoutInstance;
let logger: jest.Mocked;
let browser: HeadlessChromiumDriver;
+ let timeout: number;
beforeEach(async () => {
const schema = createMockConfigSchema(set({}, 'capture.timeouts.waitForElements', 0));
@@ -34,6 +36,7 @@ describe('getNumberOfItems', () => {
captureConfig = config.get('capture');
layout = createMockLayoutInstance(captureConfig);
logger = createMockLevelLogger();
+ timeout = durationToNumber(captureConfig.timeouts.waitForElements);
await createMockBrowserDriverFactory(core, logger, {
evaluate: jest.fn(
@@ -62,7 +65,7 @@ describe('getNumberOfItems', () => {
`;
- await expect(getNumberOfItems(captureConfig, browser, layout, logger)).resolves.toBe(10);
+ await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(10);
});
it('should determine the number of items by selector ', async () => {
@@ -72,7 +75,7 @@ describe('getNumberOfItems', () => {
`;
- await expect(getNumberOfItems(captureConfig, browser, layout, logger)).resolves.toBe(3);
+ await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(3);
});
it('should fall back to the selector when the attribute is empty', async () => {
@@ -82,6 +85,6 @@ describe('getNumberOfItems', () => {
`;
- await expect(getNumberOfItems(captureConfig, browser, layout, logger)).resolves.toBe(2);
+ await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(2);
});
});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts
index 9bbd8e07898be..9e5dfa180fd0f 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts
@@ -6,15 +6,13 @@
*/
import { i18n } from '@kbn/i18n';
-import { durationToNumber } from '../../../common/schema_utils';
import { LevelLogger, startTrace } from '../';
import { HeadlessChromiumDriver } from '../../browsers';
-import { CaptureConfig } from '../../types';
import { LayoutInstance } from '../layouts';
import { CONTEXT_GETNUMBEROFITEMS, CONTEXT_READMETADATA } from './constants';
export const getNumberOfItems = async (
- captureConfig: CaptureConfig,
+ timeout: number,
browser: HeadlessChromiumDriver,
layout: LayoutInstance,
logger: LevelLogger
@@ -33,7 +31,6 @@ export const getNumberOfItems = async (
// the dashboard is using the `itemsCountAttribute` attribute to let us
// know how many items to expect since gridster incrementally adds panels
// we have to use this hint to wait for all of them
- const timeout = durationToNumber(captureConfig.timeouts.waitForElements);
await browser.waitForSelector(
`${renderCompleteSelector},[${itemsCountAttribute}]`,
{ timeout },
@@ -65,11 +62,8 @@ export const getNumberOfItems = async (
logger.error(err);
throw new Error(
i18n.translate('xpack.reporting.screencapture.readVisualizationsError', {
- defaultMessage: `An error occurred when trying to read the page for visualization panel info. You may need to increase '{configKey}'. {error}`,
- values: {
- error: err,
- configKey: 'xpack.reporting.capture.timeouts.waitForElements',
- },
+ defaultMessage: `An error occurred when trying to read the page for visualization panel info: {error}`,
+ values: { error: err },
})
);
}
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/index.ts b/x-pack/plugins/reporting/server/lib/screenshots/index.ts
index 1ca8b5e00fee4..2b8a0d6207a9b 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/index.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/index.ts
@@ -12,6 +12,19 @@ import { LayoutInstance } from '../layouts';
export { getScreenshots$ } from './observable';
+export interface PhaseInstance {
+ timeoutValue: number;
+ configValue: string;
+ label: string;
+}
+
+export interface PhaseTimeouts {
+ openUrl: PhaseInstance;
+ waitForElements: PhaseInstance;
+ renderComplete: PhaseInstance;
+ loadDelay: number;
+}
+
export interface ScreenshotObservableOpts {
logger: LevelLogger;
urlsOrUrlLocatorTuples: UrlOrUrlLocatorTuple[];
@@ -49,6 +62,12 @@ export interface Screenshot {
description: string | null;
}
+export interface PageSetupResults {
+ elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null;
+ timeRange: string | null;
+ error?: Error;
+}
+
export interface ScreenshotResults {
timeRange: string | null;
screenshots: Screenshot[];
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
index c33bdad44f9e7..3dc06996f0f04 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts
@@ -98,7 +98,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
@@ -173,7 +172,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
@@ -225,7 +223,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
@@ -314,8 +311,7 @@ describe('Screenshot Observable Pipeline', () => {
},
},
],
- "error": [Error: An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. Error: Mock error!],
- "renderErrors": undefined,
+ "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!],
"screenshots": Array [
Object {
"data": Object {
@@ -357,8 +353,7 @@ describe('Screenshot Observable Pipeline', () => {
},
},
],
- "error": [Error: An error occurred when trying to read the page for visualization panel info. You may need to increase 'xpack.reporting.capture.timeouts.waitForElements'. Error: Mock error!],
- "renderErrors": undefined,
+ "error": [Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!],
"screenshots": Array [
Object {
"data": Object {
@@ -465,7 +460,6 @@ describe('Screenshot Observable Pipeline', () => {
},
],
"error": undefined,
- "renderErrors": undefined,
"screenshots": Array [
Object {
"data": Object {
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
index b8fecdc91a3f2..173dbaaf99557 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts
@@ -8,138 +8,70 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { catchError, concatMap, first, mergeMap, take, takeUntil, toArray } from 'rxjs/operators';
+import { durationToNumber } from '../../../common/schema_utils';
import { HeadlessChromiumDriverFactory } from '../../browsers';
import { CaptureConfig } from '../../types';
-import { ElementsPositionAndAttribute, ScreenshotObservableOpts, ScreenshotResults } from './';
-import { checkPageIsOpen } from './check_browser_open';
-import { DEFAULT_PAGELOAD_SELECTOR } from './constants';
-import { getElementPositionAndAttributes } from './get_element_position_data';
-import { getNumberOfItems } from './get_number_of_items';
-import { getScreenshots } from './get_screenshots';
-import { getTimeRange } from './get_time_range';
-import { getRenderErrors } from './get_render_errors';
-import { injectCustomCss } from './inject_css';
-import { openUrl } from './open_url';
-import { waitForRenderComplete } from './wait_for_render';
-import { waitForVisualizations } from './wait_for_visualizations';
+import {
+ ElementPosition,
+ ElementsPositionAndAttribute,
+ PageSetupResults,
+ ScreenshotObservableOpts,
+ ScreenshotResults,
+} from './';
+import { ScreenshotObservableHandler } from './observable_handler';
-const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200;
-const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800;
+export { ElementPosition, ElementsPositionAndAttribute, ScreenshotResults };
-interface ScreenSetupData {
- elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null;
- timeRange: string | null;
- renderErrors?: string[];
- error?: Error;
-}
+const getTimeouts = (captureConfig: CaptureConfig) => ({
+ openUrl: {
+ timeoutValue: durationToNumber(captureConfig.timeouts.openUrl),
+ configValue: `xpack.reporting.capture.timeouts.openUrl`,
+ label: 'open URL',
+ },
+ waitForElements: {
+ timeoutValue: durationToNumber(captureConfig.timeouts.waitForElements),
+ configValue: `xpack.reporting.capture.timeouts.waitForElements`,
+ label: 'wait for elements',
+ },
+ renderComplete: {
+ timeoutValue: durationToNumber(captureConfig.timeouts.renderComplete),
+ configValue: `xpack.reporting.capture.timeouts.renderComplete`,
+ label: 'render complete',
+ },
+ loadDelay: durationToNumber(captureConfig.loadDelay),
+});
export function getScreenshots$(
captureConfig: CaptureConfig,
browserDriverFactory: HeadlessChromiumDriverFactory,
- {
- logger,
- urlsOrUrlLocatorTuples,
- conditionalHeaders,
- layout,
- browserTimezone,
- }: ScreenshotObservableOpts
+ opts: ScreenshotObservableOpts
): Rx.Observable {
const apmTrans = apm.startTransaction(`reporting screenshot pipeline`, 'reporting');
-
const apmCreatePage = apmTrans?.startSpan('create_page', 'wait');
- const create$ = browserDriverFactory.createPage({ browserTimezone }, logger);
+ const { browserTimezone, logger } = opts;
- return create$.pipe(
+ return browserDriverFactory.createPage({ browserTimezone }, logger).pipe(
mergeMap(({ driver, exit$ }) => {
apmCreatePage?.end();
exit$.subscribe({ error: () => apmTrans?.end() });
- return Rx.from(urlsOrUrlLocatorTuples).pipe(
- concatMap((urlOrUrlLocatorTuple, index) => {
- const setup$: Rx.Observable = Rx.of(1).pipe(
- mergeMap(() => {
- // If we're moving to another page in the app, we'll want to wait for the app to tell us
- // it's loaded the next page.
- const page = index + 1;
- const pageLoadSelector =
- page > 1 ? `[data-shared-page="${page}"]` : DEFAULT_PAGELOAD_SELECTOR;
+ const screen = new ScreenshotObservableHandler(driver, opts, getTimeouts(captureConfig));
- return openUrl(
- captureConfig,
- driver,
- urlOrUrlLocatorTuple,
- pageLoadSelector,
- conditionalHeaders,
- logger
- );
- }),
- mergeMap(() => getNumberOfItems(captureConfig, driver, layout, logger)),
- mergeMap(async (itemsCount) => {
- // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout
- const viewport = layout.getViewport(itemsCount) || getDefaultViewPort();
- await Promise.all([
- driver.setViewport(viewport, logger),
- waitForVisualizations(captureConfig, driver, itemsCount, layout, logger),
- ]);
- }),
- mergeMap(async () => {
- // Waiting till _after_ elements have rendered before injecting our CSS
- // allows for them to be displayed properly in many cases
- await injectCustomCss(driver, layout, logger);
-
- const apmPositionElements = apmTrans?.startSpan('position_elements', 'correction');
- if (layout.positionElements) {
- // position panel elements for print layout
- await layout.positionElements(driver, logger);
- }
- if (apmPositionElements) apmPositionElements.end();
-
- await waitForRenderComplete(captureConfig, driver, layout, logger);
- }),
- mergeMap(async () => {
- return await Promise.all([
- getTimeRange(driver, layout, logger),
- getElementPositionAndAttributes(driver, layout, logger),
- getRenderErrors(driver, layout, logger),
- ]).then(([timeRange, elementsPositionAndAttributes, renderErrors]) => ({
- elementsPositionAndAttributes,
- timeRange,
- renderErrors,
- }));
- }),
+ return Rx.from(opts.urlsOrUrlLocatorTuples).pipe(
+ concatMap((urlOrUrlLocatorTuple, index) => {
+ return Rx.of(1).pipe(
+ screen.setupPage(index, urlOrUrlLocatorTuple, apmTrans),
catchError((err) => {
- checkPageIsOpen(driver); // if browser has closed, throw a relevant error about it
+ screen.checkPageIsOpen(); // this fails the job if the browser has closed
logger.error(err);
- return Rx.of({
- elementsPositionAndAttributes: null,
- timeRange: null,
- error: err,
- });
- })
- );
-
- return setup$.pipe(
+ return Rx.of({ ...defaultSetupResult, error: err }); // allow failover screenshot capture
+ }),
takeUntil(exit$),
- mergeMap(async (data: ScreenSetupData): Promise => {
- checkPageIsOpen(driver); // re-check that the browser has not closed
-
- const elements = data.elementsPositionAndAttributes
- ? data.elementsPositionAndAttributes
- : getDefaultElementPosition(layout.getViewport(1));
- const screenshots = await getScreenshots(driver, elements, logger);
- const { timeRange, error: setupError, renderErrors } = data;
- return {
- timeRange,
- screenshots,
- error: setupError,
- renderErrors,
- elementsPositionAndAttributes: elements,
- };
- })
+ screen.getScreenshots()
);
}),
- take(urlsOrUrlLocatorTuples.length),
+ take(opts.urlsOrUrlLocatorTuples.length),
toArray()
);
}),
@@ -147,30 +79,7 @@ export function getScreenshots$(
);
}
-/*
- * If Kibana is showing a non-HTML error message, the viewport might not be
- * provided by the browser.
- */
-const getDefaultViewPort = () => ({
- height: DEFAULT_SCREENSHOT_CLIP_HEIGHT,
- width: DEFAULT_SCREENSHOT_CLIP_WIDTH,
- zoom: 1,
-});
-/*
- * If an error happens setting up the page, we don't know if there actually
- * are any visualizations showing. These defaults should help capture the page
- * enough for the user to see the error themselves
- */
-const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => {
- const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT;
- const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH;
-
- const defaultObject = {
- position: {
- boundingClientRect: { top: 0, left: 0, height, width },
- scroll: { x: 0, y: 0 },
- },
- attributes: {},
- };
- return [defaultObject];
+const defaultSetupResult: PageSetupResults = {
+ elementsPositionAndAttributes: null,
+ timeRange: null,
};
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts
new file mode 100644
index 0000000000000..25a8bed370d86
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts
@@ -0,0 +1,160 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import * as Rx from 'rxjs';
+import { first, map } from 'rxjs/operators';
+import { HeadlessChromiumDriver } from '../../browsers';
+import { ReportingConfigType } from '../../config';
+import { ConditionalHeaders } from '../../export_types/common';
+import {
+ createMockBrowserDriverFactory,
+ createMockConfigSchema,
+ createMockLayoutInstance,
+ createMockLevelLogger,
+ createMockReportingCore,
+} from '../../test_helpers';
+import { LayoutInstance } from '../layouts';
+import { PhaseTimeouts, ScreenshotObservableOpts } from './';
+import { ScreenshotObservableHandler } from './observable_handler';
+
+const logger = createMockLevelLogger();
+
+describe('ScreenshotObservableHandler', () => {
+ let captureConfig: ReportingConfigType['capture'];
+ let layout: LayoutInstance;
+ let conditionalHeaders: ConditionalHeaders;
+ let opts: ScreenshotObservableOpts;
+ let timeouts: PhaseTimeouts;
+ let driver: HeadlessChromiumDriver;
+
+ beforeAll(async () => {
+ captureConfig = {
+ timeouts: {
+ openUrl: 30000,
+ waitForElements: 30000,
+ renderComplete: 30000,
+ },
+ loadDelay: 5000,
+ } as unknown as typeof captureConfig;
+
+ layout = createMockLayoutInstance(captureConfig);
+
+ conditionalHeaders = {
+ headers: { testHeader: 'testHeadValue' },
+ conditions: {} as unknown as ConditionalHeaders['conditions'],
+ };
+
+ opts = {
+ conditionalHeaders,
+ layout,
+ logger,
+ urlsOrUrlLocatorTuples: [],
+ };
+
+ timeouts = {
+ openUrl: {
+ timeoutValue: 60000,
+ configValue: `xpack.reporting.capture.timeouts.openUrl`,
+ label: 'open URL',
+ },
+ waitForElements: {
+ timeoutValue: 30000,
+ configValue: `xpack.reporting.capture.timeouts.waitForElements`,
+ label: 'wait for elements',
+ },
+ renderComplete: {
+ timeoutValue: 60000,
+ configValue: `xpack.reporting.capture.timeouts.renderComplete`,
+ label: 'render complete',
+ },
+ loadDelay: 5000,
+ };
+ });
+
+ beforeEach(async () => {
+ const reporting = await createMockReportingCore(createMockConfigSchema());
+ const driverFactory = await createMockBrowserDriverFactory(reporting, logger);
+ ({ driver } = await driverFactory.createPage({}, logger).pipe(first()).toPromise());
+ driver.isPageOpen = jest.fn().mockImplementation(() => true);
+ });
+
+ describe('waitUntil', () => {
+ it('catches TimeoutError and references the timeout config in a custom message', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.interval(1000).pipe(
+ screenshots.waitUntil({
+ timeoutValue: 200,
+ configValue: 'test.config.value',
+ label: 'Test Config',
+ })
+ );
+
+ const testPipeline = () => test$.toPromise();
+ await expect(testPipeline).rejects.toMatchInlineSnapshot(
+ `[Error: The "Test Config" phase took longer than 0.2 seconds. You may need to increase "test.config.value": TimeoutError: Timeout has occurred]`
+ );
+ });
+
+ it('catches other Errors and explains where they were thrown', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.throwError(new Error(`Test Error to Throw`)).pipe(
+ screenshots.waitUntil({
+ timeoutValue: 200,
+ configValue: 'test.config.value',
+ label: 'Test Config',
+ })
+ );
+
+ const testPipeline = () => test$.toPromise();
+ await expect(testPipeline).rejects.toMatchInlineSnapshot(
+ `[Error: The "Test Config" phase encountered an error: Error: Test Error to Throw]`
+ );
+ });
+
+ it('is a pass-through if there is no Error', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.of('nice to see you').pipe(
+ screenshots.waitUntil({
+ timeoutValue: 20,
+ configValue: 'xxxxxxxxxxxxxxxxx',
+ label: 'xxxxxxxxxxx',
+ })
+ );
+
+ await expect(test$.toPromise()).resolves.toBe(`nice to see you`);
+ });
+ });
+
+ describe('checkPageIsOpen', () => {
+ it('throws a decorated Error when page is not open', async () => {
+ driver.isPageOpen = jest.fn().mockImplementation(() => false);
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.of(234455).pipe(
+ map((input) => {
+ screenshots.checkPageIsOpen();
+ return input;
+ })
+ );
+
+ await expect(test$.toPromise()).rejects.toMatchInlineSnapshot(
+ `[Error: Browser was closed unexpectedly! Check the server logs for more info.]`
+ );
+ });
+
+ it('is a pass-through when the page is open', async () => {
+ const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts);
+ const test$ = Rx.of(234455).pipe(
+ map((input) => {
+ screenshots.checkPageIsOpen();
+ return input;
+ })
+ );
+
+ await expect(test$.toPromise()).resolves.toBe(234455);
+ });
+ });
+});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
new file mode 100644
index 0000000000000..87c247273ef04
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts
@@ -0,0 +1,214 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import apm from 'elastic-apm-node';
+import * as Rx from 'rxjs';
+import { catchError, mergeMap, timeout } from 'rxjs/operators';
+import { numberToDuration } from '../../../common/schema_utils';
+import { UrlOrUrlLocatorTuple } from '../../../common/types';
+import { HeadlessChromiumDriver } from '../../browsers';
+import { getChromiumDisconnectedError } from '../../browsers/chromium';
+import {
+ PageSetupResults,
+ PhaseInstance,
+ PhaseTimeouts,
+ ScreenshotObservableOpts,
+ ScreenshotResults,
+} from './';
+import { getElementPositionAndAttributes } from './get_element_position_data';
+import { getNumberOfItems } from './get_number_of_items';
+import { getRenderErrors } from './get_render_errors';
+import { getScreenshots } from './get_screenshots';
+import { getTimeRange } from './get_time_range';
+import { injectCustomCss } from './inject_css';
+import { openUrl } from './open_url';
+import { waitForRenderComplete } from './wait_for_render';
+import { waitForVisualizations } from './wait_for_visualizations';
+
+export class ScreenshotObservableHandler {
+ private conditionalHeaders: ScreenshotObservableOpts['conditionalHeaders'];
+ private layout: ScreenshotObservableOpts['layout'];
+ private logger: ScreenshotObservableOpts['logger'];
+ private waitErrorRegistered = false;
+
+ constructor(
+ private readonly driver: HeadlessChromiumDriver,
+ opts: ScreenshotObservableOpts,
+ private timeouts: PhaseTimeouts
+ ) {
+ this.conditionalHeaders = opts.conditionalHeaders;
+ this.layout = opts.layout;
+ this.logger = opts.logger;
+ }
+
+ /*
+ * Decorates a TimeoutError with context of the phase that has timed out.
+ */
+ public waitUntil(phase: PhaseInstance) {
+ const { timeoutValue, label, configValue } = phase;
+ return (source: Rx.Observable) => {
+ return source.pipe(
+ timeout(timeoutValue),
+ catchError((error: string | Error) => {
+ if (this.waitErrorRegistered) {
+ throw error; // do not create a stack of errors within the error
+ }
+
+ this.logger.error(error);
+ let throwError = new Error(`The "${label}" phase encountered an error: ${error}`);
+
+ if (error instanceof Rx.TimeoutError) {
+ throwError = new Error(
+ `The "${label}" phase took longer than` +
+ ` ${numberToDuration(timeoutValue).asSeconds()} seconds.` +
+ ` You may need to increase "${configValue}": ${error}`
+ );
+ }
+
+ this.waitErrorRegistered = true;
+ this.logger.error(throwError);
+ throw throwError;
+ })
+ );
+ };
+ }
+
+ private openUrl(index: number, urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple) {
+ return mergeMap(() =>
+ openUrl(
+ this.timeouts.openUrl.timeoutValue,
+ this.driver,
+ index,
+ urlOrUrlLocatorTuple,
+ this.conditionalHeaders,
+ this.logger
+ )
+ );
+ }
+
+ private waitForElements() {
+ const driver = this.driver;
+ const waitTimeout = this.timeouts.waitForElements.timeoutValue;
+ return (withPageOpen: Rx.Observable) =>
+ withPageOpen.pipe(
+ mergeMap(() => getNumberOfItems(waitTimeout, driver, this.layout, this.logger)),
+ mergeMap(async (itemsCount) => {
+ // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout
+ const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort();
+ await Promise.all([
+ driver.setViewport(viewport, this.logger),
+ waitForVisualizations(waitTimeout, driver, itemsCount, this.layout, this.logger),
+ ]);
+ })
+ );
+ }
+
+ private completeRender(apmTrans: apm.Transaction | null) {
+ const driver = this.driver;
+ const layout = this.layout;
+ const logger = this.logger;
+
+ return (withElements: Rx.Observable) =>
+ withElements.pipe(
+ mergeMap(async () => {
+ // Waiting till _after_ elements have rendered before injecting our CSS
+ // allows for them to be displayed properly in many cases
+ await injectCustomCss(driver, layout, logger);
+
+ const apmPositionElements = apmTrans?.startSpan('position_elements', 'correction');
+ // position panel elements for print layout
+ await layout.positionElements?.(driver, logger);
+ apmPositionElements?.end();
+
+ await waitForRenderComplete(this.timeouts.loadDelay, driver, layout, logger);
+ }),
+ mergeMap(() =>
+ Promise.all([
+ getTimeRange(driver, layout, logger),
+ getElementPositionAndAttributes(driver, layout, logger),
+ getRenderErrors(driver, layout, logger),
+ ]).then(([timeRange, elementsPositionAndAttributes, renderErrors]) => ({
+ elementsPositionAndAttributes,
+ timeRange,
+ renderErrors,
+ }))
+ )
+ );
+ }
+
+ public setupPage(
+ index: number,
+ urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple,
+ apmTrans: apm.Transaction | null
+ ) {
+ return (initial: Rx.Observable) =>
+ initial.pipe(
+ this.openUrl(index, urlOrUrlLocatorTuple),
+ this.waitUntil(this.timeouts.openUrl),
+ this.waitForElements(),
+ this.waitUntil(this.timeouts.waitForElements),
+ this.completeRender(apmTrans),
+ this.waitUntil(this.timeouts.renderComplete)
+ );
+ }
+
+ public getScreenshots() {
+ return (withRenderComplete: Rx.Observable) =>
+ withRenderComplete.pipe(
+ mergeMap(async (data: PageSetupResults): Promise => {
+ this.checkPageIsOpen(); // fail the report job if the browser has closed
+
+ const elements =
+ data.elementsPositionAndAttributes ??
+ getDefaultElementPosition(this.layout.getViewport(1));
+ const screenshots = await getScreenshots(this.driver, elements, this.logger);
+ const { timeRange, error: setupError } = data;
+
+ return {
+ timeRange,
+ screenshots,
+ error: setupError,
+ elementsPositionAndAttributes: elements,
+ };
+ })
+ );
+ }
+
+ public checkPageIsOpen() {
+ if (!this.driver.isPageOpen()) {
+ throw getChromiumDisconnectedError();
+ }
+ }
+}
+
+const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200;
+const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800;
+
+const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => {
+ const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT;
+ const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH;
+
+ return [
+ {
+ position: {
+ boundingClientRect: { top: 0, left: 0, height, width },
+ scroll: { x: 0, y: 0 },
+ },
+ attributes: {},
+ },
+ ];
+};
+
+/*
+ * If Kibana is showing a non-HTML error message, the viewport might not be
+ * provided by the browser.
+ */
+const getDefaultViewPort = () => ({
+ height: DEFAULT_SCREENSHOT_CLIP_HEIGHT,
+ width: DEFAULT_SCREENSHOT_CLIP_WIDTH,
+ zoom: 1,
+});
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts
index 588cd792bdf06..63a5e80289e3e 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts
@@ -6,21 +6,25 @@
*/
import { i18n } from '@kbn/i18n';
-import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../common/types';
import { LevelLogger, startTrace } from '../';
-import { durationToNumber } from '../../../common/schema_utils';
+import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../common/types';
import { HeadlessChromiumDriver } from '../../browsers';
import { ConditionalHeaders } from '../../export_types/common';
-import { CaptureConfig } from '../../types';
+import { DEFAULT_PAGELOAD_SELECTOR } from './constants';
export const openUrl = async (
- captureConfig: CaptureConfig,
+ timeout: number,
browser: HeadlessChromiumDriver,
+ index: number,
urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple,
- waitForSelector: string,
conditionalHeaders: ConditionalHeaders,
logger: LevelLogger
): Promise => {
+ // If we're moving to another page in the app, we'll want to wait for the app to tell us
+ // it's loaded the next page.
+ const page = index + 1;
+ const waitForSelector = page > 1 ? `[data-shared-page="${page}"]` : DEFAULT_PAGELOAD_SELECTOR;
+
const endTrace = startTrace('open_url', 'wait');
let url: string;
let locator: undefined | LocatorParams;
@@ -32,14 +36,13 @@ export const openUrl = async (
}
try {
- const timeout = durationToNumber(captureConfig.timeouts.openUrl);
await browser.open(url, { conditionalHeaders, waitForSelector, timeout, locator }, logger);
} catch (err) {
logger.error(err);
throw new Error(
i18n.translate('xpack.reporting.screencapture.couldntLoadKibana', {
- defaultMessage: `An error occurred when trying to open the Kibana URL. You may need to increase '{configKey}'. {error}`,
- values: { configKey: 'xpack.reporting.capture.timeouts.openUrl', error: err },
+ defaultMessage: `An error occurred when trying to open the Kibana URL: {error}`,
+ values: { error: err },
})
);
}
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts
index f8293bfce3346..1ac4b58b61507 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts
@@ -6,15 +6,13 @@
*/
import { i18n } from '@kbn/i18n';
-import { durationToNumber } from '../../../common/schema_utils';
-import { HeadlessChromiumDriver } from '../../browsers';
-import { CaptureConfig } from '../../types';
import { LevelLogger, startTrace } from '../';
+import { HeadlessChromiumDriver } from '../../browsers';
import { LayoutInstance } from '../layouts';
import { CONTEXT_WAITFORRENDER } from './constants';
export const waitForRenderComplete = async (
- captureConfig: CaptureConfig,
+ loadDelay: number,
browser: HeadlessChromiumDriver,
layout: LayoutInstance,
logger: LevelLogger
@@ -69,7 +67,7 @@ export const waitForRenderComplete = async (
return Promise.all(renderedTasks).then(hackyWaitForVisualizations);
},
- args: [layout.selectors.renderComplete, durationToNumber(captureConfig.loadDelay)],
+ args: [layout.selectors.renderComplete, loadDelay],
},
{ context: CONTEXT_WAITFORRENDER },
logger
diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts
index 0ab274da7e1cf..d4bf1db2a0c5a 100644
--- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts
+++ b/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts
@@ -6,10 +6,8 @@
*/
import { i18n } from '@kbn/i18n';
-import { durationToNumber } from '../../../common/schema_utils';
import { LevelLogger, startTrace } from '../';
import { HeadlessChromiumDriver } from '../../browsers';
-import { CaptureConfig } from '../../types';
import { LayoutInstance } from '../layouts';
import { CONTEXT_WAITFORELEMENTSTOBEINDOM } from './constants';
@@ -25,7 +23,7 @@ const getCompletedItemsCount = ({ renderCompleteSelector }: SelectorArgs) => {
* 3. Wait for the render complete event to be fired once for each item
*/
export const waitForVisualizations = async (
- captureConfig: CaptureConfig,
+ timeout: number,
browser: HeadlessChromiumDriver,
toEqual: number,
layout: LayoutInstance,
@@ -42,7 +40,6 @@ export const waitForVisualizations = async (
);
try {
- const timeout = durationToNumber(captureConfig.timeouts.renderComplete);
await browser.waitFor(
{ fn: getCompletedItemsCount, args: [{ renderCompleteSelector }], toEqual, timeout },
{ context: CONTEXT_WAITFORELEMENTSTOBEINDOM },
@@ -54,12 +51,8 @@ export const waitForVisualizations = async (
logger.error(err);
throw new Error(
i18n.translate('xpack.reporting.screencapture.couldntFinishRendering', {
- defaultMessage: `An error occurred when trying to wait for {count} visualizations to finish rendering. You may need to increase '{configKey}'. {error}`,
- values: {
- count: toEqual,
- configKey: 'xpack.reporting.capture.timeouts.renderComplete',
- error: err,
- },
+ defaultMessage: `An error occurred when trying to wait for {count} visualizations to finish rendering. {error}`,
+ values: { count: toEqual, error: err },
})
);
}
diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts
index 45b8b23cae477..605265f7311ba 100644
--- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts
@@ -6,7 +6,7 @@
*/
import sinon, { SinonFakeServer } from 'sinon';
-import { API_BASE_PATH } from '../../../common/constants';
+import { API_BASE_PATH } from '../../../common';
type HttpResponse = Record | any[];
@@ -54,7 +54,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
};
const setLoadSnapshotsResponse = (response: HttpResponse = {}) => {
- const defaultResponse = { errors: {}, snapshots: [], repositories: [] };
+ const defaultResponse = { errors: {}, snapshots: [], repositories: [], total: 0 };
server.respondWith('GET', `${API_BASE_PATH}snapshots`, mockResponse(defaultResponse, response));
};
diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
index 52303e1134f9d..071868e23f7fe 100644
--- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
+++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts
@@ -8,7 +8,7 @@
import { act } from 'react-dom/test-utils';
import * as fixtures from '../../test/fixtures';
import { SNAPSHOT_STATE } from '../../public/application/constants';
-import { API_BASE_PATH } from '../../common/constants';
+import { API_BASE_PATH } from '../../common';
import {
setupEnvironment,
pageHelpers,
@@ -431,6 +431,7 @@ describe('', () => {
httpRequestsMockHelpers.setLoadSnapshotsResponse({
snapshots: [],
repositories: ['my-repo'],
+ total: 0,
});
testBed = await setup();
@@ -469,6 +470,7 @@ describe('', () => {
httpRequestsMockHelpers.setLoadSnapshotsResponse({
snapshots,
repositories: [REPOSITORY_NAME],
+ total: 2,
});
testBed = await setup();
@@ -501,18 +503,10 @@ describe('', () => {
});
});
- test('should show a warning if the number of snapshots exceeded the limit', () => {
- // We have mocked the SNAPSHOT_LIST_MAX_SIZE to 2, so the warning should display
- const { find, exists } = testBed;
- expect(exists('maxSnapshotsWarning')).toBe(true);
- expect(find('maxSnapshotsWarning').text()).toContain(
- 'Cannot show the full list of snapshots'
- );
- });
-
test('should show a warning if one repository contains errors', async () => {
httpRequestsMockHelpers.setLoadSnapshotsResponse({
snapshots,
+ total: 2,
repositories: [REPOSITORY_NAME],
errors: {
repository_with_errors: {
diff --git a/x-pack/plugins/snapshot_restore/common/constants.ts b/x-pack/plugins/snapshot_restore/common/constants.ts
index a7c83ecf702e0..b18e118dc5ff6 100644
--- a/x-pack/plugins/snapshot_restore/common/constants.ts
+++ b/x-pack/plugins/snapshot_restore/common/constants.ts
@@ -65,9 +65,3 @@ export const TIME_UNITS: { [key: string]: 'd' | 'h' | 'm' | 's' } = {
MINUTE: 'm',
SECOND: 's',
};
-
-/**
- * [Temporary workaround] In order to prevent client-side performance issues for users with a large number of snapshots,
- * we set a hard-coded limit on the number of snapshots we return from the ES snapshots API
- */
-export const SNAPSHOT_LIST_MAX_SIZE = 1000;
diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/index.ts b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts
index 1ec4d5b2907f2..19a42bef4cea4 100644
--- a/x-pack/plugins/snapshot_restore/public/application/lib/index.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/lib/index.ts
@@ -6,3 +6,12 @@
*/
export { useDecodedParams } from './use_decoded_params';
+
+export {
+ SortField,
+ SortDirection,
+ SnapshotListParams,
+ getListParams,
+ getQueryFromListParams,
+ DEFAULT_SNAPSHOT_LIST_PARAMS,
+} from './snapshot_list_params';
diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts b/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
new file mode 100644
index 0000000000000..b75a3e01fb617
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Direction, Query } from '@elastic/eui';
+
+export type SortField =
+ | 'snapshot'
+ | 'repository'
+ | 'indices'
+ | 'startTimeInMillis'
+ | 'durationInMillis'
+ | 'shards.total'
+ | 'shards.failed';
+
+export type SortDirection = Direction;
+
+interface SnapshotTableParams {
+ sortField: SortField;
+ sortDirection: SortDirection;
+ pageIndex: number;
+ pageSize: number;
+}
+interface SnapshotSearchParams {
+ searchField?: string;
+ searchValue?: string;
+ searchMatch?: string;
+ searchOperator?: string;
+}
+export type SnapshotListParams = SnapshotTableParams & SnapshotSearchParams;
+
+// By default, we'll display the most recent snapshots at the top of the table (no query).
+export const DEFAULT_SNAPSHOT_LIST_PARAMS: SnapshotListParams = {
+ sortField: 'startTimeInMillis',
+ sortDirection: 'desc',
+ pageIndex: 0,
+ pageSize: 20,
+};
+
+const resetSearchOptions = (listParams: SnapshotListParams): SnapshotListParams => ({
+ ...listParams,
+ searchField: undefined,
+ searchValue: undefined,
+ searchMatch: undefined,
+ searchOperator: undefined,
+});
+
+// to init the query for repository and policyName search passed via url
+export const getQueryFromListParams = (listParams: SnapshotListParams): Query => {
+ const { searchField, searchValue } = listParams;
+ if (!searchField || !searchValue) {
+ return Query.MATCH_ALL;
+ }
+ return Query.parse(`${searchField}=${searchValue}`);
+};
+
+export const getListParams = (listParams: SnapshotListParams, query: Query): SnapshotListParams => {
+ if (!query) {
+ return resetSearchOptions(listParams);
+ }
+ const clause = query.ast.clauses[0];
+ if (!clause) {
+ return resetSearchOptions(listParams);
+ }
+ // term queries (free word search) converts to snapshot name search
+ if (clause.type === 'term') {
+ return {
+ ...listParams,
+ searchField: 'snapshot',
+ searchValue: String(clause.value),
+ searchMatch: clause.match,
+ searchOperator: 'eq',
+ };
+ }
+ if (clause.type === 'field') {
+ return {
+ ...listParams,
+ searchField: clause.field,
+ searchValue: String(clause.value),
+ searchMatch: clause.match,
+ searchOperator: clause.operator,
+ };
+ }
+ return resetSearchOptions(listParams);
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/index.ts b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/index.ts
similarity index 55%
rename from x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/index.ts
rename to x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/index.ts
index 7ea85f4150900..8c69ca0297e3e 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/index.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/index.ts
@@ -6,3 +6,7 @@
*/
export { SnapshotTable } from './snapshot_table';
+export { RepositoryError } from './repository_error';
+export { RepositoryEmptyPrompt } from './repository_empty_prompt';
+export { SnapshotEmptyPrompt } from './snapshot_empty_prompt';
+export { SnapshotSearchBar } from './snapshot_search_bar';
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx
new file mode 100644
index 0000000000000..4c5e050ea489c
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { EuiButton, EuiEmptyPrompt, EuiPageContent } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { reactRouterNavigate } from '../../../../../shared_imports';
+import { linkToAddRepository } from '../../../../services/navigation';
+
+export const RepositoryEmptyPrompt: React.FunctionComponent = () => {
+ const history = useHistory();
+ return (
+
+
+
+
+ }
+ body={
+ <>
+
+
+
+
+
+
+
+
+ >
+ }
+ data-test-subj="emptyPrompt"
+ />
+
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx
new file mode 100644
index 0000000000000..d3902770333cc
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiEmptyPrompt, EuiLink, EuiPageContent } from '@elastic/eui';
+import { reactRouterNavigate } from '../../../../../shared_imports';
+import { linkToRepositories } from '../../../../services/navigation';
+
+export const RepositoryError: React.FunctionComponent = () => {
+ const history = useHistory();
+ return (
+
+
+
+
+ }
+ body={
+
+
+
+
+ ),
+ }}
+ />
+
+ }
+ />
+
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx
new file mode 100644
index 0000000000000..2cfc1d5ebefc5
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx
@@ -0,0 +1,123 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { Fragment } from 'react';
+import { useHistory } from 'react-router-dom';
+import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink, EuiPageContent } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../../common';
+import { reactRouterNavigate, WithPrivileges } from '../../../../../shared_imports';
+import { linkToAddPolicy, linkToPolicies } from '../../../../services/navigation';
+import { useCore } from '../../../../app_context';
+
+export const SnapshotEmptyPrompt: React.FunctionComponent<{ policiesCount: number }> = ({
+ policiesCount,
+}) => {
+ const { docLinks } = useCore();
+ const history = useHistory();
+ return (
+
+
+
+
+ }
+ body={
+ `cluster.${name}`)}>
+ {({ hasPrivileges }) =>
+ hasPrivileges ? (
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+ {policiesCount === 0 ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ ) : (
+
+
+
+
+
+
+ {' '}
+
+
+
+
+ )
+ }
+
+ }
+ data-test-subj="emptyPrompt"
+ />
+
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
new file mode 100644
index 0000000000000..b3e2c24e396f0
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
@@ -0,0 +1,178 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState } from 'react';
+import useDebounce from 'react-use/lib/useDebounce';
+
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { SearchFilterConfig } from '@elastic/eui/src/components/search_bar/search_filters';
+import { SchemaType } from '@elastic/eui/src/components/search_bar/search_box';
+import { EuiSearchBarOnChangeArgs } from '@elastic/eui/src/components/search_bar/search_bar';
+import { EuiButton, EuiCallOut, EuiSearchBar, EuiSpacer, Query } from '@elastic/eui';
+import { SnapshotDeleteProvider } from '../../../../components';
+import { SnapshotDetails } from '../../../../../../common/types';
+import { getQueryFromListParams, SnapshotListParams, getListParams } from '../../../../lib';
+
+const SEARCH_DEBOUNCE_VALUE_MS = 200;
+
+const onlyOneClauseMessage = i18n.translate(
+ 'xpack.snapshotRestore.snapshotList.searchBar.onlyOneClauseMessage',
+ {
+ defaultMessage: 'You can only use one clause in the search bar',
+ }
+);
+// for now limit the search bar to snapshot, repository and policyName queries
+const searchSchema: SchemaType = {
+ strict: true,
+ fields: {
+ snapshot: {
+ type: 'string',
+ },
+ repository: {
+ type: 'string',
+ },
+ policyName: {
+ type: 'string',
+ },
+ },
+};
+
+interface Props {
+ listParams: SnapshotListParams;
+ setListParams: (listParams: SnapshotListParams) => void;
+ reload: () => void;
+ selectedItems: SnapshotDetails[];
+ onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
+ repositories: string[];
+}
+
+export const SnapshotSearchBar: React.FunctionComponent = ({
+ listParams,
+ setListParams,
+ reload,
+ selectedItems,
+ onSnapshotDeleted,
+ repositories,
+}) => {
+ const [cachedListParams, setCachedListParams] = useState(listParams);
+ // send the request after the user has stopped typing
+ useDebounce(
+ () => {
+ setListParams(cachedListParams);
+ },
+ SEARCH_DEBOUNCE_VALUE_MS,
+ [cachedListParams]
+ );
+
+ const deleteButton = selectedItems.length ? (
+
+ {(
+ deleteSnapshotPrompt: (
+ ids: Array<{ snapshot: string; repository: string }>,
+ onSuccess?: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void
+ ) => void
+ ) => {
+ return (
+
+ deleteSnapshotPrompt(
+ selectedItems.map(({ snapshot, repository }) => ({ snapshot, repository })),
+ onSnapshotDeleted
+ )
+ }
+ color="danger"
+ data-test-subj="srSnapshotListBulkDeleteActionButton"
+ >
+
+
+ );
+ }}
+
+ ) : (
+ []
+ );
+ const searchFilters: SearchFilterConfig[] = [
+ {
+ type: 'field_value_selection' as const,
+ field: 'repository',
+ name: i18n.translate('xpack.snapshotRestore.snapshotList.table.repositoryFilterLabel', {
+ defaultMessage: 'Repository',
+ }),
+ operator: 'exact',
+ multiSelect: false,
+ options: repositories.map((repository) => ({
+ value: repository,
+ view: repository,
+ })),
+ },
+ ];
+ const reloadButton = (
+
+
+
+ );
+
+ const [query, setQuery] = useState(getQueryFromListParams(listParams));
+ const [error, setError] = useState(null);
+
+ const onSearchBarChange = (args: EuiSearchBarOnChangeArgs) => {
+ const { query: changedQuery, error: queryError } = args;
+ if (queryError) {
+ setError(queryError);
+ } else if (changedQuery) {
+ setError(null);
+ setQuery(changedQuery);
+ if (changedQuery.ast.clauses.length > 1) {
+ setError({ name: onlyOneClauseMessage, message: onlyOneClauseMessage });
+ } else {
+ setCachedListParams(getListParams(listParams, changedQuery));
+ }
+ }
+ };
+
+ return (
+ <>
+
+
+ {error ? (
+ <>
+
+ }
+ />
+
+ >
+ ) : null}
+ >
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_table.tsx
similarity index 71%
rename from x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx
rename to x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_table.tsx
index 47f8d9b833e40..5db702fcbd963 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_table.tsx
@@ -7,34 +7,28 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types';
+
import {
- EuiButton,
- EuiInMemoryTable,
EuiLink,
- Query,
EuiLoadingSpinner,
EuiToolTip,
EuiButtonIcon,
+ Criteria,
+ EuiBasicTable,
} from '@elastic/eui';
-
import { SnapshotDetails } from '../../../../../../common/types';
-import { UseRequestResponse } from '../../../../../shared_imports';
+import { UseRequestResponse, reactRouterNavigate } from '../../../../../shared_imports';
import { SNAPSHOT_STATE, UIM_SNAPSHOT_SHOW_DETAILS_CLICK } from '../../../../constants';
import { useServices } from '../../../../app_context';
-import { linkToRepository, linkToRestoreSnapshot } from '../../../../services/navigation';
+import {
+ linkToRepository,
+ linkToRestoreSnapshot,
+ linkToSnapshot as openSnapshotDetailsUrl,
+} from '../../../../services/navigation';
+import { SnapshotListParams, SortDirection, SortField } from '../../../../lib';
import { DataPlaceholder, FormattedDateTime, SnapshotDeleteProvider } from '../../../../components';
-
-import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public';
-
-interface Props {
- snapshots: SnapshotDetails[];
- repositories: string[];
- reload: UseRequestResponse['resendRequest'];
- openSnapshotDetailsUrl: (repositoryName: string, snapshotId: string) => string;
- repositoryFilter?: string;
- policyFilter?: string;
- onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
-}
+import { SnapshotSearchBar } from './snapshot_search_bar';
const getLastSuccessfulManagedSnapshot = (
snapshots: SnapshotDetails[]
@@ -51,15 +45,28 @@ const getLastSuccessfulManagedSnapshot = (
return successfulSnapshots[0];
};
-export const SnapshotTable: React.FunctionComponent = ({
- snapshots,
- repositories,
- reload,
- openSnapshotDetailsUrl,
- onSnapshotDeleted,
- repositoryFilter,
- policyFilter,
-}) => {
+interface Props {
+ snapshots: SnapshotDetails[];
+ repositories: string[];
+ reload: UseRequestResponse['resendRequest'];
+ onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
+ listParams: SnapshotListParams;
+ setListParams: (listParams: SnapshotListParams) => void;
+ totalItemCount: number;
+ isLoading: boolean;
+}
+
+export const SnapshotTable: React.FunctionComponent = (props: Props) => {
+ const {
+ snapshots,
+ repositories,
+ reload,
+ onSnapshotDeleted,
+ listParams,
+ setListParams,
+ totalItemCount,
+ isLoading,
+ } = props;
const { i18n, uiMetricService, history } = useServices();
const [selectedItems, setSelectedItems] = useState([]);
@@ -71,7 +78,7 @@ export const SnapshotTable: React.FunctionComponent = ({
name: i18n.translate('xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle', {
defaultMessage: 'Snapshot',
}),
- truncateText: true,
+ truncateText: false,
sortable: true,
render: (snapshotId: string, snapshot: SnapshotDetails) => (
= ({
name: i18n.translate('xpack.snapshotRestore.snapshotList.table.repositoryColumnTitle', {
defaultMessage: 'Repository',
}),
- truncateText: true,
+ truncateText: false,
sortable: true,
render: (repositoryName: string) => (
= ({
name: i18n.translate('xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle', {
defaultMessage: 'Date created',
}),
- truncateText: true,
+ truncateText: false,
sortable: true,
render: (startTimeInMillis: number) => (
@@ -263,30 +270,20 @@ export const SnapshotTable: React.FunctionComponent = ({
},
];
- // By default, we'll display the most recent snapshots at the top of the table.
- const sorting = {
+ const sorting: EuiTableSortingType = {
sort: {
- field: 'startTimeInMillis',
- direction: 'desc' as const,
+ field: listParams.sortField as keyof SnapshotDetails,
+ direction: listParams.sortDirection,
},
};
const pagination = {
- initialPageSize: 20,
+ pageIndex: listParams.pageIndex,
+ pageSize: listParams.pageSize,
+ totalItemCount,
pageSizeOptions: [10, 20, 50],
};
- const searchSchema = {
- fields: {
- repository: {
- type: 'string',
- },
- policyName: {
- type: 'string',
- },
- },
- };
-
const selection = {
onSelectionChange: (newSelectedItems: SnapshotDetails[]) => setSelectedItems(newSelectedItems),
selectable: ({ snapshot }: SnapshotDetails) =>
@@ -306,103 +303,44 @@ export const SnapshotTable: React.FunctionComponent = ({
},
};
- const search = {
- toolsLeft: selectedItems.length ? (
-
- {(
- deleteSnapshotPrompt: (
- ids: Array<{ snapshot: string; repository: string }>,
- onSuccess?: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void
- ) => void
- ) => {
- return (
-
- deleteSnapshotPrompt(
- selectedItems.map(({ snapshot, repository }) => ({ snapshot, repository })),
- onSnapshotDeleted
- )
- }
- color="danger"
- data-test-subj="srSnapshotListBulkDeleteActionButton"
- >
-
-
- );
- }}
-
- ) : undefined,
- toolsRight: (
-
-
-
- ),
- box: {
- incremental: true,
- schema: searchSchema,
- },
- filters: [
- {
- type: 'field_value_selection' as const,
- field: 'repository',
- name: i18n.translate('xpack.snapshotRestore.snapshotList.table.repositoryFilterLabel', {
- defaultMessage: 'Repository',
- }),
- multiSelect: false,
- options: repositories.map((repository) => ({
- value: repository,
- view: repository,
- })),
- },
- ],
- defaultQuery: policyFilter
- ? Query.parse(`policyName="${policyFilter}"`, {
- schema: {
- ...searchSchema,
- strict: true,
- },
- })
- : repositoryFilter
- ? Query.parse(`repository="${repositoryFilter}"`, {
- schema: {
- ...searchSchema,
- strict: true,
- },
- })
- : '',
- };
-
return (
- ({
- 'data-test-subj': 'row',
- })}
- cellProps={() => ({
- 'data-test-subj': 'cell',
- })}
- data-test-subj="snapshotTable"
- />
+ <>
+
+ ) => {
+ const { page: { index, size } = {}, sort: { field, direction } = {} } = criteria;
+
+ setListParams({
+ ...listParams,
+ sortField: (field as SortField) ?? listParams.sortField,
+ sortDirection: (direction as SortDirection) ?? listParams.sortDirection,
+ pageIndex: index ?? listParams.pageIndex,
+ pageSize: size ?? listParams.pageSize,
+ });
+ }}
+ loading={isLoading}
+ isSelectable={true}
+ selection={selection}
+ pagination={pagination}
+ rowProps={() => ({
+ 'data-test-subj': 'row',
+ })}
+ cellProps={() => ({
+ 'data-test-subj': 'cell',
+ })}
+ data-test-subj="snapshotTable"
+ />
+ >
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx
index 92c03d1be936d..da7ec42f746a3 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx
@@ -5,37 +5,26 @@
* 2.0.
*/
-import React, { Fragment, useState, useEffect } from 'react';
+import React, { useState, useEffect } from 'react';
import { parse } from 'query-string';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
-import {
- EuiPageContent,
- EuiButton,
- EuiCallOut,
- EuiLink,
- EuiEmptyPrompt,
- EuiSpacer,
- EuiIcon,
-} from '@elastic/eui';
+import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
-import { APP_SLM_CLUSTER_PRIVILEGES, SNAPSHOT_LIST_MAX_SIZE } from '../../../../../common';
-import { WithPrivileges, PageLoading, PageError, Error } from '../../../../shared_imports';
+import { PageLoading, PageError, Error, reactRouterNavigate } from '../../../../shared_imports';
import { BASE_PATH, UIM_SNAPSHOT_LIST_LOAD } from '../../../constants';
import { useLoadSnapshots } from '../../../services/http';
-import {
- linkToRepositories,
- linkToAddRepository,
- linkToPolicies,
- linkToAddPolicy,
- linkToSnapshot,
-} from '../../../services/navigation';
-import { useCore, useServices } from '../../../app_context';
-import { useDecodedParams } from '../../../lib';
-import { SnapshotDetails } from './snapshot_details';
-import { SnapshotTable } from './snapshot_table';
+import { linkToRepositories } from '../../../services/navigation';
+import { useServices } from '../../../app_context';
+import { useDecodedParams, SnapshotListParams, DEFAULT_SNAPSHOT_LIST_PARAMS } from '../../../lib';
-import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
+import { SnapshotDetails } from './snapshot_details';
+import {
+ SnapshotTable,
+ RepositoryEmptyPrompt,
+ SnapshotEmptyPrompt,
+ RepositoryError,
+} from './components';
interface MatchParams {
repositoryName?: string;
@@ -47,22 +36,22 @@ export const SnapshotList: React.FunctionComponent {
const { repositoryName, snapshotId } = useDecodedParams();
+ const [listParams, setListParams] = useState(DEFAULT_SNAPSHOT_LIST_PARAMS);
const {
error,
+ isInitialRequest,
isLoading,
- data: { snapshots = [], repositories = [], policies = [], errors = {} },
+ data: {
+ snapshots = [],
+ repositories = [],
+ policies = [],
+ errors = {},
+ total: totalSnapshotsCount,
+ },
resendRequest: reload,
- } = useLoadSnapshots();
+ } = useLoadSnapshots(listParams);
- const { uiMetricService, i18n } = useServices();
- const { docLinks } = useCore();
-
- const openSnapshotDetailsUrl = (
- repositoryNameToOpen: string,
- snapshotIdToOpen: string
- ): string => {
- return linkToSnapshot(repositoryNameToOpen, snapshotIdToOpen);
- };
+ const { uiMetricService } = useServices();
const closeSnapshotDetails = () => {
history.push(`${BASE_PATH}/snapshots`);
@@ -86,22 +75,32 @@ export const SnapshotList: React.FunctionComponent(undefined);
- const [filteredPolicy, setFilteredPolicy] = useState(undefined);
useEffect(() => {
if (search) {
const parsedParams = parse(search.replace(/^\?/, ''), { sort: false });
const { repository, policy } = parsedParams;
- if (policy && policy !== filteredPolicy) {
- setFilteredPolicy(String(policy));
+ if (policy) {
+ setListParams((prev: SnapshotListParams) => ({
+ ...prev,
+ searchField: 'policyName',
+ searchValue: String(policy),
+ searchMatch: 'must',
+ searchOperator: 'exact',
+ }));
history.replace(`${BASE_PATH}/snapshots`);
- } else if (repository && repository !== filteredRepository) {
- setFilteredRepository(String(repository));
+ } else if (repository) {
+ setListParams((prev: SnapshotListParams) => ({
+ ...prev,
+ searchField: 'repository',
+ searchValue: String(repository),
+ searchMatch: 'must',
+ searchOperator: 'exact',
+ }));
history.replace(`${BASE_PATH}/snapshots`);
}
}
- }, [filteredPolicy, filteredRepository, history, search]);
+ }, [listParams, history, search]);
// Track component loaded
useEffect(() => {
@@ -110,7 +109,8 @@ export const SnapshotList: React.FunctionComponent
@@ -134,190 +134,11 @@ export const SnapshotList: React.FunctionComponent
);
} else if (Object.keys(errors).length && repositories.length === 0) {
- content = (
-
-
-
-
- }
- body={
-
-
-
-
- ),
- }}
- />
-
- }
- />
-
- );
+ content = ;
} else if (repositories.length === 0) {
- content = (
-
-
-
-
- }
- body={
- <>
-
-
-
-
-
-
-
-
- >
- }
- data-test-subj="emptyPrompt"
- />
-
- );
- } else if (snapshots.length === 0) {
- content = (
-
-
-
-
- }
- body={
- `cluster.${name}`)}
- >
- {({ hasPrivileges }) =>
- hasPrivileges ? (
-
-
-
-
-
- ),
- }}
- />
-
-
- {policies.length === 0 ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
- ) : (
-
-
-
-
-
-
- {' '}
-
-
-
-
- )
- }
-
- }
- data-test-subj="emptyPrompt"
- />
-
- );
+ content = ;
+ } else if (totalSnapshotsCount === 0 && !listParams.searchField && !isLoading) {
+ content = ;
} else {
const repositoryErrorsWarning = Object.keys(errors).length ? (
<>
@@ -351,53 +172,19 @@ export const SnapshotList: React.FunctionComponent
) : null;
- const maxSnapshotsWarning = snapshots.length === SNAPSHOT_LIST_MAX_SIZE && (
- <>
-
-
-
-
- ),
- }}
- />
-
-
- >
- );
-
content = (
{repositoryErrorsWarning}
- {maxSnapshotsWarning}
-
);
diff --git a/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts b/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts
index 3d64dc96958de..c02d0f053f783 100644
--- a/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/services/http/snapshot_requests.ts
@@ -5,8 +5,10 @@
* 2.0.
*/
-import { API_BASE_PATH } from '../../../../common/constants';
+import { HttpFetchQuery } from 'kibana/public';
+import { API_BASE_PATH } from '../../../../common';
import { UIM_SNAPSHOT_DELETE, UIM_SNAPSHOT_DELETE_MANY } from '../../constants';
+import { SnapshotListParams } from '../../lib';
import { UiMetricService } from '../ui_metric';
import { sendRequest, useRequest } from './use_request';
@@ -18,11 +20,12 @@ export const setUiMetricServiceSnapshot = (_uiMetricService: UiMetricService) =>
};
// End hack
-export const useLoadSnapshots = () =>
+export const useLoadSnapshots = (query: SnapshotListParams) =>
useRequest({
path: `${API_BASE_PATH}snapshots`,
method: 'get',
initialData: [],
+ query: query as unknown as HttpFetchQuery,
});
export const useLoadSnapshot = (repositoryName: string, snapshotId: string) =>
diff --git a/x-pack/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/plugins/snapshot_restore/public/shared_imports.ts
index d1b9f37703c0c..a3cda90d26f2a 100644
--- a/x-pack/plugins/snapshot_restore/public/shared_imports.ts
+++ b/x-pack/plugins/snapshot_restore/public/shared_imports.ts
@@ -26,3 +26,5 @@ export {
} from '../../../../src/plugins/es_ui_shared/public';
export { APP_WRAPPER_CLASS } from '../../../../src/core/public';
+
+export { reactRouterNavigate } from '../../../../src/plugins/kibana_react/public';
diff --git a/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.test.ts b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.test.ts
new file mode 100644
index 0000000000000..d3e5c604d22ad
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.test.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getSnapshotSearchWildcard } from './get_snapshot_search_wildcard';
+
+describe('getSnapshotSearchWildcard', () => {
+ it('exact match search converts to a wildcard without *', () => {
+ const searchParams = {
+ field: 'snapshot',
+ value: 'testSearch',
+ operator: 'exact',
+ match: 'must',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('testSearch');
+ });
+
+ it('partial match search converts to a wildcard with *', () => {
+ const searchParams = { field: 'snapshot', value: 'testSearch', operator: 'eq', match: 'must' };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*testSearch*');
+ });
+
+ it('excluding search converts to "all, except" wildcard (exact match)', () => {
+ const searchParams = {
+ field: 'snapshot',
+ value: 'testSearch',
+ operator: 'exact',
+ match: 'must_not',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*,-testSearch');
+ });
+
+ it('excluding search converts to "all, except" wildcard (partial match)', () => {
+ const searchParams = {
+ field: 'snapshot',
+ value: 'testSearch',
+ operator: 'eq',
+ match: 'must_not',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*,-*testSearch*');
+ });
+
+ it('excluding search for policy name converts to "all,_none, except" wildcard', () => {
+ const searchParams = {
+ field: 'policyName',
+ value: 'testSearch',
+ operator: 'exact',
+ match: 'must_not',
+ };
+ const wildcard = getSnapshotSearchWildcard(searchParams);
+ expect(wildcard).toEqual('*,_none,-testSearch');
+ });
+});
diff --git a/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.ts b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.ts
new file mode 100644
index 0000000000000..df8926d785712
--- /dev/null
+++ b/x-pack/plugins/snapshot_restore/server/lib/get_snapshot_search_wildcard.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+interface SearchParams {
+ field: string;
+ value: string;
+ match?: string;
+ operator?: string;
+}
+
+export const getSnapshotSearchWildcard = ({
+ field,
+ value,
+ match,
+ operator,
+}: SearchParams): string => {
+ // if the operator is NOT for exact match, convert to *value* wildcard that matches any substring
+ value = operator === 'exact' ? value : `*${value}*`;
+
+ // ES API new "-"("except") wildcard removes matching items from a list of already selected items
+ // To find all items not containing the search value, use "*,-{searchValue}"
+ // When searching for policy name, also add "_none" to find snapshots without a policy as well
+ const excludingWildcard = field === 'policyName' ? `*,_none,-${value}` : `*,-${value}`;
+
+ return match === 'must_not' ? excludingWildcard : value;
+};
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
index f71c5ec9ffc08..4ecd34a43adb9 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
@@ -51,6 +51,10 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => {
const mockRequest: RequestMock = {
method: 'get',
path: addBasePath('snapshots'),
+ query: {
+ sortField: 'startTimeInMillis',
+ sortDirection: 'desc',
+ },
};
const mockSnapshotGetManagedRepositoryEsResponse = {
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts
index 6838ae2700f3a..4de0c3011fed5 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts
@@ -7,11 +7,36 @@
import { schema, TypeOf } from '@kbn/config-schema';
import type { SnapshotDetailsEs } from '../../../common/types';
-import { SNAPSHOT_LIST_MAX_SIZE } from '../../../common/constants';
import { deserializeSnapshotDetails } from '../../../common/lib';
import type { RouteDependencies } from '../../types';
import { getManagedRepositoryName } from '../../lib';
import { addBasePath } from '../helpers';
+import { snapshotListSchema } from './validate_schemas';
+import { getSnapshotSearchWildcard } from '../../lib/get_snapshot_search_wildcard';
+
+const sortFieldToESParams = {
+ snapshot: 'name',
+ repository: 'repository',
+ indices: 'index_count',
+ startTimeInMillis: 'start_time',
+ durationInMillis: 'duration',
+ 'shards.total': 'shard_count',
+ 'shards.failed': 'failed_shard_count',
+};
+
+const isSearchingForNonExistentRepository = (
+ repositories: string[],
+ value: string,
+ match?: string,
+ operator?: string
+): boolean => {
+ // only check if searching for an exact match (repository=test)
+ if (match === 'must' && operator === 'exact') {
+ return !(repositories || []).includes(value);
+ }
+ // otherwise we will use a wildcard, so allow the request
+ return false;
+};
export function registerSnapshotsRoutes({
router,
@@ -20,9 +45,18 @@ export function registerSnapshotsRoutes({
}: RouteDependencies) {
// GET all snapshots
router.get(
- { path: addBasePath('snapshots'), validate: false },
+ { path: addBasePath('snapshots'), validate: { query: snapshotListSchema } },
license.guardApiRoute(async (ctx, req, res) => {
const { client: clusterClient } = ctx.core.elasticsearch;
+ const sortField =
+ sortFieldToESParams[(req.query as TypeOf).sortField];
+ const sortDirection = (req.query as TypeOf).sortDirection;
+ const pageIndex = (req.query as TypeOf).pageIndex;
+ const pageSize = (req.query as TypeOf).pageSize;
+ const searchField = (req.query as TypeOf).searchField;
+ const searchValue = (req.query as TypeOf).searchValue;
+ const searchMatch = (req.query as TypeOf).searchMatch;
+ const searchOperator = (req.query as TypeOf).searchOperator;
const managedRepository = await getManagedRepositoryName(clusterClient.asCurrentUser);
@@ -55,18 +89,60 @@ export function registerSnapshotsRoutes({
return handleEsError({ error: e, response: res });
}
+ // if the search is for a repository name with exact match (repository=test)
+ // and that repository doesn't exist, ES request throws an error
+ // that is why we return an empty snapshots array instead of sending an ES request
+ if (
+ searchField === 'repository' &&
+ isSearchingForNonExistentRepository(repositories, searchValue!, searchMatch, searchOperator)
+ ) {
+ return res.ok({
+ body: {
+ snapshots: [],
+ policies,
+ repositories,
+ errors: [],
+ total: 0,
+ },
+ });
+ }
try {
// If any of these repositories 504 they will cost the request significant time.
const { body: fetchedSnapshots } = await clusterClient.asCurrentUser.snapshot.get({
- repository: '_all',
- snapshot: '_all',
+ repository:
+ searchField === 'repository'
+ ? getSnapshotSearchWildcard({
+ field: searchField,
+ value: searchValue!,
+ match: searchMatch,
+ operator: searchOperator,
+ })
+ : '_all',
ignore_unavailable: true, // Allow request to succeed even if some snapshots are unavailable.
- // @ts-expect-error @elastic/elasticsearch "desc" is a new param
- order: 'desc',
- // TODO We are temporarily hard-coding the maximum number of snapshots returned
- // in order to prevent an unusable UI for users with large number of snapshots
- // In the near future, this will be resolved with server-side pagination
- size: SNAPSHOT_LIST_MAX_SIZE,
+ snapshot:
+ searchField === 'snapshot'
+ ? getSnapshotSearchWildcard({
+ field: searchField,
+ value: searchValue!,
+ match: searchMatch,
+ operator: searchOperator,
+ })
+ : '_all',
+ // @ts-expect-error @elastic/elasticsearch new API params
+ // https://github.com/elastic/elasticsearch-specification/issues/845
+ slm_policy_filter:
+ searchField === 'policyName'
+ ? getSnapshotSearchWildcard({
+ field: searchField,
+ value: searchValue!,
+ match: searchMatch,
+ operator: searchOperator,
+ })
+ : '*,_none',
+ order: sortDirection,
+ sort: sortField,
+ size: pageSize,
+ offset: pageIndex * pageSize,
});
// Decorate each snapshot with the repository with which it's associated.
@@ -79,8 +155,10 @@ export function registerSnapshotsRoutes({
snapshots: snapshots || [],
policies,
repositories,
- // @ts-expect-error @elastic/elasticsearch "failures" is a new field in the response
+ // @ts-expect-error @elastic/elasticsearch https://github.com/elastic/elasticsearch-specification/issues/845
errors: fetchedSnapshots?.failures,
+ // @ts-expect-error @elastic/elasticsearch "total" is a new field in the response
+ total: fetchedSnapshots?.total,
},
});
} catch (e) {
@@ -170,7 +248,7 @@ export function registerSnapshotsRoutes({
const snapshots = req.body;
try {
- // We intentially perform deletion requests sequentially (blocking) instead of in parallel (non-blocking)
+ // We intentionally perform deletion requests sequentially (blocking) instead of in parallel (non-blocking)
// because there can only be one snapshot deletion task performed at a time (ES restriction).
for (let i = 0; i < snapshots.length; i++) {
const { snapshot, repository } = snapshots[i];
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts b/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts
index af31466c2cefe..e93ee2b3d78ca 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/validate_schemas.ts
@@ -26,6 +26,31 @@ const snapshotRetentionSchema = schema.object({
minCount: schema.maybe(schema.oneOf([schema.number(), schema.literal('')])),
});
+export const snapshotListSchema = schema.object({
+ sortField: schema.oneOf([
+ schema.literal('snapshot'),
+ schema.literal('repository'),
+ schema.literal('indices'),
+ schema.literal('durationInMillis'),
+ schema.literal('startTimeInMillis'),
+ schema.literal('shards.total'),
+ schema.literal('shards.failed'),
+ ]),
+ sortDirection: schema.oneOf([schema.literal('desc'), schema.literal('asc')]),
+ pageIndex: schema.number(),
+ pageSize: schema.number(),
+ searchField: schema.maybe(
+ schema.oneOf([
+ schema.literal('snapshot'),
+ schema.literal('repository'),
+ schema.literal('policyName'),
+ ])
+ ),
+ searchValue: schema.maybe(schema.string()),
+ searchMatch: schema.maybe(schema.oneOf([schema.literal('must'), schema.literal('must_not')])),
+ searchOperator: schema.maybe(schema.oneOf([schema.literal('eq'), schema.literal('exact')])),
+});
+
export const policySchema = schema.object({
name: schema.string(),
snapshotName: schema.string(),
diff --git a/x-pack/plugins/snapshot_restore/server/routes/helpers.ts b/x-pack/plugins/snapshot_restore/server/routes/helpers.ts
index 1f49d2f3cabfb..e73db4d992ff2 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/helpers.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/helpers.ts
@@ -5,6 +5,6 @@
* 2.0.
*/
-import { API_BASE_PATH } from '../../common/constants';
+import { API_BASE_PATH } from '../../common';
export const addBasePath = (uri: string): string => API_BASE_PATH + uri;
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 496db1fbefb5e..852b01977b78b 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4158,7 +4158,6 @@
"inspector.requests.noRequestsLoggedTitle": "リクエストが記録されていません",
"inspector.requests.requestFailedTooltipTitle": "リクエストに失敗しました",
"inspector.requests.requestInProgressAriaLabel": "リクエストが進行中",
- "inspector.requests.requestLabel": "リクエスト:",
"inspector.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します",
"inspector.requests.requestsTitle": "リクエスト",
"inspector.requests.requestSucceededTooltipTitle": "リクエスト成功",
@@ -17929,8 +17928,7 @@
"xpack.monitoring.cluster.overview.logstashPanel.withPersistentQueuesLabel": "永続キューあり",
"xpack.monitoring.cluster.overview.pageTitle": "クラスターの概要",
"xpack.monitoring.cluster.overviewTitle": "概要",
- "xpack.monitoring.clusterAlertsNavigation.clusterAlertsLinkText": "クラスターアラート",
- "xpack.monitoring.clustersNavigation.clustersLinkText": "クラスター",
+ "xpack.monitoring.cluster.listing.tabTitle": "クラスター",
"xpack.monitoring.clusterStats.uuidNotFoundErrorMessage": "選択された時間範囲にクラスターが見つかりませんでした。UUID:{clusterUuid}",
"xpack.monitoring.clusterStats.uuidNotSpecifiedErrorMessage": "{clusterUuid} が指定されていません",
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.alertsColumnTitle": "アラート",
@@ -17942,10 +17940,10 @@
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.syncLagOpsColumnTitle": "同期の遅延(オペレーション数)",
"xpack.monitoring.elasticsearch.ccr.heading": "CCR",
"xpack.monitoring.elasticsearch.ccr.pageTitle": "Elasticsearch - CCR",
- "xpack.monitoring.elasticsearch.ccr.routeTitle": "Elasticsearch - CCR",
+ "xpack.monitoring.elasticsearch.ccr.title": "Elasticsearch - CCR",
"xpack.monitoring.elasticsearch.ccr.shard.instanceTitle": "インデックス{followerIndex} シャード:{shardId}",
"xpack.monitoring.elasticsearch.ccr.shard.pageTitle": "Elasticsearch Ccrシャード - インデックス:{followerIndex} シャード:{shardId}",
- "xpack.monitoring.elasticsearch.ccr.shard.routeTitle": "Elasticsearch - CCR - シャード",
+ "xpack.monitoring.elasticsearch.ccr.shard.title": "Elasticsearch - CCR - シャード",
"xpack.monitoring.elasticsearch.ccr.shardsTable.alertsColumnTitle": "アラート",
"xpack.monitoring.elasticsearch.ccr.shardsTable.errorColumnTitle": "エラー",
"xpack.monitoring.elasticsearch.ccr.shardsTable.lastFetchTimeColumnTitle": "最終取得時刻",
@@ -17979,7 +17977,7 @@
"xpack.monitoring.elasticsearch.indexDetailStatus.totalShardsTitle": "合計シャード数",
"xpack.monitoring.elasticsearch.indexDetailStatus.totalTitle": "合計",
"xpack.monitoring.elasticsearch.indexDetailStatus.unassignedShardsTitle": "未割り当てシャード",
- "xpack.monitoring.elasticsearch.indices.advanced.routeTitle": "Elasticsearch - インデックス - {indexName} - 高度な設定",
+ "xpack.monitoring.elasticsearch.index.advanced.title": "Elasticsearch - インデックス - {indexName} - 高度な設定",
"xpack.monitoring.elasticsearch.indices.alertsColumnTitle": "アラート",
"xpack.monitoring.elasticsearch.indices.dataTitle": "データ",
"xpack.monitoring.elasticsearch.indices.documentCountTitle": "ドキュメントカウント",
@@ -17988,8 +17986,8 @@
"xpack.monitoring.elasticsearch.indices.monitoringTablePlaceholder": "インデックスのフィルタリング…",
"xpack.monitoring.elasticsearch.indices.nameTitle": "名前",
"xpack.monitoring.elasticsearch.indices.noIndicesMatchYourSelectionDescription": "選択項目に一致するインデックスがありません。時間範囲を変更してみてください。",
- "xpack.monitoring.elasticsearch.indices.overview.pageTitle": "インデックス:{indexName}",
- "xpack.monitoring.elasticsearch.indices.overview.routeTitle": "Elasticsearch - インデックス - {indexName} - 概要",
+ "xpack.monitoring.elasticsearch.index.overview.pageTitle": "インデックス:{indexName}",
+ "xpack.monitoring.elasticsearch.index.overview.title": "Elasticsearch - インデックス - {indexName} - 概要",
"xpack.monitoring.elasticsearch.indices.pageTitle": "デフォルトのインデックス",
"xpack.monitoring.elasticsearch.indices.routeTitle": "Elasticsearch - インデックス",
"xpack.monitoring.elasticsearch.indices.searchRateTitle": "検索レート",
@@ -18008,7 +18006,7 @@
"xpack.monitoring.elasticsearch.mlJobListing.statusIconLabel": "ジョブ状態:{status}",
"xpack.monitoring.elasticsearch.mlJobs.pageTitle": "Elasticsearch - 機械学習ジョブ",
"xpack.monitoring.elasticsearch.mlJobs.routeTitle": "Elasticsearch - 機械学習ジョブ",
- "xpack.monitoring.elasticsearch.node.advanced.routeTitle": "Elasticsearch - ノード - {nodeSummaryName} - 高度な設定",
+ "xpack.monitoring.elasticsearch.node.advanced.title": "Elasticsearch - ノード - {nodeName} - 高度な設定",
"xpack.monitoring.elasticsearch.node.cells.tooltip.iconLabel": "このメトリックの詳細",
"xpack.monitoring.elasticsearch.node.cells.tooltip.max": "最高値",
"xpack.monitoring.elasticsearch.node.cells.tooltip.min": "最低値",
@@ -18017,7 +18015,7 @@
"xpack.monitoring.elasticsearch.node.cells.trendingDownText": "ダウン",
"xpack.monitoring.elasticsearch.node.cells.trendingUpText": "アップ",
"xpack.monitoring.elasticsearch.node.overview.pageTitle": "Elasticsearchノード:{node}",
- "xpack.monitoring.elasticsearch.node.overview.routeTitle": "Elasticsearch - ノード - {nodeName} - 概要",
+ "xpack.monitoring.elasticsearch.node.overview.title": "Elasticsearch - ノード - {nodeName} - 概要",
"xpack.monitoring.elasticsearch.node.statusIconLabel": "ステータス:{status}",
"xpack.monitoring.elasticsearch.nodeDetailStatus.alerts": "アラート",
"xpack.monitoring.elasticsearch.nodeDetailStatus.dataLabel": "データ",
@@ -18106,8 +18104,7 @@
"xpack.monitoring.es.nodeType.nodeLabel": "ノード",
"xpack.monitoring.esNavigation.ccrLinkText": "CCR",
"xpack.monitoring.esNavigation.indicesLinkText": "インデックス",
- "xpack.monitoring.esNavigation.instance.advancedLinkText": "高度な設定",
- "xpack.monitoring.esNavigation.instance.overviewLinkText": "概要",
+ "xpack.monitoring.esItemNavigation.advancedLinkText": "高度な設定",
"xpack.monitoring.esNavigation.jobsLinkText": "機械学習ジョブ",
"xpack.monitoring.esNavigation.nodesLinkText": "ノード",
"xpack.monitoring.esNavigation.overviewLinkText": "概要",
@@ -18226,7 +18223,6 @@
"xpack.monitoring.logstash.node.advanced.pageTitle": "Logstashノード:{nodeName}",
"xpack.monitoring.logstash.node.advanced.routeTitle": "Logstash - {nodeName} - 高度な設定",
"xpack.monitoring.logstash.node.pageTitle": "Logstashノード:{nodeName}",
- "xpack.monitoring.logstash.node.pipelines.notAvailableDescription": "パイプラインの監視は Logstash バージョン 6.0.0 以降でのみ利用できます。このノードはバージョン {logstashVersion} を実行しています。",
"xpack.monitoring.logstash.node.pipelines.pageTitle": "Logstashノードパイプライン:{nodeName}",
"xpack.monitoring.logstash.node.pipelines.routeTitle": "Logstash - {nodeName} - パイプライン",
"xpack.monitoring.logstash.node.routeTitle": "Logstash - {nodeName}",
@@ -19444,13 +19440,10 @@
"xpack.reporting.registerFeature.reportingDescription": "Discover、可視化、ダッシュボードから生成されたレポートを管理します。",
"xpack.reporting.registerFeature.reportingTitle": "レポート",
"xpack.reporting.screencapture.browserWasClosed": "ブラウザーは予期せず終了しました。詳細については、サーバーログを確認してください。",
- "xpack.reporting.screencapture.couldntFinishRendering": "{count} 件のビジュアライゼーションのレンダリングが完了するのを待つ間にエラーが発生しました。「{configKey}」を増やす必要があるかもしれません。 {error}",
- "xpack.reporting.screencapture.couldntLoadKibana": "Kibana URL を開こうとするときにエラーが発生しました。「{configKey}」を増やす必要があるかもしれません。 {error}",
"xpack.reporting.screencapture.injectCss": "Kibana CSS をレポート用に更新しようとしたときにエラーが発生しました。{error}",
"xpack.reporting.screencapture.injectingCss": "カスタム css の投入中",
"xpack.reporting.screencapture.logWaitingForElements": "要素または項目のカウント属性を待ち、または見つからないため中断",
"xpack.reporting.screencapture.noElements": "ビジュアライゼーションパネルのページを読み取る間にエラーが発生しました:パネルが見つかりませんでした。",
- "xpack.reporting.screencapture.readVisualizationsError": "ビジュアライゼーションパネル情報のページを読み取ろうとしたときにエラーが発生しました。「{configKey}」を増やす必要があるかもしれません。 {error}",
"xpack.reporting.screencapture.renderIsComplete": "レンダリングが完了しました",
"xpack.reporting.screencapture.screenshotsTaken": "撮影したスクリーンショット:{numScreenhots}",
"xpack.reporting.screencapture.takingScreenshots": "スクリーンショットの撮影中",
@@ -23896,9 +23889,6 @@
"xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle": "スナップショット",
"xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle": "日付が作成されました",
"xpack.snapshotRestore.snapshots.breadcrumbTitle": "スナップショット",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDescription": "表示可能なスナップショットの最大数に達しました。スナップショットをすべて表示するには、{docLink}を使用してください。",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDocLinkText": "Elasticsearch API",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedTitle": "スナップショットの一覧を表示できません。",
"xpack.snapshotRestore.snapshotState.completeLabel": "スナップショット完了",
"xpack.snapshotRestore.snapshotState.failedLabel": "スナップショット失敗",
"xpack.snapshotRestore.snapshotState.incompatibleLabel": "互換性のないバージョン",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index f82653aa8c3ba..9d88c757f1e58 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4197,7 +4197,6 @@
"inspector.requests.noRequestsLoggedTitle": "未记录任何请求",
"inspector.requests.requestFailedTooltipTitle": "请求失败",
"inspector.requests.requestInProgressAriaLabel": "进行中的请求",
- "inspector.requests.requestLabel": "请求:",
"inspector.requests.requestsDescriptionTooltip": "查看已收集数据的请求",
"inspector.requests.requestsTitle": "请求",
"inspector.requests.requestSucceededTooltipTitle": "请求成功",
@@ -18204,8 +18203,7 @@
"xpack.monitoring.cluster.overview.logstashPanel.withPersistentQueuesLabel": "持久性队列",
"xpack.monitoring.cluster.overview.pageTitle": "集群概览",
"xpack.monitoring.cluster.overviewTitle": "概览",
- "xpack.monitoring.clusterAlertsNavigation.clusterAlertsLinkText": "集群告警",
- "xpack.monitoring.clustersNavigation.clustersLinkText": "集群",
+ "xpack.monitoring.cluster.listing.tabTitle": "集群",
"xpack.monitoring.clusterStats.uuidNotFoundErrorMessage": "在选定时间范围内找不到该集群。UUID:{clusterUuid}",
"xpack.monitoring.clusterStats.uuidNotSpecifiedErrorMessage": "{clusterUuid} 未指定",
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.alertsColumnTitle": "告警",
@@ -18217,10 +18215,10 @@
"xpack.monitoring.elasticsearch.ccr.ccrListingTable.syncLagOpsColumnTitle": "同步延迟(操作)",
"xpack.monitoring.elasticsearch.ccr.heading": "CCR",
"xpack.monitoring.elasticsearch.ccr.pageTitle": "Elasticsearch Ccr",
- "xpack.monitoring.elasticsearch.ccr.routeTitle": "Elasticsearch - CCR",
+ "xpack.monitoring.elasticsearch.ccr.title": "Elasticsearch - CCR",
"xpack.monitoring.elasticsearch.ccr.shard.instanceTitle": "索引:{followerIndex} 分片:{shardId}",
"xpack.monitoring.elasticsearch.ccr.shard.pageTitle": "Elasticsearch Ccr 分片 - 索引:{followerIndex} 分片:{shardId}",
- "xpack.monitoring.elasticsearch.ccr.shard.routeTitle": "Elasticsearch - CCR - 分片",
+ "xpack.monitoring.elasticsearch.ccr.shard.title": "Elasticsearch - CCR - 分片",
"xpack.monitoring.elasticsearch.ccr.shardsTable.alertsColumnTitle": "告警",
"xpack.monitoring.elasticsearch.ccr.shardsTable.errorColumnTitle": "错误",
"xpack.monitoring.elasticsearch.ccr.shardsTable.lastFetchTimeColumnTitle": "上次提取时间",
@@ -18254,7 +18252,7 @@
"xpack.monitoring.elasticsearch.indexDetailStatus.totalShardsTitle": "分片合计",
"xpack.monitoring.elasticsearch.indexDetailStatus.totalTitle": "合计",
"xpack.monitoring.elasticsearch.indexDetailStatus.unassignedShardsTitle": "未分配分片",
- "xpack.monitoring.elasticsearch.indices.advanced.routeTitle": "Elasticsearch - 索引 - {indexName} - 高级",
+ "xpack.monitoring.elasticsearch.index.advanced.title": "Elasticsearch - 索引 - {indexName} - 高级",
"xpack.monitoring.elasticsearch.indices.alertsColumnTitle": "告警",
"xpack.monitoring.elasticsearch.indices.dataTitle": "数据",
"xpack.monitoring.elasticsearch.indices.documentCountTitle": "文档计数",
@@ -18263,8 +18261,8 @@
"xpack.monitoring.elasticsearch.indices.monitoringTablePlaceholder": "筛选索引……",
"xpack.monitoring.elasticsearch.indices.nameTitle": "名称",
"xpack.monitoring.elasticsearch.indices.noIndicesMatchYourSelectionDescription": "没有索引匹配您的选择。请尝试更改时间范围选择。",
- "xpack.monitoring.elasticsearch.indices.overview.pageTitle": "索引:{indexName}",
- "xpack.monitoring.elasticsearch.indices.overview.routeTitle": "Elasticsearch - 索引 - {indexName} - 概览",
+ "xpack.monitoring.elasticsearch.index.overview.pageTitle": "索引:{indexName}",
+ "xpack.monitoring.elasticsearch.index.overview.title": "Elasticsearch - 索引 - {indexName} - 概览",
"xpack.monitoring.elasticsearch.indices.pageTitle": "Elasticsearch 索引",
"xpack.monitoring.elasticsearch.indices.routeTitle": "Elasticsearch - 索引",
"xpack.monitoring.elasticsearch.indices.searchRateTitle": "搜索速率",
@@ -18283,7 +18281,7 @@
"xpack.monitoring.elasticsearch.mlJobListing.statusIconLabel": "作业状态:{status}",
"xpack.monitoring.elasticsearch.mlJobs.pageTitle": "Elasticsearch Machine Learning 作业",
"xpack.monitoring.elasticsearch.mlJobs.routeTitle": "Elasticsearch - Machine Learning 作业",
- "xpack.monitoring.elasticsearch.node.advanced.routeTitle": "Elasticsearch - 节点 - {nodeSummaryName} - 高级",
+ "xpack.monitoring.elasticsearch.node.advanced.title": "Elasticsearch - 节点 - {nodeName} - 高级",
"xpack.monitoring.elasticsearch.node.cells.tooltip.iconLabel": "有关此指标的更多信息",
"xpack.monitoring.elasticsearch.node.cells.tooltip.max": "最大值",
"xpack.monitoring.elasticsearch.node.cells.tooltip.min": "最小值",
@@ -18292,7 +18290,7 @@
"xpack.monitoring.elasticsearch.node.cells.trendingDownText": "向下",
"xpack.monitoring.elasticsearch.node.cells.trendingUpText": "向上",
"xpack.monitoring.elasticsearch.node.overview.pageTitle": "Elasticsearch 节点:{node}",
- "xpack.monitoring.elasticsearch.node.overview.routeTitle": "Elasticsearch - 节点 - {nodeName} - 概览",
+ "xpack.monitoring.elasticsearch.node.overview.title": "Elasticsearch - 节点 - {nodeName} - 概览",
"xpack.monitoring.elasticsearch.node.statusIconLabel": "状态:{status}",
"xpack.monitoring.elasticsearch.nodeDetailStatus.alerts": "告警",
"xpack.monitoring.elasticsearch.nodeDetailStatus.dataLabel": "数据",
@@ -18381,8 +18379,7 @@
"xpack.monitoring.es.nodeType.nodeLabel": "节点",
"xpack.monitoring.esNavigation.ccrLinkText": "CCR",
"xpack.monitoring.esNavigation.indicesLinkText": "索引",
- "xpack.monitoring.esNavigation.instance.advancedLinkText": "高级",
- "xpack.monitoring.esNavigation.instance.overviewLinkText": "概览",
+ "xpack.monitoring.esItemNavigation.advancedLinkText": "高级",
"xpack.monitoring.esNavigation.jobsLinkText": "Machine Learning 作业",
"xpack.monitoring.esNavigation.nodesLinkText": "节点",
"xpack.monitoring.esNavigation.overviewLinkText": "概览",
@@ -18501,7 +18498,6 @@
"xpack.monitoring.logstash.node.advanced.pageTitle": "Logstash 节点:{nodeName}",
"xpack.monitoring.logstash.node.advanced.routeTitle": "Logstash - {nodeName} - 高级",
"xpack.monitoring.logstash.node.pageTitle": "Logstash 节点:{nodeName}",
- "xpack.monitoring.logstash.node.pipelines.notAvailableDescription": "仅 Logstash 版本 6.0.0 或更高版本提供管道监测功能。此节点正在运行版本 {logstashVersion}。",
"xpack.monitoring.logstash.node.pipelines.pageTitle": "Logstash 节点管道:{nodeName}",
"xpack.monitoring.logstash.node.pipelines.routeTitle": "Logstash - {nodeName} - 管道",
"xpack.monitoring.logstash.node.routeTitle": "Logstash - {nodeName}",
@@ -19726,13 +19722,10 @@
"xpack.reporting.registerFeature.reportingDescription": "管理您从 Discover、Visualize 和 Dashboard 生成的报告。",
"xpack.reporting.registerFeature.reportingTitle": "Reporting",
"xpack.reporting.screencapture.browserWasClosed": "浏览器已意外关闭!有关更多信息,请查看服务器日志。",
- "xpack.reporting.screencapture.couldntFinishRendering": "尝试等候 {count} 个可视化完成渲染时发生错误。您可能需要增加“{configKey}”。{error}",
- "xpack.reporting.screencapture.couldntLoadKibana": "尝试打开 Kibana URL 时发生了错误。您可能需要增加“{configKey}”。{error}",
"xpack.reporting.screencapture.injectCss": "尝试为 Reporting 更新 Kibana CSS 时发生错误。{error}",
"xpack.reporting.screencapture.injectingCss": "正在注入定制 css",
"xpack.reporting.screencapture.logWaitingForElements": "等候元素或项目计数属性;或未发现要中断",
"xpack.reporting.screencapture.noElements": "读取页面以获取可视化面板时发生了错误:未找到任何面板。",
- "xpack.reporting.screencapture.readVisualizationsError": "尝试页面以获取可视化面板信息时发生了错误。您可能需要增加“{configKey}”。{error}",
"xpack.reporting.screencapture.renderIsComplete": "渲染已完成",
"xpack.reporting.screencapture.screenshotsTaken": "已捕获的屏幕截图:{numScreenhots}",
"xpack.reporting.screencapture.takingScreenshots": "正在捕获屏幕截图",
@@ -24298,9 +24291,6 @@
"xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle": "快照",
"xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle": "创建日期",
"xpack.snapshotRestore.snapshots.breadcrumbTitle": "快照",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDescription": "已达到最大可查看快照数目。要查看您的所有快照,请使用{docLink}。",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDocLinkText": "Elasticsearch API",
- "xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedTitle": "无法显示快照的完整列表",
"xpack.snapshotRestore.snapshotState.completeLabel": "快照完成",
"xpack.snapshotRestore.snapshotState.failedLabel": "快照失败",
"xpack.snapshotRestore.snapshotState.incompatibleLabel": "不兼容版本",
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts
index 4d39ff1494f89..db5dbc9735e66 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/index.ts
@@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Snapshot and Restore', () => {
- loadTestFile(require.resolve('./snapshot_restore'));
+ loadTestFile(require.resolve('./policies'));
+ loadTestFile(require.resolve('./snapshots'));
});
}
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts
index 9b4d39a3b10b3..a59c90fe29132 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/elasticsearch.ts
@@ -7,9 +7,10 @@
import { FtrProviderContext } from '../../../../ftr_provider_context';
-interface SlmPolicy {
+export interface SlmPolicy {
+ policyName: string;
+ // snapshot name
name: string;
- snapshotName: string;
schedule: string;
repository: string;
isManagedPolicy: boolean;
@@ -29,23 +30,22 @@ interface SlmPolicy {
}
/**
- * Helpers to create and delete SLM policies on the Elasticsearch instance
+ * Helpers to create and delete SLM policies, repositories and snapshots on the Elasticsearch instance
* during our tests.
- * @param {ElasticsearchClient} es The Elasticsearch client instance
*/
export const registerEsHelpers = (getService: FtrProviderContext['getService']) => {
let policiesCreated: string[] = [];
const es = getService('es');
- const createRepository = (repoName: string) => {
+ const createRepository = (repoName: string, repoPath?: string) => {
return es.snapshot
.createRepository({
repository: repoName,
body: {
type: 'fs',
settings: {
- location: '/tmp/',
+ location: repoPath ?? '/tmp/repo',
},
},
verify: false,
@@ -55,12 +55,12 @@ export const registerEsHelpers = (getService: FtrProviderContext['getService'])
const createPolicy = (policy: SlmPolicy, cachePolicy?: boolean) => {
if (cachePolicy) {
- policiesCreated.push(policy.name);
+ policiesCreated.push(policy.policyName);
}
return es.slm
.putLifecycle({
- policy_id: policy.name,
+ policy_id: policy.policyName,
// TODO: bring {@link SlmPolicy} in line with {@link PutSnapshotLifecycleRequest['body']}
// @ts-expect-error
body: policy,
@@ -90,11 +90,34 @@ export const registerEsHelpers = (getService: FtrProviderContext['getService'])
console.log(`[Cleanup error] Error deleting ES resources: ${err.message}`);
});
+ const executePolicy = (policyName: string) => {
+ return es.slm.executeLifecycle({ policy_id: policyName }).then(({ body }) => body);
+ };
+
+ const createSnapshot = (snapshotName: string, repositoryName: string) => {
+ return es.snapshot
+ .create({ snapshot: snapshotName, repository: repositoryName })
+ .then(({ body }) => body);
+ };
+
+ const deleteSnapshots = (repositoryName: string) => {
+ es.snapshot
+ .delete({ repository: repositoryName, snapshot: '*' })
+ .then(() => {})
+ .catch((err) => {
+ // eslint-disable-next-line no-console
+ console.log(`[Cleanup error] Error deleting snapshots: ${err.message}`);
+ });
+ };
+
return {
createRepository,
createPolicy,
deletePolicy,
cleanupPolicies,
getPolicy,
+ executePolicy,
+ createSnapshot,
+ deleteSnapshots,
};
};
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts
index 27a4d9c59cff0..a9721c5856598 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/lib/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export { registerEsHelpers } from './elasticsearch';
+export { registerEsHelpers, SlmPolicy } from './elasticsearch';
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/policies.ts
similarity index 95%
rename from x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts
rename to x-pack/test/api_integration/apis/management/snapshot_restore/policies.ts
index a6ac2d057c84e..e0734680887d2 100644
--- a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshot_restore.ts
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/policies.ts
@@ -19,7 +19,7 @@ export default function ({ getService }: FtrProviderContext) {
const { createRepository, createPolicy, deletePolicy, cleanupPolicies, getPolicy } =
registerEsHelpers(getService);
- describe('Snapshot Lifecycle Management', function () {
+ describe('SLM policies', function () {
this.tags(['skipCloud']); // file system repositories are not supported in cloud
before(async () => {
@@ -134,9 +134,8 @@ export default function ({ getService }: FtrProviderContext) {
describe('Update', () => {
const POLICY_NAME = 'test_update_policy';
+ const SNAPSHOT_NAME = 'my_snapshot';
const POLICY = {
- name: POLICY_NAME,
- snapshotName: 'my_snapshot',
schedule: '0 30 1 * * ?',
repository: REPO_NAME,
config: {
@@ -159,7 +158,7 @@ export default function ({ getService }: FtrProviderContext) {
before(async () => {
// Create SLM policy that can be used to test PUT request
try {
- await createPolicy(POLICY, true);
+ await createPolicy({ ...POLICY, policyName: POLICY_NAME, name: SNAPSHOT_NAME }, true);
} catch (err) {
// eslint-disable-next-line no-console
console.log('[Setup error] Error creating policy');
@@ -175,6 +174,8 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'xxx')
.send({
...POLICY,
+ name: POLICY_NAME,
+ snapshotName: SNAPSHOT_NAME,
schedule: '0 0 0 ? * 7',
})
.expect(200);
@@ -212,7 +213,7 @@ export default function ({ getService }: FtrProviderContext) {
const { body } = await supertest
.put(uri)
.set('kbn-xsrf', 'xxx')
- .send(requiredFields)
+ .send({ ...requiredFields, name: POLICY_NAME, snapshotName: SNAPSHOT_NAME })
.expect(200);
expect(body).to.eql({
diff --git a/x-pack/test/api_integration/apis/management/snapshot_restore/snapshots.ts b/x-pack/test/api_integration/apis/management/snapshot_restore/snapshots.ts
new file mode 100644
index 0000000000000..1677013dd5e7e
--- /dev/null
+++ b/x-pack/test/api_integration/apis/management/snapshot_restore/snapshots.ts
@@ -0,0 +1,729 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+import { registerEsHelpers, SlmPolicy } from './lib';
+import { SnapshotDetails } from '../../../../../plugins/snapshot_restore/common/types';
+
+const REPO_NAME_1 = 'test_repo_1';
+const REPO_NAME_2 = 'test_another_repo_2';
+const REPO_PATH_1 = '/tmp/repo_1';
+const REPO_PATH_2 = '/tmp/repo_2';
+// SLM policies to test policyName filter
+const POLICY_NAME_1 = 'test_policy_1';
+const POLICY_NAME_2 = 'test_another_policy_2';
+const POLICY_SNAPSHOT_NAME_1 = 'backup_snapshot';
+const POLICY_SNAPSHOT_NAME_2 = 'a_snapshot';
+// snapshots created without SLM policies
+const BATCH_SIZE_1 = 3;
+const BATCH_SIZE_2 = 5;
+const BATCH_SNAPSHOT_NAME_1 = 'another_snapshot';
+const BATCH_SNAPSHOT_NAME_2 = 'xyz_another_snapshot';
+// total count consists of both batches' sizes + 2 snapshots created by 2 SLM policies (one each)
+const SNAPSHOT_COUNT = BATCH_SIZE_1 + BATCH_SIZE_2 + 2;
+// API defaults used in the UI
+const PAGE_INDEX = 0;
+const PAGE_SIZE = 20;
+const SORT_FIELD = 'startTimeInMillis';
+const SORT_DIRECTION = 'desc';
+
+interface ApiParams {
+ pageIndex?: number;
+ pageSize?: number;
+
+ sortField?: string;
+ sortDirection?: string;
+
+ searchField?: string;
+ searchValue?: string;
+ searchMatch?: string;
+ searchOperator?: string;
+}
+const getApiPath = ({
+ pageIndex,
+ pageSize,
+ sortField,
+ sortDirection,
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+}: ApiParams): string => {
+ let path = `/api/snapshot_restore/snapshots?sortField=${sortField ?? SORT_FIELD}&sortDirection=${
+ sortDirection ?? SORT_DIRECTION
+ }&pageIndex=${pageIndex ?? PAGE_INDEX}&pageSize=${pageSize ?? PAGE_SIZE}`;
+ // all 4 parameters should be used at the same time to configure the correct search request
+ if (searchField && searchValue && searchMatch && searchOperator) {
+ path = `${path}&searchField=${searchField}&searchValue=${searchValue}&searchMatch=${searchMatch}&searchOperator=${searchOperator}`;
+ }
+ return path;
+};
+const getPolicyBody = (policy: Partial): SlmPolicy => {
+ return {
+ policyName: 'default_policy',
+ name: 'default_snapshot',
+ schedule: '0 30 1 * * ?',
+ repository: 'default_repo',
+ isManagedPolicy: false,
+ config: {
+ indices: ['default_index'],
+ ignoreUnavailable: true,
+ },
+ ...policy,
+ };
+};
+
+export default function ({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+
+ const {
+ createSnapshot,
+ createRepository,
+ createPolicy,
+ executePolicy,
+ cleanupPolicies,
+ deleteSnapshots,
+ } = registerEsHelpers(getService);
+
+ describe('Snapshots', function () {
+ this.tags(['skipCloud']); // file system repositories are not supported in cloud
+
+ // names of snapshots created by SLM policies have random suffixes, save full names for tests
+ let snapshotName1: string;
+ let snapshotName2: string;
+
+ before(async () => {
+ /*
+ * This setup creates following repos, SLM policies and snapshots:
+ * Repo 1 "test_repo_1" with 5 snapshots
+ * "backup_snapshot..." (created by SLM policy "test_policy_1")
+ * "a_snapshot..." (created by SLM policy "test_another_policy_2")
+ * "another_snapshot_0" to "another_snapshot_2" (no SLM policy)
+ *
+ * Repo 2 "test_another_repo_2" with 5 snapshots
+ * "xyz_another_snapshot_0" to "xyz_another_snapshot_4" (no SLM policy)
+ */
+ try {
+ await createRepository(REPO_NAME_1, REPO_PATH_1);
+ await createRepository(REPO_NAME_2, REPO_PATH_2);
+ await createPolicy(
+ getPolicyBody({
+ policyName: POLICY_NAME_1,
+ repository: REPO_NAME_1,
+ name: POLICY_SNAPSHOT_NAME_1,
+ }),
+ true
+ );
+ await createPolicy(
+ getPolicyBody({
+ policyName: POLICY_NAME_2,
+ repository: REPO_NAME_1,
+ name: POLICY_SNAPSHOT_NAME_2,
+ }),
+ true
+ );
+ ({ snapshot_name: snapshotName1 } = await executePolicy(POLICY_NAME_1));
+ // a short timeout to let the 1st snapshot start, otherwise the sorting by start time might be flaky
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ ({ snapshot_name: snapshotName2 } = await executePolicy(POLICY_NAME_2));
+ for (let i = 0; i < BATCH_SIZE_1; i++) {
+ await createSnapshot(`${BATCH_SNAPSHOT_NAME_1}_${i}`, REPO_NAME_1);
+ }
+ for (let i = 0; i < BATCH_SIZE_2; i++) {
+ await createSnapshot(`${BATCH_SNAPSHOT_NAME_2}_${i}`, REPO_NAME_2);
+ }
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.log('[Setup error] Error creating snapshots');
+ throw err;
+ }
+ });
+
+ after(async () => {
+ await cleanupPolicies();
+ await deleteSnapshots(REPO_NAME_1);
+ await deleteSnapshots(REPO_NAME_2);
+ });
+
+ describe('pagination', () => {
+ it('returns pageSize number of snapshots', async () => {
+ const pageSize = 7;
+ const {
+ body: { total, snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ pageSize,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ expect(total).to.eql(SNAPSHOT_COUNT);
+ expect(snapshots.length).to.eql(pageSize);
+ });
+
+ it('returns next page of snapshots', async () => {
+ const pageSize = 3;
+ let pageIndex = 0;
+ const {
+ body: { snapshots: firstPageSnapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ pageIndex,
+ pageSize,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ const firstPageSnapshotName = firstPageSnapshots[0].snapshot;
+ expect(firstPageSnapshots.length).to.eql(pageSize);
+
+ pageIndex = 1;
+ const {
+ body: { snapshots: secondPageSnapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ pageIndex,
+ pageSize,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ const secondPageSnapshotName = secondPageSnapshots[0].snapshot;
+ expect(secondPageSnapshots.length).to.eql(pageSize);
+ expect(secondPageSnapshotName).to.not.eql(firstPageSnapshotName);
+ });
+ });
+
+ describe('sorting', () => {
+ it('sorts by snapshot name (asc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'snapshot',
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ /*
+ * snapshots name in asc order:
+ * "a_snapshot...", "another_snapshot...", "backup_snapshot...", "xyz_another_snapshot..."
+ */
+ const snapshotName = snapshots[0].snapshot;
+ // snapshotName2 is "a_snapshot..."
+ expect(snapshotName).to.eql(snapshotName2);
+ });
+
+ it('sorts by snapshot name (desc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'snapshot',
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ /*
+ * snapshots name in desc order:
+ * "xyz_another_snapshot...", "backup_snapshot...", "another_snapshot...", "a_snapshot..."
+ */
+ const snapshotName = snapshots[0].snapshot;
+ expect(snapshotName).to.eql('xyz_another_snapshot_4');
+ });
+
+ it('sorts by repository name (asc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'repository',
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ // repositories in asc order: "test_another_repo_2", "test_repo_1"
+ const repositoryName = snapshots[0].repository;
+ expect(repositoryName).to.eql(REPO_NAME_2); // "test_another_repo_2"
+ });
+
+ it('sorts by repository name (desc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'repository',
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ // repositories in desc order: "test_repo_1", "test_another_repo_2"
+ const repositoryName = snapshots[0].repository;
+ expect(repositoryName).to.eql(REPO_NAME_1); // "test_repo_1"
+ });
+
+ it('sorts by startTimeInMillis (asc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'startTimeInMillis',
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ const snapshotName = snapshots[0].snapshot;
+ // the 1st snapshot that was created during this test setup
+ expect(snapshotName).to.eql(snapshotName1);
+ });
+
+ it('sorts by startTimeInMillis (desc)', async () => {
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ sortField: 'startTimeInMillis',
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+ const snapshotName = snapshots[0].snapshot;
+ // the last snapshot that was created during this test setup
+ expect(snapshotName).to.eql('xyz_another_snapshot_4');
+ });
+
+ // these properties are only tested as being accepted by the API
+ const sortFields = ['indices', 'durationInMillis', 'shards.total', 'shards.failed'];
+ sortFields.forEach((sortField: string) => {
+ it(`allows sorting by ${sortField} (asc)`, async () => {
+ await supertest
+ .get(
+ getApiPath({
+ sortField,
+ sortDirection: 'asc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send()
+ .expect(200);
+ });
+
+ it(`allows sorting by ${sortField} (desc)`, async () => {
+ await supertest
+ .get(
+ getApiPath({
+ sortField,
+ sortDirection: 'desc',
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send()
+ .expect(200);
+ });
+ });
+ });
+
+ describe('search', () => {
+ describe('snapshot name', () => {
+ it('exact match', async () => {
+ // list snapshots with the name "another_snapshot_2"
+ const searchField = 'snapshot';
+ const searchValue = 'another_snapshot_2';
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(1);
+ expect(snapshots[0].snapshot).to.eql('another_snapshot_2');
+ });
+
+ it('partial match', async () => {
+ // list snapshots with the name containing with "another"
+ const searchField = 'snapshot';
+ const searchValue = 'another';
+ const searchMatch = 'must';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // both batches created snapshots containing "another" in the name
+ expect(snapshots.length).to.eql(BATCH_SIZE_1 + BATCH_SIZE_2);
+ const snapshotNamesContainSearch = snapshots.every((snapshot: SnapshotDetails) =>
+ snapshot.snapshot.includes('another')
+ );
+ expect(snapshotNamesContainSearch).to.eql(true);
+ });
+
+ it('excluding search with exact match', async () => {
+ // list snapshots with the name not "another_snapshot_2"
+ const searchField = 'snapshot';
+ const searchValue = 'another_snapshot_2';
+ const searchMatch = 'must_not';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - 1);
+ const snapshotIsExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => snapshot.snapshot !== 'another_snapshot_2'
+ );
+ expect(snapshotIsExcluded).to.eql(true);
+ });
+
+ it('excluding search with partial match', async () => {
+ // list snapshots with the name not starting with "another"
+ const searchField = 'snapshot';
+ const searchValue = 'another';
+ const searchMatch = 'must_not';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // both batches created snapshots with names containing "another"
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - BATCH_SIZE_1 - BATCH_SIZE_2);
+ const snapshotsAreExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => !snapshot.snapshot.includes('another')
+ );
+ expect(snapshotsAreExcluded).to.eql(true);
+ });
+ });
+
+ describe('repository name', () => {
+ it('search for non-existent repository returns an empty snapshot array', async () => {
+ // search for non-existent repository
+ const searchField = 'repository';
+ const searchValue = 'non-existent';
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(0);
+ });
+
+ it('exact match', async () => {
+ // list snapshots from repository "test_repo_1"
+ const searchField = 'repository';
+ const searchValue = REPO_NAME_1;
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // repo 1 contains snapshots from batch 1 and 2 snapshots created by 2 SLM policies
+ expect(snapshots.length).to.eql(BATCH_SIZE_1 + 2);
+ const repositoryNameMatches = snapshots.every(
+ (snapshot: SnapshotDetails) => snapshot.repository === REPO_NAME_1
+ );
+ expect(repositoryNameMatches).to.eql(true);
+ });
+
+ it('partial match', async () => {
+ // list snapshots from repository with the name containing "another"
+ // i.e. snapshots from repo 2
+ const searchField = 'repository';
+ const searchValue = 'another';
+ const searchMatch = 'must';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // repo 2 only contains snapshots created by batch 2
+ expect(snapshots.length).to.eql(BATCH_SIZE_2);
+ const repositoryNameMatches = snapshots.every((snapshot: SnapshotDetails) =>
+ snapshot.repository.includes('another')
+ );
+ expect(repositoryNameMatches).to.eql(true);
+ });
+
+ it('excluding search with exact match', async () => {
+ // list snapshots from repositories with the name not "test_repo_1"
+ const searchField = 'repository';
+ const searchValue = REPO_NAME_1;
+ const searchMatch = 'must_not';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // snapshots not in repo 1 are only snapshots created in batch 2
+ expect(snapshots.length).to.eql(BATCH_SIZE_2);
+ const repositoryNameMatches = snapshots.every(
+ (snapshot: SnapshotDetails) => snapshot.repository !== REPO_NAME_1
+ );
+ expect(repositoryNameMatches).to.eql(true);
+ });
+
+ it('excluding search with partial match', async () => {
+ // list snapshots from repository with the name not containing "test"
+ const searchField = 'repository';
+ const searchValue = 'test';
+ const searchMatch = 'must_not';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(0);
+ });
+ });
+
+ describe('policy name', () => {
+ it('search for non-existent policy returns an empty snapshot array', async () => {
+ // search for non-existent policy
+ const searchField = 'policyName';
+ const searchValue = 'non-existent';
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(0);
+ });
+
+ it('exact match', async () => {
+ // list snapshots created by the policy "test_policy_1"
+ const searchField = 'policyName';
+ const searchValue = POLICY_NAME_1;
+ const searchMatch = 'must';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ expect(snapshots.length).to.eql(1);
+ expect(snapshots[0].policyName).to.eql(POLICY_NAME_1);
+ });
+
+ it('partial match', async () => {
+ // list snapshots created by the policy with the name containing "another"
+ const searchField = 'policyName';
+ const searchValue = 'another';
+ const searchMatch = 'must';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // 1 snapshot was created by the policy "test_another_policy_2"
+ expect(snapshots.length).to.eql(1);
+ const policyNameMatches = snapshots.every((snapshot: SnapshotDetails) =>
+ snapshot.policyName!.includes('another')
+ );
+ expect(policyNameMatches).to.eql(true);
+ });
+
+ it('excluding search with exact match', async () => {
+ // list snapshots created by the policy with the name not "test_policy_1"
+ const searchField = 'policyName';
+ const searchValue = POLICY_NAME_1;
+ const searchMatch = 'must_not';
+ const searchOperator = 'exact';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // only 1 snapshot was created by policy 1
+ // search results should also contain snapshots without SLM policy
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - 1);
+ const snapshotsExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => (snapshot.policyName ?? '') !== POLICY_NAME_1
+ );
+ expect(snapshotsExcluded).to.eql(true);
+ });
+
+ it('excluding search with partial match', async () => {
+ // list snapshots created by the policy with the name not containing "another"
+ const searchField = 'policyName';
+ const searchValue = 'another';
+ const searchMatch = 'must_not';
+ const searchOperator = 'eq';
+ const {
+ body: { snapshots },
+ } = await supertest
+ .get(
+ getApiPath({
+ searchField,
+ searchValue,
+ searchMatch,
+ searchOperator,
+ })
+ )
+ .set('kbn-xsrf', 'xxx')
+ .send();
+
+ // only 1 snapshot was created by SLM policy containing "another" in the name
+ // search results should also contain snapshots without SLM policy
+ expect(snapshots.length).to.eql(SNAPSHOT_COUNT - 1);
+ const snapshotsExcluded = snapshots.every(
+ (snapshot: SnapshotDetails) => !(snapshot.policyName ?? '').includes('another')
+ );
+ expect(snapshotsExcluded).to.eql(true);
+ });
+ });
+ });
+ });
+}
diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts
index 3690f661c621c..678f7a0d3d929 100644
--- a/x-pack/test/api_integration/config.ts
+++ b/x-pack/test/api_integration/config.ts
@@ -41,6 +41,7 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi
serverArgs: [
...xPackFunctionalTestsConfig.get('esTestCluster.serverArgs'),
'node.attr.name=apiIntegrationTestNode',
+ 'path.repo=/tmp/repo,/tmp/repo_1,/tmp/repo_2',
],
},
};
diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts
new file mode 100644
index 0000000000000..75ea10ed4d9d4
--- /dev/null
+++ b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts
@@ -0,0 +1,213 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { service, timerange } from '@elastic/apm-generator';
+import expect from '@kbn/expect';
+import { mean, meanBy, sumBy } from 'lodash';
+import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types';
+import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number';
+import { PromiseReturnType } from '../../../../plugins/observability/typings/common';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+import { registry } from '../../common/registry';
+
+export default function ApiTest({ getService }: FtrProviderContext) {
+ const apmApiClient = getService('apmApiClient');
+ const traceData = getService('traceData');
+
+ const serviceName = 'synth-go';
+ const start = new Date('2021-01-01T00:00:00.000Z').getTime();
+ const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
+
+ async function getErrorRateValues({
+ processorEvent,
+ }: {
+ processorEvent: 'transaction' | 'metric';
+ }) {
+ const commonQuery = {
+ start: new Date(start).toISOString(),
+ end: new Date(end).toISOString(),
+ environment: 'ENVIRONMENT_ALL',
+ };
+ const [
+ serviceInventoryAPIResponse,
+ transactionsErrorRateChartAPIResponse,
+ transactionsGroupDetailsAPIResponse,
+ serviceInstancesAPIResponse,
+ ] = await Promise.all([
+ apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/services',
+ params: {
+ query: {
+ ...commonQuery,
+ kuery: `service.name : "${serviceName}" and processor.event : "${processorEvent}"`,
+ },
+ },
+ }),
+ apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate',
+ params: {
+ path: { serviceName },
+ query: {
+ ...commonQuery,
+ kuery: `processor.event : "${processorEvent}"`,
+ transactionType: 'request',
+ },
+ },
+ }),
+ apmApiClient.readUser({
+ endpoint: `GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics`,
+ params: {
+ path: { serviceName },
+ query: {
+ ...commonQuery,
+ kuery: `processor.event : "${processorEvent}"`,
+ transactionType: 'request',
+ latencyAggregationType: 'avg' as LatencyAggregationType,
+ },
+ },
+ }),
+ apmApiClient.readUser({
+ endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`,
+ params: {
+ path: { serviceName },
+ query: {
+ ...commonQuery,
+ kuery: `processor.event : "${processorEvent}"`,
+ transactionType: 'request',
+ latencyAggregationType: 'avg' as LatencyAggregationType,
+ },
+ },
+ }),
+ ]);
+
+ const serviceInventoryErrorRate =
+ serviceInventoryAPIResponse.body.items[0].transactionErrorRate;
+
+ const errorRateChartApiMean = meanBy(
+ transactionsErrorRateChartAPIResponse.body.currentPeriod.transactionErrorRate.filter(
+ (item) => isFiniteNumber(item.y) && item.y > 0
+ ),
+ 'y'
+ );
+
+ const transactionsGroupErrorRateSum = sumBy(
+ transactionsGroupDetailsAPIResponse.body.transactionGroups,
+ 'errorRate'
+ );
+
+ const serviceInstancesErrorRateSum = sumBy(
+ serviceInstancesAPIResponse.body.currentPeriod,
+ 'errorRate'
+ );
+
+ return {
+ serviceInventoryErrorRate,
+ errorRateChartApiMean,
+ transactionsGroupErrorRateSum,
+ serviceInstancesErrorRateSum,
+ };
+ }
+
+ let errorRateMetricValues: PromiseReturnType;
+ let errorTransactionValues: PromiseReturnType;
+
+ registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => {
+ describe('when data is loaded ', () => {
+ const GO_PROD_LIST_RATE = 75;
+ const GO_PROD_LIST_ERROR_RATE = 25;
+ const GO_PROD_ID_RATE = 50;
+ const GO_PROD_ID_ERROR_RATE = 50;
+ before(async () => {
+ const serviceGoProdInstance = service(serviceName, 'production', 'go').instance(
+ 'instance-a'
+ );
+
+ const transactionNameProductList = 'GET /api/product/list';
+ const transactionNameProductId = 'GET /api/product/:id';
+
+ await traceData.index([
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_LIST_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductList)
+ .timestamp(timestamp)
+ .duration(1000)
+ .success()
+ .serialize()
+ ),
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_LIST_ERROR_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductList)
+ .duration(1000)
+ .timestamp(timestamp)
+ .failure()
+ .serialize()
+ ),
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_ID_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductId)
+ .timestamp(timestamp)
+ .duration(1000)
+ .success()
+ .serialize()
+ ),
+ ...timerange(start, end)
+ .interval('1m')
+ .rate(GO_PROD_ID_ERROR_RATE)
+ .flatMap((timestamp) =>
+ serviceGoProdInstance
+ .transaction(transactionNameProductId)
+ .duration(1000)
+ .timestamp(timestamp)
+ .failure()
+ .serialize()
+ ),
+ ]);
+ });
+
+ after(() => traceData.clean());
+
+ describe('compare error rate value between service inventory, error rate chart, service inventory and transactions apis', () => {
+ before(async () => {
+ [errorTransactionValues, errorRateMetricValues] = await Promise.all([
+ getErrorRateValues({ processorEvent: 'transaction' }),
+ getErrorRateValues({ processorEvent: 'metric' }),
+ ]);
+ });
+
+ it('returns same avg error rate value for Transaction-based and Metric-based data', () => {
+ [
+ errorTransactionValues.serviceInventoryErrorRate,
+ errorTransactionValues.errorRateChartApiMean,
+ errorTransactionValues.serviceInstancesErrorRateSum,
+ errorRateMetricValues.serviceInventoryErrorRate,
+ errorRateMetricValues.errorRateChartApiMean,
+ errorRateMetricValues.serviceInstancesErrorRateSum,
+ ].forEach((value) =>
+ expect(value).to.be.equal(mean([GO_PROD_LIST_ERROR_RATE, GO_PROD_ID_ERROR_RATE]) / 100)
+ );
+ });
+
+ it('returns same sum error rate value for Transaction-based and Metric-based data', () => {
+ [
+ errorTransactionValues.transactionsGroupErrorRateSum,
+ errorRateMetricValues.transactionsGroupErrorRateSum,
+ ].forEach((value) =>
+ expect(value).to.be.equal((GO_PROD_LIST_ERROR_RATE + GO_PROD_ID_ERROR_RATE) / 100)
+ );
+ });
+ });
+ });
+ });
+}
diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts
index c15a7d39a6cf6..09f4e2596ea46 100644
--- a/x-pack/test/apm_api_integration/tests/index.ts
+++ b/x-pack/test/apm_api_integration/tests/index.ts
@@ -229,6 +229,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte
loadTestFile(require.resolve('./historical_data/has_data'));
});
+ describe('error_rate/service_apis', function () {
+ loadTestFile(require.resolve('./error_rate/service_apis'));
+ });
+
describe('latency/service_apis', function () {
loadTestFile(require.resolve('./latency/service_apis'));
});
diff --git a/x-pack/test/functional/apps/maps/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/embeddable/dashboard.js
index 6c962c98c6a98..a892a0d547339 100644
--- a/x-pack/test/functional/apps/maps/embeddable/dashboard.js
+++ b/x-pack/test/functional/apps/maps/embeddable/dashboard.js
@@ -77,9 +77,12 @@ export default function ({ getPageObjects, getService }) {
await inspector.close();
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
- const gridExampleRequestNames = await inspector.getRequestNames();
+ const singleExampleRequest = await inspector.hasSingleRequest();
+ const selectedExampleRequest = await inspector.getSelectedOption();
await inspector.close();
- expect(gridExampleRequestNames).to.equal('logstash-*');
+
+ expect(singleExampleRequest).to.be(true);
+ expect(selectedExampleRequest).to.equal('logstash-*');
});
it('should apply container state (time, query, filters) to embeddable when loaded', async () => {
diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json
index ed52be26c7e53..3b2a87d924e88 100644
--- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json
+++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json
@@ -126,7 +126,7 @@
"name": "CTS Vis 2"
}, {
"type": "visualization",
- "id": "cts_vis_3",
+ "id": "cts_vis_3_default",
"name": "CTS Vis 3"
}],
"type": "dashboard",
@@ -158,7 +158,8 @@
}
],
"type": "visualization",
- "updated_at": "2017-09-21T18:49:16.270Z"
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "namespaces": ["default"]
},
"type": "_doc"
}
@@ -186,7 +187,8 @@
}
],
"type": "visualization",
- "updated_at": "2017-09-21T18:49:16.270Z"
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "namespaces": ["default"]
},
"type": "_doc"
}
@@ -195,9 +197,10 @@
{
"type": "_doc",
"value": {
- "id": "visualization:cts_vis_3",
+ "id": "visualization:cts_vis_3_default",
"index": ".kibana",
"source": {
+ "originId": "cts_vis_3",
"visualization": {
"title": "CTS vis 3 from default space",
"description": "AreaChart",
@@ -214,7 +217,8 @@
}
],
"type": "visualization",
- "updated_at": "2017-09-21T18:49:16.270Z"
+ "updated_at": "2017-09-21T18:49:16.270Z",
+ "namespaces": ["default"]
},
"type": "_doc"
}
@@ -243,7 +247,7 @@
},
{
"type": "visualization",
- "id": "cts_vis_3",
+ "id": "cts_vis_3_space_1",
"name": "CTS Vis 3"
}
],
@@ -258,7 +262,7 @@
{
"type": "_doc",
"value": {
- "id": "space_1:visualization:cts_vis_1_space_1",
+ "id": "visualization:cts_vis_1_space_1",
"index": ".kibana",
"source": {
"visualization": {
@@ -278,7 +282,7 @@
],
"type": "visualization",
"updated_at": "2017-09-21T18:49:16.270Z",
- "namespace": "space_1"
+ "namespaces": ["space_1"]
},
"type": "_doc"
}
@@ -287,7 +291,7 @@
{
"type": "_doc",
"value": {
- "id": "space_1:visualization:cts_vis_2_space_1",
+ "id": "visualization:cts_vis_2_space_1",
"index": ".kibana",
"source": {
"visualization": {
@@ -307,7 +311,7 @@
],
"type": "visualization",
"updated_at": "2017-09-21T18:49:16.270Z",
- "namespace": "space_1"
+ "namespaces": ["space_1"]
},
"type": "_doc"
}
@@ -316,9 +320,10 @@
{
"type": "_doc",
"value": {
- "id": "space_1:visualization:cts_vis_3",
+ "id": "visualization:cts_vis_3_space_1",
"index": ".kibana",
"source": {
+ "originId": "cts_vis_3",
"visualization": {
"title": "CTS vis 3 from space_1 space",
"description": "AreaChart",
@@ -336,7 +341,7 @@
],
"type": "visualization",
"updated_at": "2017-09-21T18:49:16.270Z",
- "namespace": "space_1"
+ "namespaces": ["space_1"]
},
"type": "_doc"
}
diff --git a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts
index 12333fc746070..28b19d5db20b6 100644
--- a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts
+++ b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants';
export function getUrlPrefix(spaceId?: string) {
@@ -35,3 +36,31 @@ export function getTestScenariosForSpace(spaceId: string) {
return [explicitScenario];
}
+
+export function getAggregatedSpaceData(es: KibanaClient, objectTypes: string[]) {
+ return es.search({
+ index: '.kibana',
+ body: {
+ size: 0,
+ runtime_mappings: {
+ normalized_namespace: {
+ type: 'keyword',
+ script: `
+ if (doc["namespaces"].size() > 0) {
+ emit(doc["namespaces"].value);
+ } else if (doc["namespace"].size() > 0) {
+ emit(doc["namespace"].value);
+ }
+ `,
+ },
+ },
+ query: { terms: { type: objectTypes } },
+ aggs: {
+ count: {
+ terms: { field: 'normalized_namespace', missing: DEFAULT_SPACE_ID, size: 10 },
+ aggs: { countByType: { terms: { field: 'type', missing: 'UNKNOWN', size: 10 } } },
+ },
+ },
+ },
+ });
+}
diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
index 27f1e55c3a90a..3a3f0f889c91c 100644
--- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts
@@ -11,7 +11,7 @@ import { EsArchiver } from '@kbn/es-archiver';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants';
import { CopyResponse } from '../../../../plugins/spaces/server/lib/copy_to_spaces';
-import { getUrlPrefix } from '../lib/space_test_utils';
+import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
type TestResponse = Record;
@@ -68,6 +68,9 @@ const INITIAL_COUNTS: Record> = {
space_1: { dashboard: 2, visualization: 3, 'index-pattern': 1 },
space_2: { dashboard: 1 },
};
+const UUID_PATTERN = new RegExp(
+ /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
+);
const getDestinationWithoutConflicts = () => 'space_2';
const getDestinationWithConflicts = (originSpaceId?: string) =>
@@ -79,19 +82,11 @@ export function copyToSpaceTestSuiteFactory(
supertest: SuperTest
) {
const collectSpaceContents = async () => {
- const { body: response } = await es.search({
- index: '.kibana',
- body: {
- size: 0,
- query: { terms: { type: ['visualization', 'dashboard', 'index-pattern'] } },
- aggs: {
- count: {
- terms: { field: 'namespace', missing: DEFAULT_SPACE_ID, size: 10 },
- aggs: { countByType: { terms: { field: 'type', missing: 'UNKNOWN', size: 10 } } },
- },
- },
- },
- });
+ const { body: response } = await getAggregatedSpaceData(es, [
+ 'visualization',
+ 'dashboard',
+ 'index-pattern',
+ ]);
const aggs = response.aggregations as Record<
string,
@@ -187,6 +182,14 @@ export function copyToSpaceTestSuiteFactory(
async (resp: TestResponse) => {
const destination = getDestinationWithoutConflicts();
const result = resp.body as CopyResponse;
+
+ const vis1DestinationId = result[destination].successResults![1].destinationId;
+ expect(vis1DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
+ const vis2DestinationId = result[destination].successResults![2].destinationId;
+ expect(vis2DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
+ const vis3DestinationId = result[destination].successResults![3].destinationId;
+ expect(vis3DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
+
expect(result).to.eql({
[destination]: {
success: true,
@@ -204,16 +207,19 @@ export function copyToSpaceTestSuiteFactory(
id: `cts_vis_1_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 1 from ${spaceId} space` },
+ destinationId: vis1DestinationId,
},
{
id: `cts_vis_2_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 2 from ${spaceId} space` },
+ destinationId: vis2DestinationId,
},
{
- id: 'cts_vis_3',
+ id: `cts_vis_3_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 3 from ${spaceId} space` },
+ destinationId: vis3DestinationId,
},
{
id: 'cts_dashboard',
@@ -303,6 +309,12 @@ export function copyToSpaceTestSuiteFactory(
(spaceId?: string) => async (resp: { [key: string]: any }) => {
const destination = getDestinationWithConflicts(spaceId);
const result = resp.body as CopyResponse;
+
+ const vis1DestinationId = result[destination].successResults![1].destinationId;
+ expect(vis1DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
+ const vis2DestinationId = result[destination].successResults![2].destinationId;
+ expect(vis2DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
+
expect(result).to.eql({
[destination]: {
success: true,
@@ -321,17 +333,20 @@ export function copyToSpaceTestSuiteFactory(
id: `cts_vis_1_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 1 from ${spaceId} space` },
+ destinationId: vis1DestinationId,
},
{
id: `cts_vis_2_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 2 from ${spaceId} space` },
+ destinationId: vis2DestinationId,
},
{
- id: 'cts_vis_3',
+ id: `cts_vis_3_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 3 from ${spaceId} space` },
overwrite: true,
+ destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId
},
{
id: 'cts_dashboard',
@@ -363,16 +378,23 @@ export function copyToSpaceTestSuiteFactory(
const result = resp.body as CopyResponse;
result[destination].errors!.sort(errorSorter);
+ const vis1DestinationId = result[destination].successResults![0].destinationId;
+ expect(vis1DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
+ const vis2DestinationId = result[destination].successResults![1].destinationId;
+ expect(vis2DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
+
const expectedSuccessResults = [
{
id: `cts_vis_1_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 1 from ${spaceId} space` },
+ destinationId: vis1DestinationId,
},
{
id: `cts_vis_2_${spaceId}`,
type: 'visualization',
meta: { icon: 'visualizeApp', title: `CTS vis 2 from ${spaceId} space` },
+ destinationId: vis2DestinationId,
},
];
const expectedErrors = [
@@ -397,8 +419,11 @@ export function copyToSpaceTestSuiteFactory(
},
},
{
- error: { type: 'conflict' },
- id: 'cts_vis_3',
+ error: {
+ type: 'conflict',
+ destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId
+ },
+ id: `cts_vis_3_${spaceId}`,
title: `CTS vis 3 from ${spaceId} space`,
type: 'visualization',
meta: {
@@ -437,9 +462,6 @@ export function copyToSpaceTestSuiteFactory(
// a 403 error actually comes back as an HTTP 200 response
const statusCode = outcome === 'noAccess' ? 403 : 200;
const type = 'sharedtype';
- const v4 = new RegExp(
- /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
- );
const noConflictId = `${spaceId}_only`;
const exactMatchId = 'each_space';
const inexactMatchId = `conflict_1_${spaceId}`;
@@ -463,7 +485,7 @@ export function copyToSpaceTestSuiteFactory(
expect(success).to.eql(true);
expect(successCount).to.eql(1);
const destinationId = successResults![0].destinationId;
- expect(destinationId).to.match(v4);
+ expect(destinationId).to.match(UUID_PATTERN);
const meta = { title, icon: 'beaker' };
expect(successResults).to.eql([{ type, id: sourceId, meta, destinationId }]);
expect(errors).to.be(undefined);
diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts
index 57fa6d8533890..aaca4fa843d67 100644
--- a/x-pack/test/spaces_api_integration/common/suites/delete.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts
@@ -8,7 +8,7 @@
import expect from '@kbn/expect';
import { SuperTest } from 'supertest';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
-import { getTestScenariosForSpace } from '../lib/space_test_utils';
+import { getAggregatedSpaceData, getTestScenariosForSpace } from '../lib/space_test_utils';
import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases';
import { DescribeFn, TestDefinitionAuthentication } from '../lib/types';
@@ -43,38 +43,15 @@ export function deleteTestSuiteFactory(
// Query ES to ensure that we deleted everything we expected, and nothing we didn't
// Grouping first by namespace, then by saved object type
- const { body: response } = await es.search({
- index: '.kibana',
- body: {
- size: 0,
- query: {
- terms: {
- type: ['visualization', 'dashboard', 'space', 'index-pattern'],
- // TODO: add assertions for config objects -- these assertions were removed because of flaky behavior in #92358, but we should
- // consider adding them again at some point, especially if we convert config objects to `namespaceType: 'multiple-isolated'` in
- // the future.
- },
- },
- aggs: {
- count: {
- terms: {
- field: 'namespace',
- missing: 'default',
- size: 10,
- },
- aggs: {
- countByType: {
- terms: {
- field: 'type',
- missing: 'UNKNOWN',
- size: 10,
- },
- },
- },
- },
- },
- },
- });
+ const { body: response } = await getAggregatedSpaceData(es, [
+ 'visualization',
+ 'dashboard',
+ 'space',
+ 'index-pattern',
+ // TODO: add assertions for config objects -- these assertions were removed because of flaky behavior in #92358, but we should
+ // consider adding them again at some point, especially if we convert config objects to `namespaceType: 'multiple-isolated'` in
+ // the future.
+ ]);
// @ts-expect-error @elastic/elasticsearch doesn't defined `count.buckets`.
const buckets = response.aggregations?.count.buckets;
diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
index b66949cbffe00..b190a37965b0b 100644
--- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
+++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts
@@ -58,7 +58,7 @@ export function resolveCopyToSpaceConflictsSuite(
) {
const getVisualizationAtSpace = async (spaceId: string): Promise> => {
return supertestWithAuth
- .get(`${getUrlPrefix(spaceId)}/api/saved_objects/visualization/cts_vis_3`)
+ .get(`${getUrlPrefix(spaceId)}/api/saved_objects/visualization/cts_vis_3_${spaceId}`)
.then((response: any) => response.body);
};
const getDashboardAtSpace = async (spaceId: string): Promise> => {
@@ -85,12 +85,13 @@ export function resolveCopyToSpaceConflictsSuite(
successCount: 1,
successResults: [
{
- id: 'cts_vis_3',
+ id: `cts_vis_3_${sourceSpaceId}`,
type: 'visualization',
meta: {
title: `CTS vis 3 from ${sourceSpaceId} space`,
icon: 'visualizeApp',
},
+ destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId
overwrite: true,
},
],
@@ -146,8 +147,11 @@ export function resolveCopyToSpaceConflictsSuite(
successCount: 0,
errors: [
{
- error: { type: 'conflict' },
- id: 'cts_vis_3',
+ error: {
+ type: 'conflict',
+ destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId
+ },
+ id: `cts_vis_3_${sourceSpaceId}`,
title: `CTS vis 3 from ${sourceSpaceId} space`,
meta: {
title: `CTS vis 3 from ${sourceSpaceId} space`,
@@ -444,7 +448,7 @@ export function resolveCopyToSpaceConflictsSuite(
);
const dashboardObject = { type: 'dashboard', id: 'cts_dashboard' };
- const visualizationObject = { type: 'visualization', id: 'cts_vis_3' };
+ const visualizationObject = { type: 'visualization', id: `cts_vis_3_${spaceId}` };
it(`should return ${tests.withReferencesNotOverwriting.statusCode} when not overwriting, with references`, async () => {
const destination = getDestinationSpace(spaceId);
@@ -456,7 +460,15 @@ export function resolveCopyToSpaceConflictsSuite(
objects: [dashboardObject],
includeReferences: true,
createNewCopies: false,
- retries: { [destination]: [{ ...visualizationObject, overwrite: false }] },
+ retries: {
+ [destination]: [
+ {
+ ...visualizationObject,
+ destinationId: `cts_vis_3_${destination}`,
+ overwrite: false,
+ },
+ ],
+ },
})
.expect(tests.withReferencesNotOverwriting.statusCode)
.then(tests.withReferencesNotOverwriting.response);
@@ -472,7 +484,15 @@ export function resolveCopyToSpaceConflictsSuite(
objects: [dashboardObject],
includeReferences: true,
createNewCopies: false,
- retries: { [destination]: [{ ...visualizationObject, overwrite: true }] },
+ retries: {
+ [destination]: [
+ {
+ ...visualizationObject,
+ destinationId: `cts_vis_3_${destination}`,
+ overwrite: true,
+ },
+ ],
+ },
})
.expect(tests.withReferencesOverwriting.statusCode)
.then(tests.withReferencesOverwriting.response);