)}
-
+
{isCollapsible && (
diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts
index b2d90f1444654..27d09a53ba20d 100644
--- a/src/legacy/core_plugins/kibana/public/home/index.ts
+++ b/src/legacy/core_plugins/kibana/public/home/index.ts
@@ -22,11 +22,8 @@ import { npSetup, npStart } from 'ui/new_platform';
import chrome from 'ui/chrome';
import { IPrivate } from 'ui/private';
import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin';
-import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public';
import { TelemetryOptInProvider } from '../../../telemetry/public/services';
-export const trackUiMetric = createUiStatsReporter('Kibana_home');
-
/**
* Get dependencies relying on the global angular context.
* They also have to get resolved together with the legacy imports above
@@ -54,9 +51,7 @@ let copiedLegacyCatalogue = false;
instance.setup(npSetup.core, {
...npSetup.plugins,
__LEGACY: {
- trackUiMetric,
metadata: npStart.core.injectedMetadata.getLegacyMetadata(),
- METRIC_TYPE,
getFeatureCatalogueEntries: async () => {
if (!copiedLegacyCatalogue) {
const injector = await chrome.dangerouslyGetActiveInjector();
diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts
index 0eb55a3902eda..4d9177735556d 100644
--- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts
@@ -55,7 +55,6 @@ export interface HomeKibanaServices {
savedObjectsClient: SavedObjectsClientContract;
toastNotifications: NotificationsSetup['toasts'];
banners: OverlayStart['banners'];
- METRIC_TYPE: any;
trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void;
getBasePath: () => string;
shouldShowTelemetryOptIn: boolean;
diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx
index 28bdab14193c4..55c469fa58fc6 100644
--- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx
+++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx
@@ -25,10 +25,6 @@ jest.mock('../../kibana_services', () => ({
getServices: () => ({
addBasePath: (path: string) => `root${path}`,
trackUiMetric: () => {},
- METRIC_TYPE: {
- LOADED: 'loaded',
- CLICK: 'click',
- },
}),
}));
diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx
index 9bbb7aaceb915..1b7761d068d2f 100644
--- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx
+++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx
@@ -35,6 +35,7 @@ import {
EuiIcon,
EuiPortal,
} from '@elastic/eui';
+import { METRIC_TYPE } from '@kbn/analytics';
import { FormattedMessage } from '@kbn/i18n/react';
import { getServices } from '../../kibana_services';
@@ -64,17 +65,17 @@ export class Welcome extends React.Component {
}
private onSampleDataDecline = () => {
- this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataDecline');
+ this.services.trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline');
this.props.onSkip();
};
private onSampleDataConfirm = () => {
- this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataConfirm');
+ this.services.trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm');
this.redirecToSampleData();
};
componentDidMount() {
- this.services.trackUiMetric(this.services.METRIC_TYPE.LOADED, 'welcomeScreenMount');
+ this.services.trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount');
this.props.onOptInSeen();
document.addEventListener('keydown', this.hideOnEsc);
}
diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts
index 42ab049eb5b3a..502c8f45646cf 100644
--- a/src/legacy/core_plugins/kibana/public/home/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts
@@ -18,11 +18,11 @@
*/
import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public';
-import { UiStatsMetricType } from '@kbn/analytics';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { setServices } from './kibana_services';
import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public';
+import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public';
import {
Environment,
FeatureCatalogueEntry,
@@ -41,8 +41,6 @@ export interface HomePluginStartDependencies {
export interface HomePluginSetupDependencies {
__LEGACY: {
- trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void;
- METRIC_TYPE: any;
metadata: {
app: unknown;
bundleId: string;
@@ -59,6 +57,7 @@ export interface HomePluginSetupDependencies {
getFeatureCatalogueEntries: () => Promise;
getAngularDependencies: () => Promise;
};
+ usageCollection: UsageCollectionSetup;
kibana_legacy: KibanaLegacySetup;
}
@@ -71,6 +70,7 @@ export class HomePlugin implements Plugin {
core: CoreSetup,
{
kibana_legacy,
+ usageCollection,
__LEGACY: { getAngularDependencies, ...legacyServices },
}: HomePluginSetupDependencies
) {
@@ -78,9 +78,11 @@ export class HomePlugin implements Plugin {
id: 'home',
title: 'Home',
mount: async ({ core: contextCore }, params) => {
+ const trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, 'Kibana_home');
const angularDependencies = await getAngularDependencies();
setServices({
...legacyServices,
+ trackUiMetric,
http: contextCore.http,
toastNotifications: core.notifications.toasts,
banners: contextCore.overlays.banners,
diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js
index 4100ae7205869..bd947b9cb9d7f 100644
--- a/src/legacy/core_plugins/kibana/public/kibana.js
+++ b/src/legacy/core_plugins/kibana/public/kibana.js
@@ -47,9 +47,9 @@ import 'uiExports/interpreter';
import 'ui/autoload/all';
import 'ui/kbn_top_nav';
import './home';
-import './discover';
-import './visualize';
-import './dashboard';
+import './discover/legacy';
+import './visualize/legacy';
+import './dashboard/legacy';
import './management';
import './dev_tools';
import 'ui/color_maps';
diff --git a/src/legacy/core_plugins/kibana/public/management/landing.html b/src/legacy/core_plugins/kibana/public/management/landing.html
index a69033e4131c9..39459b26f7415 100644
--- a/src/legacy/core_plugins/kibana/public/management/landing.html
+++ b/src/legacy/core_plugins/kibana/public/management/landing.html
@@ -1,3 +1,3 @@
-
+
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html
index 4b3014fd28a51..625227be3c2d2 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html
+++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.html
@@ -83,8 +83,8 @@
{{ editSection.title }}
@@ -120,7 +120,7 @@
{
- const node = document.getElementById(REACT_SOURCE_FILTERS_DOM_ELEMENT_ID);
- if (!node) {
- return;
- }
-
- render(
-
- {
- $scope.editSections = $scope.editSectionsProvider(
- $scope.indexPattern,
- $scope.fieldFilter,
- $scope.indexPatternListProvider
- );
- $scope.refreshFilters();
- $scope.$apply();
- }}
- />
- ,
- node
- );
- });
- } else {
- destroySourceFiltersTable();
- }
+const TAB_INDEXED_FIELDS = 'indexedFields';
+const TAB_SCRIPTED_FIELDS = 'scriptedFields';
+const TAB_SOURCE_FILTERS = 'sourceFilters';
+
+function updateSourceFiltersTable($scope) {
+ $scope.$$postDigest(() => {
+ const node = document.getElementById(REACT_SOURCE_FILTERS_DOM_ELEMENT_ID);
+ if (!node) {
+ return;
+ }
+
+ render(
+
+ {
+ $scope.editSections = $scope.editSectionsProvider(
+ $scope.indexPattern,
+ $scope.fieldFilter,
+ $scope.indexPatternListProvider
+ );
+ $scope.refreshFilters();
+ $scope.$apply();
+ }}
+ />
+ ,
+ node
+ );
+ });
}
function destroySourceFiltersTable() {
@@ -82,44 +84,40 @@ function destroySourceFiltersTable() {
node && unmountComponentAtNode(node);
}
-function updateScriptedFieldsTable($scope, $state) {
- if ($state.tab === 'scriptedFields') {
- $scope.$$postDigest(() => {
- const node = document.getElementById(REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID);
- if (!node) {
- return;
- }
-
- render(
-
- {
- $scope.kbnUrl.redirectToRoute(obj, route);
- $scope.$apply();
- },
- getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route),
- }}
- onRemoveField={() => {
- $scope.editSections = $scope.editSectionsProvider(
- $scope.indexPattern,
- $scope.fieldFilter,
- $scope.indexPatternListProvider
- );
- $scope.refreshFilters();
+function updateScriptedFieldsTable($scope) {
+ $scope.$$postDigest(() => {
+ const node = document.getElementById(REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID);
+ if (!node) {
+ return;
+ }
+
+ render(
+
+ {
+ $scope.kbnUrl.changeToRoute(obj, route);
$scope.$apply();
- }}
- />
- ,
- node
- );
- });
- } else {
- destroyScriptedFieldsTable();
- }
+ },
+ getRouteHref: (obj, route) => $scope.kbnUrl.getRouteHref(obj, route),
+ }}
+ onRemoveField={() => {
+ $scope.editSections = $scope.editSectionsProvider(
+ $scope.indexPattern,
+ $scope.fieldFilter,
+ $scope.indexPatternListProvider
+ );
+ $scope.refreshFilters();
+ $scope.$apply();
+ }}
+ />
+ ,
+ node
+ );
+ });
}
function destroyScriptedFieldsTable() {
@@ -127,37 +125,33 @@ function destroyScriptedFieldsTable() {
node && unmountComponentAtNode(node);
}
-function updateIndexedFieldsTable($scope, $state) {
- if ($state.tab === 'indexedFields') {
- $scope.$$postDigest(() => {
- const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID);
- if (!node) {
- return;
- }
-
- render(
-
- {
- $scope.kbnUrl.redirectToRoute(obj, route);
- $scope.$apply();
- },
- getFieldInfo: $scope.getFieldInfo,
- }}
- />
- ,
- node
- );
- });
- } else {
- destroyIndexedFieldsTable();
- }
+function updateIndexedFieldsTable($scope) {
+ $scope.$$postDigest(() => {
+ const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID);
+ if (!node) {
+ return;
+ }
+
+ render(
+
+ {
+ $scope.kbnUrl.changeToRoute(obj, route);
+ $scope.$apply();
+ },
+ getFieldInfo: $scope.getFieldInfo,
+ }}
+ />
+ ,
+ node
+ );
+ });
}
function destroyIndexedFieldsTable() {
@@ -165,6 +159,24 @@ function destroyIndexedFieldsTable() {
node && unmountComponentAtNode(node);
}
+function handleTabChange($scope, newTab) {
+ destroyIndexedFieldsTable();
+ destroySourceFiltersTable();
+ destroyScriptedFieldsTable();
+ updateTables($scope, newTab);
+}
+
+function updateTables($scope, currentTab) {
+ switch (currentTab) {
+ case TAB_SCRIPTED_FIELDS:
+ return updateScriptedFieldsTable($scope);
+ case TAB_INDEXED_FIELDS:
+ return updateIndexedFieldsTable($scope);
+ case TAB_SOURCE_FILTERS:
+ return updateSourceFiltersTable($scope);
+ }
+}
+
uiRoutes.when('/management/kibana/index_patterns/:indexPatternId', {
template,
k7Breadcrumbs: getEditBreadcrumbs,
@@ -187,10 +199,36 @@ uiModules
Promise,
config,
Private,
- AppState,
confirmModal
) {
- const $state = ($scope.state = new AppState());
+ const {
+ startSyncingState,
+ stopSyncingState,
+ setCurrentTab,
+ getCurrentTab,
+ state$,
+ } = createEditIndexPatternPageStateContainer({
+ useHashedUrl: config.get('state:storeInSessionStorage'),
+ defaultTab: TAB_INDEXED_FIELDS,
+ });
+
+ $scope.getCurrentTab = getCurrentTab;
+ $scope.setCurrentTab = setCurrentTab;
+
+ const stateChangedSub = subscribeWithScope($scope, state$, {
+ next: ({ tab }) => {
+ handleTabChange($scope, tab);
+ },
+ });
+
+ handleTabChange($scope, getCurrentTab()); // setup initial tab depending on initial tab state
+
+ startSyncingState(); // starts syncing state between state container and url
+
+ const destroyState = () => {
+ stateChangedSub.unsubscribe();
+ stopSyncingState();
+ };
$scope.fieldWildcardMatcher = (...args) =>
fieldWildcardMatcher(...args, config.get('metaFields'));
@@ -219,8 +257,6 @@ uiModules
);
$scope.refreshFilters();
$scope.fields = $scope.indexPattern.getNonScriptedFields();
- updateIndexedFieldsTable($scope, $state);
- updateScriptedFieldsTable($scope, $state);
});
$scope.refreshFilters = function() {
@@ -242,18 +278,6 @@ uiModules
$scope[filter] = val || ''; // null causes filter to check for null explicitly
};
- $scope.changeTab = function(obj) {
- $state.tab = obj.index;
- updateIndexedFieldsTable($scope, $state);
- updateScriptedFieldsTable($scope, $state);
- updateSourceFiltersTable($scope, $state);
- $state.save();
- };
-
- $scope.$watch('state.tab', function(tab) {
- if (!tab) $scope.changeTab($scope.editSections[0]);
- });
-
$scope.$watchCollection('indexPattern.fields', function() {
$scope.conflictFields = $scope.indexPattern.fields.filter(field => field.type === 'conflict');
});
@@ -329,37 +353,33 @@ uiModules
$scope.fieldFilter,
managementSetup.indexPattern.list
);
+
if ($scope.fieldFilter === undefined) {
return;
}
- switch ($state.tab) {
- case 'indexedFields':
- updateIndexedFieldsTable($scope, $state);
- case 'scriptedFields':
- updateScriptedFieldsTable($scope, $state);
- case 'sourceFilters':
- updateSourceFiltersTable($scope, $state);
- }
+ updateTables($scope, getCurrentTab());
});
$scope.$watch('indexedFieldTypeFilter', () => {
- if ($scope.indexedFieldTypeFilter !== undefined && $state.tab === 'indexedFields') {
- updateIndexedFieldsTable($scope, $state);
+ if ($scope.indexedFieldTypeFilter !== undefined && getCurrentTab() === TAB_INDEXED_FIELDS) {
+ updateIndexedFieldsTable($scope);
}
});
$scope.$watch('scriptedFieldLanguageFilter', () => {
- if ($scope.scriptedFieldLanguageFilter !== undefined && $state.tab === 'scriptedFields') {
- updateScriptedFieldsTable($scope, $state);
+ if (
+ $scope.scriptedFieldLanguageFilter !== undefined &&
+ getCurrentTab() === TAB_SCRIPTED_FIELDS
+ ) {
+ updateScriptedFieldsTable($scope);
}
});
$scope.$on('$destroy', () => {
destroyIndexedFieldsTable();
destroyScriptedFieldsTable();
+ destroySourceFiltersTable();
+ destroyState();
});
-
- updateScriptedFieldsTable($scope, $state);
- updateSourceFiltersTable($scope, $state);
});
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts
new file mode 100644
index 0000000000000..473417a7aabd6
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern_state_container.ts
@@ -0,0 +1,89 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createHashHistory } from 'history';
+import {
+ createStateContainer,
+ syncState,
+ createKbnUrlStateStorage,
+} from '../../../../../../../../plugins/kibana_utils/public';
+
+interface IEditIndexPatternState {
+ tab: string; // TODO: type those 3 tabs with enum, when edit_index_pattern.js migrated to ts
+}
+
+/**
+ * Create state container with sync config for tab navigation specific for edit_index_pattern page
+ */
+export function createEditIndexPatternPageStateContainer({
+ defaultTab,
+ useHashedUrl,
+}: {
+ defaultTab: string;
+ useHashedUrl: boolean;
+}) {
+ // until angular is used as shell - use hash history
+ const history = createHashHistory();
+ // query param to store app state at
+ const stateStorageKey = '_a';
+ // default app state, when there is no initial state in the url
+ const defaultState = {
+ tab: defaultTab,
+ };
+ const kbnUrlStateStorage = createKbnUrlStateStorage({
+ useHash: useHashedUrl,
+ history,
+ });
+ // extract starting app state from URL and use it as starting app state in state container
+ const initialStateFromUrl = kbnUrlStateStorage.get(stateStorageKey);
+ const stateContainer = createStateContainer(
+ {
+ ...defaultState,
+ ...initialStateFromUrl,
+ },
+ {
+ setTab: (state: IEditIndexPatternState) => (tab: string) => ({ ...state, tab }),
+ },
+ {
+ tab: (state: IEditIndexPatternState) => () => state.tab,
+ }
+ );
+
+ const { start, stop } = syncState({
+ storageKey: stateStorageKey,
+ stateContainer: {
+ ...stateContainer,
+ // state syncing utility requires state containers to handle "null"
+ set: state => state && stateContainer.set(state),
+ },
+ stateStorage: kbnUrlStateStorage,
+ });
+
+ // makes sure initial url is the same as initial state (this is not really required)
+ kbnUrlStateStorage.set(stateStorageKey, stateContainer.getState(), { replace: true });
+
+ // expose api needed for Controller
+ return {
+ startSyncingState: start,
+ stopSyncingState: stop,
+ setCurrentTab: (newTab: string) => stateContainer.transitions.setTab(newTab),
+ getCurrentTab: () => stateContainer.selectors.tab(),
+ state$: stateContainer.state$,
+ };
+}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts
index f113c81256f8e..a39779792b83a 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/index.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts
@@ -17,50 +17,15 @@
* under the License.
*/
-import {
- IPrivate,
- legacyChrome,
- npSetup,
- npStart,
- VisEditorTypesRegistryProvider,
-} from './legacy_imports';
-import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin';
-import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy';
-import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy';
+import { PluginInitializerContext } from 'kibana/public';
+import { VisualizePlugin } from './plugin';
export * from './np_ready/visualize_constants';
export { showNewVisModal } from './np_ready/wizard';
-/**
- * Get dependencies relying on the global angular context.
- * They also have to get resolved together with the legacy imports above
- */
-async function getAngularDependencies(): Promise {
- const injector = await legacyChrome.dangerouslyGetActiveInjector();
-
- const Private = injector.get('Private');
-
- const editorTypes = Private(VisEditorTypesRegistryProvider);
-
- return {
- legacyChrome,
- editorTypes,
- };
-}
-
-(() => {
- const instance = new VisualizePlugin();
- instance.setup(npSetup.core, {
- ...npSetup.plugins,
- __LEGACY: {
- getAngularDependencies,
- },
- });
- instance.start(npStart.core, {
- ...npStart.plugins,
- embeddables,
- visualizations,
- });
-})();
-
export { createSavedVisLoader } from './saved_visualizations/saved_visualizations';
+
+// Core will be looking for this when loading our plugin in the new platform
+export const plugin = (context: PluginInitializerContext) => {
+ return new VisualizePlugin();
+};
diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts
new file mode 100644
index 0000000000000..2a1b6130fac89
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts
@@ -0,0 +1,65 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import 'ui/collapsible_sidebar'; // used in default editor
+
+import { PluginInitializerContext } from 'kibana/public';
+import {
+ IPrivate,
+ legacyChrome,
+ npSetup,
+ npStart,
+ VisEditorTypesRegistryProvider,
+} from './legacy_imports';
+import { LegacyAngularInjectedDependencies } from './plugin';
+import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy';
+import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy';
+import { plugin } from './index';
+
+/**
+ * Get dependencies relying on the global angular context.
+ * They also have to get resolved together with the legacy imports above
+ */
+async function getAngularDependencies(): Promise {
+ const injector = await legacyChrome.dangerouslyGetActiveInjector();
+
+ const Private = injector.get('Private');
+
+ const editorTypes = Private(VisEditorTypesRegistryProvider);
+
+ return {
+ legacyChrome,
+ editorTypes,
+ };
+}
+
+(() => {
+ const instance = plugin({} as PluginInitializerContext);
+ instance.setup(npSetup.core, {
+ ...npSetup.plugins,
+ __LEGACY: {
+ getAngularDependencies,
+ },
+ });
+ instance.start(npStart.core, {
+ ...npStart.plugins,
+ embeddables,
+ visualizations,
+ });
+})();
diff --git a/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js
index 683a58c85e552..7ed2d370debd1 100644
--- a/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js
+++ b/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js
@@ -48,7 +48,7 @@ export function activemqMetricsSpecProvider(context) {
isBeta: true,
artifacts: {
application: {
- label: i18n.translate('kbn.server.tutorials.corednsMetrics.artifacts.application.label', {
+ label: i18n.translate('kbn.server.tutorials.activemqMetrics.artifacts.application.label', {
defaultMessage: 'Discover',
}),
path: '/app/kibana#/discover',
diff --git a/src/legacy/core_plugins/state_session_storage_redirect/package.json b/src/legacy/core_plugins/state_session_storage_redirect/package.json
deleted file mode 100644
index 21956e5d76d5b..0000000000000
--- a/src/legacy/core_plugins/state_session_storage_redirect/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "state_session_storage_redirect",
- "version": "kibana",
- "description": "When using the state:storeInSessionStorage setting with the short-urls, we need some way to get the full URL's hashed states into sessionStorage, this app will grab the URL from the injected state and and put the URL hashed states into sessionStorage before redirecting the user."
-}
diff --git a/src/legacy/core_plugins/vis_type_timelion/README.md b/src/legacy/core_plugins/vis_type_timelion/README.md
new file mode 100644
index 0000000000000..c306e03abf2c6
--- /dev/null
+++ b/src/legacy/core_plugins/vis_type_timelion/README.md
@@ -0,0 +1,10 @@
+# Vis type Timelion
+
+# Generate a parser
+If your grammar was changed in `public/chain.peg` you need to re-generate the static parser. You could use a grunt task:
+
+```
+grunt peg:timelion_chain
+```
+
+The generated parser will be appeared at `public/_generated_` folder, which is included in `.eslintignore`
\ No newline at end of file
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js b/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js
new file mode 100644
index 0000000000000..f812b94238d43
--- /dev/null
+++ b/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js
@@ -0,0 +1,1780 @@
+module.exports = (function() {
+ "use strict";
+
+ /*
+ * Generated by PEG.js 0.9.0.
+ *
+ * http://pegjs.org/
+ */
+
+ function peg$subclass(child, parent) {
+ function ctor() { this.constructor = child; }
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+ }
+
+ function peg$SyntaxError(message, expected, found, location) {
+ this.message = message;
+ this.expected = expected;
+ this.found = found;
+ this.location = location;
+ this.name = "SyntaxError";
+
+ if (typeof Error.captureStackTrace === "function") {
+ Error.captureStackTrace(this, peg$SyntaxError);
+ }
+ }
+
+ peg$subclass(peg$SyntaxError, Error);
+
+ function peg$parse(input) {
+ var options = arguments.length > 1 ? arguments[1] : {},
+ parser = this,
+
+ peg$FAILED = {},
+
+ peg$startRuleFunctions = { start: peg$parsestart },
+ peg$startRuleFunction = peg$parsestart,
+
+ peg$c0 = function(tree) {
+ return {
+ tree: tree.filter(function (o) {return o != null}),
+ functions: functions,
+ args: args,
+ variables: variables
+ }
+ },
+ peg$c1 = ",",
+ peg$c2 = { type: "literal", value: ",", description: "\",\"" },
+ peg$c3 = function(first, arg) {return arg},
+ peg$c4 = function(first, rest) {
+ return [first].concat(rest);
+ },
+ peg$c5 = "=",
+ peg$c6 = { type: "literal", value: "=", description: "\"=\"" },
+ peg$c7 = function(name, value) {
+ var arg = {
+ type: 'namedArg',
+ name: name,
+ value: value,
+ location: simpleLocation(location()),
+ text: text()
+ };
+ currentArgs.push(arg);
+ return arg;
+ },
+ peg$c8 = function(value) {
+ var exception = {
+ type: 'incompleteArgument',
+ currentArgs: currentArgs,
+ currentFunction: currentFunction,
+ location: simpleLocation(location()),
+ text: text()
+ }
+ error(JSON.stringify(exception));
+ },
+ peg$c9 = function(name) {
+ var exception = {
+ type: 'incompleteArgumentValue',
+ currentArgs: currentArgs,
+ currentFunction: currentFunction,
+ name: name,
+ location: simpleLocation(location()),
+ text: text()
+ }
+ error(JSON.stringify(exception));
+ },
+ peg$c10 = function(element) {return element},
+ peg$c11 = function(literal) {
+ var result = ltoo(literal);
+ result.location = simpleLocation(location()),
+ result.text = text();
+ return result;
+ },
+ peg$c12 = "$",
+ peg$c13 = { type: "literal", value: "$", description: "\"$\"" },
+ peg$c14 = function(name) {
+ if (variables[name]) {
+ return variables[name];
+ } else {
+ error('$' + name + ' is not defined')
+ }
+ },
+ peg$c15 = function(name, value) {
+ variables[name] = value;
+ },
+ peg$c16 = function(first, series) {return series},
+ peg$c17 = function(first, rest) {
+ return [first].concat(rest)
+ },
+ peg$c18 = /^[a-zA-Z]/,
+ peg$c19 = { type: "class", value: "[a-zA-Z]", description: "[a-zA-Z]" },
+ peg$c20 = /^[.a-zA-Z0-9_\-]/,
+ peg$c21 = { type: "class", value: "[.a-zA-Z0-9_-]", description: "[.a-zA-Z0-9_-]" },
+ peg$c22 = function(first, rest) {
+ currentFunction = first.join('') + rest.join('');
+ currentArgs = [];
+ return currentFunction;
+ },
+ peg$c23 = function(first, rest) { return first.join('') + rest.join('') },
+ peg$c24 = { type: "other", description: "function" },
+ peg$c25 = ".",
+ peg$c26 = { type: "literal", value: ".", description: "\".\"" },
+ peg$c27 = "(",
+ peg$c28 = { type: "literal", value: "(", description: "\"(\"" },
+ peg$c29 = ")",
+ peg$c30 = { type: "literal", value: ")", description: "\")\"" },
+ peg$c31 = function(name, arg_list) {
+ var result = {
+ type: 'function',
+ function: name,
+ arguments: arg_list || [],
+ location: simpleLocation(location()),
+ text: text()
+ }
+
+ result.arguments.forEach(function (arg) {
+ arg.function = name;
+ args.push(arg);
+ })
+
+ functions.push(result)
+ return result;
+ },
+ peg$c32 = function(func) {
+ var exception = {
+ type: 'incompleteFunction',
+ function: func,
+ location: simpleLocation(location()),
+ text: text()
+ }
+ error(JSON.stringify(exception));
+ },
+ peg$c33 = "@",
+ peg$c34 = { type: "literal", value: "@", description: "\"@\"" },
+ peg$c35 = ":",
+ peg$c36 = { type: "literal", value: ":", description: "\":\"" },
+ peg$c37 = function(plot, series) {
+ return {
+ type: 'reference',
+ plot: plot,
+ series: series
+ }
+ },
+ peg$c38 = function(plot) {
+ return {
+ type: 'reference',
+ plot: plot
+ }
+ },
+ peg$c39 = function(func, rest) {return {type: 'chain', chain: [func].concat(rest)}},
+ peg$c40 = function(grouped, functions) {
+ var first = {
+ type: 'chainList',
+ list: grouped
+ }
+ first.label = text();
+
+ return {type: "chain", chain: [first].concat(functions)};
+ },
+ peg$c41 = { type: "other", description: "literal" },
+ peg$c42 = "\"",
+ peg$c43 = { type: "literal", value: "\"", description: "\"\\\"\"" },
+ peg$c44 = function(chars) { return chars.join(''); },
+ peg$c45 = "'",
+ peg$c46 = { type: "literal", value: "'", description: "\"'\"" },
+ peg$c47 = "true",
+ peg$c48 = { type: "literal", value: "true", description: "\"true\"" },
+ peg$c49 = function() { return true; },
+ peg$c50 = "false",
+ peg$c51 = { type: "literal", value: "false", description: "\"false\"" },
+ peg$c52 = function() { return false; },
+ peg$c53 = "null",
+ peg$c54 = { type: "literal", value: "null", description: "\"null\"" },
+ peg$c55 = function() { return null; },
+ peg$c56 = /^[^()"',= \t]/,
+ peg$c57 = { type: "class", value: "[^()\"',=\\ \\t]", description: "[^()\"',=\\ \\t]" },
+ peg$c58 = function(string) { // this also matches numbers via Number()
+ var result = string.join('');
+ // Sort of hacky, but PEG doesn't have backtracking so
+ // a number rule is hard to read, and performs worse
+ if (isNaN(Number(result))) return result;
+ return Number(result)
+ },
+ peg$c59 = /^[ \t\r\n]/,
+ peg$c60 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" },
+ peg$c61 = "\\",
+ peg$c62 = { type: "literal", value: "\\", description: "\"\\\\\"" },
+ peg$c63 = function(sequence) { return sequence; },
+ peg$c64 = /^[^"]/,
+ peg$c65 = { type: "class", value: "[^\"]", description: "[^\"]" },
+ peg$c66 = /^[^']/,
+ peg$c67 = { type: "class", value: "[^']", description: "[^']" },
+ peg$c68 = /^[0-9]/,
+ peg$c69 = { type: "class", value: "[0-9]", description: "[0-9]" },
+ peg$c70 = function(digits) {return parseInt(digits.join(''))},
+
+ peg$currPos = 0,
+ peg$savedPos = 0,
+ peg$posDetailsCache = [{ line: 1, column: 1, seenCR: false }],
+ peg$maxFailPos = 0,
+ peg$maxFailExpected = [],
+ peg$silentFails = 0,
+
+ peg$result;
+
+ if ("startRule" in options) {
+ if (!(options.startRule in peg$startRuleFunctions)) {
+ throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
+ }
+
+ peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
+ }
+
+ function text() {
+ return input.substring(peg$savedPos, peg$currPos);
+ }
+
+ function location() {
+ return peg$computeLocation(peg$savedPos, peg$currPos);
+ }
+
+ function expected(description) {
+ throw peg$buildException(
+ null,
+ [{ type: "other", description: description }],
+ input.substring(peg$savedPos, peg$currPos),
+ peg$computeLocation(peg$savedPos, peg$currPos)
+ );
+ }
+
+ function error(message) {
+ throw peg$buildException(
+ message,
+ null,
+ input.substring(peg$savedPos, peg$currPos),
+ peg$computeLocation(peg$savedPos, peg$currPos)
+ );
+ }
+
+ function peg$computePosDetails(pos) {
+ var details = peg$posDetailsCache[pos],
+ p, ch;
+
+ if (details) {
+ return details;
+ } else {
+ p = pos - 1;
+ while (!peg$posDetailsCache[p]) {
+ p--;
+ }
+
+ details = peg$posDetailsCache[p];
+ details = {
+ line: details.line,
+ column: details.column,
+ seenCR: details.seenCR
+ };
+
+ while (p < pos) {
+ ch = input.charAt(p);
+ if (ch === "\n") {
+ if (!details.seenCR) { details.line++; }
+ details.column = 1;
+ details.seenCR = false;
+ } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") {
+ details.line++;
+ details.column = 1;
+ details.seenCR = true;
+ } else {
+ details.column++;
+ details.seenCR = false;
+ }
+
+ p++;
+ }
+
+ peg$posDetailsCache[pos] = details;
+ return details;
+ }
+ }
+
+ function peg$computeLocation(startPos, endPos) {
+ var startPosDetails = peg$computePosDetails(startPos),
+ endPosDetails = peg$computePosDetails(endPos);
+
+ return {
+ start: {
+ offset: startPos,
+ line: startPosDetails.line,
+ column: startPosDetails.column
+ },
+ end: {
+ offset: endPos,
+ line: endPosDetails.line,
+ column: endPosDetails.column
+ }
+ };
+ }
+
+ function peg$fail(expected) {
+ if (peg$currPos < peg$maxFailPos) { return; }
+
+ if (peg$currPos > peg$maxFailPos) {
+ peg$maxFailPos = peg$currPos;
+ peg$maxFailExpected = [];
+ }
+
+ peg$maxFailExpected.push(expected);
+ }
+
+ function peg$buildException(message, expected, found, location) {
+ function cleanupExpected(expected) {
+ var i = 1;
+
+ expected.sort(function(a, b) {
+ if (a.description < b.description) {
+ return -1;
+ } else if (a.description > b.description) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+
+ while (i < expected.length) {
+ if (expected[i - 1] === expected[i]) {
+ expected.splice(i, 1);
+ } else {
+ i++;
+ }
+ }
+ }
+
+ function buildMessage(expected, found) {
+ function stringEscape(s) {
+ function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
+
+ return s
+ .replace(/\\/g, '\\\\')
+ .replace(/"/g, '\\"')
+ .replace(/\x08/g, '\\b')
+ .replace(/\t/g, '\\t')
+ .replace(/\n/g, '\\n')
+ .replace(/\f/g, '\\f')
+ .replace(/\r/g, '\\r')
+ .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
+ .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); })
+ .replace(/[\u0100-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); })
+ .replace(/[\u1000-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); });
+ }
+
+ var expectedDescs = new Array(expected.length),
+ expectedDesc, foundDesc, i;
+
+ for (i = 0; i < expected.length; i++) {
+ expectedDescs[i] = expected[i].description;
+ }
+
+ expectedDesc = expected.length > 1
+ ? expectedDescs.slice(0, -1).join(", ")
+ + " or "
+ + expectedDescs[expected.length - 1]
+ : expectedDescs[0];
+
+ foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input";
+
+ return "Expected " + expectedDesc + " but " + foundDesc + " found.";
+ }
+
+ if (expected !== null) {
+ cleanupExpected(expected);
+ }
+
+ return new peg$SyntaxError(
+ message !== null ? message : buildMessage(expected, found),
+ expected,
+ found,
+ location
+ );
+ }
+
+ function peg$parsestart() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$parsespace();
+ if (s1 === peg$FAILED) {
+ s1 = null;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseseries();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c0(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parsearg_list() {
+ var s0, s1, s2, s3, s4, s5, s6, s7;
+
+ s0 = peg$currPos;
+ s1 = peg$parseargument();
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$currPos;
+ s4 = peg$parsespace();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 44) {
+ s5 = peg$c1;
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c2); }
+ }
+ if (s5 !== peg$FAILED) {
+ s6 = peg$parsespace();
+ if (s6 === peg$FAILED) {
+ s6 = null;
+ }
+ if (s6 !== peg$FAILED) {
+ s7 = peg$parseargument();
+ if (s7 !== peg$FAILED) {
+ peg$savedPos = s3;
+ s4 = peg$c3(s1, s7);
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$currPos;
+ s4 = peg$parsespace();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 44) {
+ s5 = peg$c1;
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c2); }
+ }
+ if (s5 !== peg$FAILED) {
+ s6 = peg$parsespace();
+ if (s6 === peg$FAILED) {
+ s6 = null;
+ }
+ if (s6 !== peg$FAILED) {
+ s7 = peg$parseargument();
+ if (s7 !== peg$FAILED) {
+ peg$savedPos = s3;
+ s4 = peg$c3(s1, s7);
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parsespace();
+ if (s3 === peg$FAILED) {
+ s3 = null;
+ }
+ if (s3 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 44) {
+ s4 = peg$c1;
+ peg$currPos++;
+ } else {
+ s4 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c2); }
+ }
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c4(s1, s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseargument() {
+ var s0, s1, s2, s3, s4, s5;
+
+ s0 = peg$currPos;
+ s1 = peg$parseargument_name();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parsespace();
+ if (s2 === peg$FAILED) {
+ s2 = null;
+ }
+ if (s2 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 61) {
+ s3 = peg$c5;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c6); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parsespace();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parsearg_type();
+ if (s5 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c7(s1, s5);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parsespace();
+ if (s1 === peg$FAILED) {
+ s1 = null;
+ }
+ if (s1 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 61) {
+ s2 = peg$c5;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c6); }
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parsespace();
+ if (s3 === peg$FAILED) {
+ s3 = null;
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parsearg_type();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c8(s4);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseargument_name();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parsespace();
+ if (s2 === peg$FAILED) {
+ s2 = null;
+ }
+ if (s2 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 61) {
+ s3 = peg$c5;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c6); }
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c9(s1);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parsearg_type();
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c10(s1);
+ }
+ s0 = s1;
+ }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parsearg_type() {
+ var s0, s1;
+
+ s0 = peg$parsevariable_get();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseseries_type();
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseliteral();
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c11(s1);
+ }
+ s0 = s1;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parsevariable_get() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 36) {
+ s1 = peg$c12;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c13); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseargument_name();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c14(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parsevariable_set() {
+ var s0, s1, s2, s3, s4, s5, s6;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 36) {
+ s1 = peg$c12;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c13); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseargument_name();
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parsespace();
+ if (s3 === peg$FAILED) {
+ s3 = null;
+ }
+ if (s3 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 61) {
+ s4 = peg$c5;
+ peg$currPos++;
+ } else {
+ s4 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c6); }
+ }
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parsespace();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ if (s5 !== peg$FAILED) {
+ s6 = peg$parsearg_type();
+ if (s6 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c15(s2, s6);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseseries_type() {
+ var s0;
+
+ s0 = peg$parsevariable_set();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parsevariable_get();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parsegroup();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parsechain();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parsereference();
+ }
+ }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseseries() {
+ var s0, s1, s2, s3, s4, s5, s6, s7;
+
+ s0 = peg$currPos;
+ s1 = peg$parseseries_type();
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$currPos;
+ s4 = peg$parsespace();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 44) {
+ s5 = peg$c1;
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c2); }
+ }
+ if (s5 !== peg$FAILED) {
+ s6 = peg$parsespace();
+ if (s6 === peg$FAILED) {
+ s6 = null;
+ }
+ if (s6 !== peg$FAILED) {
+ s7 = peg$parseseries_type();
+ if (s7 !== peg$FAILED) {
+ peg$savedPos = s3;
+ s4 = peg$c16(s1, s7);
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$currPos;
+ s4 = peg$parsespace();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 44) {
+ s5 = peg$c1;
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c2); }
+ }
+ if (s5 !== peg$FAILED) {
+ s6 = peg$parsespace();
+ if (s6 === peg$FAILED) {
+ s6 = null;
+ }
+ if (s6 !== peg$FAILED) {
+ s7 = peg$parseseries_type();
+ if (s7 !== peg$FAILED) {
+ peg$savedPos = s3;
+ s4 = peg$c16(s1, s7);
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ }
+ if (s2 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 44) {
+ s3 = peg$c1;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c2); }
+ }
+ if (s3 === peg$FAILED) {
+ s3 = null;
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c17(s1, s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parsefunction_name() {
+ var s0, s1, s2, s3;
+
+ s0 = peg$currPos;
+ s1 = [];
+ if (peg$c18.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c19); }
+ }
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ if (peg$c18.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c19); }
+ }
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ if (peg$c20.test(input.charAt(peg$currPos))) {
+ s3 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c21); }
+ }
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ if (peg$c20.test(input.charAt(peg$currPos))) {
+ s3 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c21); }
+ }
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c22(s1, s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseargument_name() {
+ var s0, s1, s2, s3;
+
+ s0 = peg$currPos;
+ s1 = [];
+ if (peg$c18.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c19); }
+ }
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ if (peg$c18.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c19); }
+ }
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ if (peg$c20.test(input.charAt(peg$currPos))) {
+ s3 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c21); }
+ }
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ if (peg$c20.test(input.charAt(peg$currPos))) {
+ s3 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c21); }
+ }
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c23(s1, s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parsefunction() {
+ var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parsespace();
+ if (s1 === peg$FAILED) {
+ s1 = null;
+ }
+ if (s1 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 46) {
+ s2 = peg$c25;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c26); }
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parsefunction_name();
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parsespace();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 40) {
+ s5 = peg$c27;
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c28); }
+ }
+ if (s5 !== peg$FAILED) {
+ s6 = peg$parsespace();
+ if (s6 === peg$FAILED) {
+ s6 = null;
+ }
+ if (s6 !== peg$FAILED) {
+ s7 = peg$parsearg_list();
+ if (s7 === peg$FAILED) {
+ s7 = null;
+ }
+ if (s7 !== peg$FAILED) {
+ s8 = peg$parsespace();
+ if (s8 === peg$FAILED) {
+ s8 = null;
+ }
+ if (s8 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 41) {
+ s9 = peg$c29;
+ peg$currPos++;
+ } else {
+ s9 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c30); }
+ }
+ if (s9 !== peg$FAILED) {
+ s10 = peg$parsespace();
+ if (s10 === peg$FAILED) {
+ s10 = null;
+ }
+ if (s10 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c31(s3, s7);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 46) {
+ s1 = peg$c25;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c26); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parsefunction_name();
+ if (s2 === peg$FAILED) {
+ s2 = null;
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c32(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c24); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsereference() {
+ var s0, s1, s2, s3, s4;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 64) {
+ s1 = peg$c33;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c34); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseinteger();
+ if (s2 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c35;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c36); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseinteger();
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c37(s2, s4);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 64) {
+ s1 = peg$c33;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c34); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseinteger();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c38(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parsechain() {
+ var s0, s1, s2, s3, s4;
+
+ s0 = peg$currPos;
+ s1 = peg$parsefunction();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parsespace();
+ if (s2 === peg$FAILED) {
+ s2 = null;
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = [];
+ s4 = peg$parsefunction();
+ while (s4 !== peg$FAILED) {
+ s3.push(s4);
+ s4 = peg$parsefunction();
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c39(s1, s3);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parsegroup() {
+ var s0, s1, s2, s3, s4, s5, s6, s7;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 40) {
+ s1 = peg$c27;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c28); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parsespace();
+ if (s2 === peg$FAILED) {
+ s2 = null;
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseseries();
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parsespace();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 41) {
+ s5 = peg$c29;
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c30); }
+ }
+ if (s5 !== peg$FAILED) {
+ s6 = [];
+ s7 = peg$parsefunction();
+ while (s7 !== peg$FAILED) {
+ s6.push(s7);
+ s7 = peg$parsefunction();
+ }
+ if (s6 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c40(s3, s6);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseliteral() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 34) {
+ s1 = peg$c42;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c43); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$parsedq_char();
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$parsedq_char();
+ }
+ if (s2 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 34) {
+ s3 = peg$c42;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c43); }
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c44(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 39) {
+ s1 = peg$c45;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c46); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$parsesq_char();
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$parsesq_char();
+ }
+ if (s2 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 39) {
+ s3 = peg$c45;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c46); }
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c44(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 4) === peg$c47) {
+ s1 = peg$c47;
+ peg$currPos += 4;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c48); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c49();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 5) === peg$c50) {
+ s1 = peg$c50;
+ peg$currPos += 5;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c51); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c52();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 4) === peg$c53) {
+ s1 = peg$c53;
+ peg$currPos += 4;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c54); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c55();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = [];
+ if (peg$c56.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c57); }
+ }
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ if (peg$c56.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c57); }
+ }
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c58(s1);
+ }
+ s0 = s1;
+ }
+ }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c41); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsespace() {
+ var s0, s1;
+
+ s0 = [];
+ if (peg$c59.test(input.charAt(peg$currPos))) {
+ s1 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c60); }
+ }
+ if (s1 !== peg$FAILED) {
+ while (s1 !== peg$FAILED) {
+ s0.push(s1);
+ if (peg$c59.test(input.charAt(peg$currPos))) {
+ s1 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c60); }
+ }
+ }
+ } else {
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parsedq_char() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 92) {
+ s1 = peg$c61;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c62); }
+ }
+ if (s1 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 34) {
+ s2 = peg$c42;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c43); }
+ }
+ if (s2 === peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 92) {
+ s2 = peg$c61;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c62); }
+ }
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c63(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ if (peg$c64.test(input.charAt(peg$currPos))) {
+ s0 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c65); }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parsesq_char() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 92) {
+ s1 = peg$c61;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c62); }
+ }
+ if (s1 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 39) {
+ s2 = peg$c45;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c46); }
+ }
+ if (s2 === peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 92) {
+ s2 = peg$c61;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c62); }
+ }
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c63(s2);
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ if (peg$c66.test(input.charAt(peg$currPos))) {
+ s0 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c67); }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseinteger() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ if (peg$c68.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c69); }
+ }
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ if (peg$c68.test(input.charAt(peg$currPos))) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$c69); }
+ }
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$c70(s1);
+ }
+ s0 = s1;
+
+ return s0;
+ }
+
+
+ function ltoo (literal) {
+ return {type: 'literal', value: literal}
+ }
+
+ function simpleLocation (location) {
+ // Returns an object representing the position of the function within the expression,
+ // demarcated by the position of its first character and last character. We calculate these values
+ // using the offset because the expression could span multiple lines, and we don't want to deal
+ // with column and line values.
+ return {
+ min: location.start.offset,
+ max: location.end.offset
+ }
+ }
+
+ var currentFunction;
+ var currentArgs = [];
+
+ var functions = [];
+ var args = [];
+ var variables = {};
+
+
+
+ peg$result = peg$startRuleFunction();
+
+ if (peg$result !== peg$FAILED && peg$currPos === input.length) {
+ return peg$result;
+ } else {
+ if (peg$result !== peg$FAILED && peg$currPos < input.length) {
+ peg$fail({ type: "end", description: "end of input" });
+ }
+
+ throw peg$buildException(
+ null,
+ peg$maxFailExpected,
+ peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
+ peg$maxFailPos < input.length
+ ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
+ : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
+ );
+ }
+ }
+
+ return {
+ SyntaxError: peg$SyntaxError,
+ parse: peg$parse
+ };
+})();
\ No newline at end of file
diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts
similarity index 70%
rename from src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js
rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts
index ea2d44bcaefe0..18a0c0872dc03 100644
--- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js
+++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts
@@ -17,19 +17,16 @@
* under the License.
*/
-import expect from '@kbn/expect';
-import PEG from 'pegjs';
-import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg';
-import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers';
-import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions';
-import {
- setIndexPatterns,
- setSavedObjectsClient,
-} from '../../../../vis_type_timelion/public/helpers/plugin_services';
+import { SUGGESTION_TYPE, suggest } from './timelion_expression_input_helpers';
+import { getArgValueSuggestions } from '../helpers/arg_value_suggestions';
+import { setIndexPatterns, setSavedObjectsClient } from '../helpers/plugin_services';
+import { IndexPatterns } from 'src/plugins/data/public';
+import { SavedObjectsClient } from 'kibana/public';
+import { ITimelionFunction } from '../../common/types';
describe('Timelion expression suggestions', () => {
- setIndexPatterns({});
- setSavedObjectsClient({});
+ setIndexPatterns({} as IndexPatterns);
+ setSavedObjectsClient({} as SavedObjectsClient);
const argValueSuggestions = getArgValueSuggestions();
@@ -45,17 +42,13 @@ describe('Timelion expression suggestions', () => {
suggestions: [{ name: 'value1' }],
},
],
- };
+ } as ITimelionFunction;
const myFunc2 = {
name: 'myFunc2',
chainable: false,
args: [{ name: 'argA' }, { name: 'argAB' }, { name: 'argABC' }],
- };
+ } as ITimelionFunction;
const functionList = [func1, myFunc2];
- let Parser;
- beforeEach(function() {
- Parser = PEG.generate(grammar);
- });
describe('parse exception', () => {
describe('incompleteFunction', () => {
@@ -65,17 +58,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [func1, myFunc2],
- location: {
- min: 0,
- max: 1,
- },
- type: 'functions',
+ type: SUGGESTION_TYPE.FUNCTIONS,
});
});
it('should filter function suggestions by function name', async () => {
@@ -84,17 +72,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [myFunc2],
- location: {
- min: 0,
- max: 4,
- },
- type: 'functions',
+ type: SUGGESTION_TYPE.FUNCTIONS,
});
});
});
@@ -106,17 +89,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [],
- location: {
- min: 11,
- max: 12,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
@@ -126,17 +104,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: myFunc2.args,
- location: {
- min: 9,
- max: 10,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
@@ -146,17 +119,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: myFunc2.args,
- location: {
- min: 9,
- max: 25,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
@@ -166,17 +134,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [{ name: 'argA' }, { name: 'argAB', suggestions: [{ name: 'value1' }] }],
- location: {
- min: 7,
- max: 8,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
@@ -186,17 +149,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [{ name: 'argA' }, { name: 'argABC' }],
- location: {
- min: 24,
- max: 25,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
});
@@ -208,17 +166,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [],
- location: {
- min: 11,
- max: 11,
- },
- type: 'argument_value',
+ type: SUGGESTION_TYPE.ARGUMENT_VALUE,
});
});
@@ -228,17 +181,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [{ name: 'value1' }],
- location: {
- min: 11,
- max: 11,
- },
- type: 'argument_value',
+ type: SUGGESTION_TYPE.ARGUMENT_VALUE,
});
});
});
@@ -252,17 +200,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [func1],
- location: {
- min: 0,
- max: 8,
- },
- type: 'functions',
+ type: SUGGESTION_TYPE.FUNCTIONS,
});
});
});
@@ -275,17 +218,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: myFunc2.args,
- location: {
- min: 9,
- max: 9,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
it('should not provide argument suggestions for argument that is all ready set in function def', async () => {
@@ -294,18 +232,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions.type).to.equal(SUGGESTION_TYPE.ARGUMENTS);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [{ name: 'argA' }, { name: 'argABC' }],
- location: {
- min: 24,
- max: 24,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
it('should filter argument suggestions by argument name', async () => {
@@ -314,17 +246,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [{ name: 'argAB' }, { name: 'argABC' }],
- location: {
- min: 9,
- max: 14,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
it('should not show first argument for chainable functions', async () => {
@@ -333,17 +260,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [{ name: 'argA' }, { name: 'argAB', suggestions: [{ name: 'value1' }] }],
- location: {
- min: 7,
- max: 7,
- },
- type: 'arguments',
+ type: SUGGESTION_TYPE.ARGUMENTS,
});
});
});
@@ -354,17 +276,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [],
- location: {
- min: 14,
- max: 16,
- },
- type: 'argument_value',
+ type: SUGGESTION_TYPE.ARGUMENT_VALUE,
});
});
@@ -374,17 +291,12 @@ describe('Timelion expression suggestions', () => {
const suggestions = await suggest(
expression,
functionList,
- Parser,
cursorPosition,
argValueSuggestions
);
- expect(suggestions).to.eql({
+ expect(suggestions).toEqual({
list: [{ name: 'value1' }],
- location: {
- min: 13,
- max: 16,
- },
- type: 'argument_value',
+ type: SUGGESTION_TYPE.ARGUMENT_VALUE,
});
});
});
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts
index 5aa05fb16466b..d7818680c9543 100644
--- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts
+++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts
@@ -18,18 +18,17 @@
*/
import { get, startsWith } from 'lodash';
-import PEG from 'pegjs';
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
+import { i18n } from '@kbn/i18n';
+import { Parser } from 'pegjs';
+
// @ts-ignore
-import grammar from 'raw-loader!../chain.peg';
+import { parse } from '../_generated_/chain';
-import { i18n } from '@kbn/i18n';
import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types';
import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions';
-const Parser = PEG.generate(grammar);
-
export enum SUGGESTION_TYPE {
ARGUMENTS = 'arguments',
ARGUMENT_VALUE = 'argument_value',
@@ -57,7 +56,7 @@ function getArgumentsHelp(
}
async function extractSuggestionsFromParsedResult(
- result: ReturnType,
+ result: ReturnType,
cursorPosition: number,
functionList: ITimelionFunction[],
argValueSuggestions: ArgValueSuggestions
@@ -141,7 +140,7 @@ export async function suggest(
argValueSuggestions: ArgValueSuggestions
) {
try {
- const result = await Parser.parse(expression);
+ const result = await parse(expression);
return await extractSuggestionsFromParsedResult(
result,
diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/vega_parser.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/vega_parser.js
deleted file mode 100644
index 50bcff2469710..0000000000000
--- a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/vega_parser.js
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import expect from '@kbn/expect';
-import { VegaParser } from '../vega_parser';
-import { bypassExternalUrlCheck } from '../../vega_view/vega_base_view';
-
-describe(`VegaParser._setDefaultValue`, () => {
- function test(spec, expected, ...params) {
- return () => {
- const vp = new VegaParser(spec);
- vp._setDefaultValue(...params);
- expect(vp.spec).to.eql(expected);
- expect(vp.warnings).to.have.length(0);
- };
- }
-
- it(`empty`, test({}, { config: { test: 42 } }, 42, 'config', 'test'));
- it(`exists`, test({ config: { test: 42 } }, { config: { test: 42 } }, 1, 'config', 'test'));
- it(`exists non-obj`, test({ config: false }, { config: false }, 42, 'config', 'test'));
-});
-
-describe(`VegaParser._setDefaultColors`, () => {
- function test(spec, isVegaLite, expected) {
- return () => {
- const vp = new VegaParser(spec);
- vp.isVegaLite = isVegaLite;
- vp._setDefaultColors();
- expect(vp.spec).to.eql(expected);
- expect(vp.warnings).to.have.length(0);
- };
- }
-
- it(
- `vegalite`,
- test({}, true, {
- config: {
- range: { category: { scheme: 'elastic' } },
- mark: { color: '#54B399' },
- },
- })
- );
-
- it(
- `vega`,
- test({}, false, {
- config: {
- range: { category: { scheme: 'elastic' } },
- arc: { fill: '#54B399' },
- area: { fill: '#54B399' },
- line: { stroke: '#54B399' },
- path: { stroke: '#54B399' },
- rect: { fill: '#54B399' },
- rule: { stroke: '#54B399' },
- shape: { stroke: '#54B399' },
- symbol: { fill: '#54B399' },
- trail: { fill: '#54B399' },
- },
- })
- );
-});
-
-describe('VegaParser._resolveEsQueries', () => {
- function test(spec, expected, warnCount) {
- return async () => {
- const vp = new VegaParser(spec, { search: async () => [[42]] }, 0, 0, {
- getFileLayers: async () => [{ name: 'file1', url: 'url1' }],
- getUrlForRegionLayer: async layer => {
- return layer.url;
- },
- });
- await vp._resolveDataUrls();
-
- expect(vp.spec).to.eql(expected);
- expect(vp.warnings).to.have.length(warnCount || 0);
- };
- }
-
- it('no data', test({}, {}));
- it('no data2', test({ a: 1 }, { a: 1 }));
- it('non-es data', test({ data: { a: 10 } }, { data: { a: 10 } }));
- it('es', test({ data: { url: { index: 'a' }, x: 1 } }, { data: { values: [42], x: 1 } }));
- it(
- 'es',
- test({ data: { url: { '%type%': 'elasticsearch', index: 'a' } } }, { data: { values: [42] } })
- );
- it(
- 'es arr',
- test(
- { arr: [{ data: { url: { index: 'a' }, x: 1 } }] },
- { arr: [{ data: { values: [42], x: 1 } }] }
- )
- );
- it(
- 'emsfile',
- test(
- { data: { url: { '%type%': 'emsfile', name: 'file1' } } },
- { data: { url: bypassExternalUrlCheck('url1') } }
- )
- );
-});
-
-describe('VegaParser._parseSchema', () => {
- function test(schema, isVegaLite, warningCount) {
- return () => {
- const vp = new VegaParser({ $schema: schema });
- expect(vp._parseSchema()).to.be(isVegaLite);
- expect(vp.spec).to.eql({ $schema: schema });
- expect(vp.warnings).to.have.length(warningCount);
- };
- }
-
- it('should warn on no vega version specified', () => {
- const vp = new VegaParser({});
- expect(vp._parseSchema()).to.be(false);
- expect(vp.spec).to.eql({ $schema: 'https://vega.github.io/schema/vega/v3.0.json' });
- expect(vp.warnings).to.have.length(1);
- });
-
- it(
- 'should not warn on current vega version',
- test('https://vega.github.io/schema/vega/v4.0.json', false, 0)
- );
- it(
- 'should not warn on older vega version',
- test('https://vega.github.io/schema/vega/v3.0.json', false, 0)
- );
- it(
- 'should warn on vega version too new to be supported',
- test('https://vega.github.io/schema/vega/v5.0.json', false, 1)
- );
-
- it(
- 'should not warn on current vega-lite version',
- test('https://vega.github.io/schema/vega-lite/v2.0.json', true, 0)
- );
- it(
- 'should warn on vega-lite version too new to be supported',
- test('https://vega.github.io/schema/vega-lite/v3.0.json', true, 1)
- );
-});
-
-describe('VegaParser._parseTooltips', () => {
- function test(tooltips, position, padding, centerOnMark) {
- return () => {
- const vp = new VegaParser(tooltips !== undefined ? { config: { kibana: { tooltips } } } : {});
- vp._config = vp._parseConfig();
- if (position === undefined) {
- // error
- expect(() => vp._parseTooltips()).to.throwError();
- } else if (position === false) {
- expect(vp._parseTooltips()).to.eql(false);
- } else {
- expect(vp._parseTooltips()).to.eql({ position, padding, centerOnMark });
- }
- };
- }
-
- it('undefined', test(undefined, 'top', 16, 50));
- it('{}', test({}, 'top', 16, 50));
- it('left', test({ position: 'left' }, 'left', 16, 50));
- it('padding', test({ position: 'bottom', padding: 60 }, 'bottom', 60, 50));
- it('padding2', test({ padding: 70 }, 'top', 70, 50));
- it('centerOnMark', test({}, 'top', 16, 50));
- it('centerOnMark=10', test({ centerOnMark: 10 }, 'top', 16, 10));
- it('centerOnMark=true', test({ centerOnMark: true }, 'top', 16, Number.MAX_VALUE));
- it('centerOnMark=false', test({ centerOnMark: false }, 'top', 16, -1));
-
- it('false', test(false, false));
-
- it('err1', test(true, undefined));
- it('err2', test({ position: 'foo' }, undefined));
- it('err3', test({ padding: 'foo' }, undefined));
- it('err4', test({ centerOnMark: {} }, undefined));
-});
-
-describe('VegaParser._parseMapConfig', () => {
- function test(config, expected, warnCount) {
- return () => {
- const vp = new VegaParser();
- vp._config = config;
- expect(vp._parseMapConfig()).to.eql(expected);
- expect(vp.warnings).to.have.length(warnCount);
- };
- }
-
- it(
- 'empty',
- test(
- {},
- {
- delayRepaint: true,
- latitude: 0,
- longitude: 0,
- mapStyle: 'default',
- zoomControl: true,
- scrollWheelZoom: false,
- },
- 0
- )
- );
-
- it(
- 'filled',
- test(
- {
- delayRepaint: true,
- latitude: 0,
- longitude: 0,
- mapStyle: 'default',
- zoomControl: true,
- scrollWheelZoom: false,
- maxBounds: [1, 2, 3, 4],
- },
- {
- delayRepaint: true,
- latitude: 0,
- longitude: 0,
- mapStyle: 'default',
- zoomControl: true,
- scrollWheelZoom: false,
- maxBounds: [1, 2, 3, 4],
- },
- 0
- )
- );
-
- it(
- 'warnings',
- test(
- {
- delayRepaint: true,
- latitude: 0,
- longitude: 0,
- zoom: 'abc', // ignored
- mapStyle: 'abc',
- zoomControl: 'abc',
- scrollWheelZoom: 'abc',
- maxBounds: [2, 3, 4],
- },
- {
- delayRepaint: true,
- latitude: 0,
- longitude: 0,
- mapStyle: 'default',
- zoomControl: true,
- scrollWheelZoom: false,
- },
- 5
- )
- );
-});
-
-describe('VegaParser._parseConfig', () => {
- function test(spec, expectedConfig, expectedSpec, warnCount) {
- return async () => {
- expectedSpec = expectedSpec || _.cloneDeep(spec);
- const vp = new VegaParser(spec);
- const config = await vp._parseConfig();
- expect(config).to.eql(expectedConfig);
- expect(vp.spec).to.eql(expectedSpec);
- expect(vp.warnings).to.have.length(warnCount || 0);
- };
- }
-
- it('no config', test({}, {}, {}));
- it('simple config', test({ config: { a: 1 } }, {}));
- it('kibana config', test({ config: { kibana: { a: 1 } } }, { a: 1 }, { config: {} }));
- it('_hostConfig', test({ _hostConfig: { a: 1 } }, { a: 1 }, {}, 1));
-});
-
-describe('VegaParser._calcSizing', () => {
- function test(spec, useResize, paddingWidth, paddingHeight, isVegaLite, expectedSpec, warnCount) {
- return async () => {
- expectedSpec = expectedSpec || _.cloneDeep(spec);
- const vp = new VegaParser(spec);
- vp.isVegaLite = !!isVegaLite;
- vp._calcSizing();
- expect(vp.useResize).to.eql(useResize);
- expect(vp.paddingWidth).to.eql(paddingWidth);
- expect(vp.paddingHeight).to.eql(paddingHeight);
- expect(vp.spec).to.eql(expectedSpec);
- expect(vp.warnings).to.have.length(warnCount || 0);
- };
- }
-
- it('no size', test({ autosize: {} }, false, 0, 0));
- it('fit', test({ autosize: 'fit' }, true, 0, 0));
- it('fit obj', test({ autosize: { type: 'fit' } }, true, 0, 0));
- it('padding const', test({ autosize: 'fit', padding: 10 }, true, 20, 20));
- it(
- 'padding obj',
- test({ autosize: 'fit', padding: { left: 5, bottom: 7, right: 6, top: 8 } }, true, 11, 15)
- );
- it('width height', test({ autosize: 'fit', width: 1, height: 2 }, true, 0, 0, false, false, 1));
- it(
- 'VL width height',
- test({ autosize: 'fit', width: 1, height: 2 }, true, 0, 0, true, { autosize: 'fit' }, 0)
- );
-});
diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/es_query_parser.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js
similarity index 59%
rename from src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/es_query_parser.js
rename to src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js
index a725bb9ed4cb5..691e5e8944241 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/es_query_parser.js
+++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/es_query_parser.test.js
@@ -17,11 +17,13 @@
* under the License.
*/
-import _ from 'lodash';
-import expect from '@kbn/expect';
-import sinon from 'sinon';
+import { cloneDeep } from 'lodash';
import moment from 'moment';
-import { EsQueryParser } from '../es_query_parser';
+import { EsQueryParser } from './es_query_parser';
+
+jest.mock('../helpers', () => ({
+ getEsShardTimeout: jest.fn(() => '10000'),
+}));
const second = 1000;
const minute = 60 * second;
@@ -39,41 +41,57 @@ function create(min, max, dashboardCtx) {
getTimeBounds: () => ({ min, max }),
},
() => {},
- _.cloneDeep(dashboardCtx),
+ cloneDeep(dashboardCtx),
() => (inst.$$$warnCount = (inst.$$$warnCount || 0) + 1)
);
return inst;
}
describe(`EsQueryParser time`, () => {
- it(`roundInterval(4s)`, () => expect(EsQueryParser._roundInterval(4 * second)).to.be(`1s`));
- it(`roundInterval(4hr)`, () => expect(EsQueryParser._roundInterval(4 * hour)).to.be(`3h`));
- it(`getTimeBound`, () => expect(create(1000, 2000)._getTimeBound({}, `min`)).to.be(1000));
- it(`getTimeBound(shift 2d)`, () =>
- expect(create(5, 2000)._getTimeBound({ shift: 2 }, `min`)).to.be(5 + 2 * day));
- it(`getTimeBound(shift -2hr)`, () =>
- expect(create(10 * day, 20 * day)._getTimeBound({ shift: -2, unit: `h` }, `min`)).to.be(
+ test(`roundInterval(4s)`, () => {
+ expect(EsQueryParser._roundInterval(4 * second)).toBe(`1s`);
+ });
+
+ test(`roundInterval(4hr)`, () => {
+ expect(EsQueryParser._roundInterval(4 * hour)).toBe(`3h`);
+ });
+
+ test(`getTimeBound`, () => {
+ expect(create(1000, 2000)._getTimeBound({}, `min`)).toBe(1000);
+ });
+
+ test(`getTimeBound(shift 2d)`, () => {
+ expect(create(5, 2000)._getTimeBound({ shift: 2 }, `min`)).toBe(5 + 2 * day);
+ });
+
+ test(`getTimeBound(shift -2hr)`, () => {
+ expect(create(10 * day, 20 * day)._getTimeBound({ shift: -2, unit: `h` }, `min`)).toBe(
10 * day - 2 * hour
- ));
- it(`createRangeFilter({})`, () => {
+ );
+ });
+
+ test(`createRangeFilter({})`, () => {
const obj = {};
- expect(create(1000, 2000)._createRangeFilter(obj))
- .to.eql({
- format: 'strict_date_optional_time',
- gte: moment(1000).toISOString(),
- lte: moment(2000).toISOString(),
- })
- .and.to.be(obj);
+ const result = create(1000, 2000)._createRangeFilter(obj);
+
+ expect(result).toEqual({
+ format: 'strict_date_optional_time',
+ gte: moment(1000).toISOString(),
+ lte: moment(2000).toISOString(),
+ });
+ expect(result).toBe(obj);
});
- it(`createRangeFilter(shift 1s)`, () => {
+
+ test(`createRangeFilter(shift 1s)`, () => {
const obj = { shift: 5, unit: 's' };
- expect(create(1000, 2000)._createRangeFilter(obj))
- .to.eql({
- format: 'strict_date_optional_time',
- gte: moment(6000).toISOString(),
- lte: moment(7000).toISOString(),
- })
- .and.to.be(obj);
+ const result = create(1000, 2000)._createRangeFilter(obj);
+
+ expect(result).toEqual({
+ format: 'strict_date_optional_time',
+ gte: moment(6000).toISOString(),
+ lte: moment(7000).toISOString(),
+ });
+ expect(result).toBe(obj);
});
});
@@ -82,79 +100,78 @@ describe('EsQueryParser.populateData', () => {
let parser;
beforeEach(() => {
- searchStub = sinon.stub();
+ searchStub = jest.fn(() => Promise.resolve([{}, {}]));
parser = new EsQueryParser({}, { search: searchStub }, undefined, undefined);
-
- searchStub.returns(Promise.resolve([{}, {}]));
});
- it('should set the timeout for each request', async () => {
+
+ test('should set the timeout for each request', async () => {
await parser.populateData([
{ url: { body: {} }, dataObject: {} },
{ url: { body: {} }, dataObject: {} },
]);
- expect(searchStub.firstCall.args[0][0].body.timeout).to.be.defined;
+ expect(searchStub.mock.calls[0][0][0].body.timeout).toBe.defined;
});
- it('should remove possible timeout parameters on a request', async () => {
+ test('should remove possible timeout parameters on a request', async () => {
await parser.populateData([
{ url: { timeout: '500h', body: { timeout: '500h' } }, dataObject: {} },
]);
- expect(searchStub.firstCall.args[0][0].body.timeout).to.be.defined;
- expect(searchStub.firstCall.args[0][0].timeout).to.be(undefined);
+ expect(searchStub.mock.calls[0][0][0].body.timeout).toBe.defined;
+ expect(searchStub.mock.calls[0][0][0].timeout).toBe(undefined);
});
});
describe(`EsQueryParser.injectQueryContextVars`, () => {
- function test(obj, expected, ctx) {
+ function check(obj, expected, ctx) {
return () => {
create(rangeStart, rangeEnd, ctx)._injectContextVars(obj, true);
- expect(obj).to.eql(expected);
+ expect(obj).toEqual(expected);
};
}
- it(`empty`, test({}, {}));
- it(`simple`, () => {
+ test(`empty`, check({}, {}));
+ test(`simple`, () => {
const obj = { a: { c: 10 }, b: [{ d: 2 }, 4, 5], c: [], d: {} };
- test(obj, _.cloneDeep(obj));
+ check(obj, cloneDeep(obj));
});
- it(`must clause empty`, test({ arr: ['%dashboard_context-must_clause%'] }, { arr: [] }, {}));
- it(
+ test(`must clause empty`, check({ arr: ['%dashboard_context-must_clause%'] }, { arr: [] }, {}));
+ test(
`must clause arr`,
- test({ arr: ['%dashboard_context-must_clause%'] }, { arr: [...ctxArr.bool.must] }, ctxArr)
+ check({ arr: ['%dashboard_context-must_clause%'] }, { arr: [...ctxArr.bool.must] }, ctxArr)
);
- it(
+ test(
`must clause obj`,
- test({ arr: ['%dashboard_context-must_clause%'] }, { arr: [ctxObj.bool.must] }, ctxObj)
+ check({ arr: ['%dashboard_context-must_clause%'] }, { arr: [ctxObj.bool.must] }, ctxObj)
);
- it(
+ test(
`mixed clause arr`,
- test(
+ check(
{ arr: [1, '%dashboard_context-must_clause%', 2, '%dashboard_context-must_not_clause%'] },
{ arr: [1, ...ctxArr.bool.must, 2, ...ctxArr.bool.must_not] },
ctxArr
)
);
- it(
+ test(
`mixed clause obj`,
- test(
+ check(
{ arr: ['%dashboard_context-must_clause%', 1, '%dashboard_context-must_not_clause%', 2] },
{ arr: [ctxObj.bool.must, 1, ctxObj.bool.must_not, 2] },
ctxObj
)
);
- it(
+ test(
`%autointerval% = true`,
- test({ interval: { '%autointerval%': true } }, { interval: `1h` }, ctxObj)
+ check({ interval: { '%autointerval%': true } }, { interval: `1h` }, ctxObj)
);
- it(
+ test(
`%autointerval% = 10`,
- test({ interval: { '%autointerval%': 10 } }, { interval: `3h` }, ctxObj)
+ check({ interval: { '%autointerval%': 10 } }, { interval: `3h` }, ctxObj)
);
- it(`%timefilter% = min`, test({ a: { '%timefilter%': 'min' } }, { a: rangeStart }));
- it(`%timefilter% = max`, test({ a: { '%timefilter%': 'max' } }, { a: rangeEnd }));
- it(
+ test(`%timefilter% = min`, check({ a: { '%timefilter%': 'min' } }, { a: rangeStart }));
+ test(`%timefilter% = max`, check({ a: { '%timefilter%': 'max' } }, { a: rangeEnd }));
+ test(
`%timefilter% = true`,
- test(
+ check(
{ a: { '%timefilter%': true } },
{
a: {
@@ -168,24 +185,24 @@ describe(`EsQueryParser.injectQueryContextVars`, () => {
});
describe(`EsQueryParser.parseEsRequest`, () => {
- function test(req, ctx, expected) {
+ function check(req, ctx, expected) {
return () => {
create(rangeStart, rangeEnd, ctx).parseUrl({}, req);
- expect(req).to.eql(expected);
+ expect(req).toEqual(expected);
};
}
- it(
+ test(
`%context_query%=true`,
- test({ index: '_all', '%context_query%': true }, ctxArr, {
+ check({ index: '_all', '%context_query%': true }, ctxArr, {
index: '_all',
body: { query: ctxArr },
})
);
- it(
+ test(
`%context%=true`,
- test({ index: '_all', '%context%': true }, ctxArr, { index: '_all', body: { query: ctxArr } })
+ check({ index: '_all', '%context%': true }, ctxArr, { index: '_all', body: { query: ctxArr } })
);
const expectedForCtxAndTimefield = {
@@ -211,23 +228,23 @@ describe(`EsQueryParser.parseEsRequest`, () => {
},
};
- it(
+ test(
`%context_query%='abc'`,
- test({ index: '_all', '%context_query%': 'abc' }, ctxArr, expectedForCtxAndTimefield)
+ check({ index: '_all', '%context_query%': 'abc' }, ctxArr, expectedForCtxAndTimefield)
);
- it(
+ test(
`%context%=true, %timefield%='abc'`,
- test(
+ check(
{ index: '_all', '%context%': true, '%timefield%': 'abc' },
ctxArr,
expectedForCtxAndTimefield
)
);
- it(
+ test(
`%timefield%='abc'`,
- test({ index: '_all', '%timefield%': 'abc' }, ctxArr, {
+ check({ index: '_all', '%timefield%': 'abc' }, ctxArr, {
index: '_all',
body: {
query: {
@@ -243,11 +260,11 @@ describe(`EsQueryParser.parseEsRequest`, () => {
})
);
- it(`no esRequest`, test({ index: '_all' }, ctxArr, { index: '_all', body: {} }));
+ test(`no esRequest`, check({ index: '_all' }, ctxArr, { index: '_all', body: {} }));
- it(
+ test(
`esRequest`,
- test({ index: '_all', body: { query: 2 } }, ctxArr, {
+ check({ index: '_all', body: { query: 2 } }, ctxArr, {
index: '_all',
body: { query: 2 },
})
diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/search_cache.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js
similarity index 75%
rename from src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/search_cache.js
rename to src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js
index 2b28d2cadfa3f..0ec018f46c02b 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/search_cache.js
+++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/search_cache.test.js
@@ -17,8 +17,7 @@
* under the License.
*/
-import expect from '@kbn/expect';
-import { SearchCache } from '../search_cache';
+import { SearchCache } from './search_cache';
describe(`SearchCache`, () => {
class FauxEs {
@@ -45,27 +44,27 @@ describe(`SearchCache`, () => {
// empty request
let res = await sc.search([]);
- expect(res).to.eql([]);
- expect(sc._es.searches).to.eql([]);
+ expect(res).toEqual([]);
+ expect(sc._es.searches).toEqual([]);
// single request
res = await sc.search([request1]);
- expect(res).to.eql([expected1]);
- expect(sc._es.searches).to.eql([request1]);
+ expect(res).toEqual([expected1]);
+ expect(sc._es.searches).toEqual([request1]);
// repeat the same search, use array notation
res = await sc.search([request1]);
- expect(res).to.eql([expected1]);
- expect(sc._es.searches).to.eql([request1]); // no new entries
+ expect(res).toEqual([expected1]);
+ expect(sc._es.searches).toEqual([request1]); // no new entries
// new single search
res = await sc.search([request2]);
- expect(res).to.eql([expected2]);
- expect(sc._es.searches).to.eql([request1, request2]);
+ expect(res).toEqual([expected2]);
+ expect(sc._es.searches).toEqual([request1, request2]);
// multiple search, some new, some old
res = await sc.search([request1, request3, request2]);
- expect(res).to.eql([expected1, expected3, expected2]);
- expect(sc._es.searches).to.eql([request1, request2, request3]);
+ expect(res).toEqual([expected1, expected3, expected2]);
+ expect(sc._es.searches).toEqual([request1, request2, request3]);
});
});
diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/time_cache.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js
similarity index 72%
rename from src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/time_cache.js
rename to src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js
index 21e9256295382..b76709ea2c934 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/time_cache.js
+++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/time_cache.test.js
@@ -17,8 +17,7 @@
* under the License.
*/
-import expect from '@kbn/expect';
-import { TimeCache } from '../time_cache';
+import { TimeCache } from './time_cache';
describe(`TimeCache`, () => {
class FauxTimefilter {
@@ -70,29 +69,29 @@ describe(`TimeCache`, () => {
let filterAccess = 0;
// first call - gets bounds
- expect(tc.getTimeBounds()).to.eql({ min: 10000, max: 20000 });
- expect(time._accessCount).to.be(++timeAccess);
- expect(timefilter._accessCount).to.be(++filterAccess);
+ expect(tc.getTimeBounds()).toEqual({ min: 10000, max: 20000 });
+ expect(time._accessCount).toBe(++timeAccess);
+ expect(timefilter._accessCount).toBe(++filterAccess);
// short diff, same result
time.increment(10);
timefilter.setTime(10010, 20010);
- expect(tc.getTimeBounds()).to.eql({ min: 10000, max: 20000 });
- expect(time._accessCount).to.be(++timeAccess);
- expect(timefilter._accessCount).to.be(filterAccess);
+ expect(tc.getTimeBounds()).toEqual({ min: 10000, max: 20000 });
+ expect(time._accessCount).toBe(++timeAccess);
+ expect(timefilter._accessCount).toBe(filterAccess);
// longer diff, gets bounds but returns original
time.increment(200);
timefilter.setTime(10210, 20210);
- expect(tc.getTimeBounds()).to.eql({ min: 10000, max: 20000 });
- expect(time._accessCount).to.be(++timeAccess);
- expect(timefilter._accessCount).to.be(++filterAccess);
+ expect(tc.getTimeBounds()).toEqual({ min: 10000, max: 20000 });
+ expect(time._accessCount).toBe(++timeAccess);
+ expect(timefilter._accessCount).toBe(++filterAccess);
// long diff, new result
time.increment(10000);
timefilter.setTime(20220, 30220);
- expect(tc.getTimeBounds()).to.eql({ min: 20220, max: 30220 });
- expect(time._accessCount).to.be(++timeAccess);
- expect(timefilter._accessCount).to.be(++filterAccess);
+ expect(tc.getTimeBounds()).toEqual({ min: 20220, max: 30220 });
+ expect(time._accessCount).toBe(++timeAccess);
+ expect(timefilter._accessCount).toBe(++filterAccess);
});
});
diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js
new file mode 100644
index 0000000000000..1bc8b1f90daab
--- /dev/null
+++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.test.js
@@ -0,0 +1,327 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { cloneDeep } from 'lodash';
+import { VegaParser } from './vega_parser';
+import { bypassExternalUrlCheck } from '../vega_view/vega_base_view';
+
+describe(`VegaParser._setDefaultValue`, () => {
+ function check(spec, expected, ...params) {
+ return () => {
+ const vp = new VegaParser(spec);
+ vp._setDefaultValue(...params);
+ expect(vp.spec).toEqual(expected);
+ expect(vp.warnings).toHaveLength(0);
+ };
+ }
+
+ test(`empty`, check({}, { config: { test: 42 } }, 42, 'config', 'test'));
+ test(`exists`, check({ config: { test: 42 } }, { config: { test: 42 } }, 1, 'config', 'test'));
+ test(`exists non-obj`, check({ config: false }, { config: false }, 42, 'config', 'test'));
+});
+
+describe(`VegaParser._setDefaultColors`, () => {
+ function check(spec, isVegaLite, expected) {
+ return () => {
+ const vp = new VegaParser(spec);
+ vp.isVegaLite = isVegaLite;
+ vp._setDefaultColors();
+ expect(vp.spec).toEqual(expected);
+ expect(vp.warnings).toHaveLength(0);
+ };
+ }
+
+ test(
+ `vegalite`,
+ check({}, true, {
+ config: {
+ range: { category: { scheme: 'elastic' } },
+ mark: { color: '#54B399' },
+ },
+ })
+ );
+
+ test(
+ `vega`,
+ check({}, false, {
+ config: {
+ range: { category: { scheme: 'elastic' } },
+ arc: { fill: '#54B399' },
+ area: { fill: '#54B399' },
+ line: { stroke: '#54B399' },
+ path: { stroke: '#54B399' },
+ rect: { fill: '#54B399' },
+ rule: { stroke: '#54B399' },
+ shape: { stroke: '#54B399' },
+ symbol: { fill: '#54B399' },
+ trail: { fill: '#54B399' },
+ },
+ })
+ );
+});
+
+describe('VegaParser._resolveEsQueries', () => {
+ function check(spec, expected, warnCount) {
+ return async () => {
+ const vp = new VegaParser(spec, { search: async () => [[42]] }, 0, 0, {
+ getFileLayers: async () => [{ name: 'file1', url: 'url1' }],
+ getUrlForRegionLayer: async layer => {
+ return layer.url;
+ },
+ });
+ await vp._resolveDataUrls();
+
+ expect(vp.spec).toEqual(expected);
+ expect(vp.warnings).toHaveLength(warnCount || 0);
+ };
+ }
+
+ test('no data', check({}, {}));
+ test('no data2', check({ a: 1 }, { a: 1 }));
+ test('non-es data', check({ data: { a: 10 } }, { data: { a: 10 } }));
+ test('es', check({ data: { url: { index: 'a' }, x: 1 } }, { data: { values: [42], x: 1 } }));
+ test(
+ 'es 2',
+ check({ data: { url: { '%type%': 'elasticsearch', index: 'a' } } }, { data: { values: [42] } })
+ );
+ test(
+ 'es arr',
+ check(
+ { arr: [{ data: { url: { index: 'a' }, x: 1 } }] },
+ { arr: [{ data: { values: [42], x: 1 } }] }
+ )
+ );
+ test(
+ 'emsfile',
+ check(
+ { data: { url: { '%type%': 'emsfile', name: 'file1' } } },
+ { data: { url: bypassExternalUrlCheck('url1') } }
+ )
+ );
+});
+
+describe('VegaParser._parseSchema', () => {
+ function check(schema, isVegaLite, warningCount) {
+ return () => {
+ const vp = new VegaParser({ $schema: schema });
+ expect(vp._parseSchema()).toBe(isVegaLite);
+ expect(vp.spec).toEqual({ $schema: schema });
+ expect(vp.warnings).toHaveLength(warningCount);
+ };
+ }
+
+ test('should warn on no vega version specified', () => {
+ const vp = new VegaParser({});
+ expect(vp._parseSchema()).toBe(false);
+ expect(vp.spec).toEqual({ $schema: 'https://vega.github.io/schema/vega/v3.0.json' });
+ expect(vp.warnings).toHaveLength(1);
+ });
+
+ test(
+ 'should not warn on current vega version',
+ check('https://vega.github.io/schema/vega/v4.0.json', false, 0)
+ );
+ test(
+ 'should not warn on older vega version',
+ check('https://vega.github.io/schema/vega/v3.0.json', false, 0)
+ );
+ test(
+ 'should warn on vega version too new to be supported',
+ check('https://vega.github.io/schema/vega/v5.0.json', false, 1)
+ );
+
+ test(
+ 'should not warn on current vega-lite version',
+ check('https://vega.github.io/schema/vega-lite/v2.0.json', true, 0)
+ );
+ test(
+ 'should warn on vega-lite version too new to be supported',
+ check('https://vega.github.io/schema/vega-lite/v3.0.json', true, 1)
+ );
+});
+
+describe('VegaParser._parseTooltips', () => {
+ function check(tooltips, position, padding, centerOnMark) {
+ return () => {
+ const vp = new VegaParser(tooltips !== undefined ? { config: { kibana: { tooltips } } } : {});
+ vp._config = vp._parseConfig();
+ if (position === undefined) {
+ // error
+ expect(() => vp._parseTooltips()).toThrow();
+ } else if (position === false) {
+ expect(vp._parseTooltips()).toEqual(false);
+ } else {
+ expect(vp._parseTooltips()).toEqual({ position, padding, centerOnMark });
+ }
+ };
+ }
+
+ test('undefined', check(undefined, 'top', 16, 50));
+ test('{}', check({}, 'top', 16, 50));
+ test('left', check({ position: 'left' }, 'left', 16, 50));
+ test('padding', check({ position: 'bottom', padding: 60 }, 'bottom', 60, 50));
+ test('padding2', check({ padding: 70 }, 'top', 70, 50));
+ test('centerOnMark', check({}, 'top', 16, 50));
+ test('centerOnMark=10', check({ centerOnMark: 10 }, 'top', 16, 10));
+ test('centerOnMark=true', check({ centerOnMark: true }, 'top', 16, Number.MAX_VALUE));
+ test('centerOnMark=false', check({ centerOnMark: false }, 'top', 16, -1));
+
+ test('false', check(false, false));
+
+ test('err1', check(true, undefined));
+ test('err2', check({ position: 'foo' }, undefined));
+ test('err3', check({ padding: 'foo' }, undefined));
+ test('err4', check({ centerOnMark: {} }, undefined));
+});
+
+describe('VegaParser._parseMapConfig', () => {
+ function check(config, expected, warnCount) {
+ return () => {
+ const vp = new VegaParser();
+ vp._config = config;
+ expect(vp._parseMapConfig()).toEqual(expected);
+ expect(vp.warnings).toHaveLength(warnCount);
+ };
+ }
+
+ test(
+ 'empty',
+ check(
+ {},
+ {
+ delayRepaint: true,
+ latitude: 0,
+ longitude: 0,
+ mapStyle: 'default',
+ zoomControl: true,
+ scrollWheelZoom: false,
+ },
+ 0
+ )
+ );
+
+ test(
+ 'filled',
+ check(
+ {
+ delayRepaint: true,
+ latitude: 0,
+ longitude: 0,
+ mapStyle: 'default',
+ zoomControl: true,
+ scrollWheelZoom: false,
+ maxBounds: [1, 2, 3, 4],
+ },
+ {
+ delayRepaint: true,
+ latitude: 0,
+ longitude: 0,
+ mapStyle: 'default',
+ zoomControl: true,
+ scrollWheelZoom: false,
+ maxBounds: [1, 2, 3, 4],
+ },
+ 0
+ )
+ );
+
+ test(
+ 'warnings',
+ check(
+ {
+ delayRepaint: true,
+ latitude: 0,
+ longitude: 0,
+ zoom: 'abc', // ignored
+ mapStyle: 'abc',
+ zoomControl: 'abc',
+ scrollWheelZoom: 'abc',
+ maxBounds: [2, 3, 4],
+ },
+ {
+ delayRepaint: true,
+ latitude: 0,
+ longitude: 0,
+ mapStyle: 'default',
+ zoomControl: true,
+ scrollWheelZoom: false,
+ },
+ 5
+ )
+ );
+});
+
+describe('VegaParser._parseConfig', () => {
+ function check(spec, expectedConfig, expectedSpec, warnCount) {
+ return async () => {
+ expectedSpec = expectedSpec || cloneDeep(spec);
+ const vp = new VegaParser(spec);
+ const config = await vp._parseConfig();
+ expect(config).toEqual(expectedConfig);
+ expect(vp.spec).toEqual(expectedSpec);
+ expect(vp.warnings).toHaveLength(warnCount || 0);
+ };
+ }
+
+ test('no config', check({}, {}, {}));
+ test('simple config', check({ config: { a: 1 } }, {}));
+ test('kibana config', check({ config: { kibana: { a: 1 } } }, { a: 1 }, { config: {} }));
+ test('_hostConfig', check({ _hostConfig: { a: 1 } }, { a: 1 }, {}, 1));
+});
+
+describe('VegaParser._calcSizing', () => {
+ function check(
+ spec,
+ useResize,
+ paddingWidth,
+ paddingHeight,
+ isVegaLite,
+ expectedSpec,
+ warnCount
+ ) {
+ return async () => {
+ expectedSpec = expectedSpec || cloneDeep(spec);
+ const vp = new VegaParser(spec);
+ vp.isVegaLite = !!isVegaLite;
+ vp._calcSizing();
+ expect(vp.useResize).toEqual(useResize);
+ expect(vp.paddingWidth).toEqual(paddingWidth);
+ expect(vp.paddingHeight).toEqual(paddingHeight);
+ expect(vp.spec).toEqual(expectedSpec);
+ expect(vp.warnings).toHaveLength(warnCount || 0);
+ };
+ }
+
+ test('no size', check({ autosize: {} }, false, 0, 0));
+ test('fit', check({ autosize: 'fit' }, true, 0, 0));
+ test('fit obj', check({ autosize: { type: 'fit' } }, true, 0, 0));
+ test('padding const', check({ autosize: 'fit', padding: 10 }, true, 20, 20));
+ test(
+ 'padding obj',
+ check({ autosize: 'fit', padding: { left: 5, bottom: 7, right: 6, top: 8 } }, true, 11, 15)
+ );
+ test(
+ 'width height',
+ check({ autosize: 'fit', width: 1, height: 2 }, true, 0, 0, false, false, 1)
+ );
+ test(
+ 'VL width height',
+ check({ autosize: 'fit', width: 1, height: 2 }, true, 0, 0, true, { autosize: 'fit' }, 0)
+ );
+});
diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js
index 8ab55d4c517b5..6991527a9503c 100644
--- a/src/legacy/server/kbn_server.js
+++ b/src/legacy/server/kbn_server.js
@@ -36,7 +36,6 @@ import * as Plugins from './plugins';
import { indexPatternsMixin } from './index_patterns';
import { savedObjectsMixin } from './saved_objects/saved_objects_mixin';
import { capabilitiesMixin } from './capabilities';
-import { urlShorteningMixin } from './url_shortening';
import { serverExtensionsMixin } from './server_extensions';
import { uiMixin } from '../ui';
import { sassMixin } from './sass';
@@ -123,9 +122,6 @@ export default class KbnServer {
// setup capabilities routes
capabilitiesMixin,
- // setup routes for short urls
- urlShorteningMixin,
-
// ensure that all bundles are built, or that the
// watch bundle server is running
optimizeMixin,
diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js b/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js
deleted file mode 100644
index ff2b0f214e5ee..0000000000000
--- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { parse } from 'url';
-import { trim } from 'lodash';
-import Boom from 'boom';
-
-export function shortUrlAssertValid(url) {
- const { protocol, hostname, pathname } = parse(url);
-
- if (protocol) {
- throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`);
- }
-
- if (hostname) {
- throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`);
- }
-
- const pathnameParts = trim(pathname, '/').split('/');
- if (pathnameParts.length !== 2) {
- throw Boom.notAcceptable(
- `Short url target path must be in the format "/app/{{appId}}", found "${pathname}"`
- );
- }
-}
diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js
deleted file mode 100644
index f83073e6aefe9..0000000000000
--- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { shortUrlAssertValid } from './short_url_assert_valid';
-
-describe('shortUrlAssertValid()', () => {
- const invalid = [
- ['protocol', 'http://localhost:5601/app/kibana'],
- ['protocol', 'https://localhost:5601/app/kibana'],
- ['protocol', 'mailto:foo@bar.net'],
- ['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url
- ['hostname', 'localhost/app/kibana'],
- ['hostname and port', 'local.host:5601/app/kibana'],
- ['hostname and auth', 'user:pass@localhost.net/app/kibana'],
- ['path traversal', '/app/../../not-kibana'],
- ['deep path', '/app/kibana/foo'],
- ['deep path', '/app/kibana/foo/bar'],
- ['base path', '/base/app/kibana'],
- ];
-
- invalid.forEach(([desc, url]) => {
- it(`fails when url has ${desc}`, () => {
- try {
- shortUrlAssertValid(url);
- throw new Error(`expected assertion to throw`);
- } catch (err) {
- if (!err || !err.isBoom) {
- throw err;
- }
- }
- });
- });
-
- const valid = [
- '/app/kibana',
- '/app/monitoring#angular/route',
- '/app/text#document-id',
- '/app/some?with=query',
- '/app/some?with=query#and-a-hash',
- ];
-
- valid.forEach(url => {
- it(`allows ${url}`, () => {
- shortUrlAssertValid(url);
- });
- });
-});
diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js
deleted file mode 100644
index 4eca6320ec834..0000000000000
--- a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import { handleShortUrlError } from './short_url_error';
-
-function createErrorWithStatus(status) {
- const error = new Error();
- error.status = status;
- return error;
-}
-
-function createErrorWithStatusCode(statusCode) {
- const error = new Error();
- error.statusCode = statusCode;
- return error;
-}
-
-describe('handleShortUrlError()', () => {
- const caughtErrorsWithStatus = [
- createErrorWithStatus(401),
- createErrorWithStatus(403),
- createErrorWithStatus(404),
- ];
-
- const caughtErrorsWithStatusCode = [
- createErrorWithStatusCode(401),
- createErrorWithStatusCode(403),
- createErrorWithStatusCode(404),
- ];
-
- const uncaughtErrors = [new Error(), createErrorWithStatus(500), createErrorWithStatusCode(500)];
-
- caughtErrorsWithStatus.forEach(err => {
- it(`should handle errors with status of ${err.status}`, function() {
- expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.status);
- });
- });
-
- caughtErrorsWithStatusCode.forEach(err => {
- it(`should handle errors with statusCode of ${err.statusCode}`, function() {
- expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.statusCode);
- });
- });
-
- uncaughtErrors.forEach(err => {
- it(`should not handle unknown errors`, function() {
- expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(500);
- });
- });
-});
diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js
deleted file mode 100644
index e8bf72a142d11..0000000000000
--- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import sinon from 'sinon';
-import { shortUrlLookupProvider } from './short_url_lookup';
-import { SavedObjectsClient } from '../../../../../core/server';
-
-describe('shortUrlLookupProvider', () => {
- const ID = 'bf00ad16941fc51420f91a93428b27a0';
- const TYPE = 'url';
- const URL = 'http://elastic.co';
- const server = { log: sinon.stub() };
- const sandbox = sinon.createSandbox();
-
- let savedObjectsClient;
- let req;
- let shortUrl;
-
- beforeEach(() => {
- savedObjectsClient = {
- get: sandbox.stub(),
- create: sandbox.stub().returns(Promise.resolve({ id: ID })),
- update: sandbox.stub(),
- errors: SavedObjectsClient.errors,
- };
-
- req = { getSavedObjectsClient: () => savedObjectsClient };
- shortUrl = shortUrlLookupProvider(server);
- });
-
- afterEach(() => {
- sandbox.restore();
- });
-
- describe('getUrl', () => {
- beforeEach(() => {
- const attributes = { accessCount: 2, url: URL };
- savedObjectsClient.get.returns({ id: ID, attributes });
- });
-
- it('provides the ID to savedObjectsClient', async () => {
- await shortUrl.getUrl(ID, req);
-
- sinon.assert.calledOnce(savedObjectsClient.get);
- const [type, id] = savedObjectsClient.get.getCall(0).args;
-
- expect(type).toEqual(TYPE);
- expect(id).toEqual(ID);
- });
-
- it('returns the url', async () => {
- const response = await shortUrl.getUrl(ID, req);
- expect(response).toEqual(URL);
- });
-
- it('increments accessCount', async () => {
- await shortUrl.getUrl(ID, req);
-
- sinon.assert.calledOnce(savedObjectsClient.update);
- const [type, id, attributes] = savedObjectsClient.update.getCall(0).args;
-
- expect(type).toEqual(TYPE);
- expect(id).toEqual(ID);
- expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']);
- expect(attributes.accessCount).toEqual(3);
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/param_types/field.ts b/src/legacy/ui/public/agg_types/param_types/field.ts
index 0ca60267becec..a0fa6ad6e3189 100644
--- a/src/legacy/ui/public/agg_types/param_types/field.ts
+++ b/src/legacy/ui/public/agg_types/param_types/field.ts
@@ -115,7 +115,10 @@ export class FieldParamType extends BaseParamType {
const filteredFields = fields.filter((field: Field) => {
const { onlyAggregatable, scriptable, filterFieldTypes } = this;
- if ((onlyAggregatable && !field.aggregatable) || (!scriptable && field.scripted)) {
+ if (
+ (onlyAggregatable && (!field.aggregatable || field.subType?.nested)) ||
+ (!scriptable && field.scripted)
+ ) {
return false;
}
diff --git a/src/legacy/ui/public/chrome/api/controls.ts b/src/legacy/ui/public/chrome/api/controls.ts
index 6dde26be20df2..a95d53ec2eab6 100644
--- a/src/legacy/ui/public/chrome/api/controls.ts
+++ b/src/legacy/ui/public/chrome/api/controls.ts
@@ -42,4 +42,6 @@ export function initChromeControlsApi(chrome: { [key: string]: any }) {
* might be incorrect in the moments just before the UI is updated.
*/
chrome.getVisible = () => visible$.getValue();
+
+ chrome.visible$ = visible$.asObservable();
}
diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.js b/src/legacy/ui/public/chrome/directives/kbn_chrome.js
index 72d26a37a60a1..4c5d7981e962a 100644
--- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js
+++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js
@@ -30,6 +30,7 @@ import {
chromeHeaderNavControlsRegistry,
NavControlSide,
} from '../../registry/chrome_header_nav_controls';
+import { subscribeWithScope } from '../../utils/subscribe_with_scope';
export function kbnChromeProvider(chrome, internals) {
uiModules.get('kibana').directive('kbnChrome', () => {
@@ -83,6 +84,15 @@ export function kbnChromeProvider(chrome, internals) {
);
}
+ const chromeVisibility = subscribeWithScope($scope, chrome.visible$, {
+ next: () => {
+ // just makes sure change detection is triggered when chrome visibility changes
+ },
+ });
+ $scope.$on('$destroy', () => {
+ chromeVisibility.unsubscribe();
+ });
+
return chrome;
},
};
diff --git a/src/legacy/ui/public/directives/__tests__/css_truncate.js b/src/legacy/ui/public/directives/__tests__/css_truncate.js
index 9d470c10358cc..bf102f5a29fdb 100644
--- a/src/legacy/ui/public/directives/__tests__/css_truncate.js
+++ b/src/legacy/ui/public/directives/__tests__/css_truncate.js
@@ -20,7 +20,7 @@
import angular from 'angular';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
-import 'plugins/kibana/discover/index';
+import 'plugins/kibana/discover/legacy';
let $parentScope;
diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js
index 289d4b8006cba..c2274eae59f50 100644
--- a/src/legacy/ui/public/state_management/state.js
+++ b/src/legacy/ui/public/state_management/state.js
@@ -29,7 +29,6 @@ import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import angular from 'angular';
import rison from 'rison-node';
-import { applyDiff } from './utils/diff_object';
import { EventsProvider } from '../events';
import { fatalError, toastNotifications } from '../notify';
import './config_provider';
@@ -38,6 +37,7 @@ import {
hashedItemStore,
isStateHash,
createStateHash,
+ applyDiff,
} from '../../../../plugins/kibana_utils/public';
export function StateProvider(
diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts
index c25e5df2b168a..f7d0dddd5bf03 100644
--- a/src/plugins/data/public/query/filter_manager/filter_manager.ts
+++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts
@@ -22,7 +22,8 @@ import { Subject } from 'rxjs';
import { IUiSettingsClient } from 'src/core/public';
-import { compareFilters } from './lib/compare_filters';
+import { compareFilters, COMPARE_ALL_OPTIONS } from './lib/compare_filters';
+import { sortFilters } from './lib/sort_filters';
import { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
import { uniqFilters } from './lib/uniq_filters';
import { onlyDisabledFiltersChanged } from './lib/only_disabled';
@@ -76,20 +77,9 @@ export class FilterManager {
}
private handleStateUpdate(newFilters: esFilters.Filter[]) {
- // global filters should always be first
-
- newFilters.sort(({ $state: a }: esFilters.Filter, { $state: b }: esFilters.Filter): number => {
- if (a!.store === b!.store) {
- return 0;
- } else {
- return a!.store === esFilters.FilterStateStore.GLOBAL_STATE &&
- b!.store !== esFilters.FilterStateStore.GLOBAL_STATE
- ? -1
- : 1;
- }
- });
+ newFilters.sort(sortFilters);
- const filtersUpdated = !_.isEqual(this.filters, newFilters);
+ const filtersUpdated = !compareFilters(this.filters, newFilters, COMPARE_ALL_OPTIONS);
const updatedOnlyDisabledFilters = onlyDisabledFiltersChanged(newFilters, this.filters);
this.filters = newFilters;
diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts
index 34fd662c4ba46..9cc5938750c4e 100644
--- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { compareFilters } from './compare_filters';
+import { compareFilters, COMPARE_ALL_OPTIONS } from './compare_filters';
import { esFilters } from '../../../../common';
describe('filter manager utilities', () => {
@@ -83,5 +83,134 @@ describe('filter manager utilities', () => {
expect(compareFilters(f1, f2)).toBeTruthy();
});
+
+ test('should compare filters array to non array', () => {
+ const f1 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ );
+
+ const f2 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'mochi', type: 'phrase' } } },
+ 'index',
+ ''
+ );
+
+ expect(compareFilters([f1, f2], f1)).toBeFalsy();
+ });
+
+ test('should compare filters array to partial array', () => {
+ const f1 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ );
+
+ const f2 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'mochi', type: 'phrase' } } },
+ 'index',
+ ''
+ );
+
+ expect(compareFilters([f1, f2], [f1])).toBeFalsy();
+ });
+
+ test('should compare filters array to exact array', () => {
+ const f1 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ );
+
+ const f2 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'mochi', type: 'phrase' } } },
+ 'index',
+ ''
+ );
+
+ expect(compareFilters([f1, f2], [f1, f2])).toBeTruthy();
+ });
+
+ test('should compare array of duplicates, ignoring meta attributes', () => {
+ const f1 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index1',
+ ''
+ );
+ const f2 = esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index2',
+ ''
+ );
+
+ expect(compareFilters([f1], [f2])).toBeTruthy();
+ });
+
+ test('should compare array of duplicates, ignoring $state attributes', () => {
+ const f1 = {
+ $state: { store: esFilters.FilterStateStore.APP_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+ const f2 = {
+ $state: { store: esFilters.FilterStateStore.GLOBAL_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+
+ expect(compareFilters([f1], [f2])).toBeTruthy();
+ });
+
+ test('should compare duplicates with COMPARE_ALL_OPTIONS should check store', () => {
+ const f1 = {
+ $state: { store: esFilters.FilterStateStore.APP_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+ const f2 = {
+ $state: { store: esFilters.FilterStateStore.GLOBAL_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+
+ expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeFalsy();
+ });
+
+ test('should compare duplicates with COMPARE_ALL_OPTIONS should not check key and value ', () => {
+ const f1 = {
+ $state: { store: esFilters.FilterStateStore.GLOBAL_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+ const f2 = {
+ $state: { store: esFilters.FilterStateStore.GLOBAL_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+
+ f2.meta.key = 'wassup';
+ f2.meta.value = 'dog';
+
+ expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeTruthy();
+ });
});
});
diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts
index 9b171ab0aacb2..218b9d492b61f 100644
--- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts
@@ -17,32 +17,64 @@
* under the License.
*/
-import { defaults, isEqual, omit } from 'lodash';
+import { defaults, isEqual, omit, map } from 'lodash';
import { esFilters } from '../../../../common';
+export interface FilterCompareOptions {
+ disabled?: boolean;
+ negate?: boolean;
+ state?: boolean;
+}
+
+/**
+ * Include disabled, negate and store when comparing filters
+ */
+export const COMPARE_ALL_OPTIONS: FilterCompareOptions = {
+ disabled: true,
+ negate: true,
+ state: true,
+};
+
+const mapFilter = (
+ filter: esFilters.Filter,
+ comparators: FilterCompareOptions,
+ excludedAttributes: string[]
+) => {
+ const cleaned: esFilters.FilterMeta = omit(filter, excludedAttributes);
+
+ if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate);
+ if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled);
+
+ return cleaned;
+};
+
+const mapFilterArray = (
+ filters: esFilters.Filter[],
+ comparators: FilterCompareOptions,
+ excludedAttributes: string[]
+) => {
+ return map(filters, (filter: esFilters.Filter) =>
+ mapFilter(filter, comparators, excludedAttributes)
+ );
+};
+
/**
- * Compare two filters to see if they match
+ * Compare two filters or filter arrays to see if they match.
+ * For filter arrays, the assumption is they are sorted.
*
- * @param {object} first The first filter to compare
- * @param {object} second The second filter to compare
- * @param {object} comparatorOptions Parameters to use for comparison
+ * @param {esFilters.Filter | esFilters.Filter[]} first The first filter or filter array to compare
+ * @param {esFilters.Filter | esFilters.Filter[]} second The second filter or filter array to compare
+ * @param {FilterCompareOptions} comparatorOptions Parameters to use for comparison
*
* @returns {bool} Filters are the same
*/
export const compareFilters = (
- first: esFilters.Filter,
- second: esFilters.Filter,
- comparatorOptions: any = {}
+ first: esFilters.Filter | esFilters.Filter[],
+ second: esFilters.Filter | esFilters.Filter[],
+ comparatorOptions: FilterCompareOptions = {}
) => {
- let comparators: any = {};
- const mapFilter = (filter: esFilters.Filter) => {
- const cleaned: esFilters.FilterMeta = omit(filter, excludedAttributes);
-
- if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate);
- if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled);
+ let comparators: FilterCompareOptions = {};
- return cleaned;
- };
const excludedAttributes: string[] = ['$$hashKey', 'meta'];
comparators = defaults(comparatorOptions || {}, {
@@ -53,5 +85,17 @@ export const compareFilters = (
if (!comparators.state) excludedAttributes.push('$state');
- return isEqual(mapFilter(first), mapFilter(second));
+ if (Array.isArray(first) && Array.isArray(second)) {
+ return isEqual(
+ mapFilterArray(first, comparators, excludedAttributes),
+ mapFilterArray(second, comparators, excludedAttributes)
+ );
+ } else if (!Array.isArray(first) && !Array.isArray(second)) {
+ return isEqual(
+ mapFilter(first, comparators, excludedAttributes),
+ mapFilter(second, comparators, excludedAttributes)
+ );
+ } else {
+ return false;
+ }
};
diff --git a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts
index 6dae14f480b4f..897a645e87b5a 100644
--- a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts
@@ -18,7 +18,7 @@
*/
import { filter, find } from 'lodash';
-import { compareFilters } from './compare_filters';
+import { compareFilters, FilterCompareOptions } from './compare_filters';
import { esFilters } from '../../../../common';
/**
@@ -33,7 +33,7 @@ import { esFilters } from '../../../../common';
export const dedupFilters = (
existingFilters: esFilters.Filter[],
filters: esFilters.Filter[],
- comparatorOptions: any = {}
+ comparatorOptions: FilterCompareOptions = {}
) => {
if (!Array.isArray(filters)) {
filters = [filters];
diff --git a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts
index c040d2f2960c7..3f35c94a3f858 100644
--- a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts
@@ -17,8 +17,9 @@
* under the License.
*/
-import { filter, isEqual } from 'lodash';
+import { filter } from 'lodash';
import { esFilters } from '../../../../common';
+import { compareFilters, COMPARE_ALL_OPTIONS } from './compare_filters';
const isEnabled = (f: esFilters.Filter) => f && f.meta && !f.meta.disabled;
@@ -35,5 +36,5 @@ export const onlyDisabledFiltersChanged = (
const newEnabledFilters = filter(newFilters || [], isEnabled);
const oldEnabledFilters = filter(oldFilters || [], isEnabled);
- return isEqual(oldEnabledFilters, newEnabledFilters);
+ return compareFilters(oldEnabledFilters, newEnabledFilters, COMPARE_ALL_OPTIONS);
};
diff --git a/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts
new file mode 100644
index 0000000000000..949c57e43ce74
--- /dev/null
+++ b/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts
@@ -0,0 +1,91 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { sortFilters } from './sort_filters';
+import { esFilters } from '../../../../common';
+
+describe('sortFilters', () => {
+ describe('sortFilters()', () => {
+ test('Not sort two application level filters', () => {
+ const f1 = {
+ $state: { store: esFilters.FilterStateStore.APP_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+ const f2 = {
+ $state: { store: esFilters.FilterStateStore.APP_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+
+ const filters = [f1, f2].sort(sortFilters);
+ expect(filters[0]).toBe(f1);
+ });
+
+ test('Not sort two global level filters', () => {
+ const f1 = {
+ $state: { store: esFilters.FilterStateStore.GLOBAL_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+ const f2 = {
+ $state: { store: esFilters.FilterStateStore.GLOBAL_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+
+ const filters = [f1, f2].sort(sortFilters);
+ expect(filters[0]).toBe(f1);
+ });
+
+ test('Move global level filter to the beginning of the array', () => {
+ const f1 = {
+ $state: { store: esFilters.FilterStateStore.APP_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+ const f2 = {
+ $state: { store: esFilters.FilterStateStore.GLOBAL_STATE },
+ ...esFilters.buildQueryFilter(
+ { _type: { match: { query: 'apache', type: 'phrase' } } },
+ 'index',
+ ''
+ ),
+ };
+
+ const filters = [f1, f2].sort(sortFilters);
+ expect(filters[0]).toBe(f2);
+ });
+ });
+});
diff --git a/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts b/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts
new file mode 100644
index 0000000000000..657c80fe0ccb2
--- /dev/null
+++ b/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts
@@ -0,0 +1,42 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { esFilters } from '../../../../common';
+
+/**
+ * Sort filters according to their store - global filters go first
+ *
+ * @param {object} first The first filter to compare
+ * @param {object} second The second filter to compare
+ *
+ * @returns {number} Sorting order of filters
+ */
+export const sortFilters = (
+ { $state: a }: esFilters.Filter,
+ { $state: b }: esFilters.Filter
+): number => {
+ if (a!.store === b!.store) {
+ return 0;
+ } else {
+ return a!.store === esFilters.FilterStateStore.GLOBAL_STATE &&
+ b!.store !== esFilters.FilterStateStore.GLOBAL_STATE
+ ? -1
+ : 1;
+ }
+};
diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap
index bc08c87304fca..d8db53d4c6020 100644
--- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap
+++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap
@@ -304,6 +304,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
},
},
},
+ "fatalErrors": Object {
+ "add": [MockFunction],
+ "get$": [MockFunction],
+ },
"http": Object {
"addLoadingCountSource": [MockFunction],
"anonymousPaths": Object {
@@ -930,6 +934,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
},
},
},
+ "fatalErrors": Object {
+ "add": [MockFunction],
+ "get$": [MockFunction],
+ },
"http": Object {
"addLoadingCountSource": [MockFunction],
"anonymousPaths": Object {
@@ -1538,6 +1546,10 @@ exports[`QueryStringInput Should pass the query language to the language switche
},
},
},
+ "fatalErrors": Object {
+ "add": [MockFunction],
+ "get$": [MockFunction],
+ },
"http": Object {
"addLoadingCountSource": [MockFunction],
"anonymousPaths": Object {
@@ -2161,6 +2173,10 @@ exports[`QueryStringInput Should pass the query language to the language switche
},
},
},
+ "fatalErrors": Object {
+ "add": [MockFunction],
+ "get$": [MockFunction],
+ },
"http": Object {
"addLoadingCountSource": [MockFunction],
"anonymousPaths": Object {
@@ -2769,6 +2785,10 @@ exports[`QueryStringInput Should render the given query 1`] = `
},
},
},
+ "fatalErrors": Object {
+ "add": [MockFunction],
+ "get$": [MockFunction],
+ },
"http": Object {
"addLoadingCountSource": [MockFunction],
"anonymousPaths": Object {
@@ -3392,6 +3412,10 @@ exports[`QueryStringInput Should render the given query 1`] = `
},
},
},
+ "fatalErrors": Object {
+ "add": [MockFunction],
+ "get$": [MockFunction],
+ },
"http": Object {
"addLoadingCountSource": [MockFunction],
"anonymousPaths": Object {
diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js
index 3ec903d5b18e4..8ddd18c2c67f4 100644
--- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js
+++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js
@@ -37,7 +37,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => {
describe('conflicts', () => {
it('returns a field for each in response, no filtering', () => {
const fields = readFieldCapsResponse(esResponse);
- expect(fields).toHaveLength(25);
+ expect(fields).toHaveLength(24);
});
it(
@@ -68,8 +68,8 @@ describe('index_patterns/field_capabilities/field_caps_response', () => {
sandbox.spy(shouldReadFieldFromDocValuesNS, 'shouldReadFieldFromDocValues');
const fields = readFieldCapsResponse(esResponse);
const conflictCount = fields.filter(f => f.type === 'conflict').length;
- // +1 is for the object field which is filtered out of the final return value from readFieldCapsResponse
- sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 1);
+ // +2 is for the object and nested fields which get filtered out of the final return value from readFieldCapsResponse
+ sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 2);
});
it('converts es types to kibana types', () => {
@@ -143,13 +143,6 @@ describe('index_patterns/field_capabilities/field_caps_response', () => {
expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' } });
});
- it('returns nested sub-fields as non-aggregatable', () => {
- const fields = readFieldCapsResponse(esResponse);
- // Normally a keyword field would be aggregatable, but the fact that it is nested overrides that
- const child = fields.find(f => f.name === 'nested_object_parent.child.keyword');
- expect(child).toHaveProperty('aggregatable', false);
- });
-
it('handles fields that are both nested and multi', () => {
const fields = readFieldCapsResponse(esResponse);
const child = fields.find(f => f.name === 'nested_object_parent.child.keyword');
@@ -159,12 +152,10 @@ describe('index_patterns/field_capabilities/field_caps_response', () => {
});
});
- it('returns the nested parent as not searchable or aggregatable', () => {
+ it('does not include the field actually mapped as nested itself', () => {
const fields = readFieldCapsResponse(esResponse);
const child = fields.find(f => f.name === 'nested_object_parent');
- expect(child.type).toBe('nested');
- expect(child.aggregatable).toBe(false);
- expect(child.searchable).toBe(false);
+ expect(child).toBeUndefined();
});
it('should not confuse object children for multi or nested field children', () => {
diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts
index 0c8c2ce48fa84..06eb30db0b24b 100644
--- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts
+++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts
@@ -182,19 +182,11 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie
if (Object.keys(subType).length > 0) {
field.subType = subType;
-
- // We don't support aggregating on nested fields, trying to do so in the UI will return
- // blank results. For now we will stop showing nested fields as an option for aggregation.
- // Once we add support for nested fields this condition should be removed and old index
- // patterns should be migrated.
- if (field.subType.nested) {
- field.aggregatable = false;
- }
}
}
});
return kibanaFormattedCaps.filter(field => {
- return !['object'].includes(field.type);
+ return !['object', 'nested'].includes(field.type);
});
}
diff --git a/src/plugins/kibana_react/public/field_icon/field_icon.tsx b/src/plugins/kibana_react/public/field_icon/field_icon.tsx
index 7c44fe89d0e7f..2e199a7471a64 100644
--- a/src/plugins/kibana_react/public/field_icon/field_icon.tsx
+++ b/src/plugins/kibana_react/public/field_icon/field_icon.tsx
@@ -36,8 +36,8 @@ interface FieldIconProps {
| 'number'
| '_source'
| 'string'
- | 'nested'
- | string;
+ | string
+ | 'nested';
label?: string;
size?: IconSize;
useColor?: boolean;
diff --git a/src/legacy/server/url_shortening/index.js b/src/plugins/kibana_utils/public/history/index.ts
similarity index 92%
rename from src/legacy/server/url_shortening/index.js
rename to src/plugins/kibana_utils/public/history/index.ts
index 031af0618d7bc..b4b5658c1c886 100644
--- a/src/legacy/server/url_shortening/index.js
+++ b/src/plugins/kibana_utils/public/history/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export { urlShorteningMixin } from './url_shortening_mixin';
+export { removeQueryParam } from './remove_query_param';
diff --git a/src/plugins/kibana_utils/public/history/remove_query_param.test.ts b/src/plugins/kibana_utils/public/history/remove_query_param.test.ts
new file mode 100644
index 0000000000000..0b2547ae94668
--- /dev/null
+++ b/src/plugins/kibana_utils/public/history/remove_query_param.test.ts
@@ -0,0 +1,75 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { removeQueryParam } from './remove_query_param';
+import { createMemoryHistory, Location } from 'history';
+
+describe('removeQueryParam', () => {
+ it('should remove query param from url', () => {
+ const startLocation: Location = {
+ pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735',
+ search: "?_a=(description:'')&_b=3",
+ state: null,
+ hash: '',
+ };
+
+ const history = createMemoryHistory();
+ history.push(startLocation);
+ removeQueryParam(history, '_a');
+
+ expect(history.location).toEqual(
+ expect.objectContaining({
+ pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735',
+ search: '?_b=3',
+ state: null,
+ hash: '',
+ })
+ );
+ });
+
+ it('should not fail if nothing to remove', () => {
+ const startLocation: Location = {
+ pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735',
+ search: "?_a=(description:'')&_b=3",
+ state: null,
+ hash: '',
+ };
+
+ const history = createMemoryHistory();
+ history.push(startLocation);
+ removeQueryParam(history, '_c');
+
+ expect(history.location).toEqual(expect.objectContaining(startLocation));
+ });
+
+ it('should not fail if no search', () => {
+ const startLocation: Location = {
+ pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735',
+ search: '',
+ state: null,
+ hash: '',
+ };
+
+ const history = createMemoryHistory();
+ history.push(startLocation);
+ removeQueryParam(history, '_c');
+
+ expect(history.location).toEqual(expect.objectContaining(startLocation));
+ });
+});
diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js b/src/plugins/kibana_utils/public/history/remove_query_param.ts
similarity index 54%
rename from src/legacy/server/url_shortening/routes/lib/short_url_lookup.js
rename to src/plugins/kibana_utils/public/history/remove_query_param.ts
index a8a01d1433a7a..fbf985998b4cd 100644
--- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js
+++ b/src/plugins/kibana_utils/public/history/remove_query_param.ts
@@ -17,27 +17,23 @@
* under the License.
*/
-import { get } from 'lodash';
+import { History, Location } from 'history';
+import { parse } from 'querystring';
+import { stringifyQueryString } from '../state_management/url/stringify_query_string'; // TODO: extract it to ../url
-export function shortUrlLookupProvider(server) {
- async function updateMetadata(doc, req) {
- try {
- await req.getSavedObjectsClient().update('url', doc.id, {
- accessDate: new Date(),
- accessCount: get(doc, 'attributes.accessCount', 0) + 1,
- });
- } catch (err) {
- server.log('Warning: Error updating url metadata', err);
- //swallow errors. It isn't critical if there is no update.
- }
- }
-
- return {
- async getUrl(id, req) {
- const doc = await req.getSavedObjectsClient().get('url', id);
- updateMetadata(doc, req);
-
- return doc.attributes.url;
- },
+export function removeQueryParam(history: History, param: string, replace: boolean = true) {
+ const oldLocation = history.location;
+ const search = (oldLocation.search || '').replace(/^\?/, '');
+ const query = parse(search);
+ delete query[param];
+ const newSearch = stringifyQueryString(query);
+ const newLocation: Location = {
+ ...oldLocation,
+ search: newSearch,
};
+ if (replace) {
+ history.replace(newLocation);
+ } else {
+ history.push(newLocation);
+ }
}
diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts
index fa58a61e51232..00c1c95028b4d 100644
--- a/src/plugins/kibana_utils/public/index.ts
+++ b/src/plugins/kibana_utils/public/index.ts
@@ -58,3 +58,5 @@ export {
StartSyncStateFnType,
StopSyncStateFnType,
} from './state_sync';
+export { removeQueryParam } from './history';
+export { applyDiff } from './state_management/utils/diff_object';
diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts
index f1c527d3d5309..6e4c505c62ebc 100644
--- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts
+++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts
@@ -85,6 +85,7 @@ describe('kbn_url_storage', () => {
beforeEach(() => {
history = createMemoryHistory();
urlControls = createKbnUrlControls(history);
+ urlControls.update('/', true);
});
const getCurrentUrl = () => createPath(history.location);
@@ -143,17 +144,6 @@ describe('kbn_url_storage', () => {
expect(cb).toHaveBeenCalledTimes(3);
});
- it('should flush async url updates', async () => {
- const pr1 = urlControls.updateAsync(() => '/1', false);
- const pr2 = urlControls.updateAsync(() => '/2', false);
- const pr3 = urlControls.updateAsync(() => '/3', false);
- expect(getCurrentUrl()).toBe('/');
- urlControls.flush();
- expect(getCurrentUrl()).toBe('/3');
- await Promise.all([pr1, pr2, pr3]);
- expect(getCurrentUrl()).toBe('/3');
- });
-
it('flush should take priority over regular replace behaviour', async () => {
const pr1 = urlControls.updateAsync(() => '/1', true);
const pr2 = urlControls.updateAsync(() => '/2', false);
@@ -174,6 +164,48 @@ describe('kbn_url_storage', () => {
await Promise.all([pr1, pr2, pr3]);
expect(getCurrentUrl()).toBe('/');
});
+
+ it('should retrieve pending url ', async () => {
+ const pr1 = urlControls.updateAsync(() => '/1', true);
+ const pr2 = urlControls.updateAsync(() => '/2', false);
+ const pr3 = urlControls.updateAsync(() => '/3', true);
+ expect(urlControls.getPendingUrl()).toEqual('/3');
+ expect(getCurrentUrl()).toBe('/');
+ await Promise.all([pr1, pr2, pr3]);
+ expect(getCurrentUrl()).toBe('/3');
+
+ expect(urlControls.getPendingUrl()).toBeUndefined();
+ });
+ });
+
+ describe('urlControls - browser history integration', () => {
+ let history: History;
+ let urlControls: IKbnUrlControls;
+ beforeEach(() => {
+ history = createBrowserHistory();
+ urlControls = createKbnUrlControls(history);
+ urlControls.update('/', true);
+ });
+
+ const getCurrentUrl = () => window.location.href;
+
+ it('should flush async url updates', async () => {
+ const pr1 = urlControls.updateAsync(() => '/1', false);
+ const pr2 = urlControls.updateAsync(() => '/2', false);
+ const pr3 = urlControls.updateAsync(() => '/3', false);
+ expect(getCurrentUrl()).toBe('http://localhost/');
+ expect(urlControls.flush()).toBe('http://localhost/3');
+ expect(getCurrentUrl()).toBe('http://localhost/3');
+ await Promise.all([pr1, pr2, pr3]);
+ expect(getCurrentUrl()).toBe('http://localhost/3');
+ });
+
+ it('flush() should return undefined, if no url updates happened', () => {
+ expect(urlControls.flush()).toBeUndefined();
+ urlControls.updateAsync(() => 'http://localhost/1', false);
+ urlControls.updateAsync(() => 'http://localhost/', false);
+ expect(urlControls.flush()).toBeUndefined();
+ });
});
describe('getRelativeToHistoryPath', () => {
diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts
index 03c136ea3d092..1dd204e717213 100644
--- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts
+++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts
@@ -107,25 +107,34 @@ export interface IKbnUrlControls {
listen: (cb: () => void) => () => void;
/**
- * Updates url synchronously
+ * Updates url synchronously, if needed
+ * skips the update and returns undefined in case when trying to update to current url
+ * otherwise returns new url
+ *
* @param url - url to update to
* @param replace - use replace instead of push
*/
- update: (url: string, replace: boolean) => string;
+ update: (url: string, replace: boolean) => string | undefined;
/**
* Schedules url update to next microtask,
* Useful to batch sync changes to url to cause only one browser history update
* @param updater - fn which receives current url and should return next url to update to
* @param replace - use replace instead of push
+ *
*/
- updateAsync: (updater: UrlUpdaterFnType, replace?: boolean) => Promise;
+ updateAsync: (updater: UrlUpdaterFnType, replace?: boolean) => Promise;
/**
- * Synchronously flushes scheduled url updates
+ * If there is a pending url update - returns url that is scheduled for update
+ */
+ getPendingUrl: () => string | undefined;
+
+ /**
+ * Synchronously flushes scheduled url updates. Returns new flushed url, if there was an update. Otherwise - undefined.
* @param replace - if replace passed in, then uses it instead of push. Otherwise push or replace is picked depending on updateQueue
*/
- flush: (replace?: boolean) => string;
+ flush: (replace?: boolean) => string | undefined;
/**
* Cancels any pending url updates
@@ -143,9 +152,9 @@ export const createKbnUrlControls = (
// if any call in a queue asked to push, then we should push
let shouldReplace = true;
- function updateUrl(newUrl: string, replace = false): string {
+ function updateUrl(newUrl: string, replace = false): string | undefined {
const currentUrl = getCurrentUrl();
- if (newUrl === currentUrl) return currentUrl; // skip update
+ if (newUrl === currentUrl) return undefined; // skip update
const historyPath = getRelativeToHistoryPath(newUrl, history);
@@ -166,15 +175,22 @@ export const createKbnUrlControls = (
// runs scheduled url updates
function flush(replace = shouldReplace) {
- if (updateQueue.length === 0) return getCurrentUrl();
- const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl());
+ const nextUrl = getPendingUrl();
- cleanUp();
+ if (!nextUrl) return;
- const newUrl = updateUrl(resultUrl, replace);
+ cleanUp();
+ const newUrl = updateUrl(nextUrl, replace);
return newUrl;
}
+ function getPendingUrl() {
+ if (updateQueue.length === 0) return undefined;
+ const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl());
+
+ return resultUrl;
+ }
+
return {
listen: (cb: () => void) =>
history.listen(() => {
@@ -199,6 +215,9 @@ export const createKbnUrlControls = (
cancel: () => {
cleanUp();
},
+ getPendingUrl: () => {
+ return getPendingUrl();
+ },
};
};
diff --git a/src/legacy/ui/public/state_management/utils/diff_object.test.ts b/src/plugins/kibana_utils/public/state_management/utils/diff_object.test.ts
similarity index 100%
rename from src/legacy/ui/public/state_management/utils/diff_object.test.ts
rename to src/plugins/kibana_utils/public/state_management/utils/diff_object.test.ts
diff --git a/src/legacy/ui/public/state_management/utils/diff_object.ts b/src/plugins/kibana_utils/public/state_management/utils/diff_object.ts
similarity index 100%
rename from src/legacy/ui/public/state_management/utils/diff_object.ts
rename to src/plugins/kibana_utils/public/state_management/utils/diff_object.ts
diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts
index 08ad1551420d2..17f41483a0a21 100644
--- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts
+++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts
@@ -291,6 +291,42 @@ describe('state_sync', () => {
stop();
});
+
+ it("should preserve reference to unchanged state slices if them didn't change", async () => {
+ const otherUnchangedSlice = { a: 'test' };
+ const oldState = {
+ todos: container.get().todos,
+ otherUnchangedSlice,
+ };
+ container.set(oldState as any);
+
+ const { stop, start } = syncStates([
+ {
+ stateContainer: withDefaultState(container, defaultState),
+ storageKey: key,
+ stateStorage: urlSyncStrategy,
+ },
+ ]);
+ await urlSyncStrategy.set('_s', container.get());
+ expect(getCurrentUrl()).toMatchInlineSnapshot(
+ `"/#?_s=(otherUnchangedSlice:(a:test),todos:!((completed:!f,id:0,text:'Learning%20state%20containers')))"`
+ );
+ start();
+
+ history.replace(
+ "/#?_s=(otherUnchangedSlice:(a:test),todos:!((completed:!t,id:0,text:'Learning%20state%20containers')))"
+ );
+
+ const newState = container.get();
+ expect(newState.todos).toEqual([
+ { id: 0, text: 'Learning state containers', completed: true },
+ ]);
+
+ // reference to unchanged slice is preserved
+ expect((newState as any).otherUnchangedSlice).toBe(otherUnchangedSlice);
+
+ stop();
+ });
});
});
diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.ts
index 9c1116e5da531..28d133829e07c 100644
--- a/src/plugins/kibana_utils/public/state_sync/state_sync.ts
+++ b/src/plugins/kibana_utils/public/state_sync/state_sync.ts
@@ -24,6 +24,7 @@ import { IStateSyncConfig } from './types';
import { IStateStorage } from './state_sync_state_storage';
import { distinctUntilChangedWithInitialValue } from '../../common';
import { BaseState } from '../state_containers';
+import { applyDiff } from '../state_management/utils/diff_object';
/**
* Utility for syncing application state wrapped in state container
@@ -100,7 +101,18 @@ export function syncState<
const updateState = () => {
const newState = stateStorage.get(storageKey);
const oldState = stateContainer.get();
- if (!defaultComparator(newState, oldState)) {
+ if (newState) {
+ // apply only real differences to new state
+ const mergedState = { ...oldState } as State;
+ // merges into 'mergedState' all differences from newState,
+ // but leaves references if they are deeply the same
+ const diff = applyDiff(mergedState, newState);
+
+ if (diff.keys.length > 0) {
+ stateContainer.set(mergedState);
+ }
+ } else if (oldState !== newState) {
+ // empty new state case
stateContainer.set(newState);
}
};
diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts
index 826122176e061..cc3f1df7c1e00 100644
--- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts
+++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts
@@ -46,9 +46,11 @@ describe('KbnUrlStateStorage', () => {
const key = '_s';
urlStateStorage.set(key, state);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`);
- urlStateStorage.flush();
+ expect(urlStateStorage.flush()).toBe(true);
expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=(ok:1,test:test)"`);
expect(urlStateStorage.get(key)).toEqual(state);
+
+ expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update
});
it('should cancel url updates', async () => {
@@ -62,6 +64,19 @@ describe('KbnUrlStateStorage', () => {
expect(urlStateStorage.get(key)).toEqual(null);
});
+ it('should cancel url updates if synchronously returned to the same state', async () => {
+ const state1 = { test: 'test', ok: 1 };
+ const state2 = { test: 'test', ok: 2 };
+ const key = '_s';
+ const pr1 = urlStateStorage.set(key, state1);
+ await pr1;
+ const historyLength = history.length;
+ const pr2 = urlStateStorage.set(key, state2);
+ const pr3 = urlStateStorage.set(key, state1);
+ await Promise.all([pr2, pr3]);
+ expect(history.length).toBe(historyLength);
+ });
+
it('should notify about url changes', async () => {
expect(urlStateStorage.change$).toBeDefined();
const key = '_s';
diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts
index 245006349ad55..082eaa5095ab9 100644
--- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts
+++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts
@@ -28,7 +28,11 @@ import {
} from '../../state_management/url';
export interface IKbnUrlStateStorage extends IStateStorage {
- set: (key: string, state: State, opts?: { replace: boolean }) => Promise;
+ set: (
+ key: string,
+ state: State,
+ opts?: { replace: boolean }
+ ) => Promise;
get: (key: string) => State | null;
change$: (key: string) => Observable;
@@ -36,7 +40,8 @@ export interface IKbnUrlStateStorage extends IStateStorage {
cancel: () => void;
// synchronously runs any pending url updates
- flush: (opts?: { replace?: boolean }) => void;
+ // returned boolean indicates if change occurred
+ flush: (opts?: { replace?: boolean }) => boolean;
}
/**
@@ -60,7 +65,11 @@ export const createKbnUrlStateStorage = (
replace
);
},
- get: key => getStateFromKbnUrl(key),
+ get: key => {
+ // if there is a pending url update, then state will be extracted from that pending url,
+ // otherwise current url will be used to retrieve state from
+ return getStateFromKbnUrl(key, url.getPendingUrl());
+ },
change$: (key: string) =>
new Observable(observer => {
const unlisten = url.listen(() => {
@@ -75,7 +84,7 @@ export const createKbnUrlStateStorage = (
share()
),
flush: ({ replace = false }: { replace?: boolean } = {}) => {
- url.flush(replace);
+ return !!url.flush(replace);
},
cancel() {
url.cancel();
diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts
index faec466dbd671..4ece75bbf36da 100644
--- a/src/plugins/management/public/index.ts
+++ b/src/plugins/management/public/index.ts
@@ -24,7 +24,12 @@ export function plugin(initializerContext: PluginInitializerContext) {
return new ManagementPlugin();
}
-export { ManagementSetup, ManagementStart, RegisterManagementApp } from './types';
+export {
+ ManagementSetup,
+ ManagementStart,
+ RegisterManagementApp,
+ RegisterManagementAppArgs,
+} from './types';
export { ManagementApp } from './management_app';
export { ManagementSection } from './management_section';
export { ManagementSidebarNav } from './components'; // for use in legacy management apps
diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx
index 30e00ba7df525..02b3ea306c23d 100644
--- a/src/plugins/management/public/management_app.tsx
+++ b/src/plugins/management/public/management_app.tsx
@@ -34,7 +34,7 @@ export class ManagementApp {
readonly basePath: string;
readonly order: number;
readonly mount: ManagementSectionMount;
- protected enabledStatus: boolean = true;
+ private enabledStatus = true;
constructor(
{ id, title, basePath, order = 100, mount }: CreateManagementApp,
@@ -54,6 +54,11 @@ export class ManagementApp {
title,
mount: async ({}, params) => {
let appUnmount: Unmount;
+ if (!this.enabledStatus) {
+ const [coreStart] = await getStartServices();
+ coreStart.application.navigateToApp('kibana#/management');
+ return () => {};
+ }
async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) {
const [coreStart] = await getStartServices();
coreStart.chrome.setBreadcrumbs([
diff --git a/src/plugins/newsfeed/public/plugin.tsx b/src/plugins/newsfeed/public/plugin.tsx
index 5ea5e5b324717..c4e042fe452f9 100644
--- a/src/plugins/newsfeed/public/plugin.tsx
+++ b/src/plugins/newsfeed/public/plugin.tsx
@@ -23,7 +23,7 @@ import ReactDOM from 'react-dom';
import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
-import { NewsfeedPluginInjectedConfig } from '../types';
+import { FetchResult, NewsfeedPluginInjectedConfig } from '../types';
import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button';
import { getApi } from './lib/api';
@@ -54,10 +54,14 @@ export class NewsfeedPublicPlugin implements Plugin {
private fetchNewsfeed(core: CoreStart) {
const { http, injectedMetadata } = core;
- const config = injectedMetadata.getInjectedVar(
- 'newsfeed'
- ) as NewsfeedPluginInjectedConfig['newsfeed'];
+ const config = injectedMetadata.getInjectedVar('newsfeed') as
+ | NewsfeedPluginInjectedConfig['newsfeed']
+ | undefined;
+ if (!config) {
+ // running in new platform, injected metadata not available
+ return new Rx.Observable();
+ }
return getApi(http, config, this.kibanaVersion).pipe(
takeUntil(this.stop$), // stop the interval when stop method is called
catchError(() => Rx.of(null)) // do not throw error
diff --git a/src/legacy/core_plugins/state_session_storage_redirect/index.js b/src/plugins/share/common/short_url_routes.ts
similarity index 67%
rename from src/legacy/core_plugins/state_session_storage_redirect/index.js
rename to src/plugins/share/common/short_url_routes.ts
index 2d4d7c97232c0..7b42534de2ab1 100644
--- a/src/legacy/core_plugins/state_session_storage_redirect/index.js
+++ b/src/plugins/share/common/short_url_routes.ts
@@ -17,16 +17,15 @@
* under the License.
*/
-export default function(kibana) {
- return new kibana.Plugin({
- uiExports: {
- app: {
- require: ['kibana'],
- title: 'Redirecting',
- id: 'stateSessionStorageRedirect',
- main: 'plugins/state_session_storage_redirect',
- hidden: true,
- },
- },
- });
-}
+export const GOTO_PREFIX = '/goto';
+
+export const getUrlIdFromGotoRoute = (path: string) =>
+ path.match(new RegExp(`${GOTO_PREFIX}/(.*)$`))?.[1];
+
+export const getGotoPath = (urlId: string) => `${GOTO_PREFIX}/${urlId}`;
+
+export const GETTER_PREFIX = '/api/short_url';
+
+export const getUrlPath = (urlId: string) => `${GETTER_PREFIX}/${urlId}`;
+
+export const CREATE_PATH = '/api/shorten_url';
diff --git a/src/plugins/share/public/lib/url_shortener.ts b/src/plugins/share/public/lib/url_shortener.ts
index 29d91bdb1aae6..f2259f1fabf7d 100644
--- a/src/plugins/share/public/lib/url_shortener.ts
+++ b/src/plugins/share/public/lib/url_shortener.ts
@@ -19,6 +19,7 @@
import url from 'url';
import { HttpStart } from 'kibana/public';
+import { CREATE_PATH, getGotoPath } from '../../common/short_url_routes';
export async function shortenUrl(
absoluteUrl: string,
@@ -34,10 +35,10 @@ export async function shortenUrl(
const body = JSON.stringify({ url: relativeUrl });
- const resp = await post('/api/shorten_url', { body });
+ const resp = await post(CREATE_PATH, { body });
return url.format({
protocol: parsedUrl.protocol,
host: parsedUrl.host,
- pathname: `${basePath}/goto/${resp.urlId}`,
+ pathname: `${basePath}${getGotoPath(resp.urlId)}`,
});
}
diff --git a/src/plugins/share/public/plugin.test.ts b/src/plugins/share/public/plugin.test.ts
index 5610490be33b3..730814fe9ed23 100644
--- a/src/plugins/share/public/plugin.test.ts
+++ b/src/plugins/share/public/plugin.test.ts
@@ -20,6 +20,7 @@
import { registryMock, managerMock } from './plugin.test.mocks';
import { SharePlugin } from './plugin';
import { CoreStart } from 'kibana/public';
+import { coreMock } from '../../../core/public/mocks';
describe('SharePlugin', () => {
beforeEach(() => {
@@ -30,16 +31,28 @@ describe('SharePlugin', () => {
describe('setup', () => {
test('wires up and returns registry', async () => {
- const setup = await new SharePlugin().setup();
+ const coreSetup = coreMock.createSetup();
+ const setup = await new SharePlugin().setup(coreSetup);
expect(registryMock.setup).toHaveBeenCalledWith();
expect(setup.register).toBeDefined();
});
+
+ test('registers redirect app', async () => {
+ const coreSetup = coreMock.createSetup();
+ await new SharePlugin().setup(coreSetup);
+ expect(coreSetup.application.register).toHaveBeenCalledWith(
+ expect.objectContaining({
+ id: 'short_url_redirect',
+ })
+ );
+ });
});
describe('start', () => {
test('wires up and returns show function, but not registry', async () => {
+ const coreSetup = coreMock.createSetup();
const service = new SharePlugin();
- await service.setup();
+ await service.setup(coreSetup);
const start = await service.start({} as CoreStart);
expect(registryMock.start).toHaveBeenCalled();
expect(managerMock.start).toHaveBeenCalledWith(
diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts
index 6d78211cf9954..01c248624950a 100644
--- a/src/plugins/share/public/plugin.ts
+++ b/src/plugins/share/public/plugin.ts
@@ -17,15 +17,17 @@
* under the License.
*/
-import { CoreStart, Plugin } from 'src/core/public';
+import { CoreSetup, CoreStart, Plugin } from 'src/core/public';
import { ShareMenuManager, ShareMenuManagerStart } from './services';
import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services';
+import { createShortUrlRedirectApp } from './services/short_url_redirect_app';
export class SharePlugin implements Plugin {
private readonly shareMenuRegistry = new ShareMenuRegistry();
private readonly shareContextMenu = new ShareMenuManager();
- public async setup() {
+ public async setup(core: CoreSetup) {
+ core.application.register(createShortUrlRedirectApp(core, window.location));
return {
...this.shareMenuRegistry.setup(),
};
diff --git a/src/plugins/share/public/services/short_url_redirect_app.test.ts b/src/plugins/share/public/services/short_url_redirect_app.test.ts
new file mode 100644
index 0000000000000..206e637451ec0
--- /dev/null
+++ b/src/plugins/share/public/services/short_url_redirect_app.test.ts
@@ -0,0 +1,46 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createShortUrlRedirectApp } from './short_url_redirect_app';
+import { coreMock } from '../../../../core/public/mocks';
+import { hashUrl } from '../../../kibana_utils/public';
+
+jest.mock('../../../kibana_utils/public', () => ({ hashUrl: jest.fn(x => `${x}/hashed`) }));
+
+describe('short_url_redirect_app', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should fetch url and redirect to hashed version', async () => {
+ const coreSetup = coreMock.createSetup({ basePath: 'base' });
+ coreSetup.http.get.mockResolvedValueOnce({ url: '/app/abc' });
+ const locationMock = { pathname: '/base/goto/12345', href: '' } as Location;
+
+ const { mount } = createShortUrlRedirectApp(coreSetup, locationMock);
+ await mount();
+
+ // check for fetching the complete URL
+ expect(coreSetup.http.get).toHaveBeenCalledWith('/api/short_url/12345');
+ // check for hashing the URL returned from the server
+ expect(hashUrl).toHaveBeenCalledWith('/app/abc');
+ // check for redirecting to the prepended path
+ expect(locationMock.href).toEqual('base/app/abc/hashed');
+ });
+});
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts b/src/plugins/share/public/services/short_url_redirect_app.ts
similarity index 50%
rename from src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts
rename to src/plugins/share/public/services/short_url_redirect_app.ts
index d9dea35a8a1c0..6f72b711f6602 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts
+++ b/src/plugins/share/public/services/short_url_redirect_app.ts
@@ -17,32 +17,29 @@
* under the License.
*/
-import { AppStateClass } from '../legacy_imports';
+import { CoreSetup } from 'kibana/public';
+import { getUrlIdFromGotoRoute, getUrlPath, GOTO_PREFIX } from '../../common/short_url_routes';
+import { hashUrl } from '../../../kibana_utils/public';
-/**
- * A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector.
- * This could be improved if we extract the appState and state classes externally of their angular providers.
- * @return {AppStateMock}
- */
-export function getAppStateMock(): AppStateClass {
- class AppStateMock {
- constructor(defaults: any) {
- Object.assign(this, defaults);
- }
+export const createShortUrlRedirectApp = (core: CoreSetup, location: Location) => ({
+ id: 'short_url_redirect',
+ appRoute: GOTO_PREFIX,
+ chromeless: true,
+ title: 'Short URL Redirect',
+ async mount() {
+ const urlId = getUrlIdFromGotoRoute(location.pathname);
- on() {}
- off() {}
- toJSON() {
- return '';
- }
- save() {}
- translateHashToRison(stateHashOrRison: string | string[]) {
- return stateHashOrRison;
+ if (!urlId) {
+ throw new Error('Url id not present in path');
}
- getQueryParamName() {
- return '';
- }
- }
- return AppStateMock;
-}
+ const response = await core.http.get<{ url: string }>(getUrlPath(urlId));
+ const redirectUrl = response.url;
+ const hashedUrl = hashUrl(redirectUrl);
+ const url = core.http.basePath.prepend(hashedUrl);
+
+ location.href = url;
+
+ return () => {};
+ },
+});
diff --git a/src/plugins/share/server/routes/create_routes.ts b/src/plugins/share/server/routes/create_routes.ts
index bd4b6fdb08791..22d10c25197c9 100644
--- a/src/plugins/share/server/routes/create_routes.ts
+++ b/src/plugins/share/server/routes/create_routes.ts
@@ -22,11 +22,13 @@ import { CoreSetup, Logger } from 'kibana/server';
import { shortUrlLookupProvider } from './lib/short_url_lookup';
import { createGotoRoute } from './goto';
import { createShortenUrlRoute } from './shorten_url';
+import { createGetterRoute } from './get';
export function createRoutes({ http }: CoreSetup, logger: Logger) {
const shortUrlLookup = shortUrlLookupProvider({ logger });
const router = http.createRouter();
createGotoRoute({ router, shortUrlLookup, http });
+ createGetterRoute({ router, shortUrlLookup, http });
createShortenUrlRoute({ router, shortUrlLookup });
}
diff --git a/src/legacy/server/url_shortening/routes/goto.js b/src/plugins/share/server/routes/get.ts
similarity index 55%
rename from src/legacy/server/url_shortening/routes/goto.js
rename to src/plugins/share/server/routes/get.ts
index 7a874786423d8..d6b191341dbe1 100644
--- a/src/legacy/server/url_shortening/routes/goto.js
+++ b/src/plugins/share/server/routes/get.ts
@@ -17,23 +17,40 @@
* under the License.
*/
-import { handleShortUrlError } from './lib/short_url_error';
+import { CoreSetup, IRouter } from 'kibana/server';
+import { schema } from '@kbn/config-schema';
+
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
+import { ShortUrlLookupService } from './lib/short_url_lookup';
+import { getUrlPath } from '../../common/short_url_routes';
-export const createGotoRoute = ({ server, shortUrlLookup }) => ({
- method: 'GET',
- path: '/goto_LP/{urlId}',
- handler: async function(request, h) {
- try {
- const url = await shortUrlLookup.getUrl(request.params.urlId, request);
+export const createGetterRoute = ({
+ router,
+ shortUrlLookup,
+ http,
+}: {
+ router: IRouter;
+ shortUrlLookup: ShortUrlLookupService;
+ http: CoreSetup['http'];
+}) => {
+ router.get(
+ {
+ path: getUrlPath('{urlId}'),
+ validate: {
+ params: schema.object({ urlId: schema.string() }),
+ },
+ },
+ router.handleLegacyErrors(async function(context, request, response) {
+ const url = await shortUrlLookup.getUrl(request.params.urlId, {
+ savedObjects: context.core.savedObjects.client,
+ });
shortUrlAssertValid(url);
- const app = server.getHiddenUiAppById('stateSessionStorageRedirect');
- return h.renderApp(app, {
- redirectUrl: url,
+ return response.ok({
+ body: {
+ url,
+ },
});
- } catch (err) {
- throw handleShortUrlError(err);
- }
- },
-});
+ })
+ );
+};
diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts
index 7343dc1bd34a2..5c3a4e441099f 100644
--- a/src/plugins/share/server/routes/goto.ts
+++ b/src/plugins/share/server/routes/goto.ts
@@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema';
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
import { ShortUrlLookupService } from './lib/short_url_lookup';
+import { getGotoPath } from '../../common/short_url_routes';
export const createGotoRoute = ({
router,
@@ -34,7 +35,7 @@ export const createGotoRoute = ({
}) => {
router.get(
{
- path: '/goto/{urlId}',
+ path: getGotoPath('{urlId}'),
validate: {
params: schema.object({ urlId: schema.string() }),
},
@@ -54,10 +55,13 @@ export const createGotoRoute = ({
},
});
}
- return response.redirected({
+ const body = await context.core.rendering.render();
+
+ return response.ok({
headers: {
- location: http.basePath.prepend('/goto_LP/' + request.params.urlId),
+ 'content-security-policy': http.csp.header,
},
+ body,
});
})
);
diff --git a/src/plugins/share/server/routes/shorten_url.ts b/src/plugins/share/server/routes/shorten_url.ts
index 116b90c6971c5..41570f8a5f453 100644
--- a/src/plugins/share/server/routes/shorten_url.ts
+++ b/src/plugins/share/server/routes/shorten_url.ts
@@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema';
import { shortUrlAssertValid } from './lib/short_url_assert_valid';
import { ShortUrlLookupService } from './lib/short_url_lookup';
+import { CREATE_PATH } from '../../common/short_url_routes';
export const createShortenUrlRoute = ({
shortUrlLookup,
@@ -32,7 +33,7 @@ export const createShortenUrlRoute = ({
}) => {
router.post(
{
- path: '/api/shorten_url',
+ path: CREATE_PATH,
validate: {
body: schema.object({ url: schema.string() }),
},
diff --git a/tasks/config/peg.js b/tasks/config/peg.js
index 0c57c6139d298..1d8667840faba 100644
--- a/tasks/config/peg.js
+++ b/tasks/config/peg.js
@@ -25,4 +25,8 @@ module.exports = {
allowedStartRules: ['start', 'Literal'],
},
},
+ timelion_chain: {
+ src: 'src/legacy/core_plugins/vis_type_timelion/public/chain.peg',
+ dest: 'src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js',
+ },
};
diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js
index 555056173ec62..c4c71abdae125 100644
--- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js
+++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js
@@ -72,15 +72,7 @@ export default function({ getService }) {
readFromDocValues: true,
},
{
- aggregatable: false,
- esTypes: ['nested'],
- name: 'nestedField',
- readFromDocValues: false,
- searchable: false,
- type: 'nested',
- },
- {
- aggregatable: false,
+ aggregatable: true,
esTypes: ['keyword'],
name: 'nestedField.child',
readFromDocValues: true,
@@ -162,15 +154,7 @@ export default function({ getService }) {
readFromDocValues: true,
},
{
- aggregatable: false,
- esTypes: ['nested'],
- name: 'nestedField',
- readFromDocValues: false,
- searchable: false,
- type: 'nested',
- },
- {
- aggregatable: false,
+ aggregatable: true,
esTypes: ['keyword'],
name: 'nestedField.child',
readFromDocValues: true,
diff --git a/test/functional/apps/dashboard/dashboard_clone.js b/test/functional/apps/dashboard/dashboard_clone.js
index f5485c1db206e..8b7f6ba6a34dd 100644
--- a/test/functional/apps/dashboard/dashboard_clone.js
+++ b/test/functional/apps/dashboard/dashboard_clone.js
@@ -37,7 +37,7 @@ export default function({ getService, getPageObjects }) {
await PageObjects.dashboard.addVisualizations(
PageObjects.dashboard.getTestVisualizationNames()
);
- await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName);
+ await PageObjects.dashboard.saveDashboard(dashboardName);
await PageObjects.dashboard.clickClone();
await PageObjects.dashboard.confirmClone();
diff --git a/test/plugin_functional/plugins/core_app_status/public/application.tsx b/test/plugin_functional/plugins/core_app_status/public/application.tsx
index 323774392a6d7..b9ebd8d3692f1 100644
--- a/test/plugin_functional/plugins/core_app_status/public/application.tsx
+++ b/test/plugin_functional/plugins/core_app_status/public/application.tsx
@@ -31,15 +31,15 @@ import {
EuiTitle,
} from '@elastic/eui';
-import { AppMountContext, AppMountParameters } from 'kibana/public';
+import { AppMountParameters } from 'kibana/public';
-const AppStatusApp = () => (
+const AppStatusApp = ({ appId }: { appId: string }) => (
- Welcome to App Status Test App!
+ Welcome to {appId} Test App!
@@ -47,18 +47,18 @@ const AppStatusApp = () => (
- App Status Test App home page section title
+ {appId} Test App home page section title
- App Status Test App content
+ {appId} Test App content
);
-export const renderApp = (context: AppMountContext, { element }: AppMountParameters) => {
- render( , element);
+export const renderApp = (appId: string, { element }: AppMountParameters) => {
+ render( , element);
return () => unmountComponentAtNode(element);
};
diff --git a/test/plugin_functional/plugins/core_app_status/public/index.ts b/test/plugin_functional/plugins/core_app_status/public/index.ts
index e0ad7c25a54b8..f52b7ff5fea44 100644
--- a/test/plugin_functional/plugins/core_app_status/public/index.ts
+++ b/test/plugin_functional/plugins/core_app_status/public/index.ts
@@ -18,7 +18,7 @@
*/
import { PluginInitializer } from 'kibana/public';
-import { CoreAppStatusPlugin, CoreAppStatusPluginSetup, CoreAppStatusPluginStart } from './plugin';
+import { CoreAppStatusPlugin, CoreAppStatusPluginStart } from './plugin';
-export const plugin: PluginInitializer = () =>
+export const plugin: PluginInitializer<{}, CoreAppStatusPluginStart> = () =>
new CoreAppStatusPlugin();
diff --git a/test/plugin_functional/plugins/core_app_status/public/plugin.tsx b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx
index 85caaaf5f9090..af23bfbe1f8f5 100644
--- a/test/plugin_functional/plugins/core_app_status/public/plugin.tsx
+++ b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx
@@ -17,22 +17,38 @@
* under the License.
*/
-import { Plugin, CoreSetup, AppUpdater, AppUpdatableFields, CoreStart } from 'kibana/public';
import { BehaviorSubject } from 'rxjs';
+import {
+ Plugin,
+ CoreSetup,
+ AppUpdater,
+ AppUpdatableFields,
+ CoreStart,
+ AppMountParameters,
+} from 'kibana/public';
+import './types';
-export class CoreAppStatusPlugin
- implements Plugin {
+export class CoreAppStatusPlugin implements Plugin<{}, CoreAppStatusPluginStart> {
private appUpdater = new BehaviorSubject(() => ({}));
public setup(core: CoreSetup, deps: {}) {
+ core.application.register({
+ id: 'app_status_start',
+ title: 'App Status Start Page',
+ async mount(params: AppMountParameters) {
+ const { renderApp } = await import('./application');
+ return renderApp('app_status_start', params);
+ },
+ });
+
core.application.register({
id: 'app_status',
title: 'App Status',
euiIconType: 'snowflake',
updater$: this.appUpdater,
- async mount(context, params) {
+ async mount(params: AppMountParameters) {
const { renderApp } = await import('./application');
- return renderApp(context, params);
+ return renderApp('app_status', params);
},
});
@@ -40,7 +56,7 @@ export class CoreAppStatusPlugin
}
public start(core: CoreStart) {
- return {
+ const startContract = {
setAppStatus: (status: Partial) => {
this.appUpdater.next(() => status);
},
@@ -48,9 +64,10 @@ export class CoreAppStatusPlugin
return core.application.navigateToApp(appId);
},
};
+ window.__coreAppStatus = startContract;
+ return startContract;
}
public stop() {}
}
-export type CoreAppStatusPluginSetup = ReturnType;
export type CoreAppStatusPluginStart = ReturnType;
diff --git a/src/legacy/server/url_shortening/url_shortening_mixin.js b/test/plugin_functional/plugins/core_app_status/public/types.ts
similarity index 84%
rename from src/legacy/server/url_shortening/url_shortening_mixin.js
rename to test/plugin_functional/plugins/core_app_status/public/types.ts
index 867898cac845a..7c708e6c26d91 100644
--- a/src/legacy/server/url_shortening/url_shortening_mixin.js
+++ b/test/plugin_functional/plugins/core_app_status/public/types.ts
@@ -16,8 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { createRoutes } from './routes/create_routes';
-export function urlShorteningMixin(kbnServer, server) {
- createRoutes(server);
+import { CoreAppStatusPluginStart } from './plugin';
+
+declare global {
+ interface Window {
+ __coreAppStatus: CoreAppStatusPluginStart;
+ }
}
diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx
index 8b7cdd653ed8c..f3b7a19f70ae3 100644
--- a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx
+++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx
@@ -62,6 +62,22 @@ export class ManagementTestPlugin
};
},
});
+
+ testSection!
+ .registerApp({
+ id: 'test-management-disabled',
+ title: 'Management Test Disabled',
+ mount(params) {
+ params.setBreadcrumbs([{ text: 'Management Test Disabled' }]);
+ ReactDOM.render(This is a secret that should never be seen!
, params.element);
+
+ return () => {
+ ReactDOM.unmountComponentAtNode(params.element);
+ };
+ },
+ })
+ .disable();
+
return {};
}
diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts
index 753ee6fa40ac5..0cc64277efe11 100644
--- a/test/plugin_functional/test_suites/core_plugins/application_status.ts
+++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts
@@ -24,44 +24,26 @@ import {
AppUpdatableFields,
} from '../../../../src/core/public/application/types';
import { PluginFunctionalProviderContext } from '../../services';
-import { CoreAppStatusPluginStart } from '../../plugins/core_app_status/public/plugin';
-import '../../plugins/core_provider_plugin/types';
+import '../../plugins/core_app_status/public/types';
// eslint-disable-next-line import/no-default-export
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
const PageObjects = getPageObjects(['common', 'settings']);
const browser = getService('browser');
const appsMenu = getService('appsMenu');
+ const testSubjects = getService('testSubjects');
const setAppStatus = async (s: Partial) => {
- await browser.executeAsync(async (status: Partial, cb: Function) => {
- const plugin = window.__coreProvider.start.plugins
- .core_app_status as CoreAppStatusPluginStart;
- plugin.setAppStatus(status);
+ return browser.executeAsync(async (status: Partial, cb: Function) => {
+ window.__coreAppStatus.setAppStatus(status);
cb();
}, s);
};
- const navigateToApp = async (i: string): Promise<{ error?: string }> => {
+ const navigateToApp = async (i: string) => {
return (await browser.executeAsync(async (appId, cb: Function) => {
- // navigating in legacy mode performs a page refresh
- // and webdriver seems to re-execute the script after the reload
- // as it considers it didn't end on the previous session.
- // however when testing navigation to NP app, __coreProvider is not accessible
- // so we need to check for existence.
- if (!window.__coreProvider) {
- cb({});
- }
- const plugin = window.__coreProvider.start.plugins
- .core_app_status as CoreAppStatusPluginStart;
- try {
- await plugin.navigateToApp(appId);
- cb({});
- } catch (e) {
- cb({
- error: e.message,
- });
- }
+ await window.__coreAppStatus.navigateToApp(appId);
+ cb();
}, i)) as any;
};
@@ -71,7 +53,7 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
});
beforeEach(async () => {
- await PageObjects.common.navigateToApp('settings');
+ await PageObjects.common.navigateToApp('app_status_start');
});
it('can change the navLink status at runtime', async () => {
@@ -102,10 +84,10 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
status: AppStatus.inaccessible,
});
- const result = await navigateToApp('app_status');
- expect(result.error).to.contain(
- 'Trying to navigate to an inaccessible application: app_status'
- );
+ await navigateToApp('app_status');
+
+ expect(await testSubjects.exists('appNotFoundPageContent')).to.eql(true);
+ expect(await testSubjects.exists('appStatusApp')).to.eql(false);
});
it('allows to navigate to an accessible app', async () => {
@@ -113,8 +95,35 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
status: AppStatus.accessible,
});
- const result = await navigateToApp('app_status');
- expect(result.error).to.eql(undefined);
+ await navigateToApp('app_status');
+
+ expect(await testSubjects.exists('appNotFoundPageContent')).to.eql(false);
+ expect(await testSubjects.exists('appStatusApp')).to.eql(true);
+ });
+
+ it('can change the state of the currently mounted app', async () => {
+ await setAppStatus({
+ status: AppStatus.accessible,
+ });
+
+ await navigateToApp('app_status');
+
+ expect(await testSubjects.exists('appNotFoundPageContent')).to.eql(false);
+ expect(await testSubjects.exists('appStatusApp')).to.eql(true);
+
+ await setAppStatus({
+ status: AppStatus.inaccessible,
+ });
+
+ expect(await testSubjects.exists('appNotFoundPageContent')).to.eql(true);
+ expect(await testSubjects.exists('appStatusApp')).to.eql(false);
+
+ await setAppStatus({
+ status: AppStatus.accessible,
+ });
+
+ expect(await testSubjects.exists('appNotFoundPageContent')).to.eql(false);
+ expect(await testSubjects.exists('appStatusApp')).to.eql(true);
});
});
}
diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js
index d65fb1dcd3a7e..0c185f4b385b5 100644
--- a/test/plugin_functional/test_suites/management/management_plugin.js
+++ b/test/plugin_functional/test_suites/management/management_plugin.js
@@ -36,5 +36,13 @@ export default function({ getService, getPageObjects }) {
await testSubjects.click('test-management-link-basepath');
await testSubjects.existOrFail('test-management-link-one');
});
+
+ it('should redirect when app is disabled', async () => {
+ await PageObjects.common.navigateToActualUrl(
+ 'kibana',
+ 'management/test-section/test-management-disabled'
+ );
+ await testSubjects.existOrFail('management-landing');
+ });
});
}
diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js
index 391973f6d909b..fbf917054edbf 100644
--- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js
+++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js
@@ -34,6 +34,7 @@ import 'ui/color_maps';
import 'ui/agg_response';
import 'ui/agg_types';
import 'leaflet';
+import 'plugins/kibana/dashboard/legacy';
import { npStart } from 'ui/new_platform';
import { localApplicationService } from 'plugins/kibana/local_application_service';
diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts
index 9622466ad795c..b248776c884f1 100644
--- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts
+++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts
@@ -51,3 +51,5 @@ export * from './fielddata_parameter';
export * from './split_queries_on_whitespace_parameter';
export * from './locale_parameter';
+
+export * from './max_shingle_size_parameter';
diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx
new file mode 100644
index 0000000000000..bc1917b2da966
--- /dev/null
+++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { i18n } from '@kbn/i18n';
+
+import { getFieldConfig } from '../../../lib';
+import { EditFieldFormRow } from '../fields/edit_field';
+import { UseField, Field } from '../../../shared_imports';
+
+interface Props {
+ defaultToggleValue: boolean;
+}
+
+export const MaxShingleSizeParameter = ({ defaultToggleValue }: Props) => (
+
+
+
+);
diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx
index 83541ec982ee6..dafbebd24b3fa 100644
--- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx
+++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx
@@ -14,6 +14,7 @@ import {
NormsParameter,
SimilarityParameter,
TermVectorParameter,
+ MaxShingleSizeParameter,
} from '../../field_parameters';
import { BasicParametersSection, AdvancedParametersSection } from '../edit_field';
@@ -24,7 +25,8 @@ interface Props {
const getDefaultToggleValue = (param: string, field: FieldType) => {
switch (param) {
case 'similarity':
- case 'term_vector': {
+ case 'term_vector':
+ case 'max_shingle_size': {
return field[param] !== undefined && field[param] !== getFieldConfig(param).defaultValue;
}
case 'analyzers': {
@@ -47,6 +49,10 @@ export const SearchAsYouType = React.memo(({ field }: Props) => {
+
+
{
enable_position_increments: [],
depth_limit: true,
dims: false,
+ max_shingle_size: 'string_not_allowed',
},
// All the parameters in "goodField" have the correct format
// and should still be there after the validation ran.
@@ -307,6 +308,7 @@ describe('Properties validator', () => {
enable_position_increments: true,
depth_limit: 20,
dims: 'abc',
+ max_shingle_size: 2,
},
};
diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts
index 0fce3422344bc..b7bf4e6b112d3 100644
--- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts
+++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts
@@ -119,7 +119,8 @@ export type ParameterName =
| 'points_only'
| 'path'
| 'dims'
- | 'depth_limit';
+ | 'depth_limit'
+ | 'max_shingle_size';
export interface Parameter {
fieldConfig: FieldConfig;
diff --git a/x-pack/legacy/plugins/infra/public/components/fixed_datepicker.tsx b/x-pack/legacy/plugins/infra/public/components/fixed_datepicker.tsx
new file mode 100644
index 0000000000000..aab1bcd1da873
--- /dev/null
+++ b/x-pack/legacy/plugins/infra/public/components/fixed_datepicker.tsx
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { EuiDatePicker, EuiDatePickerProps } from '@elastic/eui';
+import euiStyled from '../../../../common/eui_styled_components';
+
+export const FixedDatePicker = euiStyled(
+ ({
+ className,
+ inputClassName,
+ ...datePickerProps
+ }: {
+ className?: string;
+ inputClassName?: string;
+ } & EuiDatePickerProps) => (
+
+ )
+)`
+ z-index: 3 !important;
+`;
diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx
index 4319f844b1dcc..02119fd1c09dd 100644
--- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/initial_configuration_step/analysis_setup_timerange_form.tsx
@@ -5,8 +5,6 @@
*/
import {
- EuiDatePicker,
- EuiDatePickerProps,
EuiDescribedFormGroup,
EuiFlexGroup,
EuiFormControlLayout,
@@ -16,8 +14,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import moment, { Moment } from 'moment';
import React, { useMemo } from 'react';
-
-import { euiStyled } from '../../../../../../../common/eui_styled_components';
+import { FixedDatePicker } from '../../../fixed_datepicker';
const startTimeLabel = i18n.translate('xpack.infra.analysisSetup.startTimeLabel', {
defaultMessage: 'Start time',
@@ -138,18 +135,3 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
);
};
-
-const FixedDatePicker = euiStyled(
- ({
- className,
- inputClassName,
- ...datePickerProps
- }: {
- className?: string;
- inputClassName?: string;
- } & EuiDatePickerProps) => (
-
- )
-)`
- z-index: 3 !important;
-`;
diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx
index 8f5705a9b9c56..5095edd4c715c 100644
--- a/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx
@@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import moment, { Moment } from 'moment';
import React from 'react';
+import { FixedDatePicker } from '../fixed_datepicker';
const noop = () => undefined;
@@ -56,7 +57,7 @@ export class LogTimeControls extends React.PureComponent {
return (
- {
height: '100%',
display: 'inline-block',
};
- return
;
+ return (
+
+
+
+ );
});
return {
value: palette.id,
diff --git a/x-pack/legacy/plugins/ml/common/constants/field_types.ts b/x-pack/legacy/plugins/ml/common/constants/field_types.ts
index 6c9af703c148a..9402e4c20e46f 100644
--- a/x-pack/legacy/plugins/ml/common/constants/field_types.ts
+++ b/x-pack/legacy/plugins/ml/common/constants/field_types.ts
@@ -16,3 +16,4 @@ export enum ML_JOB_FIELD_TYPES {
}
export const MLCATEGORY = 'mlcategory';
+export const DOC_COUNT = 'doc_count';
diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts
index b8df021990f58..a85674986c7f7 100644
--- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts
+++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts
@@ -15,6 +15,7 @@ import { AppStateSelectedCells } from '../explorer/explorer_utils';
declare interface ExplorerProps {
explorerState: ExplorerState;
+ severity: number;
showCharts: boolean;
setSelectedCells: (swimlaneSelectedCells: AppStateSelectedCells) => void;
}
diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js
index 7907131996578..6a1c5339de1f5 100644
--- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js
+++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js
@@ -96,6 +96,7 @@ export class Explorer extends React.Component {
static propTypes = {
explorerState: PropTypes.object.isRequired,
setSelectedCells: PropTypes.func.isRequired,
+ severity: PropTypes.number.isRequired,
showCharts: PropTypes.bool.isRequired,
};
@@ -260,7 +261,7 @@ export class Explorer extends React.Component {
};
render() {
- const { showCharts } = this.props;
+ const { showCharts, severity } = this.props;
const {
annotationsData,
@@ -276,7 +277,6 @@ export class Explorer extends React.Component {
queryString,
selectedCells,
selectedJobs,
- severity,
swimlaneContainerWidth,
tableData,
tableQueryString,
diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js
index 583375c87007e..a255b6b0434e4 100644
--- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js
+++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js
@@ -53,7 +53,7 @@ export const ExplorerChartSingleMetric = injectI18n(
static propTypes = {
tooManyBuckets: PropTypes.bool,
seriesConfig: PropTypes.object,
- severity: PropTypes.number,
+ severity: PropTypes.number.isRequired,
};
componentDidMount() {
@@ -312,13 +312,16 @@ export const ExplorerChartSingleMetric = injectI18n(
})
.on('mouseout', () => mlChartTooltipService.hide());
+ const isAnomalyVisible = d =>
+ _.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity;
+
// Update all dots to new positions.
dots
.attr('cx', d => lineChartXScale(d.date))
.attr('cy', d => lineChartYScale(d.value))
.attr('class', d => {
let markerClass = 'metric-value';
- if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity) {
+ if (isAnomalyVisible(d)) {
markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore).id}`;
}
return markerClass;
@@ -328,9 +331,7 @@ export const ExplorerChartSingleMetric = injectI18n(
const multiBucketMarkers = lineChartGroup
.select('.chart-markers')
.selectAll('.multi-bucket')
- .data(
- data.filter(d => d.anomalyScore !== null && showMultiBucketAnomalyMarker(d) === true)
- );
+ .data(data.filter(d => isAnomalyVisible(d) && showMultiBucketAnomalyMarker(d) === true));
// Remove multi-bucket markers that are no longer needed
multiBucketMarkers.exit().remove();
diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js
index 4fb4e7d4df94f..14d356c0d1c81 100644
--- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js
+++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js
@@ -280,11 +280,13 @@ export function loadViewByTopFieldValuesForSelectedTime(
const topFieldValues = [];
const topInfluencers = resp.influencers[viewBySwimlaneFieldName];
- topInfluencers.forEach(influencerData => {
- if (influencerData.maxAnomalyScore > 0) {
- topFieldValues.push(influencerData.influencerFieldValue);
- }
- });
+ if (Array.isArray(topInfluencers)) {
+ topInfluencers.forEach(influencerData => {
+ if (influencerData.maxAnomalyScore > 0) {
+ topFieldValues.push(influencerData.influencerFieldValue);
+ }
+ });
+ }
resolve(topFieldValues);
});
} else {
diff --git a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts
index 99b9aceab3696..663a3f3d8f955 100644
--- a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts
+++ b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts
@@ -20,6 +20,7 @@ describe('ML - roundToDecimalPlace formatter', () => {
expect(roundToDecimalPlace(0.0005)).toBe('5.00e-4');
expect(roundToDecimalPlace(-0.0005)).toBe('-5.00e-4');
expect(roundToDecimalPlace(-12.045)).toBe(-12.04);
+ expect(roundToDecimalPlace(0)).toBe(0);
});
it('returns the correct format using specified decimal place', () => {
@@ -31,5 +32,6 @@ describe('ML - roundToDecimalPlace formatter', () => {
expect(roundToDecimalPlace(0.0005, 4)).toBe(0.0005);
expect(roundToDecimalPlace(0.00005, 4)).toBe('5.00e-5');
expect(roundToDecimalPlace(-0.00005, 4)).toBe('-5.00e-5');
+ expect(roundToDecimalPlace(0, 4)).toBe(0);
});
});
diff --git a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts
index f863fe6d76e57..5a030d7619e98 100644
--- a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts
+++ b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts
@@ -4,7 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export function roundToDecimalPlace(num: number, dp: number = 2) {
+export function roundToDecimalPlace(num: number, dp: number = 2): number | string {
+ if (num % 1 === 0) {
+ // no decimal place
+ return num;
+ }
+
if (Math.abs(num) < Math.pow(10, -dp)) {
return Number.parseFloat(String(num)).toExponential(2);
}
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js
index eb0a905725d75..9984f3be299ae 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js
@@ -6,6 +6,7 @@
import numeral from '@elastic/numeral';
import { formatDate } from '@elastic/eui/lib/services/format';
+import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place';
import { toLocaleString } from '../../../../util/string_utils';
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
@@ -35,6 +36,8 @@ export function formatValues([key, value]) {
case 'established_model_memory':
case 'input_bytes':
case 'model_bytes':
+ case 'model_bytes_exceeded':
+ case 'model_bytes_memory_limit':
value = formatData(value);
break;
@@ -53,9 +56,16 @@ export function formatValues([key, value]) {
case 'total_over_field_count':
case 'total_partition_field_count':
case 'bucket_allocation_failures_count':
+ case 'search_count':
value = toLocaleString(value);
break;
+ // numbers rounded to 3 decimal places
+ case 'average_search_time_per_bucket_ms':
+ case 'exponential_average_search_time_per_hour_ms':
+ value = typeof value === 'number' ? roundToDecimalPlace(value, 3).toLocaleString() : value;
+ break;
+
default:
break;
}
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts
index b0eb1b98cd02b..4530c00c10535 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts
@@ -183,7 +183,7 @@ export class AdvancedJobCreator extends JobCreator {
public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
- const detectors = getRichDetectors(job, datafeed, this.scriptFields, true);
+ const detectors = getRichDetectors(job, datafeed, this.additionalFields, true);
// keep track of the custom rules for each detector
const customRules = this._detectors.map(d => d.custom_rules);
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts
index 7c070ccc6bc53..71619311c4361 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts
@@ -140,7 +140,7 @@ export class CategorizationJobCreator extends JobCreator {
public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
this.createdBy = CREATED_BY_LABEL.CATEGORIZATION;
- const detectors = getRichDetectors(job, datafeed, this.scriptFields, false);
+ const detectors = getRichDetectors(job, datafeed, this.additionalFields, false);
const dtr = detectors[0];
if (detectors.length && dtr.agg !== null && dtr.field !== null) {
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
index 513c8239db01e..90c189c9d6197 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
@@ -19,7 +19,7 @@ import {
CREATED_BY_LABEL,
SHARED_RESULTS_INDEX_NAME,
} from '../../../../../../common/constants/new_job';
-import { isSparseDataJob } from './util/general';
+import { isSparseDataJob, collectAggs } from './util/general';
import { parseInterval } from '../../../../../../common/util/parse_interval';
import { Calendar } from '../../../../../../common/types/calendars';
import { mlCalendarService } from '../../../../services/calendar_service';
@@ -43,6 +43,7 @@ export class JobCreator {
protected _aggs: Aggregation[] = [];
protected _fields: Field[] = [];
protected _scriptFields: Field[] = [];
+ protected _aggregationFields: Field[] = [];
protected _sparseData: boolean = false;
private _stopAllRefreshPolls: {
stop: boolean;
@@ -450,6 +451,14 @@ export class JobCreator {
return this._scriptFields;
}
+ public get aggregationFields(): Field[] {
+ return this._aggregationFields;
+ }
+
+ public get additionalFields(): Field[] {
+ return [...this._scriptFields, ...this._aggregationFields];
+ }
+
public get subscribers(): ProgressSubscriber[] {
return this._subscribers;
}
@@ -603,6 +612,7 @@ export class JobCreator {
}
this._sparseData = isSparseDataJob(job, datafeed);
+ this._scriptFields = [];
if (this._datafeed_config.script_fields !== undefined) {
this._scriptFields = Object.keys(this._datafeed_config.script_fields).map(f => ({
id: f,
@@ -610,8 +620,11 @@ export class JobCreator {
type: ES_FIELD_TYPES.KEYWORD,
aggregatable: true,
}));
- } else {
- this._scriptFields = [];
+ }
+
+ this._aggregationFields = [];
+ if (this._datafeed_config.aggregations?.buckets !== undefined) {
+ collectAggs(this._datafeed_config.aggregations.buckets, this._aggregationFields);
}
}
}
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts
index 8a4411bf9025f..7c5fba028d9e8 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts
@@ -153,7 +153,7 @@ export class MultiMetricJobCreator extends JobCreator {
public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
this.createdBy = CREATED_BY_LABEL.MULTI_METRIC;
- const detectors = getRichDetectors(job, datafeed, this.scriptFields, false);
+ const detectors = getRichDetectors(job, datafeed, this.additionalFields, false);
if (datafeed.aggregations !== undefined) {
// if we've converting from a single metric job,
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts
index 9300e53c578e1..3009d68ca67ca 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts
@@ -135,7 +135,7 @@ export class PopulationJobCreator extends JobCreator {
public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
this.createdBy = CREATED_BY_LABEL.POPULATION;
- const detectors = getRichDetectors(job, datafeed, this.scriptFields, false);
+ const detectors = getRichDetectors(job, datafeed, this.additionalFields, false);
this.removeAllDetectors();
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts
index f98fd4dbe970a..9f3500185c2bf 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts
@@ -190,7 +190,7 @@ export class SingleMetricJobCreator extends JobCreator {
public cloneFromExistingJob(job: Job, datafeed: Datafeed) {
this._overrideConfigs(job, datafeed);
this.createdBy = CREATED_BY_LABEL.SINGLE_METRIC;
- const detectors = getRichDetectors(job, datafeed, this.scriptFields, false);
+ const detectors = getRichDetectors(job, datafeed, this.additionalFields, false);
this.removeAllDetectors();
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts
index 6443539a9877d..e5b6212a4326e 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts
@@ -11,7 +11,8 @@ import {
ML_JOB_AGGREGATION,
SPARSE_DATA_AGGREGATIONS,
} from '../../../../../../../common/constants/aggregation_types';
-import { MLCATEGORY } from '../../../../../../../common/constants/field_types';
+import { MLCATEGORY, DOC_COUNT } from '../../../../../../../common/constants/field_types';
+import { ES_FIELD_TYPES } from '../../../../../../../../../../../src/plugins/data/public';
import {
EVENT_RATE_FIELD_ID,
Field,
@@ -27,14 +28,14 @@ import {
} from '../index';
import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../../common/constants/new_job';
-const getFieldByIdFactory = (scriptFields: Field[]) => (id: string) => {
+const getFieldByIdFactory = (additionalFields: Field[]) => (id: string) => {
let field = newJobCapsService.getFieldById(id);
// if no field could be found it may be a pretend field, like mlcategory or a script field
if (field === null) {
if (id === MLCATEGORY) {
field = mlCategory;
- } else if (scriptFields.length) {
- field = scriptFields.find(f => f.id === id) || null;
+ } else if (additionalFields.length) {
+ field = additionalFields.find(f => f.id === id) || null;
}
}
return field;
@@ -44,12 +45,12 @@ const getFieldByIdFactory = (scriptFields: Field[]) => (id: string) => {
export function getRichDetectors(
job: Job,
datafeed: Datafeed,
- scriptFields: Field[],
+ additionalFields: Field[],
advanced: boolean = false
) {
const detectors = advanced ? getDetectorsAdvanced(job, datafeed) : getDetectors(job, datafeed);
- const getFieldById = getFieldByIdFactory(scriptFields);
+ const getFieldById = getFieldByIdFactory(additionalFields);
return detectors.map(d => {
let field = null;
@@ -82,19 +83,19 @@ export function getRichDetectors(
});
}
-export function createFieldOptions(fields: Field[]) {
- return fields
- .filter(f => f.id !== EVENT_RATE_FIELD_ID)
- .map(f => ({
- label: f.name,
- }))
- .sort((a, b) => a.label.localeCompare(b.label));
-}
-
-export function createScriptFieldOptions(scriptFields: Field[]) {
- return scriptFields.map(f => ({
- label: f.id,
- }));
+export function createFieldOptions(fields: Field[], additionalFields: Field[]) {
+ return [
+ ...fields
+ .filter(f => f.id !== EVENT_RATE_FIELD_ID)
+ .map(f => ({
+ label: f.name,
+ })),
+ ...additionalFields
+ .filter(f => fields.some(f2 => f2.id === f.id) === false)
+ .map(f => ({
+ label: f.id,
+ })),
+ ].sort((a, b) => a.label.localeCompare(b.label));
}
export function createMlcategoryFieldOption(categorizationFieldName: string | null) {
@@ -108,6 +109,16 @@ export function createMlcategoryFieldOption(categorizationFieldName: string | nu
];
}
+export function createDocCountFieldOption(usingAggregations: boolean) {
+ return usingAggregations
+ ? [
+ {
+ label: DOC_COUNT,
+ },
+ ]
+ : [];
+}
+
function getDetectorsAdvanced(job: Job, datafeed: Datafeed) {
return processFieldlessAggs(job.analysis_config.detectors);
}
@@ -305,3 +316,26 @@ export function getJobCreatorTitle(jobCreator: JobCreatorType) {
return '';
}
}
+
+// recurse through a datafeed aggregation object,
+// adding top level keys from each nested agg to an array
+// of fields
+export function collectAggs(o: any, aggFields: Field[]) {
+ for (const i in o) {
+ if (o[i] !== null && typeof o[i] === 'object') {
+ if (i === 'aggregations' || i === 'aggs') {
+ Object.keys(o[i]).forEach(k => {
+ if (k !== 'aggregations' && i !== 'aggs') {
+ aggFields.push({
+ id: k,
+ name: k,
+ type: ES_FIELD_TYPES.KEYWORD,
+ aggregatable: true,
+ });
+ }
+ });
+ }
+ collectAggs(o[i], aggFields);
+ }
+ }
+}
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/time_field/time_field_select.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/time_field/time_field_select.tsx
index f2e2516866835..9af1226d1fe6c 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/time_field/time_field_select.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/time_field/time_field_select.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC } from 'react';
+import React, { FC, useContext } from 'react';
import { EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui';
+import { JobCreatorContext } from '../../../job_creator_context';
import { Field } from '../../../../../../../../../common/types/fields';
import { createFieldOptions } from '../../../../../common/job_creator/util/general';
@@ -17,7 +18,8 @@ interface Props {
}
export const TimeFieldSelect: FC = ({ fields, changeHandler, selectedField }) => {
- const options: EuiComboBoxOptionProps[] = createFieldOptions(fields);
+ const { jobCreator } = useContext(JobCreatorContext);
+ const options: EuiComboBoxOptionProps[] = createFieldOptions(fields, jobCreator.additionalFields);
const selection: EuiComboBoxOptionProps[] = [];
if (selectedField !== null) {
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
index 06c8068a9c005..753cea7adcb35 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
@@ -18,7 +18,6 @@ import { JobCreatorContext } from '../../../job_creator_context';
import { AdvancedJobCreator } from '../../../../../common/job_creator';
import {
createFieldOptions,
- createScriptFieldOptions,
createMlcategoryFieldOption,
} from '../../../../../common/job_creator/util/general';
import {
@@ -88,7 +87,7 @@ export const AdvancedDetectorModal: FC = ({
const [fieldOptionEnabled, setFieldOptionEnabled] = useState(true);
const { descriptionPlaceholder, setDescriptionPlaceholder } = useDetectorPlaceholder(detector);
- const usingScriptFields = jobCreator.scriptFields.length > 0;
+ const usingScriptFields = jobCreator.additionalFields.length > 0;
// list of aggregation combobox options.
const aggOptions: EuiComboBoxOptionProps[] = aggs
@@ -98,12 +97,12 @@ export const AdvancedDetectorModal: FC = ({
// fields available for the selected agg
const { currentFieldOptions, setCurrentFieldOptions } = useCurrentFieldOptions(
detector.agg,
- jobCreator.scriptFields
+ jobCreator.additionalFields,
+ fields
);
const allFieldOptions: EuiComboBoxOptionProps[] = [
- ...createFieldOptions(fields),
- ...createScriptFieldOptions(jobCreator.scriptFields),
+ ...createFieldOptions(fields, jobCreator.additionalFields),
].sort(comboBoxOptionsSort);
const splitFieldOptions: EuiComboBoxOptionProps[] = [
@@ -127,7 +126,9 @@ export const AdvancedDetectorModal: FC = ({
return mlCategory;
}
return (
- fields.find(f => f.id === title) || jobCreator.scriptFields.find(f => f.id === title) || null
+ fields.find(f => f.id === title) ||
+ jobCreator.additionalFields.find(f => f.id === title) ||
+ null
);
}
@@ -365,21 +366,27 @@ function useDetectorPlaceholder(detector: RichDetector) {
}
// creates list of combobox options based on an aggregation's field list
-function createFieldOptionsFromAgg(agg: Aggregation | null) {
- return createFieldOptions(agg !== null && agg.fields !== undefined ? agg.fields : []);
+function createFieldOptionsFromAgg(agg: Aggregation | null, additionalFields: Field[]) {
+ return createFieldOptions(
+ agg !== null && agg.fields !== undefined ? agg.fields : [],
+ additionalFields
+ );
}
// custom hook for storing combobox options based on an aggregation field list
-function useCurrentFieldOptions(aggregation: Aggregation | null, scriptFields: Field[]) {
+function useCurrentFieldOptions(
+ aggregation: Aggregation | null,
+ additionalFields: Field[],
+ fields: Field[]
+) {
const [currentFieldOptions, setCurrentFieldOptions] = useState(
- createFieldOptionsFromAgg(aggregation)
+ createFieldOptionsFromAgg(aggregation, additionalFields)
);
- const scriptFieldOptions = createScriptFieldOptions(scriptFields);
return {
currentFieldOptions,
setCurrentFieldOptions: (agg: Aggregation | null) =>
- setCurrentFieldOptions([...createFieldOptionsFromAgg(agg), ...scriptFieldOptions]),
+ setCurrentFieldOptions(createFieldOptionsFromAgg(agg, additionalFields)),
};
}
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx
index d995d40284aba..6451c2785eae0 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx
@@ -9,10 +9,7 @@ import { EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui';
import { JobCreatorContext } from '../../../job_creator_context';
import { Field } from '../../../../../../../../../common/types/fields';
-import {
- createFieldOptions,
- createScriptFieldOptions,
-} from '../../../../../common/job_creator/util/general';
+import { createFieldOptions } from '../../../../../common/job_creator/util/general';
interface Props {
fields: Field[];
@@ -23,8 +20,7 @@ interface Props {
export const CategorizationFieldSelect: FC = ({ fields, changeHandler, selectedField }) => {
const { jobCreator } = useContext(JobCreatorContext);
const options: EuiComboBoxOptionProps[] = [
- ...createFieldOptions(fields),
- ...createScriptFieldOptions(jobCreator.scriptFields),
+ ...createFieldOptions(fields, jobCreator.additionalFields),
];
const selection: EuiComboBoxOptionProps[] = [];
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx
index 639bdb9ec76bf..d4ac470f4ea4f 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx
@@ -11,7 +11,6 @@ import { JobCreatorContext } from '../../../job_creator_context';
import { Field } from '../../../../../../../../../common/types/fields';
import {
createFieldOptions,
- createScriptFieldOptions,
createMlcategoryFieldOption,
} from '../../../../../common/job_creator/util/general';
@@ -24,8 +23,7 @@ interface Props {
export const InfluencersSelect: FC = ({ fields, changeHandler, selectedInfluencers }) => {
const { jobCreator } = useContext(JobCreatorContext);
const options: EuiComboBoxOptionProps[] = [
- ...createFieldOptions(fields),
- ...createScriptFieldOptions(jobCreator.scriptFields),
+ ...createFieldOptions(fields, jobCreator.additionalFields),
...createMlcategoryFieldOption(jobCreator.categorizationFieldName),
];
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx
index efe32e3173cad..6fe3aaf0a8652 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx
@@ -11,7 +11,7 @@ import { JobCreatorContext } from '../../../job_creator_context';
import { Field } from '../../../../../../../../../common/types/fields';
import {
createFieldOptions,
- createScriptFieldOptions,
+ createDocCountFieldOption,
} from '../../../../../common/job_creator/util/general';
interface Props {
@@ -23,8 +23,8 @@ interface Props {
export const SummaryCountFieldSelect: FC = ({ fields, changeHandler, selectedField }) => {
const { jobCreator } = useContext(JobCreatorContext);
const options: EuiComboBoxOptionProps[] = [
- ...createFieldOptions(fields),
- ...createScriptFieldOptions(jobCreator.scriptFields),
+ ...createFieldOptions(fields, jobCreator.additionalFields),
+ ...createDocCountFieldOption(jobCreator.aggregationFields.length > 0),
];
const selection: EuiComboBoxOptionProps[] = [];
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx
index 141ed5d1bbb8f..c4a96d9e373c8 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx
@@ -77,6 +77,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => {
const [kibanaObjects, setKibanaObjects] = useState({});
const [saveState, setSaveState] = useState(SAVE_STATE.NOT_SAVED);
const [resultsUrl, setResultsUrl] = useState('');
+ const [existingGroups, setExistingGroups] = useState(existingGroupIds);
// #endregion
const {
@@ -109,6 +110,10 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => {
setKibanaObjects(kibanaObjectsResult);
setSaveState(SAVE_STATE.NOT_SAVED);
+
+ // mix existing groups from the server with the groups used across all jobs in the module.
+ const moduleGroups = [...response.jobs.map(j => j.config.groups || [])].flat();
+ setExistingGroups([...new Set([...existingGroups, ...moduleGroups])]);
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
@@ -222,6 +227,12 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => {
...jobOverrides,
[job.job_id as string]: job,
});
+ if (job.groups !== undefined) {
+ // add newly added jobs to the list of existing groups
+ // for use when editing other jobs in the module
+ const groups = [...new Set([...existingGroups, ...job.groups])];
+ setExistingGroups(groups);
+ }
};
const isFormVisible = [SAVE_STATE.NOT_SAVED, SAVE_STATE.SAVING].includes(saveState);
@@ -304,7 +315,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => {
jobs={jobs}
jobPrefix={jobPrefix}
saveState={saveState}
- existingGroupIds={existingGroupIds}
+ existingGroupIds={existingGroups}
jobOverrides={jobOverrides}
onJobOverridesChange={onJobOverridesChange}
/>
diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx
index 6aaad5294369b..633efc2856dac 100644
--- a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx
@@ -184,6 +184,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim
explorerState,
setSelectedCells,
showCharts,
+ severity: tableSeverity.val,
}}
/>
diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
index c3c644d43fa59..f824faf7845c6 100644
--- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx
@@ -26,7 +26,10 @@ import { APP_STATE_ACTION } from '../../timeseriesexplorer/timeseriesexplorer_co
import {
createTimeSeriesJobData,
getAutoZoomDuration,
+ validateJobSelection,
} from '../../timeseriesexplorer/timeseriesexplorer_utils';
+import { TimeSeriesExplorerPage } from '../../timeseriesexplorer/timeseriesexplorer_page';
+import { TimeseriesexplorerNoJobsFound } from '../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found';
import { useUrlState } from '../../util/url_state';
import { useTableInterval } from '../../components/controls/select_interval';
import { useTableSeverity } from '../../components/controls/select_severity';
@@ -81,6 +84,7 @@ export const TimeSeriesExplorerUrlStateManager: FC();
const refresh = useRefresh();
useEffect(() => {
@@ -141,6 +145,10 @@ export const TimeSeriesExplorerUrlStateManager: FC
+
+
+ );
+ }
+
+ if (selectedJobId === undefined || autoZoomDuration === undefined || bounds === undefined) {
+ return null;
+ }
+
return (
{
- let jobFilter = {};
- // if no jobId specified, load all of the messages
- if (jobId !== undefined) {
- jobFilter = {
- bool: {
- should: [
- {
- term: {
- job_id: '', // catch system messages
- },
- },
- {
- term: {
- job_id: jobId, // messages for specified jobId
- },
- },
- ],
- },
- };
- }
-
- let timeFilter = {};
- if (fromRange !== undefined && fromRange !== '') {
- timeFilter = {
- range: {
- timestamp: {
- gte: `now-${fromRange}`,
- lte: 'now',
- },
- },
- };
- }
-
- ml.esSearch({
- index: ML_NOTIFICATION_INDEX_PATTERN,
- ignore_unavailable: true,
- rest_total_hits_as_int: true,
- size: 1000,
- body: {
- sort: [{ timestamp: { order: 'asc' } }, { job_id: { order: 'asc' } }],
- query: {
- bool: {
- filter: [
- {
- bool: {
- must_not: {
- term: {
- level: 'activity',
- },
- },
- },
- },
- anomalyDetectorTypeFilter,
- jobFilter,
- timeFilter,
- ],
- },
- },
- },
- })
- .then(resp => {
- let messages = [];
- if (resp.hits.total !== 0) {
- messages = resp.hits.hits.map(hit => hit._source);
- }
- resolve({ messages });
- })
- .catch(resp => {
- reject(resp);
- });
- });
-}
-
-// search highest, most recent audit messages for all jobs for the last 24hrs.
-function getAuditMessagesSummary() {
- return new Promise((resolve, reject) => {
- ml.esSearch({
- index: ML_NOTIFICATION_INDEX_PATTERN,
- ignore_unavailable: true,
- rest_total_hits_as_int: true,
- size: 0,
- body: {
- query: {
- bool: {
- filter: [
- {
- range: {
- timestamp: {
- gte: 'now-1d',
- },
- },
- },
- anomalyDetectorTypeFilter,
- ],
- },
- },
- aggs: {
- levelsPerJob: {
- terms: {
- field: 'job_id',
- },
- aggs: {
- levels: {
- terms: {
- field: 'level',
- },
- aggs: {
- latestMessage: {
- terms: {
- field: 'message.raw',
- size: 1,
- order: {
- latestMessage: 'desc',
- },
- },
- aggs: {
- latestMessage: {
- max: {
- field: 'timestamp',
- },
- },
- },
- },
- },
- },
- },
- },
- },
- },
- })
- .then(resp => {
- let messagesPerJob = [];
- if (
- resp.hits.total !== 0 &&
- resp.aggregations &&
- resp.aggregations.levelsPerJob &&
- resp.aggregations.levelsPerJob.buckets &&
- resp.aggregations.levelsPerJob.buckets.length
- ) {
- messagesPerJob = resp.aggregations.levelsPerJob.buckets;
- }
- resolve({ messagesPerJob });
- })
- .catch(resp => {
- reject(resp);
- });
- });
-}
-
-export const jobMessagesService = {
- getJobAuditMessages,
- getAuditMessagesSummary,
-};
diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/index.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/index.js
rename to x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/index.ts
diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.tsx
similarity index 100%
rename from x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.js
rename to x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseriesexplorer_no_jobs_found/timeseriesexplorer_no_jobs_found.tsx
diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts
index 651c609004236..6c1bb01137c91 100644
--- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts
+++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts
@@ -12,12 +12,11 @@ import { getDateFormatTz, TimeRangeBounds } from '../explorer/explorer_utils';
declare const TimeSeriesExplorer: FC<{
appStateHandler: (action: string, payload: any) => void;
- autoZoomDuration?: number;
- bounds?: TimeRangeBounds;
+ autoZoomDuration: number;
+ bounds: TimeRangeBounds;
dateFormatTz: string;
- jobsWithTimeRange: any[];
lastRefresh: number;
- selectedJobIds: string[];
+ selectedJobId: string;
selectedDetectorIndex: number;
selectedEntities: any[];
selectedForecastId: string;
diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js
index 1862ead045743..f3d8692bfb3e9 100644
--- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js
+++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js
@@ -8,7 +8,7 @@
* React component for rendering Single Metric Viewer.
*/
-import { debounce, difference, each, find, get, has, isEqual, without } from 'lodash';
+import { debounce, each, find, get, has, isEqual } from 'lodash';
import moment from 'moment-timezone';
import { Subject, Subscription, forkJoin } from 'rxjs';
import { map, debounceTime, switchMap, tap, withLatestFrom } from 'rxjs/operators';
@@ -24,7 +24,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
- EuiProgress,
EuiSelect,
EuiSpacer,
EuiText,
@@ -49,15 +48,12 @@ import { AnomaliesTable } from '../components/anomalies_table/anomalies_table';
import { ChartTooltip } from '../components/chart_tooltip';
import { EntityControl } from './components/entity_control';
import { ForecastingModal } from './components/forecasting_modal/forecasting_modal';
-import { JobSelector } from '../components/job_selector';
-import { getTimeRangeFromSelection } from '../components/job_selector/job_select_service_utils';
import { LoadingIndicator } from '../components/loading_indicator/loading_indicator';
-import { NavigationMenu } from '../components/navigation_menu';
import { SelectInterval } from '../components/controls/select_interval/select_interval';
import { SelectSeverity } from '../components/controls/select_severity/select_severity';
import { TimeseriesChart } from './components/timeseries_chart/timeseries_chart';
-import { TimeseriesexplorerNoJobsFound } from './components/timeseriesexplorer_no_jobs_found';
import { TimeseriesexplorerNoChartData } from './components/timeseriesexplorer_no_chart_data';
+import { TimeSeriesExplorerPage } from './timeseriesexplorer_page';
import { ml } from '../services/ml_api_service';
import { mlFieldFormatService } from '../services/field_format_service';
@@ -154,44 +150,16 @@ function getTimeseriesexplorerDefaultState() {
};
}
-const TimeSeriesExplorerPage = ({ children, jobSelectorProps, loading, resizeRef }) => (
-
-
- {/* Show animated progress bar while loading */}
- {loading && }
- {/* Show a progress bar with progress 0% when not loading.
- If we'd just show no progress bar when not loading it would result in a flickering height effect. */}
- {!loading && (
-
- )}
-
-
- {children}
-
-
-);
-
const containerPadding = 24;
export class TimeSeriesExplorer extends React.Component {
static propTypes = {
appStateHandler: PropTypes.func.isRequired,
- autoZoomDuration: PropTypes.number,
- bounds: PropTypes.object,
+ autoZoomDuration: PropTypes.number.isRequired,
+ bounds: PropTypes.object.isRequired,
dateFormatTz: PropTypes.string.isRequired,
- jobsWithTimeRange: PropTypes.array.isRequired,
lastRefresh: PropTypes.number.isRequired,
- selectedJobIds: PropTypes.arrayOf(PropTypes.string),
+ selectedJobId: PropTypes.string.isRequired,
selectedDetectorIndex: PropTypes.number,
selectedEntities: PropTypes.object,
selectedForecastId: PropTypes.string,
@@ -285,9 +253,9 @@ export class TimeSeriesExplorer extends React.Component {
contextChartSelectedInitCallDone = false;
getFocusAggregationInterval(selection) {
- const { selectedJobIds } = this.props;
+ const { selectedJobId } = this.props;
const jobs = createTimeSeriesJobData(mlJobService.jobs);
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const selectedJob = mlJobService.getJob(selectedJobId);
// Calculate the aggregation interval for the focus chart.
const bounds = { min: moment(selection.from), max: moment(selection.to) };
@@ -299,9 +267,9 @@ export class TimeSeriesExplorer extends React.Component {
* Gets focus data for the current component state/
*/
getFocusData(selection) {
- const { selectedJobIds, selectedForecastId, selectedDetectorIndex } = this.props;
+ const { selectedJobId, selectedForecastId, selectedDetectorIndex } = this.props;
const { modelPlotEnabled } = this.state;
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const selectedJob = mlJobService.getJob(selectedJobId);
const entityControls = this.getControlsForDetector();
// Calculate the aggregation interval for the focus chart.
@@ -356,11 +324,11 @@ export class TimeSeriesExplorer extends React.Component {
const {
dateFormatTz,
selectedDetectorIndex,
- selectedJobIds,
+ selectedJobId,
tableInterval,
tableSeverity,
} = this.props;
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const selectedJob = mlJobService.getJob(selectedJobId);
const entityControls = this.getControlsForDetector();
return ml.results
@@ -424,8 +392,8 @@ export class TimeSeriesExplorer extends React.Component {
loadEntityValues = async (entities, searchTerm = {}) => {
this.setState({ entitiesLoading: true });
- const { bounds, selectedJobIds, selectedDetectorIndex } = this.props;
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const { bounds, selectedJobId, selectedDetectorIndex } = this.props;
+ const selectedJob = mlJobService.getJob(selectedJobId);
// Populate the entity input datalists with the values from the top records by score
// for the selected detector across the full time range. No need to pass through finish().
@@ -479,17 +447,13 @@ export class TimeSeriesExplorer extends React.Component {
bounds,
selectedDetectorIndex,
selectedForecastId,
- selectedJobIds,
+ selectedJobId,
zoom,
} = this.props;
- if (selectedJobIds === undefined || bounds === undefined) {
- return;
- }
-
const { loadCounter: currentLoadCounter } = this.state;
- const currentSelectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const currentSelectedJob = mlJobService.getJob(selectedJobId);
if (currentSelectedJob === undefined) {
return;
@@ -526,7 +490,7 @@ export class TimeSeriesExplorer extends React.Component {
const { loadCounter, modelPlotEnabled } = this.state;
const jobs = createTimeSeriesJobData(mlJobService.jobs);
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const selectedJob = mlJobService.getJob(selectedJobId);
const detectorIndex = selectedDetectorIndex;
let awaitingCount = 3;
@@ -717,8 +681,8 @@ export class TimeSeriesExplorer extends React.Component {
* @param callback to invoke after a state update.
*/
getControlsForDetector = () => {
- const { selectedDetectorIndex, selectedEntities, selectedJobIds } = this.props;
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const { selectedDetectorIndex, selectedEntities, selectedJobId } = this.props;
+ const selectedJob = mlJobService.getJob(selectedJobId);
const entities = [];
@@ -871,9 +835,9 @@ export class TimeSeriesExplorer extends React.Component {
}
}),
switchMap(selection => {
- const { selectedJobIds } = this.props;
+ const { selectedJobId } = this.props;
const jobs = createTimeSeriesJobData(mlJobService.jobs);
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const selectedJob = mlJobService.getJob(selectedJobId);
// Calculate the aggregation interval for the focus chart.
const bounds = { min: moment(selection.from), max: moment(selection.to) };
@@ -927,133 +891,19 @@ export class TimeSeriesExplorer extends React.Component {
this.componentDidUpdate();
}
- /**
- * returns true/false if setGlobalState has been triggered
- * or returns the job id which should be loaded.
- */
- checkJobSelection() {
- const { jobsWithTimeRange, selectedJobIds, setGlobalState } = this.props;
-
- const jobs = createTimeSeriesJobData(mlJobService.jobs);
- const timeSeriesJobIds = jobs.map(j => j.id);
-
- // Check if any of the jobs set in the URL are not time series jobs
- // (e.g. if switching to this view straight from the Anomaly Explorer).
- const invalidIds = difference(selectedJobIds, timeSeriesJobIds);
- const validSelectedJobIds = without(selectedJobIds, ...invalidIds);
- if (invalidIds.length > 0) {
- let warningText = i18n.translate(
- 'xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage',
- {
- defaultMessage: `You can't view requested {invalidIdsCount, plural, one {job} other {jobs}} {invalidIds} in this dashboard`,
- values: {
- invalidIdsCount: invalidIds.length,
- invalidIds,
- },
- }
- );
- if (validSelectedJobIds.length === 0 && timeSeriesJobIds.length > 0) {
- warningText += i18n.translate('xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText', {
- defaultMessage: ', auto selecting first job',
- });
- }
- toastNotifications.addWarning(warningText);
- }
-
- if (validSelectedJobIds.length > 1) {
- // if more than one job or a group has been loaded from the URL
- if (validSelectedJobIds.length > 1) {
- // if more than one job, select the first job from the selection.
- toastNotifications.addWarning(
- i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', {
- defaultMessage: 'You can only view one job at a time in this dashboard',
- })
- );
- setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] });
- return true;
- } else {
- // if a group has been loaded
- if (selectedJobIds.length > 0) {
- // if the group contains valid jobs, select the first
- toastNotifications.addWarning(
- i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', {
- defaultMessage: 'You can only view one job at a time in this dashboard',
- })
- );
- setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] });
- return true;
- } else if (jobs.length > 0) {
- // if there are no valid jobs in the group but there are valid jobs
- // in the list of all jobs, select the first
- const jobIds = [jobs[0].id];
- const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds);
- setGlobalState({
- ...{ ml: { jobIds } },
- ...(time !== undefined ? { time } : {}),
- });
- return true;
- } else {
- // if there are no valid jobs left.
- return false;
- }
- }
- } else if (invalidIds.length > 0 && validSelectedJobIds.length > 0) {
- // if some ids have been filtered out because they were invalid.
- // refresh the URL with the first valid id
- setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] });
- return true;
- } else if (validSelectedJobIds.length > 0) {
- // normal behavior. a job ID has been loaded from the URL
- // Clear the detectorIndex, entities and forecast info.
- return validSelectedJobIds[0];
- } else {
- if (validSelectedJobIds.length === 0 && jobs.length > 0) {
- // no jobs were loaded from the URL, so add the first job
- // from the full jobs list.
- const jobIds = [jobs[0].id];
- const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds);
- setGlobalState({
- ...{ ml: { jobIds } },
- ...(time !== undefined ? { time } : {}),
- });
- return true;
- } else {
- // Jobs exist, but no time series jobs.
- return false;
- }
- }
- }
-
componentDidUpdate(previousProps) {
- if (
- previousProps === undefined ||
- !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds)
- ) {
- const update = this.checkJobSelection();
- // - true means a setGlobalState got triggered and
- // we'll just wait for the next React render.
- // - false means there are either no jobs or no time based jobs present.
- // - if we get back a string it means we got back a job id we can load.
- if (update === true) {
- return;
- } else if (update === false) {
- this.setState({ loading: false });
- return;
- } else if (typeof update === 'string') {
- this.contextChartSelectedInitCallDone = false;
- this.setState({ fullRefresh: false, loading: true }, () => {
- this.loadForJobId(update);
- });
- }
+ if (previousProps === undefined || previousProps.selectedJobId !== this.props.selectedJobId) {
+ this.contextChartSelectedInitCallDone = false;
+ this.setState({ fullRefresh: false, loading: true }, () => {
+ this.loadForJobId(this.props.selectedJobId);
+ });
}
if (
- this.props.bounds !== undefined &&
- this.props.selectedJobIds !== undefined &&
- (previousProps === undefined ||
- !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) ||
- previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex ||
- !isEqual(previousProps.selectedEntities, this.props.selectedEntities))
+ previousProps === undefined ||
+ previousProps.selectedJobId !== this.props.selectedJobId ||
+ previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex ||
+ !isEqual(previousProps.selectedEntities, this.props.selectedEntities)
) {
const entityControls = this.getControlsForDetector();
this.loadEntityValues(entityControls);
@@ -1076,7 +926,7 @@ export class TimeSeriesExplorer extends React.Component {
!isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) ||
!isEqual(previousProps.selectedEntities, this.props.selectedEntities) ||
!isEqual(previousProps.selectedForecastId, this.props.selectedForecastId) ||
- !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) ||
+ previousProps.selectedJobId !== this.props.selectedJobId ||
!isEqual(previousProps.zoom, this.props.zoom)
) {
const fullRefresh =
@@ -1086,7 +936,7 @@ export class TimeSeriesExplorer extends React.Component {
!isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) ||
!isEqual(previousProps.selectedEntities, this.props.selectedEntities) ||
!isEqual(previousProps.selectedForecastId, this.props.selectedForecastId) ||
- !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds);
+ previousProps.selectedJobId !== this.props.selectedJobId;
this.loadSingleMetricData(fullRefresh);
}
@@ -1159,7 +1009,7 @@ export class TimeSeriesExplorer extends React.Component {
dateFormatTz,
lastRefresh,
selectedDetectorIndex,
- selectedJobIds,
+ selectedJobId,
} = this.props;
const {
@@ -1211,34 +1061,13 @@ export class TimeSeriesExplorer extends React.Component {
autoZoomDuration,
};
- const jobSelectorProps = {
- dateFormatTz,
- singleSelection: true,
- timeseriesOnly: true,
- };
-
const jobs = createTimeSeriesJobData(mlJobService.jobs);
- if (jobs.length === 0) {
- return (
-
-
-
- );
- }
-
- if (
- selectedJobIds === undefined ||
- selectedJobIds.length > 1 ||
- selectedDetectorIndex === undefined ||
- mlJobService.getJob(selectedJobIds[0]) === undefined
- ) {
- return (
-
- );
+ if (selectedDetectorIndex === undefined || mlJobService.getJob(selectedJobId) === undefined) {
+ return ;
}
- const selectedJob = mlJobService.getJob(selectedJobIds[0]);
+ const selectedJob = mlJobService.getJob(selectedJobId);
const entityControls = this.getControlsForDetector();
const fieldNamesWithEmptyValues = entityControls
@@ -1280,7 +1109,7 @@ export class TimeSeriesExplorer extends React.Component {
return (
diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_page.tsx b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_page.tsx
new file mode 100644
index 0000000000000..9da1a79232fce
--- /dev/null
+++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_page.tsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+
+import { EuiProgress } from '@elastic/eui';
+
+import { JobSelector } from '../components/job_selector';
+import { NavigationMenu } from '../components/navigation_menu';
+
+interface TimeSeriesExplorerPageProps {
+ dateFormatTz: string;
+ loading?: boolean;
+ resizeRef?: any;
+}
+
+export const TimeSeriesExplorerPage: FC = ({
+ children,
+ dateFormatTz,
+ loading,
+ resizeRef,
+}) => {
+ return (
+ <>
+
+ {/* Show animated progress bar while loading */}
+ {loading === true && (
+
+ )}
+ {/* Show a progress bar with progress 0% when not loading.
+ If we'd just show no progress bar when not loading it would result in a flickering height effect. */}
+ {loading === false && (
+
+ )}
+
+
+ {children}
+
+ >
+ );
+};
diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts
index 578dbdf1277a0..dcfbe94c97cc6 100644
--- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts
+++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/index.ts
@@ -6,3 +6,4 @@
export { getFocusData } from './get_focus_data';
export * from './timeseriesexplorer_utils';
+export { validateJobSelection } from './validate_job_selection';
diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts
new file mode 100644
index 0000000000000..f1cdaf3ba8c1b
--- /dev/null
+++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { difference, without } from 'lodash';
+
+import { i18n } from '@kbn/i18n';
+
+import { toastNotifications } from 'ui/notify';
+
+import { MlJobWithTimeRange } from '../../../../common/types/jobs';
+
+import { getTimeRangeFromSelection } from '../../components/job_selector/job_select_service_utils';
+import { mlJobService } from '../../services/job_service';
+
+import { createTimeSeriesJobData } from './timeseriesexplorer_utils';
+
+/**
+ * returns true/false if setGlobalState has been triggered
+ * or returns the job id which should be loaded.
+ */
+export function validateJobSelection(
+ jobsWithTimeRange: MlJobWithTimeRange[],
+ selectedJobIds: string[],
+ setGlobalState: (...args: any) => void
+) {
+ const jobs = createTimeSeriesJobData(mlJobService.jobs);
+ const timeSeriesJobIds: string[] = jobs.map((j: any) => j.id);
+
+ // Check if any of the jobs set in the URL are not time series jobs
+ // (e.g. if switching to this view straight from the Anomaly Explorer).
+ const invalidIds: string[] = difference(selectedJobIds, timeSeriesJobIds);
+ const validSelectedJobIds = without(selectedJobIds, ...invalidIds);
+ if (invalidIds.length > 0) {
+ let warningText = i18n.translate(
+ 'xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage',
+ {
+ defaultMessage: `You can't view requested {invalidIdsCount, plural, one {job} other {jobs}} {invalidIds} in this dashboard`,
+ values: {
+ invalidIdsCount: invalidIds.length,
+ invalidIds: invalidIds.join(', '),
+ },
+ }
+ );
+ if (validSelectedJobIds.length === 0 && timeSeriesJobIds.length > 0) {
+ warningText += i18n.translate('xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText', {
+ defaultMessage: ', auto selecting first job',
+ });
+ }
+ toastNotifications.addWarning(warningText);
+ }
+
+ if (validSelectedJobIds.length > 1) {
+ // if more than one job, select the first job from the selection.
+ toastNotifications.addWarning(
+ i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', {
+ defaultMessage: 'You can only view one job at a time in this dashboard',
+ })
+ );
+ setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] });
+ return true;
+ } else if (invalidIds.length > 0 && validSelectedJobIds.length > 0) {
+ // if some ids have been filtered out because they were invalid.
+ // refresh the URL with the first valid id
+ setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] });
+ return true;
+ } else if (validSelectedJobIds.length === 1) {
+ // normal behavior. a job ID has been loaded from the URL
+ // Clear the detectorIndex, entities and forecast info.
+ return validSelectedJobIds[0];
+ } else if (validSelectedJobIds.length === 0 && jobs.length > 0) {
+ // no jobs were loaded from the URL, so add the first job
+ // from the full jobs list.
+ const jobIds = [jobs[0].id];
+ const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds);
+ setGlobalState({
+ ...{ ml: { jobIds } },
+ ...(time !== undefined ? { time } : {}),
+ });
+ return true;
+ } else {
+ // Jobs exist, but no time series jobs.
+ return false;
+ }
+}
diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
index c9cc8a3da574a..52495b3b732d0 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
+++ b/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
@@ -10,6 +10,30 @@ import moment from 'moment';
const SIZE = 1000;
const LEVEL = { system_info: -1, info: 0, warning: 1, error: 2 };
+// filter to match job_type: 'anomaly_detector' or no job_type field at all
+// if no job_type field exist, we can assume the message is for an anomaly detector job
+const anomalyDetectorTypeFilter = {
+ bool: {
+ should: [
+ {
+ term: {
+ job_type: 'anomaly_detector',
+ },
+ },
+ {
+ bool: {
+ must_not: {
+ exists: {
+ field: 'job_type',
+ },
+ },
+ },
+ },
+ ],
+ minimum_should_match: 1,
+ },
+};
+
export function jobAuditMessagesProvider(callWithRequest) {
// search for audit messages,
// jobId is optional. without it, all jobs will be listed.
@@ -47,13 +71,9 @@ export function jobAuditMessagesProvider(callWithRequest) {
level: 'activity',
},
},
- must: {
- term: {
- job_type: 'anomaly_detector',
- },
- },
},
},
+ anomalyDetectorTypeFilter,
timeFilter,
],
},
@@ -119,6 +139,7 @@ export function jobAuditMessagesProvider(callWithRequest) {
},
},
},
+ anomalyDetectorTypeFilter,
],
},
};
diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts
index 82d94422b70ce..52e26b3132007 100644
--- a/x-pack/legacy/plugins/reporting/index.ts
+++ b/x-pack/legacy/plugins/reporting/index.ts
@@ -7,47 +7,20 @@
import { resolve } from 'path';
import { i18n } from '@kbn/i18n';
import { Legacy } from 'kibana';
-import { IUiSettingsClient } from 'src/core/server';
-import { XPackMainPlugin } from '../xpack_main/server/xpack_main';
import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants';
-// @ts-ignore untyped module defintition
-import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
-import { registerRoutes } from './server/routes';
-import {
- LevelLogger,
- checkLicenseFactory,
- getExportTypesRegistry,
- runValidations,
-} from './server/lib';
-import { createBrowserDriverFactory } from './server/browsers';
-import { registerReportingUsageCollector } from './server/usage';
import { ReportingConfigOptions, ReportingPluginSpecOptions } from './types.d';
import { config as reportingConfig } from './config';
-import { logConfiguration } from './log_configuration';
+import {
+ LegacySetup,
+ ReportingPlugin,
+ ReportingSetupDeps,
+ reportingPluginFactory,
+} from './server/plugin';
const kbToBase64Length = (kb: number) => {
return Math.floor((kb * 1024 * 8) / 6);
};
-type LegacyPlugins = Legacy.Server['plugins'];
-
-export interface ServerFacade {
- config: Legacy.Server['config'];
- info: Legacy.Server['info'];
- log: Legacy.Server['log'];
- plugins: {
- elasticsearch: LegacyPlugins['elasticsearch'];
- security: LegacyPlugins['security'];
- xpack_main: XPackMainPlugin & {
- status?: any;
- };
- };
- route: Legacy.Server['route'];
- savedObjects: Legacy.Server['savedObjects'];
- uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory'];
- fieldFormatServiceFactory: (uiConfig: IUiSettingsClient) => unknown;
-}
-
export const reporting = (kibana: any) => {
return new kibana.Plugin({
id: PLUGIN_ID,
@@ -93,7 +66,11 @@ export const reporting = (kibana: any) => {
},
async init(server: Legacy.Server) {
- const serverFacade: ServerFacade = {
+ const coreSetup = server.newPlatform.setup.core;
+ const pluginsSetup: ReportingSetupDeps = {
+ usageCollection: server.newPlatform.setup.plugins.usageCollection,
+ };
+ const __LEGACY: LegacySetup = {
config: server.config,
info: server.info,
route: server.route.bind(server),
@@ -108,38 +85,9 @@ export const reporting = (kibana: any) => {
fieldFormatServiceFactory: server.fieldFormatServiceFactory,
log: server.log.bind(server),
};
- const exportTypesRegistry = getExportTypesRegistry();
-
- let isCollectorReady = false;
- // Register a function with server to manage the collection of usage stats
- const { usageCollection } = server.newPlatform.setup.plugins;
- registerReportingUsageCollector(
- usageCollection,
- serverFacade,
- () => isCollectorReady,
- exportTypesRegistry
- );
-
- const logger = LevelLogger.createForServer(serverFacade, [PLUGIN_ID]);
- const browserDriverFactory = await createBrowserDriverFactory(serverFacade);
-
- logConfiguration(serverFacade, logger);
- runValidations(serverFacade, logger, browserDriverFactory);
-
- const { xpack_main: xpackMainPlugin } = serverFacade.plugins;
- mirrorPluginStatus(xpackMainPlugin, this);
- const checkLicense = checkLicenseFactory(exportTypesRegistry);
- (xpackMainPlugin as any).status.once('green', () => {
- // Register a function that is called whenever the xpack info changes,
- // to re-compute the license check results for this plugin
- xpackMainPlugin.info.feature(this.id).registerLicenseCheckResultsGenerator(checkLicense);
- });
-
- // Post initialization of the above code, the collector is now ready to fetch its data
- isCollectorReady = true;
- // Reporting routes
- registerRoutes(serverFacade, exportTypesRegistry, browserDriverFactory, logger);
+ const plugin: ReportingPlugin = reportingPluginFactory(__LEGACY, this);
+ await plugin.setup(coreSetup, pluginsSetup);
},
deprecations({ unused }: any) {
diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts
new file mode 100644
index 0000000000000..934a3487209c4
--- /dev/null
+++ b/x-pack/legacy/plugins/reporting/server/plugin.ts
@@ -0,0 +1,107 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Legacy } from 'kibana';
+import { CoreSetup, CoreStart, Plugin } from 'src/core/server';
+import { IUiSettingsClient } from 'src/core/server';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { XPackMainPlugin } from '../../xpack_main/server/xpack_main';
+// @ts-ignore
+import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status';
+import { PLUGIN_ID } from '../common/constants';
+import { ReportingPluginSpecOptions } from '../types.d';
+import { registerRoutes } from './routes';
+import { LevelLogger, checkLicenseFactory, getExportTypesRegistry, runValidations } from './lib';
+import { createBrowserDriverFactory } from './browsers';
+import { registerReportingUsageCollector } from './usage';
+import { logConfiguration } from '../log_configuration';
+
+// For now there is no exposed functionality to other plugins
+export type ReportingSetup = object;
+export type ReportingStart = object;
+
+export interface ReportingSetupDeps {
+ usageCollection: UsageCollectionSetup;
+}
+export type ReportingStartDeps = object;
+
+type LegacyPlugins = Legacy.Server['plugins'];
+
+export interface LegacySetup {
+ config: Legacy.Server['config'];
+ info: Legacy.Server['info'];
+ log: Legacy.Server['log'];
+ plugins: {
+ elasticsearch: LegacyPlugins['elasticsearch'];
+ security: LegacyPlugins['security'];
+ xpack_main: XPackMainPlugin & {
+ status?: any;
+ };
+ };
+ route: Legacy.Server['route'];
+ savedObjects: Legacy.Server['savedObjects'];
+ uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory'];
+ fieldFormatServiceFactory: (uiConfig: IUiSettingsClient) => unknown;
+}
+
+export type ReportingPlugin = Plugin<
+ ReportingSetup,
+ ReportingStart,
+ ReportingSetupDeps,
+ ReportingStartDeps
+>;
+
+/* We need a factory that returns an instance of the class because the class
+ * implementation itself restricts against having Legacy dependencies passed
+ * into `setup`. The factory parameters take the legacy dependencies, and the
+ * `setup` method gets it from enclosure */
+export function reportingPluginFactory(
+ __LEGACY: LegacySetup,
+ legacyPlugin: ReportingPluginSpecOptions
+) {
+ return new (class ReportingPlugin implements ReportingPlugin {
+ public async setup(core: CoreSetup, plugins: ReportingSetupDeps): Promise {
+ const exportTypesRegistry = getExportTypesRegistry();
+
+ let isCollectorReady = false;
+ // Register a function with server to manage the collection of usage stats
+ const { usageCollection } = plugins;
+ registerReportingUsageCollector(
+ usageCollection,
+ __LEGACY,
+ () => isCollectorReady,
+ exportTypesRegistry
+ );
+
+ const logger = LevelLogger.createForServer(__LEGACY, [PLUGIN_ID]);
+ const browserDriverFactory = await createBrowserDriverFactory(__LEGACY);
+
+ logConfiguration(__LEGACY, logger);
+ runValidations(__LEGACY, logger, browserDriverFactory);
+
+ const { xpack_main: xpackMainPlugin } = __LEGACY.plugins;
+ mirrorPluginStatus(xpackMainPlugin, legacyPlugin);
+ const checkLicense = checkLicenseFactory(exportTypesRegistry);
+ (xpackMainPlugin as any).status.once('green', () => {
+ // Register a function that is called whenever the xpack info changes,
+ // to re-compute the license check results for this plugin
+ xpackMainPlugin.info.feature(PLUGIN_ID).registerLicenseCheckResultsGenerator(checkLicense);
+ });
+
+ // Post initialization of the above code, the collector is now ready to fetch its data
+ isCollectorReady = true;
+
+ // Reporting routes
+ registerRoutes(__LEGACY, exportTypesRegistry, browserDriverFactory, logger);
+
+ return {};
+ }
+
+ public start(core: CoreStart, plugins: ReportingStartDeps): ReportingStart {
+ return {};
+ }
+ })();
+}
diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts
index f67f44860f14a..9fae60afee4e8 100644
--- a/x-pack/legacy/plugins/reporting/types.d.ts
+++ b/x-pack/legacy/plugins/reporting/types.d.ts
@@ -15,7 +15,7 @@ import { CancellationToken } from './common/cancellation_token';
import { LevelLogger } from './server/lib/level_logger';
import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory';
import { BrowserType } from './server/browsers/types';
-import { ServerFacade } from './index';
+import { LegacySetup } from './server/plugin';
export type ReportingPlugin = object; // For Plugin contract
@@ -69,6 +69,8 @@ interface GenerateExportTypePayload {
* Legacy System
*/
+export type ServerFacade = LegacySetup;
+
export type ReportingPluginSpecOptions = Legacy.PluginSpecOptions;
export type EnqueueJobFn = (
@@ -353,5 +355,3 @@ export interface InterceptedRequest {
frameId: string;
resourceType: string;
}
-
-export { ServerFacade };
diff --git a/x-pack/legacy/plugins/security/common/model.ts b/x-pack/legacy/plugins/security/common/model.ts
deleted file mode 100644
index 733e89f774db8..0000000000000
--- a/x-pack/legacy/plugins/security/common/model.ts
+++ /dev/null
@@ -1,28 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export {
- ApiKey,
- ApiKeyToInvalidate,
- AuthenticatedUser,
- BuiltinESPrivileges,
- EditUser,
- FeaturesPrivileges,
- InlineRoleTemplate,
- InvalidRoleTemplate,
- KibanaPrivileges,
- RawKibanaFeaturePrivileges,
- RawKibanaPrivileges,
- Role,
- RoleIndexPrivilege,
- RoleKibanaPrivilege,
- RoleMapping,
- RoleTemplate,
- StoredRoleTemplate,
- User,
- canUserChangePassword,
- getUserDisplayName,
-} from '../../../../plugins/security/common/model';
diff --git a/x-pack/legacy/plugins/security/index.d.ts b/x-pack/legacy/plugins/security/index.d.ts
index 18284c8be689a..d453415f73376 100644
--- a/x-pack/legacy/plugins/security/index.d.ts
+++ b/x-pack/legacy/plugins/security/index.d.ts
@@ -5,7 +5,7 @@
*/
import { Legacy } from 'kibana';
-import { AuthenticatedUser } from './common/model';
+import { AuthenticatedUser } from '../../../plugins/security/public';
/**
* Public interface of the security plugin.
diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js
index bc403b803b8df..4988c30a1398b 100644
--- a/x-pack/legacy/plugins/security/index.js
+++ b/x-pack/legacy/plugins/security/index.js
@@ -40,8 +40,6 @@ export const security = kibana =>
},
uiExports: {
- chromeNavControls: [],
- managementSections: ['plugins/security/views/management'],
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
apps: [
{
@@ -76,7 +74,6 @@ export const security = kibana =>
'plugins/security/hacks/on_unauthorized_response',
'plugins/security/hacks/register_account_management_app',
],
- home: ['plugins/security/register_feature'],
injectDefaultVars: server => {
const securityPlugin = server.newPlatform.setup.plugins.security;
if (!securityPlugin) {
diff --git a/x-pack/legacy/plugins/security/public/documentation_links.js b/x-pack/legacy/plugins/security/public/documentation_links.js
deleted file mode 100644
index 8050289b95e9d..0000000000000
--- a/x-pack/legacy/plugins/security/public/documentation_links.js
+++ /dev/null
@@ -1,16 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
-
-const ES_REF_URL = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`;
-
-export const documentationLinks = {
- dashboardViewMode: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-view-modes.html`,
- esClusterPrivileges: `${ES_REF_URL}/security-privileges.html#privileges-list-cluster`,
- esIndicesPrivileges: `${ES_REF_URL}/security-privileges.html#privileges-list-indices`,
- esRunAsPrivileges: `${ES_REF_URL}/security-privileges.html#_run_as_privilege`,
-};
diff --git a/x-pack/legacy/plugins/security/public/images/logout.svg b/x-pack/legacy/plugins/security/public/images/logout.svg
deleted file mode 100644
index d6533c0719904..0000000000000
--- a/x-pack/legacy/plugins/security/public/images/logout.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/x-pack/legacy/plugins/security/public/images/person.svg b/x-pack/legacy/plugins/security/public/images/person.svg
deleted file mode 100644
index 988ddac8859d7..0000000000000
--- a/x-pack/legacy/plugins/security/public/images/person.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/x-pack/legacy/plugins/security/public/index.scss b/x-pack/legacy/plugins/security/public/index.scss
index 2d7696bed3989..187ad5231534d 100644
--- a/x-pack/legacy/plugins/security/public/index.scss
+++ b/x-pack/legacy/plugins/security/public/index.scss
@@ -15,3 +15,6 @@ $secFormWidth: 460px;
// Public views
@import './views/index';
+// Styles of Kibana Platform plugin
+@import '../../../../plugins/security/public/index';
+
diff --git a/x-pack/legacy/plugins/security/public/lib/__tests__/util.js b/x-pack/legacy/plugins/security/public/lib/__tests__/util.js
deleted file mode 100644
index 3f7d8aea53a85..0000000000000
--- a/x-pack/legacy/plugins/security/public/lib/__tests__/util.js
+++ /dev/null
@@ -1,49 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import expect from '@kbn/expect';
-import { toggle, toggleSort } from '../../../public/lib/util';
-
-describe('util', () => {
- describe('toggle', () => {
- it('should add an item to a collection if not already included', () => {
- const collection = [1, 2, 3, 4, 5];
- toggle(collection, 6);
- expect(collection.indexOf(6)).to.be.above(0);
- });
-
- it('should remove an item from a collection if already included', () => {
- const collection = [1, 2, 3, 4, 5];
- toggle(collection, 3);
- expect(collection.indexOf(3)).to.be.below(0);
- });
- });
-
- describe('toggleSort', () => {
- it('should toggle reverse if called with the same orderBy', () => {
- const sort = { orderBy: 'foo', reverse: false };
-
- toggleSort(sort, 'foo');
- expect(sort.reverse).to.be.true;
-
- toggleSort(sort, 'foo');
- expect(sort.reverse).to.be.false;
- });
-
- it('should change orderBy and set reverse to false when called with a different orderBy', () => {
- const sort = { orderBy: 'foo', reverse: false };
-
- toggleSort(sort, 'bar');
- expect(sort.orderBy).to.equal('bar');
- expect(sort.reverse).to.be.false;
-
- sort.reverse = true;
- toggleSort(sort, 'foo');
- expect(sort.orderBy).to.equal('foo');
- expect(sort.reverse).to.be.false;
- });
- });
-});
diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts
deleted file mode 100644
index c5c6994bf4be3..0000000000000
--- a/x-pack/legacy/plugins/security/public/lib/api.ts
+++ /dev/null
@@ -1,56 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { kfetch } from 'ui/kfetch';
-import { Role, User, EditUser } from '../../common/model';
-
-const usersUrl = '/internal/security/users';
-const rolesUrl = '/api/security/role';
-
-export class UserAPIClient {
- public async getUsers(): Promise {
- return await kfetch({ pathname: usersUrl });
- }
-
- public async getUser(username: string): Promise {
- const url = `${usersUrl}/${encodeURIComponent(username)}`;
- return await kfetch({ pathname: url });
- }
-
- public async deleteUser(username: string) {
- const url = `${usersUrl}/${encodeURIComponent(username)}`;
- await kfetch({ pathname: url, method: 'DELETE' }, {});
- }
-
- public async saveUser(user: EditUser) {
- const url = `${usersUrl}/${encodeURIComponent(user.username)}`;
-
- await kfetch({ pathname: url, body: JSON.stringify(user), method: 'POST' });
- }
-
- public async getRoles(): Promise {
- return await kfetch({ pathname: rolesUrl });
- }
-
- public async getRole(name: string): Promise {
- const url = `${rolesUrl}/${encodeURIComponent(name)}`;
- return await kfetch({ pathname: url });
- }
-
- public async changePassword(username: string, password: string, currentPassword: string) {
- const data: Record = {
- newPassword: password,
- };
- if (currentPassword) {
- data.password = currentPassword;
- }
- await kfetch({
- pathname: `${usersUrl}/${encodeURIComponent(username)}/password`,
- method: 'POST',
- body: JSON.stringify(data),
- });
- }
-}
diff --git a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts b/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts
deleted file mode 100644
index fbc0460c5908a..0000000000000
--- a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts
+++ /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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { kfetch } from 'ui/kfetch';
-import { ApiKey, ApiKeyToInvalidate } from '../../common/model';
-
-interface CheckPrivilegesResponse {
- areApiKeysEnabled: boolean;
- isAdmin: boolean;
-}
-
-interface InvalidateApiKeysResponse {
- itemsInvalidated: ApiKeyToInvalidate[];
- errors: any[];
-}
-
-interface GetApiKeysResponse {
- apiKeys: ApiKey[];
-}
-
-const apiKeysUrl = `/internal/security/api_key`;
-
-export class ApiKeysApi {
- public static async checkPrivileges(): Promise {
- return kfetch({ pathname: `${apiKeysUrl}/privileges` });
- }
-
- public static async getApiKeys(isAdmin: boolean = false): Promise {
- const query = {
- isAdmin,
- };
-
- return kfetch({ pathname: apiKeysUrl, query });
- }
-
- public static async invalidateApiKeys(
- apiKeys: ApiKeyToInvalidate[],
- isAdmin: boolean = false
- ): Promise {
- const pathname = `${apiKeysUrl}/invalidate`;
- const body = JSON.stringify({ apiKeys, isAdmin });
- return kfetch({ pathname, method: 'POST', body });
- }
-}
diff --git a/x-pack/legacy/plugins/security/public/lib/role_utils.ts b/x-pack/legacy/plugins/security/public/lib/role_utils.ts
deleted file mode 100644
index c33b7385306fb..0000000000000
--- a/x-pack/legacy/plugins/security/public/lib/role_utils.ts
+++ /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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { cloneDeep, get } from 'lodash';
-import { Role } from '../../common/model';
-
-/**
- * Returns whether given role is enabled or not
- *
- * @param role Object Role JSON, as returned by roles API
- * @return Boolean true if role is enabled; false otherwise
- */
-export function isRoleEnabled(role: Partial) {
- return get(role, 'transient_metadata.enabled', true);
-}
-
-/**
- * Returns whether given role is reserved or not.
- *
- * @param {role} the Role as returned by roles API
- */
-export function isReservedRole(role: Partial) {
- return get(role, 'metadata._reserved', false);
-}
-
-/**
- * Returns whether given role is editable through the UI or not.
- *
- * @param role the Role as returned by roles API
- */
-export function isReadOnlyRole(role: Partial): boolean {
- return isReservedRole(role) || !!(role._transform_error && role._transform_error.length > 0);
-}
-
-/**
- * Returns a deep copy of the role.
- *
- * @param role the Role to copy.
- */
-export function copyRole(role: Role) {
- return cloneDeep(role);
-}
-
-/**
- * Creates a deep copy of the role suitable for cloning.
- *
- * @param role the Role to clone.
- */
-export function prepareRoleClone(role: Role): Role {
- const clone = copyRole(role);
-
- clone.name = '';
-
- return clone;
-}
diff --git a/x-pack/legacy/plugins/security/public/lib/roles_api.ts b/x-pack/legacy/plugins/security/public/lib/roles_api.ts
deleted file mode 100644
index 20c1491ccaac6..0000000000000
--- a/x-pack/legacy/plugins/security/public/lib/roles_api.ts
+++ /dev/null
@@ -1,25 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { kfetch } from 'ui/kfetch';
-import { Role } from '../../common/model';
-
-export class RolesApi {
- public static async getRoles(): Promise {
- return kfetch({ pathname: '/api/security/role' });
- }
-
- public static async getRole(roleName: string): Promise {
- return kfetch({ pathname: `/api/security/role/${encodeURIComponent(roleName)}` });
- }
-
- public static async deleteRole(roleName: string) {
- return kfetch({
- pathname: `/api/security/role/${encodeURIComponent(roleName)}`,
- method: 'DELETE',
- });
- }
-}
diff --git a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts b/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts
deleted file mode 100644
index 861ba530050a1..0000000000000
--- a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts
+++ /dev/null
@@ -1,41 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { Role, RoleIndexPrivilege } from '../../common/model';
-import { isGlobalPrivilegeDefinition } from './privilege_utils';
-
-export function transformRoleForSave(role: Role, spacesEnabled: boolean) {
- // Remove any placeholder index privileges
- role.elasticsearch.indices = role.elasticsearch.indices.filter(
- indexPrivilege => !isPlaceholderPrivilege(indexPrivilege)
- );
-
- // Remove any placeholder query entries
- role.elasticsearch.indices.forEach(index => index.query || delete index.query);
-
- // If spaces are disabled, then do not persist any space privileges
- if (!spacesEnabled) {
- role.kibana = role.kibana.filter(isGlobalPrivilegeDefinition);
- }
-
- role.kibana.forEach(kibanaPrivilege => {
- // If a base privilege is defined, then do not persist feature privileges
- if (kibanaPrivilege.base.length > 0) {
- kibanaPrivilege.feature = {};
- }
- });
-
- delete role.name;
- delete role.transient_metadata;
- delete role._unrecognized_applications;
- delete role._transform_error;
-
- return role;
-}
-
-function isPlaceholderPrivilege(indexPrivilege: RoleIndexPrivilege) {
- return indexPrivilege.names.length === 0;
-}
diff --git a/x-pack/legacy/plugins/security/public/lib/util.js b/x-pack/legacy/plugins/security/public/lib/util.js
deleted file mode 100644
index bdf44aa3f10bb..0000000000000
--- a/x-pack/legacy/plugins/security/public/lib/util.js
+++ /dev/null
@@ -1,19 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export function toggle(collection, item) {
- const i = collection.indexOf(item);
- if (i >= 0) collection.splice(i, 1);
- else collection.push(item);
-}
-
-export function toggleSort(sort, orderBy) {
- if (sort.orderBy === orderBy) sort.reverse = !sort.reverse;
- else {
- sort.orderBy = orderBy;
- sort.reverse = false;
- }
-}
diff --git a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts b/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts
deleted file mode 100644
index 91d98782dab42..0000000000000
--- a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts
+++ /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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import { IHttpResponse } from 'angular';
-import chrome from 'ui/chrome';
-
-const apiBase = chrome.addBasePath(`/internal/security/fields`);
-
-export async function getFields($http: any, query: string): Promise {
- return await $http
- .get(`${apiBase}/${query}`)
- .then((response: IHttpResponse) => response.data || []);
-}
diff --git a/x-pack/legacy/plugins/security/public/objects/lib/roles.ts b/x-pack/legacy/plugins/security/public/objects/lib/roles.ts
deleted file mode 100644
index e33cbe4c6c031..0000000000000
--- a/x-pack/legacy/plugins/security/public/objects/lib/roles.ts
+++ /dev/null
@@ -1,21 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import chrome from 'ui/chrome';
-import { Role } from '../../../common/model';
-import { copyRole } from '../../lib/role_utils';
-import { transformRoleForSave } from '../../lib/transform_role_for_save';
-
-const apiBase = chrome.addBasePath(`/api/security/role`);
-
-export async function saveRole($http: any, role: Role, spacesEnabled: boolean) {
- const data = transformRoleForSave(copyRole(role), spacesEnabled);
-
- return await $http.put(`${apiBase}/${role.name}`, data);
-}
-
-export async function deleteRole($http: any, name: string) {
- return await $http.delete(`${apiBase}/${name}`);
-}
diff --git a/x-pack/legacy/plugins/security/public/register_feature.js b/x-pack/legacy/plugins/security/public/register_feature.js
deleted file mode 100644
index c0bd42690b6fd..0000000000000
--- a/x-pack/legacy/plugins/security/public/register_feature.js
+++ /dev/null
@@ -1,29 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import {
- FeatureCatalogueRegistryProvider,
- FeatureCatalogueCategory,
-} from 'ui/registry/feature_catalogue';
-
-import { i18n } from '@kbn/i18n';
-
-FeatureCatalogueRegistryProvider.register(() => {
- return {
- id: 'security',
- title: i18n.translate('xpack.security.registerFeature.securitySettingsTitle', {
- defaultMessage: 'Security Settings',
- }),
- description: i18n.translate('xpack.security.registerFeature.securitySettingsDescription', {
- defaultMessage:
- 'Protect your data and easily manage who has access to what with users and roles.',
- }),
- icon: 'securityApp',
- path: '/app/kibana#/management/security',
- showOnHomePage: true,
- category: FeatureCatalogueCategory.ADMIN,
- };
-});
diff --git a/x-pack/legacy/plugins/security/public/services/shield_indices.js b/x-pack/legacy/plugins/security/public/services/shield_indices.js
deleted file mode 100644
index 791fa6cb59648..0000000000000
--- a/x-pack/legacy/plugins/security/public/services/shield_indices.js
+++ /dev/null
@@ -1,18 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { uiModules } from 'ui/modules';
-
-const module = uiModules.get('security', []);
-module.service('shieldIndices', ($http, chrome) => {
- return {
- getFields: query => {
- return $http
- .get(chrome.addBasePath(`/internal/security/fields/${query}`))
- .then(response => response.data);
- },
- };
-});
diff --git a/x-pack/legacy/plugins/security/public/services/shield_role.js b/x-pack/legacy/plugins/security/public/services/shield_role.js
deleted file mode 100644
index 261d3449a7a2d..0000000000000
--- a/x-pack/legacy/plugins/security/public/services/shield_role.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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import 'angular-resource';
-import { omit } from 'lodash';
-import angular from 'angular';
-import { uiModules } from 'ui/modules';
-
-const module = uiModules.get('security', ['ngResource']);
-module.service('ShieldRole', ($resource, chrome) => {
- return $resource(
- chrome.addBasePath('/api/security/role/:name'),
- {
- name: '@name',
- },
- {
- save: {
- method: 'PUT',
- transformRequest(data) {
- return angular.toJson(
- omit(data, 'name', 'transient_metadata', '_unrecognized_applications')
- );
- },
- },
- }
- );
-});
diff --git a/x-pack/legacy/plugins/security/public/views/_index.scss b/x-pack/legacy/plugins/security/public/views/_index.scss
index b85a7e1997390..6c2a091adf536 100644
--- a/x-pack/legacy/plugins/security/public/views/_index.scss
+++ b/x-pack/legacy/plugins/security/public/views/_index.scss
@@ -1,5 +1,2 @@
// Login styles
@import './login/index';
-
-// Management styles
-@import './management/index';
diff --git a/x-pack/legacy/plugins/security/public/views/account/account.html b/x-pack/legacy/plugins/security/public/views/account/account.html
deleted file mode 100644
index 0935c415b1829..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/account/account.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/x-pack/legacy/plugins/security/public/views/account/account.js b/x-pack/legacy/plugins/security/public/views/account/account.js
index 70a7b8dce727e..13abc44e08f96 100644
--- a/x-pack/legacy/plugins/security/public/views/account/account.js
+++ b/x-pack/legacy/plugins/security/public/views/account/account.js
@@ -4,17 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import routes from 'ui/routes';
-import template from './account.html';
-import { i18n } from '@kbn/i18n';
-import { I18nContext } from 'ui/i18n';
-import { npSetup } from 'ui/new_platform';
-import { AccountManagementPage } from './components';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
+import { i18n } from '@kbn/i18n';
+import { npStart } from 'ui/new_platform';
+import routes from 'ui/routes';
routes.when('/account', {
- template,
+ template: '
',
k7Breadcrumbs: () => [
{
text: i18n.translate('xpack.security.account.breadcrumb', {
@@ -24,19 +21,15 @@ routes.when('/account', {
],
controllerAs: 'accountController',
controller($scope) {
- $scope.$on('$destroy', () => {
- const elem = document.getElementById('userProfileReactRoot');
- if (elem) {
- unmountComponentAtNode(elem);
- }
- });
$scope.$$postDigest(() => {
+ const domNode = document.getElementById('userProfileReactRoot');
+
render(
-
-
- ,
- document.getElementById('userProfileReactRoot')
+ ,
+ domNode
);
+
+ $scope.$on('$destroy', () => unmountComponentAtNode(domNode));
});
},
});
diff --git a/x-pack/legacy/plugins/security/public/views/login/_index.scss b/x-pack/legacy/plugins/security/public/views/login/_index.scss
index 9f133940f7977..9083c8dc3b775 100644
--- a/x-pack/legacy/plugins/security/public/views/login/_index.scss
+++ b/x-pack/legacy/plugins/security/public/views/login/_index.scss
@@ -5,5 +5,4 @@
// loginChart__legend--small
// loginChart__legend-isLoading
-@import 'login';
-
+@import './components/index';
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/_index.scss b/x-pack/legacy/plugins/security/public/views/login/components/_index.scss
new file mode 100644
index 0000000000000..a6f9598b9cc04
--- /dev/null
+++ b/x-pack/legacy/plugins/security/public/views/login/components/_index.scss
@@ -0,0 +1 @@
+@import './login_page/index';
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx
index 93451453a523a..3a970d582bdc8 100644
--- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx
@@ -7,7 +7,7 @@
import { EuiButton, EuiCallOut } from '@elastic/eui';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
-import { LoginState } from '../../../../../common/login_state';
+import { LoginState } from '../../login_state';
import { BasicLoginForm } from './basic_login_form';
const createMockHttp = ({ simulateError = false } = {}) => {
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx
index e6d3b5b7536b6..c263381fbdb56 100644
--- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx
@@ -9,7 +9,7 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { ChangeEvent, Component, FormEvent, Fragment, MouseEvent } from 'react';
import ReactMarkdown from 'react-markdown';
import { EuiText } from '@elastic/eui';
-import { LoginState } from '../../../../../common/login_state';
+import { LoginState } from '../../login_state';
interface Props {
http: any;
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss
new file mode 100644
index 0000000000000..4dd2c0cabfb5e
--- /dev/null
+++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss
@@ -0,0 +1 @@
+@import './login_page';
diff --git a/x-pack/legacy/plugins/security/public/views/login/_login.scss b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss
similarity index 88%
rename from x-pack/legacy/plugins/security/public/views/login/_login.scss
rename to x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss
index 607e9e6ec5e3f..cdfad55ee064a 100644
--- a/x-pack/legacy/plugins/security/public/views/login/_login.scss
+++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss
@@ -1,4 +1,3 @@
-
.loginWelcome {
@include kibanaFullScreenGraphics;
}
@@ -16,10 +15,6 @@
margin-bottom: $euiSizeXL;
}
-.loginWelcome__footerAction {
- margin-right: $euiSizeS;
-}
-
.loginWelcome__content {
position: relative;
margin: auto;
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx
index c16db007bdbdc..a0318d50a45e5 100644
--- a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx
@@ -6,7 +6,7 @@
import { shallow } from 'enzyme';
import React from 'react';
-import { LoginLayout, LoginState } from '../../../../../common/login_state';
+import { LoginLayout, LoginState } from '../../login_state';
import { LoginPage } from './login_page';
const createMockHttp = ({ simulateError = false } = {}) => {
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx
index e7e56947ca58f..8035789a30e9d 100644
--- a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx
@@ -19,7 +19,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import classNames from 'classnames';
-import { LoginState } from '../../../../../common/login_state';
+import { LoginState } from '../../login_state';
import { BasicLoginForm } from '../basic_login_form';
import { DisabledLoginForm } from '../disabled_login_form';
diff --git a/x-pack/legacy/plugins/security/public/views/login/login.html b/x-pack/legacy/plugins/security/public/views/login/login.html
deleted file mode 100644
index 2695fabdd6367..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/login/login.html
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/x-pack/legacy/plugins/security/public/views/login/login.tsx b/x-pack/legacy/plugins/security/public/views/login/login.tsx
index d9daf2d1f4d0d..0b89ac553c9a8 100644
--- a/x-pack/legacy/plugins/security/public/views/login/login.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/login.tsx
@@ -6,16 +6,14 @@
import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
-import { parseNext } from 'plugins/security/lib/parse_next';
import { LoginPage } from 'plugins/security/views/login/components';
-// @ts-ignore
-import template from 'plugins/security/views/login/login.html';
import React from 'react';
import { render } from 'react-dom';
import chrome from 'ui/chrome';
import { I18nContext } from 'ui/i18n';
import { parse } from 'url';
-import { LoginState } from '../../../common/login_state';
+import { parseNext } from './parse_next';
+import { LoginState } from './login_state';
const messageMap = {
SESSION_EXPIRED: i18n.translate('xpack.security.login.sessionExpiredDescription', {
defaultMessage: 'Your session has timed out. Please log in again.',
@@ -31,7 +29,7 @@ interface AnyObject {
(chrome as AnyObject)
.setVisible(false)
- .setRootTemplate(template)
+ .setRootTemplate('
')
.setRootController(
'login',
(
diff --git a/x-pack/legacy/plugins/security/common/login_state.ts b/x-pack/legacy/plugins/security/public/views/login/login_state.ts
similarity index 100%
rename from x-pack/legacy/plugins/security/common/login_state.ts
rename to x-pack/legacy/plugins/security/public/views/login/login_state.ts
diff --git a/x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js b/x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts
similarity index 80%
rename from x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js
rename to x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts
index 7516433c77f83..b5e6c7dca41d8 100644
--- a/x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js
+++ b/x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts
@@ -4,12 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import expect from '@kbn/expect';
-import { parseNext } from '../parse_next';
+import { parseNext } from './parse_next';
describe('parseNext', () => {
it('should return a function', () => {
- expect(parseNext).to.be.a('function');
+ expect(parseNext).toBeInstanceOf(Function);
});
describe('with basePath defined', () => {
@@ -17,14 +16,14 @@ describe('parseNext', () => {
it('should return basePath with a trailing slash when next is not specified', () => {
const basePath = '/iqf';
const href = `${basePath}/login`;
- expect(parseNext(href, basePath)).to.equal(`${basePath}/`);
+ expect(parseNext(href, basePath)).toEqual(`${basePath}/`);
});
it('should properly handle next without hash', () => {
const basePath = '/iqf';
const next = `${basePath}/app/kibana`;
const href = `${basePath}/login?next=${next}`;
- expect(parseNext(href, basePath)).to.equal(next);
+ expect(parseNext(href, basePath)).toEqual(next);
});
it('should properly handle next with hash', () => {
@@ -32,7 +31,7 @@ describe('parseNext', () => {
const next = `${basePath}/app/kibana`;
const hash = '/discover/New-Saved-Search';
const href = `${basePath}/login?next=${next}#${hash}`;
- expect(parseNext(href, basePath)).to.equal(`${next}#${hash}`);
+ expect(parseNext(href, basePath)).toEqual(`${next}#${hash}`);
});
it('should properly decode special characters', () => {
@@ -40,7 +39,7 @@ describe('parseNext', () => {
const next = `${encodeURIComponent(basePath)}%2Fapp%2Fkibana`;
const hash = '/discover/New-Saved-Search';
const href = `${basePath}/login?next=${next}#${hash}`;
- expect(parseNext(href, basePath)).to.equal(decodeURIComponent(`${next}#${hash}`));
+ expect(parseNext(href, basePath)).toEqual(decodeURIComponent(`${next}#${hash}`));
});
// to help prevent open redirect to a different url
@@ -48,7 +47,7 @@ describe('parseNext', () => {
const basePath = '/iqf';
const next = `https://example.com${basePath}/app/kibana`;
const href = `${basePath}/login?next=${next}`;
- expect(parseNext(href, basePath)).to.equal(`${basePath}/`);
+ expect(parseNext(href, basePath)).toEqual(`${basePath}/`);
});
// to help prevent open redirect to a different url by abusing encodings
@@ -58,7 +57,7 @@ describe('parseNext', () => {
const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`;
const hash = '/discover/New-Saved-Search';
const href = `${basePath}/login?next=${next}#${hash}`;
- expect(parseNext(href, basePath)).to.equal(`${basePath}/`);
+ expect(parseNext(href, basePath)).toEqual(`${basePath}/`);
});
// to help prevent open redirect to a different port
@@ -66,7 +65,7 @@ describe('parseNext', () => {
const basePath = '/iqf';
const next = `http://localhost:5601${basePath}/app/kibana`;
const href = `${basePath}/login?next=${next}`;
- expect(parseNext(href, basePath)).to.equal(`${basePath}/`);
+ expect(parseNext(href, basePath)).toEqual(`${basePath}/`);
});
// to help prevent open redirect to a different port by abusing encodings
@@ -76,7 +75,7 @@ describe('parseNext', () => {
const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`;
const hash = '/discover/New-Saved-Search';
const href = `${basePath}/login?next=${next}#${hash}`;
- expect(parseNext(href, basePath)).to.equal(`${basePath}/`);
+ expect(parseNext(href, basePath)).toEqual(`${basePath}/`);
});
// to help prevent open redirect to a different base path
@@ -84,18 +83,18 @@ describe('parseNext', () => {
const basePath = '/iqf';
const next = '/notbasepath/app/kibana';
const href = `${basePath}/login?next=${next}`;
- expect(parseNext(href, basePath)).to.equal(`${basePath}/`);
+ expect(parseNext(href, basePath)).toEqual(`${basePath}/`);
});
// disallow network-path references
it('should return / if next is url without protocol', () => {
const nextWithTwoSlashes = '//example.com';
const hrefWithTwoSlashes = `/login?next=${nextWithTwoSlashes}`;
- expect(parseNext(hrefWithTwoSlashes)).to.equal('/');
+ expect(parseNext(hrefWithTwoSlashes)).toEqual('/');
const nextWithThreeSlashes = '///example.com';
const hrefWithThreeSlashes = `/login?next=${nextWithThreeSlashes}`;
- expect(parseNext(hrefWithThreeSlashes)).to.equal('/');
+ expect(parseNext(hrefWithThreeSlashes)).toEqual('/');
});
});
@@ -103,34 +102,34 @@ describe('parseNext', () => {
// trailing slash is important since it must match the cookie path exactly
it('should return / with a trailing slash when next is not specified', () => {
const href = '/login';
- expect(parseNext(href)).to.equal('/');
+ expect(parseNext(href)).toEqual('/');
});
it('should properly handle next without hash', () => {
const next = '/app/kibana';
const href = `/login?next=${next}`;
- expect(parseNext(href)).to.equal(next);
+ expect(parseNext(href)).toEqual(next);
});
it('should properly handle next with hash', () => {
const next = '/app/kibana';
const hash = '/discover/New-Saved-Search';
const href = `/login?next=${next}#${hash}`;
- expect(parseNext(href)).to.equal(`${next}#${hash}`);
+ expect(parseNext(href)).toEqual(`${next}#${hash}`);
});
it('should properly decode special characters', () => {
const next = '%2Fapp%2Fkibana';
const hash = '/discover/New-Saved-Search';
const href = `/login?next=${next}#${hash}`;
- expect(parseNext(href)).to.equal(decodeURIComponent(`${next}#${hash}`));
+ expect(parseNext(href)).toEqual(decodeURIComponent(`${next}#${hash}`));
});
// to help prevent open redirect to a different url
it('should return / if next includes a protocol/hostname', () => {
const next = 'https://example.com/app/kibana';
const href = `/login?next=${next}`;
- expect(parseNext(href)).to.equal('/');
+ expect(parseNext(href)).toEqual('/');
});
// to help prevent open redirect to a different url by abusing encodings
@@ -139,14 +138,14 @@ describe('parseNext', () => {
const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`;
const hash = '/discover/New-Saved-Search';
const href = `/login?next=${next}#${hash}`;
- expect(parseNext(href)).to.equal('/');
+ expect(parseNext(href)).toEqual('/');
});
// to help prevent open redirect to a different port
it('should return / if next includes a port', () => {
const next = 'http://localhost:5601/app/kibana';
const href = `/login?next=${next}`;
- expect(parseNext(href)).to.equal('/');
+ expect(parseNext(href)).toEqual('/');
});
// to help prevent open redirect to a different port by abusing encodings
@@ -155,18 +154,18 @@ describe('parseNext', () => {
const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`;
const hash = '/discover/New-Saved-Search';
const href = `/login?next=${next}#${hash}`;
- expect(parseNext(href)).to.equal('/');
+ expect(parseNext(href)).toEqual('/');
});
// disallow network-path references
it('should return / if next is url without protocol', () => {
const nextWithTwoSlashes = '//example.com';
const hrefWithTwoSlashes = `/login?next=${nextWithTwoSlashes}`;
- expect(parseNext(hrefWithTwoSlashes)).to.equal('/');
+ expect(parseNext(hrefWithTwoSlashes)).toEqual('/');
const nextWithThreeSlashes = '///example.com';
const hrefWithThreeSlashes = `/login?next=${nextWithThreeSlashes}`;
- expect(parseNext(hrefWithThreeSlashes)).to.equal('/');
+ expect(parseNext(hrefWithThreeSlashes)).toEqual('/');
});
});
});
diff --git a/x-pack/legacy/plugins/security/public/lib/parse_next.ts b/x-pack/legacy/plugins/security/public/views/login/parse_next.ts
similarity index 100%
rename from x-pack/legacy/plugins/security/public/lib/parse_next.ts
rename to x-pack/legacy/plugins/security/public/views/login/parse_next.ts
diff --git a/x-pack/legacy/plugins/security/public/views/management/_index.scss b/x-pack/legacy/plugins/security/public/views/management/_index.scss
deleted file mode 100644
index 78b53845071e4..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/_index.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-@import './change_password_form/index';
-@import './edit_role/index';
-@import './edit_user/index';
-@import './role_mappings/edit_role_mapping/index';
\ No newline at end of file
diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html
deleted file mode 100644
index e46c6f72b5d20..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js
deleted file mode 100644
index e7143b1020814..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js
+++ /dev/null
@@ -1,35 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import routes from 'ui/routes';
-import template from './api_keys.html';
-import { API_KEYS_PATH } from '../management_urls';
-import { getApiKeysBreadcrumbs } from '../breadcrumbs';
-import { I18nContext } from 'ui/i18n';
-import { ApiKeysGridPage } from './components';
-
-routes.when(API_KEYS_PATH, {
- template,
- k7Breadcrumbs: getApiKeysBreadcrumbs,
- controller($scope) {
- $scope.$$postDigest(() => {
- const domNode = document.getElementById('apiKeysGridReactRoot');
-
- render(
-
-
- ,
- domNode
- );
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(domNode);
- });
- });
- },
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts b/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts
deleted file mode 100644
index 4ab7e45e84849..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts
+++ /dev/null
@@ -1,115 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { i18n } from '@kbn/i18n';
-import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs';
-
-export function getUsersBreadcrumbs() {
- return [
- MANAGEMENT_BREADCRUMB,
- {
- text: i18n.translate('xpack.security.users.breadcrumb', {
- defaultMessage: 'Users',
- }),
- href: '#/management/security/users',
- },
- ];
-}
-
-export function getEditUserBreadcrumbs($route: Record) {
- const { username } = $route.current.params;
- return [
- ...getUsersBreadcrumbs(),
- {
- text: username,
- href: `#/management/security/users/edit/${username}`,
- },
- ];
-}
-
-export function getCreateUserBreadcrumbs() {
- return [
- ...getUsersBreadcrumbs(),
- {
- text: i18n.translate('xpack.security.users.createBreadcrumb', {
- defaultMessage: 'Create',
- }),
- },
- ];
-}
-
-export function getRolesBreadcrumbs() {
- return [
- MANAGEMENT_BREADCRUMB,
- {
- text: i18n.translate('xpack.security.roles.breadcrumb', {
- defaultMessage: 'Roles',
- }),
- href: '#/management/security/roles',
- },
- ];
-}
-
-export function getEditRoleBreadcrumbs($route: Record) {
- const { name } = $route.current.params;
- return [
- ...getRolesBreadcrumbs(),
- {
- text: name,
- href: `#/management/security/roles/edit/${name}`,
- },
- ];
-}
-
-export function getCreateRoleBreadcrumbs() {
- return [
- ...getUsersBreadcrumbs(),
- {
- text: i18n.translate('xpack.security.roles.createBreadcrumb', {
- defaultMessage: 'Create',
- }),
- },
- ];
-}
-
-export function getApiKeysBreadcrumbs() {
- return [
- MANAGEMENT_BREADCRUMB,
- {
- text: i18n.translate('xpack.security.apiKeys.breadcrumb', {
- defaultMessage: 'API Keys',
- }),
- href: '#/management/security/api_keys',
- },
- ];
-}
-
-export function getRoleMappingBreadcrumbs() {
- return [
- MANAGEMENT_BREADCRUMB,
- {
- text: i18n.translate('xpack.security.roleMapping.breadcrumb', {
- defaultMessage: 'Role Mappings',
- }),
- href: '#/management/security/role_mappings',
- },
- ];
-}
-
-export function getEditRoleMappingBreadcrumbs($route: Record) {
- const { name } = $route.current.params;
- return [
- ...getRoleMappingBreadcrumbs(),
- {
- text:
- name ||
- i18n.translate('xpack.security.roleMappings.createBreadcrumb', {
- defaultMessage: 'Create',
- }),
- href: `#/management/security/role_mappings/edit/${name}`,
- },
- ];
-}
diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss b/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss
deleted file mode 100644
index 98331c2070a31..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-.secChangePasswordForm__panel {
- max-width: $secFormWidth;
-}
-
-.secChangePasswordForm__subLabel {
- margin-bottom: $euiSizeS;
-}
-
-.secChangePasswordForm__footer {
- display: flex;
- justify-content: flex-start;
- align-items: center;
-
- .kuiButton + .kuiButton {
- margin-left: $euiSizeS;
- }
-}
diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss b/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss
deleted file mode 100644
index a6058b5ddebbf..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import './change_password_form';
diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html b/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html
deleted file mode 100644
index 92fb95861a6f8..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html
+++ /dev/null
@@ -1,141 +0,0 @@
-
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js b/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js
deleted file mode 100644
index d9aa59f6df142..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js
+++ /dev/null
@@ -1,48 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { uiModules } from 'ui/modules';
-import template from './change_password_form.html';
-
-const module = uiModules.get('security', ['kibana']);
-module.directive('kbnChangePasswordForm', function() {
- return {
- template,
- scope: {
- requireCurrentPassword: '=',
- showKibanaWarning: '=',
- onChangePassword: '&',
- },
- restrict: 'E',
- replace: true,
- controllerAs: 'changePasswordController',
- controller: function($scope) {
- this.currentPassword = null;
- this.newPassword = null;
- this.newPasswordConfirmation = null;
- this.isFormVisible = false;
- this.isIncorrectPassword = false;
-
- this.showForm = () => {
- this.isFormVisible = true;
- };
-
- this.hideForm = () => {
- $scope.changePasswordForm.$setPristine();
- $scope.changePasswordForm.$setUntouched();
- this.currentPassword = null;
- this.newPassword = null;
- this.newPasswordConfirmation = null;
- this.isFormVisible = false;
- this.isIncorrectPassword = false;
- };
-
- this.onIncorrectPassword = () => {
- this.isIncorrectPassword = true;
- };
- },
- };
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss
deleted file mode 100644
index 192091fb04e3c..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import './components/index';
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss
deleted file mode 100644
index 32b3832e7a9fa..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-@import './collapsible_panel/collapsible_panel';
-@import './privileges/kibana/space_aware_privilege_section/index';
-@import './privileges/kibana/feature_table/index';
-@import './spaces_popover_list/spaces_popover_list';
-
-.secPrivilegeFeatureIcon {
- flex-shrink: 0;
- margin-right: $euiSizeS;
-}
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx
deleted file mode 100644
index 67c32c8393171..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx
+++ /dev/null
@@ -1,716 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ReactWrapper } from 'enzyme';
-import React from 'react';
-import { mountWithIntl } from 'test_utils/enzyme_helpers';
-import { UICapabilities } from 'ui/capabilities';
-import { Space } from '../../../../../../spaces/common/model/space';
-import { Feature } from '../../../../../../../../plugins/features/public';
-// These modules should be moved into a common directory
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { Actions } from '../../../../../../../../plugins/security/server/authorization/actions';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { privilegesFactory } from '../../../../../../../../plugins/security/server/authorization/privileges';
-import { RawKibanaPrivileges, Role } from '../../../../../common/model';
-import { EditRolePage } from './edit_role_page';
-import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section';
-import { SpaceAwarePrivilegeSection } from './privileges/kibana/space_aware_privilege_section';
-import { TransformErrorSection } from './privileges/kibana/transform_error_section';
-
-const buildFeatures = () => {
- return [
- {
- id: 'feature1',
- name: 'Feature 1',
- icon: 'addDataApp',
- app: ['feature1App'],
- privileges: {
- all: {
- app: ['feature1App'],
- ui: ['feature1-ui'],
- savedObject: {
- all: [],
- read: [],
- },
- },
- },
- },
- {
- id: 'feature2',
- name: 'Feature 2',
- icon: 'addDataApp',
- app: ['feature2App'],
- privileges: {
- all: {
- app: ['feature2App'],
- ui: ['feature2-ui'],
- savedObject: {
- all: ['feature2'],
- read: ['config'],
- },
- },
- },
- },
- ] as Feature[];
-};
-
-const buildRawKibanaPrivileges = () => {
- return privilegesFactory(new Actions('unit_test_version'), {
- getFeatures: () => buildFeatures(),
- }).get();
-};
-
-const buildBuiltinESPrivileges = () => {
- return {
- cluster: ['all', 'manage', 'monitor'],
- index: ['all', 'read', 'write', 'index'],
- };
-};
-
-const buildUICapabilities = (canManageSpaces = true) => {
- return {
- catalogue: {},
- management: {},
- navLinks: {},
- spaces: {
- manage: canManageSpaces,
- },
- } as UICapabilities;
-};
-
-const buildSpaces = () => {
- return [
- {
- id: 'default',
- name: 'Default',
- disabledFeatures: [],
- _reserved: true,
- },
- {
- id: 'space_1',
- name: 'Space 1',
- disabledFeatures: [],
- },
- {
- id: 'space_2',
- name: 'Space 2',
- disabledFeatures: ['feature2'],
- },
- ] as Space[];
-};
-
-const expectReadOnlyFormButtons = (wrapper: ReactWrapper) => {
- expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(1);
- expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(0);
-};
-
-const expectSaveFormButtons = (wrapper: ReactWrapper) => {
- expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(0);
- expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(1);
-};
-
-describe(' ', () => {
- describe('with spaces enabled', () => {
- it('can render a reserved role', () => {
- const role: Role = {
- name: 'superuser',
- metadata: {
- _reserved: true,
- },
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const spaces: Space[] = buildSpaces();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1);
- expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
- expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
- expectReadOnlyFormButtons(wrapper);
- });
-
- it('can render a user defined role', () => {
- const role: Role = {
- name: 'my custom role',
- metadata: {},
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const spaces: Space[] = buildSpaces();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0);
- expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
- expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
- expectSaveFormButtons(wrapper);
- });
-
- it('can render when creating a new role', () => {
- // @ts-ignore
- const role: Role = {
- metadata: {},
- elasticsearch: {
- cluster: [],
- indices: [],
- run_as: [],
- },
- kibana: [],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const spaces: Space[] = buildSpaces();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
- expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
- expectSaveFormButtons(wrapper);
- });
-
- it('can render when cloning an existing role', () => {
- const role: Role = {
- metadata: {
- _reserved: false,
- },
- name: '',
- elasticsearch: {
- cluster: ['all', 'manage'],
- indices: [
- {
- names: ['foo*'],
- privileges: ['all'],
- field_security: {
- except: ['f'],
- grant: ['b*'],
- },
- },
- ],
- run_as: ['elastic'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const spaces: Space[] = buildSpaces();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
- expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
- expectSaveFormButtons(wrapper);
- });
-
- it('renders an auth error when not authorized to manage spaces', () => {
- const role: Role = {
- name: 'my custom role',
- metadata: {},
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const spaces: Space[] = buildSpaces();
- const uiCapabilities: UICapabilities = buildUICapabilities(false);
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0);
-
- expect(
- wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]')
- ).toHaveLength(1);
-
- expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
- expectSaveFormButtons(wrapper);
- });
-
- it('renders a partial read-only view when there is a transform error', () => {
- const role: Role = {
- name: 'my custom role',
- metadata: {},
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [],
- _transform_error: ['kibana'],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const spaces: Space[] = buildSpaces();
- const uiCapabilities: UICapabilities = buildUICapabilities(false);
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find(TransformErrorSection)).toHaveLength(1);
- expectReadOnlyFormButtons(wrapper);
- });
- });
-
- describe('with spaces disabled', () => {
- it('can render a reserved role', () => {
- const role: Role = {
- name: 'superuser',
- metadata: {
- _reserved: true,
- },
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1);
- expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
- expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
- expectReadOnlyFormButtons(wrapper);
- });
-
- it('can render a user defined role', () => {
- const role: Role = {
- name: 'my custom role',
- metadata: {},
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0);
- expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
- expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
- expectSaveFormButtons(wrapper);
- });
-
- it('can render when creating a new role', () => {
- // @ts-ignore
- const role: Role = {
- metadata: {},
- elasticsearch: {
- cluster: [],
- indices: [],
- run_as: [],
- },
- kibana: [],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
- expectSaveFormButtons(wrapper);
- });
-
- it('can render when cloning an existing role', () => {
- const role: Role = {
- metadata: {
- _reserved: false,
- },
- name: '',
- elasticsearch: {
- cluster: ['all', 'manage'],
- indices: [
- {
- names: ['foo*'],
- privileges: ['all'],
- field_security: {
- except: ['f'],
- grant: ['b*'],
- },
- },
- ],
- run_as: ['elastic'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const uiCapabilities: UICapabilities = buildUICapabilities();
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
- expectSaveFormButtons(wrapper);
- });
-
- it('does not care if user cannot manage spaces', () => {
- const role: Role = {
- name: 'my custom role',
- metadata: {},
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [
- {
- spaces: ['*'],
- base: ['all'],
- feature: {},
- },
- ],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const uiCapabilities: UICapabilities = buildUICapabilities(false);
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0);
-
- expect(
- wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]')
- ).toHaveLength(0);
-
- expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
- expectSaveFormButtons(wrapper);
- });
-
- it('renders a partial read-only view when there is a transform error', () => {
- const role: Role = {
- name: 'my custom role',
- metadata: {},
- elasticsearch: {
- cluster: ['all'],
- indices: [],
- run_as: ['*'],
- },
- kibana: [],
- _transform_error: ['kibana'],
- };
-
- const features: Feature[] = buildFeatures();
- const mockHttpClient = jest.fn();
- const indexPatterns: string[] = ['foo*', 'bar*'];
- const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
- const builtinESPrivileges = buildBuiltinESPrivileges();
- const uiCapabilities: UICapabilities = buildUICapabilities(false);
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(wrapper.find(TransformErrorSection)).toHaveLength(1);
- expectReadOnlyFormButtons(wrapper);
- });
- });
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx
deleted file mode 100644
index 2ba012afa689d..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx
+++ /dev/null
@@ -1,409 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import {
- EuiButton,
- EuiButtonEmpty,
- EuiFieldText,
- EuiFlexGroup,
- EuiFlexItem,
- EuiForm,
- EuiFormRow,
- EuiPanel,
- EuiSpacer,
- EuiText,
- EuiTitle,
-} from '@elastic/eui';
-import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
-import { get } from 'lodash';
-import React, { ChangeEvent, Component, Fragment, HTMLProps } from 'react';
-import { UICapabilities } from 'ui/capabilities';
-import { toastNotifications } from 'ui/notify';
-import { Space } from '../../../../../../spaces/common/model/space';
-import { Feature } from '../../../../../../../../plugins/features/public';
-import {
- KibanaPrivileges,
- RawKibanaPrivileges,
- Role,
- BuiltinESPrivileges,
-} from '../../../../../common/model';
-import {
- isReadOnlyRole,
- isReservedRole,
- copyRole,
- prepareRoleClone,
-} from '../../../../lib/role_utils';
-import { deleteRole, saveRole } from '../../../../objects';
-import { ROLES_PATH } from '../../management_urls';
-import { RoleValidationResult, RoleValidator } from '../lib/validate_role';
-import { DeleteRoleButton } from './delete_role_button';
-import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges';
-import { ReservedRoleBadge } from './reserved_role_badge';
-
-interface Props {
- action: 'edit' | 'clone';
- role: Role;
- runAsUsers: string[];
- indexPatterns: string[];
- httpClient: any;
- allowDocumentLevelSecurity: boolean;
- allowFieldLevelSecurity: boolean;
- kibanaPrivileges: RawKibanaPrivileges;
- builtinESPrivileges: BuiltinESPrivileges;
- spaces?: Space[];
- spacesEnabled: boolean;
- intl: InjectedIntl;
- uiCapabilities: UICapabilities;
- features: Feature[];
-}
-
-interface State {
- role: Role;
- formError: RoleValidationResult | null;
-}
-
-class EditRolePageUI extends Component {
- private validator: RoleValidator;
-
- constructor(props: Props) {
- super(props);
-
- this.validator = new RoleValidator({ shouldValidate: false });
-
- let role: Role;
- if (props.action === 'clone') {
- role = prepareRoleClone(props.role);
- } else {
- role = copyRole(props.role);
- }
-
- this.state = {
- role,
- formError: null,
- };
- }
-
- public componentDidMount() {
- if (this.props.action === 'clone' && isReservedRole(this.props.role)) {
- this.backToRoleList();
- }
- }
-
- public render() {
- const description = this.props.spacesEnabled ? (
-
- ) : (
-
- );
-
- return (
-
-
- {this.getFormTitle()}
-
-
-
- {description}
-
- {isReservedRole(this.state.role) && (
-
-
-
-
-
-
-
-
- )}
-
-
-
- {this.getRoleName()}
-
- {this.getElasticsearchPrivileges()}
-
- {this.getKibanaPrivileges()}
-
-
-
- {this.getFormButtons()}
-
-
- );
- }
-
- private getFormTitle = () => {
- let titleText;
- const props: HTMLProps = {
- tabIndex: 0,
- };
- if (isReservedRole(this.state.role)) {
- titleText = (
-
- );
- props['aria-describedby'] = 'reservedRoleDescription';
- } else if (this.editingExistingRole()) {
- titleText = (
-
- );
- } else {
- titleText = (
-
- );
- }
-
- return (
-
-
- {titleText}
-
-
- );
- };
-
- private getActionButton = () => {
- if (this.editingExistingRole() && !isReadOnlyRole(this.state.role)) {
- return (
-
-
-
- );
- }
-
- return null;
- };
-
- private getRoleName = () => {
- return (
-
-
- }
- helpText={
- !isReservedRole(this.state.role) && this.editingExistingRole() ? (
-
- ) : (
- undefined
- )
- }
- {...this.validator.validateRoleName(this.state.role)}
- >
-
-
-
- );
- };
-
- private onNameChange = (e: ChangeEvent) => {
- const rawValue = e.target.value;
- const name = rawValue.replace(/\s/g, '_');
-
- this.setState({
- role: {
- ...this.state.role,
- name,
- },
- });
- };
-
- private getElasticsearchPrivileges() {
- return (
-
-
-
-
- );
- }
-
- private onRoleChange = (role: Role) => {
- this.setState({
- role,
- });
- };
-
- private getKibanaPrivileges = () => {
- return (
-
-
-
-
- );
- };
-
- private getFormButtons = () => {
- if (isReadOnlyRole(this.state.role)) {
- return this.getReturnToRoleListButton();
- }
-
- return (
-
- {this.getSaveButton()}
- {this.getCancelButton()}
-
- {this.getActionButton()}
-
- );
- };
-
- private getReturnToRoleListButton = () => {
- return (
-
-
-
- );
- };
-
- private getSaveButton = () => {
- const saveText = this.editingExistingRole() ? (
-
- ) : (
-
- );
-
- return (
-
- {saveText}
-
- );
- };
-
- private getCancelButton = () => {
- return (
-
-
-
- );
- };
-
- private editingExistingRole = () => {
- return !!this.props.role.name && this.props.action === 'edit';
- };
-
- private saveRole = () => {
- this.validator.enableValidation();
-
- const result = this.validator.validateForSave(this.state.role);
- if (result.isInvalid) {
- this.setState({
- formError: result,
- });
- } else {
- this.setState({
- formError: null,
- });
-
- const { httpClient, intl, spacesEnabled } = this.props;
-
- saveRole(httpClient, this.state.role, spacesEnabled)
- .then(() => {
- toastNotifications.addSuccess(
- intl.formatMessage({
- id: 'xpack.security.management.editRole.roleSuccessfullySavedNotificationMessage',
- defaultMessage: 'Saved role',
- })
- );
- this.backToRoleList();
- })
- .catch((error: any) => {
- toastNotifications.addDanger(get(error, 'data.message'));
- });
- }
- };
-
- private handleDeleteRole = () => {
- const { httpClient, role, intl } = this.props;
-
- deleteRole(httpClient, role.name)
- .then(() => {
- toastNotifications.addSuccess(
- intl.formatMessage({
- id: 'xpack.security.management.editRole.roleSuccessfullyDeletedNotificationMessage',
- defaultMessage: 'Deleted role',
- })
- );
- this.backToRoleList();
- })
- .catch((error: any) => {
- toastNotifications.addDanger(get(error, 'data.message'));
- });
- };
-
- private backToRoleList = () => {
- window.location.hash = ROLES_PATH;
- };
-}
-
-export const EditRolePage = injectI18n(EditRolePageUI);
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx
deleted file mode 100644
index 5ba3d1daf61ac..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx
+++ /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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
-import { RoleValidator } from '../../../lib/validate_role';
-import { ClusterPrivileges } from './cluster_privileges';
-import { ElasticsearchPrivileges } from './elasticsearch_privileges';
-import { IndexPrivileges } from './index_privileges';
-
-test('it renders without crashing', () => {
- const props = {
- role: {
- name: '',
- elasticsearch: {
- cluster: [],
- indices: [],
- run_as: [],
- },
- kibana: [],
- },
- editable: true,
- httpClient: jest.fn(),
- onChange: jest.fn(),
- runAsUsers: [],
- indexPatterns: [],
- allowDocumentLevelSecurity: true,
- allowFieldLevelSecurity: true,
- validator: new RoleValidator(),
- builtinESPrivileges: {
- cluster: ['all', 'manage', 'monitor'],
- index: ['all', 'read', 'write', 'index'],
- },
- };
- const wrapper = shallowWithIntl( );
- expect(wrapper).toMatchSnapshot();
-});
-
-test('it renders ClusterPrivileges', () => {
- const props = {
- role: {
- name: '',
- elasticsearch: {
- cluster: [],
- indices: [],
- run_as: [],
- },
- kibana: [],
- },
- editable: true,
- httpClient: jest.fn(),
- onChange: jest.fn(),
- runAsUsers: [],
- indexPatterns: [],
- allowDocumentLevelSecurity: true,
- allowFieldLevelSecurity: true,
- validator: new RoleValidator(),
- builtinESPrivileges: {
- cluster: ['all', 'manage', 'monitor'],
- index: ['all', 'read', 'write', 'index'],
- },
- };
- const wrapper = mountWithIntl( );
- expect(wrapper.find(ClusterPrivileges)).toHaveLength(1);
-});
-
-test('it renders IndexPrivileges', () => {
- const props = {
- role: {
- name: '',
- elasticsearch: {
- cluster: [],
- indices: [],
- run_as: [],
- },
- kibana: [],
- },
- editable: true,
- httpClient: jest.fn(),
- onChange: jest.fn(),
- runAsUsers: [],
- indexPatterns: [],
- allowDocumentLevelSecurity: true,
- allowFieldLevelSecurity: true,
- validator: new RoleValidator(),
- builtinESPrivileges: {
- cluster: ['all', 'manage', 'monitor'],
- index: ['all', 'read', 'write', 'index'],
- },
- };
- const wrapper = mountWithIntl( );
- expect(wrapper.find(IndexPrivileges)).toHaveLength(1);
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html b/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html
deleted file mode 100644
index ca4073dcad6f5..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js
deleted file mode 100644
index 27c9beb4ba828..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js
+++ /dev/null
@@ -1,176 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import _ from 'lodash';
-import routes from 'ui/routes';
-import { capabilities } from 'ui/capabilities';
-import { kfetch } from 'ui/kfetch';
-import { fatalError, toastNotifications } from 'ui/notify';
-import { npStart } from 'ui/new_platform';
-import template from 'plugins/security/views/management/edit_role/edit_role.html';
-import 'plugins/security/services/shield_role';
-import 'plugins/security/services/shield_indices';
-import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
-import { UserAPIClient } from '../../../lib/api';
-import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls';
-import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs';
-
-import { EditRolePage } from './components';
-
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { I18nContext } from 'ui/i18n';
-import { i18n } from '@kbn/i18n';
-
-const routeDefinition = action => ({
- template,
- k7Breadcrumbs: ($injector, $route) =>
- $injector.invoke(
- action === 'edit' && $route.current.params.name
- ? getEditRoleBreadcrumbs
- : getCreateRoleBreadcrumbs
- ),
- resolve: {
- role($route, ShieldRole, Promise, kbnUrl) {
- const name = $route.current.params.name;
-
- let role;
-
- if (name != null) {
- role = ShieldRole.get({ name }).$promise.catch(response => {
- if (response.status === 404) {
- toastNotifications.addDanger({
- title: i18n.translate('xpack.security.management.roles.roleNotFound', {
- defaultMessage: 'No "{roleName}" role found.',
- values: { roleName: name },
- }),
- });
- kbnUrl.redirect(ROLES_PATH);
- } else {
- return fatalError(response);
- }
- });
- } else {
- role = Promise.resolve(
- new ShieldRole({
- elasticsearch: {
- cluster: [],
- indices: [],
- run_as: [],
- },
- kibana: [],
- _unrecognized_applications: [],
- })
- );
- }
-
- return role.then(res => res.toJSON());
- },
- users() {
- return new UserAPIClient().getUsers().then(users => _.map(users, 'username'));
- },
- indexPatterns() {
- return npStart.plugins.data.indexPatterns.getTitles();
- },
- spaces(spacesEnabled) {
- if (spacesEnabled) {
- return kfetch({ method: 'get', pathname: '/api/spaces/space' });
- }
- return [];
- },
- kibanaPrivileges() {
- return kfetch({
- method: 'get',
- pathname: '/api/security/privileges',
- query: { includeActions: true },
- });
- },
- builtinESPrivileges() {
- return kfetch({ method: 'get', pathname: '/internal/security/esPrivileges/builtin' });
- },
- features() {
- return kfetch({ method: 'get', pathname: '/api/features' }).catch(e => {
- // TODO: This check can be removed once all of these `resolve` entries are moved out of Angular and into the React app.
- const unauthorizedForFeatures = _.get(e, 'body.statusCode') === 404;
- if (unauthorizedForFeatures) {
- return [];
- }
- throw e;
- });
- },
- },
- controllerAs: 'editRole',
- controller($injector, $scope, $http, enableSpaceAwarePrivileges) {
- const $route = $injector.get('$route');
- const role = $route.current.locals.role;
-
- const allowDocumentLevelSecurity = xpackInfo.get(
- 'features.security.allowRoleDocumentLevelSecurity'
- );
- const allowFieldLevelSecurity = xpackInfo.get('features.security.allowRoleFieldLevelSecurity');
- if (role.elasticsearch.indices.length === 0) {
- const emptyOption = {
- names: [],
- privileges: [],
- };
-
- if (allowFieldLevelSecurity) {
- emptyOption.field_security = {
- grant: ['*'],
- except: [],
- };
- }
-
- if (allowDocumentLevelSecurity) {
- emptyOption.query = '';
- }
-
- role.elasticsearch.indices.push(emptyOption);
- }
-
- const {
- users,
- indexPatterns,
- spaces,
- kibanaPrivileges,
- builtinESPrivileges,
- features,
- } = $route.current.locals;
-
- $scope.$$postDigest(async () => {
- const domNode = document.getElementById('editRoleReactRoot');
-
- render(
-
-
- ,
- domNode
- );
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(domNode);
- });
- });
- },
-});
-
-routes.when(`${CLONE_ROLES_PATH}/:name`, routeDefinition('clone'));
-routes.when(`${EDIT_ROLES_PATH}/:name?`, routeDefinition('edit'));
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss
deleted file mode 100644
index c5da74aa3f785..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import './users';
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html
deleted file mode 100644
index 4fa2768480874..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js
deleted file mode 100644
index ab218022c6ee6..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import routes from 'ui/routes';
-import template from 'plugins/security/views/management/edit_user/edit_user.html';
-import 'angular-resource';
-import 'ui/angular_ui_select';
-import 'plugins/security/services/shield_role';
-import { EDIT_USERS_PATH } from '../management_urls';
-import { EditUserPage } from './components';
-import { UserAPIClient } from '../../../lib/api';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { I18nContext } from 'ui/i18n';
-import { npSetup } from 'ui/new_platform';
-import { getEditUserBreadcrumbs, getCreateUserBreadcrumbs } from '../breadcrumbs';
-
-const renderReact = (elem, changeUrl, username) => {
- render(
-
-
- ,
- elem
- );
-};
-
-routes.when(`${EDIT_USERS_PATH}/:username?`, {
- template,
- k7Breadcrumbs: ($injector, $route) =>
- $injector.invoke(
- $route.current.params.username ? getEditUserBreadcrumbs : getCreateUserBreadcrumbs
- ),
- controllerAs: 'editUser',
- controller($scope, $route, kbnUrl) {
- $scope.$on('$destroy', () => {
- const elem = document.getElementById('editUserReactRoot');
- if (elem) {
- unmountComponentAtNode(elem);
- }
- });
- $scope.$$postDigest(() => {
- const elem = document.getElementById('editUserReactRoot');
- const username = $route.current.params.username;
- const changeUrl = url => {
- kbnUrl.change(url);
- $scope.$apply();
- };
- renderReact(elem, changeUrl, username);
- });
- },
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/management.js b/x-pack/legacy/plugins/security/public/views/management/management.js
deleted file mode 100644
index f0369f232aeba..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/management.js
+++ /dev/null
@@ -1,134 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import 'plugins/security/views/management/change_password_form/change_password_form';
-import 'plugins/security/views/management/password_form/password_form';
-import 'plugins/security/views/management/users_grid/users';
-import 'plugins/security/views/management/roles_grid/roles';
-import 'plugins/security/views/management/api_keys_grid/api_keys';
-import 'plugins/security/views/management/edit_user/edit_user';
-import 'plugins/security/views/management/edit_role/index';
-import 'plugins/security/views/management/role_mappings/role_mappings_grid';
-import 'plugins/security/views/management/role_mappings/edit_role_mapping';
-import routes from 'ui/routes';
-import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
-import { ROLES_PATH, USERS_PATH, API_KEYS_PATH, ROLE_MAPPINGS_PATH } from './management_urls';
-
-import { management } from 'ui/management';
-import { npSetup } from 'ui/new_platform';
-import { i18n } from '@kbn/i18n';
-import { toastNotifications } from 'ui/notify';
-
-routes
- .defaults(/^\/management\/security(\/|$)/, {
- resolve: {
- showLinks(kbnUrl, Promise) {
- if (!xpackInfo.get('features.security.showLinks')) {
- toastNotifications.addDanger({
- title: xpackInfo.get('features.security.linksMessage'),
- });
- kbnUrl.redirect('/management');
- return Promise.halt();
- }
- },
- },
- })
- .defaults(/\/management/, {
- resolve: {
- securityManagementSection: function() {
- const showSecurityLinks = xpackInfo.get('features.security.showLinks');
- const showRoleMappingsManagementLink = xpackInfo.get(
- 'features.security.showRoleMappingsManagement'
- );
-
- function deregisterSecurity() {
- management.deregister('security');
- }
-
- function deregisterRoleMappingsManagement() {
- if (management.hasItem('security')) {
- const security = management.getSection('security');
- if (security.hasItem('roleMappings')) {
- security.deregister('roleMappings');
- }
- }
- }
-
- function ensureSecurityRegistered() {
- const registerSecurity = () =>
- management.register('security', {
- display: i18n.translate('xpack.security.management.securityTitle', {
- defaultMessage: 'Security',
- }),
- order: 100,
- icon: 'securityApp',
- });
- const getSecurity = () => management.getSection('security');
-
- const security = management.hasItem('security') ? getSecurity() : registerSecurity();
-
- if (!security.hasItem('users')) {
- security.register('users', {
- name: 'securityUsersLink',
- order: 10,
- display: i18n.translate('xpack.security.management.usersTitle', {
- defaultMessage: 'Users',
- }),
- url: `#${USERS_PATH}`,
- });
- }
-
- if (!security.hasItem('roles')) {
- security.register('roles', {
- name: 'securityRolesLink',
- order: 20,
- display: i18n.translate('xpack.security.management.rolesTitle', {
- defaultMessage: 'Roles',
- }),
- url: `#${ROLES_PATH}`,
- });
- }
-
- if (!security.hasItem('apiKeys')) {
- security.register('apiKeys', {
- name: 'securityApiKeysLink',
- order: 30,
- display: i18n.translate('xpack.security.management.apiKeysTitle', {
- defaultMessage: 'API Keys',
- }),
- url: `#${API_KEYS_PATH}`,
- });
- }
-
- if (showRoleMappingsManagementLink && !security.hasItem('roleMappings')) {
- security.register('roleMappings', {
- name: 'securityRoleMappingLink',
- order: 30,
- display: i18n.translate('xpack.security.management.roleMappingsTitle', {
- defaultMessage: 'Role Mappings',
- }),
- url: `#${ROLE_MAPPINGS_PATH}`,
- });
- }
- }
-
- if (!showSecurityLinks) {
- deregisterSecurity();
- } else {
- if (!showRoleMappingsManagementLink) {
- deregisterRoleMappingsManagement();
- }
-
- // getCurrentUser will reject if there is no authenticated user, so we prevent them from
- // seeing the security management screens.
- return npSetup.plugins.security.authc
- .getCurrentUser()
- .then(ensureSecurityRegistered)
- .catch(deregisterSecurity);
- }
- },
- },
- });
diff --git a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html b/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html
deleted file mode 100644
index 72956992100f5..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js b/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js
deleted file mode 100644
index edcccdb5e6e69..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { uiModules } from 'ui/modules';
-import template from './password_form.html';
-
-const module = uiModules.get('security', ['kibana']);
-module.directive('kbnPasswordForm', function() {
- return {
- template,
- scope: {
- password: '=',
- },
- restrict: 'E',
- replace: true,
- controllerAs: 'passwordController',
- controller: function() {
- this.confirmation = null;
- },
- };
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss
deleted file mode 100644
index 80e08ebcf1226..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import './components/rule_editor_panel/index';
\ No newline at end of file
diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx
deleted file mode 100644
index 375a8d9f374a8..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx
+++ /dev/null
@@ -1,341 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
-import { findTestSubject } from 'test_utils/find_test_subject';
-
-// brace/ace uses the Worker class, which is not currently provided by JSDOM.
-// This is not required for the tests to pass, but it rather suppresses lengthy
-// warnings in the console which adds unnecessary noise to the test output.
-import 'test_utils/stub_web_worker';
-
-import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api';
-import { EditRoleMappingPage } from '.';
-import { NoCompatibleRealms, SectionLoading, PermissionDenied } from '../../components';
-import { VisualRuleEditor } from './rule_editor_panel/visual_rule_editor';
-import { JSONRuleEditor } from './rule_editor_panel/json_rule_editor';
-import { EuiComboBox } from '@elastic/eui';
-
-jest.mock('../../../../../lib/roles_api', () => {
- return {
- RolesApi: {
- getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]),
- },
- };
-});
-
-describe('EditRoleMappingPage', () => {
- it('allows a role mapping to be created', async () => {
- const roleMappingsAPI = ({
- saveRoleMapping: jest.fn().mockResolvedValue(null),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- canUseInlineScripts: true,
- canUseStoredScripts: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
-
- await nextTick();
- wrapper.update();
-
- findTestSubject(wrapper, 'roleMappingFormNameInput').simulate('change', {
- target: { value: 'my-role-mapping' },
- });
-
- (wrapper
- .find(EuiComboBox)
- .filter('[data-test-subj="roleMappingFormRoleComboBox"]')
- .props() as any).onChange([{ label: 'foo_role' }]);
-
- findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click');
-
- findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click');
-
- expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({
- name: 'my-role-mapping',
- enabled: true,
- roles: ['foo_role'],
- role_templates: [],
- rules: {
- all: [{ field: { username: '*' } }],
- },
- metadata: {},
- });
- });
-
- it('allows a role mapping to be updated', async () => {
- const roleMappingsAPI = ({
- saveRoleMapping: jest.fn().mockResolvedValue(null),
- getRoleMapping: jest.fn().mockResolvedValue({
- name: 'foo',
- role_templates: [
- {
- template: { id: 'foo' },
- },
- ],
- enabled: true,
- rules: {
- any: [{ field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }],
- },
- metadata: {
- foo: 'bar',
- bar: 'baz',
- },
- }),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- canUseInlineScripts: true,
- canUseStoredScripts: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl(
-
- );
-
- await nextTick();
- wrapper.update();
-
- findTestSubject(wrapper, 'switchToRolesButton').simulate('click');
-
- (wrapper
- .find(EuiComboBox)
- .filter('[data-test-subj="roleMappingFormRoleComboBox"]')
- .props() as any).onChange([{ label: 'foo_role' }]);
-
- findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click');
- wrapper.find('button[id="addRuleOption"]').simulate('click');
-
- findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click');
-
- expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({
- name: 'foo',
- enabled: true,
- roles: ['foo_role'],
- role_templates: [],
- rules: {
- any: [
- { field: { 'metadata.someCustomOption': [false, true, 'asdf'] } },
- { field: { username: '*' } },
- ],
- },
- metadata: {
- foo: 'bar',
- bar: 'baz',
- },
- });
- });
-
- it('renders a permission denied message when unauthorized to manage role mappings', async () => {
- const roleMappingsAPI = ({
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: false,
- hasCompatibleRealms: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- expect(wrapper.find(SectionLoading)).toHaveLength(1);
- expect(wrapper.find(PermissionDenied)).toHaveLength(0);
-
- await nextTick();
- wrapper.update();
-
- expect(wrapper.find(SectionLoading)).toHaveLength(0);
- expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0);
- expect(wrapper.find(PermissionDenied)).toHaveLength(1);
- });
-
- it('renders a warning when there are no compatible realms enabled', async () => {
- const roleMappingsAPI = ({
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: false,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- expect(wrapper.find(SectionLoading)).toHaveLength(1);
- expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0);
-
- await nextTick();
- wrapper.update();
-
- expect(wrapper.find(SectionLoading)).toHaveLength(0);
- expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1);
- });
-
- it('renders a warning when editing a mapping with a stored role template, when stored scripts are disabled', async () => {
- const roleMappingsAPI = ({
- getRoleMapping: jest.fn().mockResolvedValue({
- name: 'foo',
- role_templates: [
- {
- template: { id: 'foo' },
- },
- ],
- enabled: true,
- rules: {
- field: { username: '*' },
- },
- }),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- canUseInlineScripts: true,
- canUseStoredScripts: false,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0);
- expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0);
-
- await nextTick();
- wrapper.update();
-
- expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0);
- expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(1);
- });
-
- it('renders a warning when editing a mapping with an inline role template, when inline scripts are disabled', async () => {
- const roleMappingsAPI = ({
- getRoleMapping: jest.fn().mockResolvedValue({
- name: 'foo',
- role_templates: [
- {
- template: { source: 'foo' },
- },
- ],
- enabled: true,
- rules: {
- field: { username: '*' },
- },
- }),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- canUseInlineScripts: false,
- canUseStoredScripts: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl(
-
- );
-
- expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0);
- expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0);
-
- await nextTick();
- wrapper.update();
-
- expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(1);
- expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0);
- });
-
- it('renders the visual editor by default for simple rule sets', async () => {
- const roleMappingsAPI = ({
- getRoleMapping: jest.fn().mockResolvedValue({
- name: 'foo',
- roles: ['superuser'],
- enabled: true,
- rules: {
- all: [
- {
- field: {
- username: '*',
- },
- },
- {
- field: {
- dn: null,
- },
- },
- {
- field: {
- realm: ['ldap', 'pki', null, 12],
- },
- },
- ],
- },
- }),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- canUseInlineScripts: true,
- canUseStoredScripts: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl(
-
- );
-
- await nextTick();
- wrapper.update();
-
- expect(wrapper.find(VisualRuleEditor)).toHaveLength(1);
- expect(wrapper.find(JSONRuleEditor)).toHaveLength(0);
- });
-
- it('renders the JSON editor by default for complex rule sets', async () => {
- const createRule = (depth: number): Record => {
- if (depth > 0) {
- const rule = {
- all: [
- {
- field: {
- username: '*',
- },
- },
- ],
- } as Record;
-
- const subRule = createRule(depth - 1);
- if (subRule) {
- rule.all.push(subRule);
- }
-
- return rule;
- }
- return null as any;
- };
-
- const roleMappingsAPI = ({
- getRoleMapping: jest.fn().mockResolvedValue({
- name: 'foo',
- roles: ['superuser'],
- enabled: true,
- rules: createRule(10),
- }),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- canUseInlineScripts: true,
- canUseStoredScripts: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl(
-
- );
-
- await nextTick();
- wrapper.update();
-
- expect(wrapper.find(VisualRuleEditor)).toHaveLength(0);
- expect(wrapper.find(JSONRuleEditor)).toHaveLength(1);
- });
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html
deleted file mode 100644
index ca8ab9c35c49b..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx
deleted file mode 100644
index b064a4dc50a22..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx
+++ /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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import routes from 'ui/routes';
-import { I18nContext } from 'ui/i18n';
-import { npSetup } from 'ui/new_platform';
-import { RoleMappingsAPI } from '../../../../lib/role_mappings_api';
-// @ts-ignore
-import template from './edit_role_mapping.html';
-import { CREATE_ROLE_MAPPING_PATH } from '../../management_urls';
-import { getEditRoleMappingBreadcrumbs } from '../../breadcrumbs';
-import { EditRoleMappingPage } from './components';
-
-routes.when(`${CREATE_ROLE_MAPPING_PATH}/:name?`, {
- template,
- k7Breadcrumbs: getEditRoleMappingBreadcrumbs,
- controller($scope, $route) {
- $scope.$$postDigest(() => {
- const domNode = document.getElementById('editRoleMappingReactRoot');
-
- const { name } = $route.current.params;
-
- render(
-
-
- ,
- domNode
- );
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- if (domNode) {
- unmountComponentAtNode(domNode);
- }
- });
- });
- },
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx
deleted file mode 100644
index 259cdc71e25a2..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx
+++ /dev/null
@@ -1,182 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
-import { RoleMappingsGridPage } from '.';
-import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../../components';
-import { EmptyPrompt } from './empty_prompt';
-import { findTestSubject } from 'test_utils/find_test_subject';
-import { EuiLink } from '@elastic/eui';
-import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api';
-import { act } from '@testing-library/react';
-
-describe('RoleMappingsGridPage', () => {
- it('renders an empty prompt when no role mappings exist', async () => {
- const roleMappingsAPI = ({
- getRoleMappings: jest.fn().mockResolvedValue([]),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- expect(wrapper.find(SectionLoading)).toHaveLength(1);
- expect(wrapper.find(EmptyPrompt)).toHaveLength(0);
-
- await nextTick();
- wrapper.update();
-
- expect(wrapper.find(SectionLoading)).toHaveLength(0);
- expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0);
- expect(wrapper.find(EmptyPrompt)).toHaveLength(1);
- });
-
- it('renders a permission denied message when unauthorized to manage role mappings', async () => {
- const roleMappingsAPI = ({
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: false,
- hasCompatibleRealms: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- expect(wrapper.find(SectionLoading)).toHaveLength(1);
- expect(wrapper.find(PermissionDenied)).toHaveLength(0);
-
- await nextTick();
- wrapper.update();
-
- expect(wrapper.find(SectionLoading)).toHaveLength(0);
- expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0);
- expect(wrapper.find(PermissionDenied)).toHaveLength(1);
- });
-
- it('renders a warning when there are no compatible realms enabled', async () => {
- const roleMappingsAPI = ({
- getRoleMappings: jest.fn().mockResolvedValue([
- {
- name: 'some realm',
- enabled: true,
- roles: [],
- rules: { field: { username: '*' } },
- },
- ]),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: false,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- expect(wrapper.find(SectionLoading)).toHaveLength(1);
- expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0);
-
- await nextTick();
- wrapper.update();
-
- expect(wrapper.find(SectionLoading)).toHaveLength(0);
- expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1);
- });
-
- it('renders links to mapped roles', async () => {
- const roleMappingsAPI = ({
- getRoleMappings: jest.fn().mockResolvedValue([
- {
- name: 'some realm',
- enabled: true,
- roles: ['superuser'],
- rules: { field: { username: '*' } },
- },
- ]),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- await nextTick();
- wrapper.update();
-
- const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink);
- expect(links).toHaveLength(1);
- expect(links.at(0).props()).toMatchObject({
- href: '#/management/security/roles/edit/superuser',
- });
- });
-
- it('describes the number of mapped role templates', async () => {
- const roleMappingsAPI = ({
- getRoleMappings: jest.fn().mockResolvedValue([
- {
- name: 'some realm',
- enabled: true,
- role_templates: [{}, {}],
- rules: { field: { username: '*' } },
- },
- ]),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- }),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- await nextTick();
- wrapper.update();
-
- const templates = findTestSubject(wrapper, 'roleMappingRoles');
- expect(templates).toHaveLength(1);
- expect(templates.text()).toEqual(`2 role templates defined`);
- });
-
- it('allows role mappings to be deleted, refreshing the grid after', async () => {
- const roleMappingsAPI = ({
- getRoleMappings: jest.fn().mockResolvedValue([
- {
- name: 'some-realm',
- enabled: true,
- roles: ['superuser'],
- rules: { field: { username: '*' } },
- },
- ]),
- checkRoleMappingFeatures: jest.fn().mockResolvedValue({
- canManageRoleMappings: true,
- hasCompatibleRealms: true,
- }),
- deleteRoleMappings: jest.fn().mockReturnValue(
- Promise.resolve([
- {
- name: 'some-realm',
- success: true,
- },
- ])
- ),
- } as unknown) as RoleMappingsAPI;
-
- const wrapper = mountWithIntl( );
- await nextTick();
- wrapper.update();
-
- expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(1);
- expect(roleMappingsAPI.deleteRoleMappings).not.toHaveBeenCalled();
-
- findTestSubject(wrapper, `deleteRoleMappingButton-some-realm`).simulate('click');
- expect(findTestSubject(wrapper, 'deleteRoleMappingConfirmationModal')).toHaveLength(1);
-
- await act(async () => {
- findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click');
- await nextTick();
- wrapper.update();
- });
-
- expect(roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['some-realm']);
- // Expect an additional API call to refresh the grid
- expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(2);
- });
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx
deleted file mode 100644
index 9e925d0fa6dc0..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx
+++ /dev/null
@@ -1,40 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import routes from 'ui/routes';
-import { I18nContext } from 'ui/i18n';
-import { npSetup } from 'ui/new_platform';
-import { RoleMappingsAPI } from '../../../../lib/role_mappings_api';
-// @ts-ignore
-import template from './role_mappings.html';
-import { ROLE_MAPPINGS_PATH } from '../../management_urls';
-import { getRoleMappingBreadcrumbs } from '../../breadcrumbs';
-import { RoleMappingsGridPage } from './components';
-
-routes.when(ROLE_MAPPINGS_PATH, {
- template,
- k7Breadcrumbs: getRoleMappingBreadcrumbs,
- controller($scope) {
- $scope.$$postDigest(() => {
- const domNode = document.getElementById('roleMappingsGridReactRoot');
-
- render(
-
-
- ,
- domNode
- );
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- if (domNode) {
- unmountComponentAtNode(domNode);
- }
- });
- });
- },
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html
deleted file mode 100644
index cff3b821d132c..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html b/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html
deleted file mode 100644
index 0552b655afafd..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js b/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js
deleted file mode 100644
index e9c42824711b3..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js
+++ /dev/null
@@ -1,35 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import routes from 'ui/routes';
-import template from 'plugins/security/views/management/roles_grid/roles.html';
-import { ROLES_PATH } from '../management_urls';
-import { getRolesBreadcrumbs } from '../breadcrumbs';
-import { I18nContext } from 'ui/i18n';
-import { RolesGridPage } from './components';
-
-routes.when(ROLES_PATH, {
- template,
- k7Breadcrumbs: getRolesBreadcrumbs,
- controller($scope) {
- $scope.$$postDigest(() => {
- const domNode = document.getElementById('rolesGridReactRoot');
-
- render(
-
-
- ,
- domNode
- );
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(domNode);
- });
- });
- },
-});
diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html
deleted file mode 100644
index 3dce7326d001a..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js
deleted file mode 100644
index 8d4e0526251d7..0000000000000
--- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import routes from 'ui/routes';
-import template from 'plugins/security/views/management/users_grid/users.html';
-import { SECURITY_PATH, USERS_PATH } from '../management_urls';
-import { UsersListPage } from './components';
-import { UserAPIClient } from '../../../lib/api';
-import { I18nContext } from 'ui/i18n';
-import { getUsersBreadcrumbs } from '../breadcrumbs';
-
-routes.when(SECURITY_PATH, {
- redirectTo: USERS_PATH,
-});
-
-const renderReact = (elem, changeUrl) => {
- render(
-
-
- ,
- elem
- );
-};
-
-routes.when(USERS_PATH, {
- template,
- k7Breadcrumbs: getUsersBreadcrumbs,
- controller($scope, $http, kbnUrl) {
- $scope.$on('$destroy', () => {
- const elem = document.getElementById('usersReactRoot');
- if (elem) unmountComponentAtNode(elem);
- });
- $scope.$$postDigest(() => {
- const elem = document.getElementById('usersReactRoot');
- const changeUrl = url => {
- kbnUrl.change(url);
- $scope.$apply();
- };
- renderReact(elem, $http, changeUrl);
- });
- },
-});
diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx
index fb39c517e1c2c..4c79c499cc0e6 100644
--- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx
+++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx
@@ -11,8 +11,7 @@ import { render } from 'react-dom';
import chrome from 'ui/chrome';
import { I18nContext } from 'ui/i18n';
import { npSetup } from 'ui/new_platform';
-import { SecurityPluginSetup } from '../../../../../../plugins/security/public';
-import { AuthenticatedUser } from '../../../common/model';
+import { AuthenticatedUser, SecurityPluginSetup } from '../../../../../../plugins/security/public';
import { AuthenticationStatePage } from '../../components/authentication_state_page';
chrome
diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts
index afeb8c3c13a4f..142729189e49b 100644
--- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts
+++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts
@@ -99,7 +99,7 @@ describe('ml conditional links', () => {
loginAndWaitForPage(mlNetworkSingleIpNullKqlQuery);
cy.url().should(
'include',
- '/app/siem#/network/ip/127.0.0.1?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
+ '/app/siem#/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))'
);
});
@@ -107,7 +107,7 @@ describe('ml conditional links', () => {
loginAndWaitForPage(mlNetworkSingleIpKqlQuery);
cy.url().should(
'include',
- "/app/siem#/network/ip/127.0.0.1?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
+ "/app/siem#/network/ip/127.0.0.1/source?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))"
);
});
diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx
index 87d83f7f2972c..0b99a8b059df7 100644
--- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx
@@ -16,11 +16,11 @@ import { MatrixHistogramGqlQuery } from '../../containers/matrix_histogram/index
const ID = 'alertsOverTimeQuery';
export const alertsStackByOptions: MatrixHistogramOption[] = [
{
- text: i18n.CATEGORY,
+ text: 'event.category',
value: 'event.category',
},
{
- text: i18n.MODULE,
+ text: 'event.module',
value: 'event.module',
},
];
@@ -54,7 +54,6 @@ export const AlertsView = ({
<>
diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts
index 8c6248e38c057..408c406a854be 100644
--- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts
@@ -14,10 +14,14 @@ export const TOTAL_COUNT_OF_ALERTS = i18n.translate('xpack.siem.alertsView.total
defaultMessage: 'alerts match the search criteria',
});
-export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsDocumentType', {
+export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsTableTitle', {
defaultMessage: 'Alerts',
});
+export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.alertsView.alertsGraphTitle', {
+ defaultMessage: 'Alert detection frequency',
+});
+
export const ALERTS_STACK_BY_MODULE = i18n.translate(
'xpack.siem.alertsView.alertsStackByOptions.module',
{
diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx
index 77d51f68c18ca..05a33eeba1434 100644
--- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx
@@ -42,6 +42,10 @@ const WrappedByAutoSizer = styled.div`
`; // required by AutoSizer
WrappedByAutoSizer.displayName = 'WrappedByAutoSizer';
+const StyledEuiPanel = styled(EuiPanel)`
+ max-width: 100%;
+`;
+
interface Props {
browserFields: BrowserFields;
columns: ColumnHeader[];
@@ -113,7 +117,7 @@ const EventsViewerComponent: React.FC = ({
);
return (
-
+
{({ measureRef, content: { width = 0 } }) => (
<>
@@ -225,7 +229,7 @@ const EventsViewerComponent: React.FC = ({
>
)}
-
+
);
};
diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx
index db6ff7cf55f92..5a286532fabfc 100644
--- a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx
@@ -55,7 +55,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine
display="condensed"
navTabs={
hideDetectionEngine
- ? pickBy((_, key) => key !== SiemPageName.detectionEngine, navTabs)
+ ? pickBy((_, key) => key !== SiemPageName.detections, navTabs)
: navTabs
}
/>
diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx
index 7c15af3fe642a..3180fc955c690 100644
--- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx
@@ -20,6 +20,7 @@ import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_ho
import { RedirectToNetworkPage } from './redirect_to_network';
import { RedirectToOverviewPage } from './redirect_to_overview';
import { RedirectToTimelinesPage } from './redirect_to_timelines';
+import { DetectionEngineTab } from '../../pages/detection_engine/types';
interface LinkToPageProps {
match: RouteMatch<{}>;
@@ -60,26 +61,32 @@ export const LinkToPage = React.memo(({ match }) => (
+
;
-export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine';
+export const DETECTION_ENGINE_PAGE_NAME = 'detections';
export const RedirectToDetectionEnginePage = ({
+ match: {
+ params: { tabName },
+ },
location: { search },
-}: DetectionEngineComponentProps) => (
-
-);
+}: DetectionEngineComponentProps) => {
+ const defaultSelectedTab = DetectionEngineTab.signals;
+ const selectedTab = tabName ? tabName : defaultSelectedTab;
+ const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`;
+
+ return ;
+};
export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => {
return ;
@@ -28,7 +37,7 @@ export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineCom
export const RedirectToCreateRulePage = ({
location: { search },
}: DetectionEngineComponentProps) => {
- return ;
+ return ;
};
export const RedirectToRuleDetailsPage = ({
@@ -44,6 +53,8 @@ export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngine
};
export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`;
+export const getDetectionEngineAlertUrl = () =>
+ `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`;
export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`;
export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`;
export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`;
diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx
index 56ebbb06f3eb9..cdd62c430a50c 100644
--- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx
@@ -46,12 +46,12 @@ export const MatrixHistogramComponent: React.FC {
expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, {
detailName: undefined,
navTabs: {
- 'detection-engine': {
+ detections: {
disabled: false,
- href: '#/link-to/detection-engine',
- id: 'detection-engine',
- name: 'Detection engine',
- urlKey: 'detection-engine',
+ href: '#/link-to/detections',
+ id: 'detections',
+ name: 'Detections',
+ urlKey: 'detections',
},
hosts: {
disabled: false,
@@ -146,12 +146,12 @@ describe('SIEM Navigation', () => {
detailName: undefined,
filters: [],
navTabs: {
- 'detection-engine': {
+ detections: {
disabled: false,
- href: '#/link-to/detection-engine',
- id: 'detection-engine',
- name: 'Detection engine',
- urlKey: 'detection-engine',
+ href: '#/link-to/detections',
+ id: 'detections',
+ name: 'Detections',
+ urlKey: 'detections',
},
hosts: {
disabled: false,
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx
index c7259edbdc593..009ab141e958e 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx
@@ -45,11 +45,25 @@ const MyEuiFlexItem = styled(EuiFlexItem)`
white-space: nowrap;
`;
-const EuiSelectableContainer = styled.div`
+const EuiSelectableContainer = styled.div<{ loading: boolean }>`
.euiSelectable {
.euiFormControlLayout__childrenWrapper {
display: flex;
}
+ ${({ loading }) => `${
+ loading
+ ? `
+ .euiFormControlLayoutIcons {
+ display: none;
+ }
+ .euiFormControlLayoutIcons.euiFormControlLayoutIcons--right {
+ display: block;
+ left: 12px;
+ top: 12px;
+ }`
+ : ''
+ }
+ `}
}
`;
@@ -265,7 +279,7 @@ const SearchTimelineSuperSelectComponent: React.FC
{({ timelines, loading, totalCount }) => (
-
+
{
- describe('isKqlForRoute', () => {
- test('host page and host page kuery', () => {
- const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsPage);
- expect(result).toBeTruthy();
- });
- test('host page and host details kuery', () => {
- const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsDetails);
- expect(result).toBeFalsy();
- });
- test('host details and host details kuery', () => {
- const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsDetails);
- expect(result).toBeTruthy();
- });
- test('host details and host page kuery', () => {
- const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsPage);
- expect(result).toBeFalsy();
- });
- test('network page and network page kuery', () => {
- const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkPage);
- expect(result).toBeTruthy();
- });
- test('network page and network details kuery', () => {
- const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkDetails);
- expect(result).toBeFalsy();
- });
- test('network details and network details kuery', () => {
- const result = isKqlForRoute(SiemPageName.network, '10.100.7.198', CONSTANTS.networkDetails);
- expect(result).toBeTruthy();
- });
- test('network details and network page kuery', () => {
- const result = isKqlForRoute(SiemPageName.network, '123.234.34', CONSTANTS.networkPage);
- expect(result).toBeFalsy();
- });
- });
describe('getTitle', () => {
test('host page name', () => {
const result = getTitle('hosts', undefined, navTabs);
diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts
index aa340b54c1699..6ba5810f794b0 100644
--- a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts
+++ b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts
@@ -78,8 +78,8 @@ export const getUrlType = (pageName: string): UrlStateType => {
return 'host';
} else if (pageName === SiemPageName.network) {
return 'network';
- } else if (pageName === SiemPageName.detectionEngine) {
- return 'detection-engine';
+ } else if (pageName === SiemPageName.detections) {
+ return 'detections';
} else if (pageName === SiemPageName.timelines) {
return 'timeline';
}
@@ -111,31 +111,14 @@ export const getCurrentLocation = (
return CONSTANTS.networkDetails;
}
return CONSTANTS.networkPage;
- } else if (pageName === SiemPageName.detectionEngine) {
- return CONSTANTS.detectionEnginePage;
+ } else if (pageName === SiemPageName.detections) {
+ return CONSTANTS.detectionsPage;
} else if (pageName === SiemPageName.timelines) {
return CONSTANTS.timelinePage;
}
return CONSTANTS.unknown;
};
-export const isKqlForRoute = (
- pageName: string,
- detailName: string | undefined,
- queryLocation: LocationTypes | null = null
-): boolean => {
- const currentLocation = getCurrentLocation(pageName, detailName);
- if (
- (currentLocation === CONSTANTS.hostsPage && queryLocation === CONSTANTS.hostsPage) ||
- (currentLocation === CONSTANTS.networkPage && queryLocation === CONSTANTS.networkPage) ||
- (currentLocation === CONSTANTS.hostsDetails && queryLocation === CONSTANTS.hostsDetails) ||
- (currentLocation === CONSTANTS.networkDetails && queryLocation === CONSTANTS.networkDetails)
- ) {
- return true;
- }
- return false;
-};
-
export const makeMapStateToProps = () => {
const getInputsSelector = inputsSelectors.inputsSelector();
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts
index a48653a7ea6f4..be1ae1ad63bd4 100644
--- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts
+++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts
@@ -24,7 +24,7 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [
];
export const URL_STATE_KEYS: Record = {
- 'detection-engine': [
+ detections: [
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
@@ -56,7 +56,7 @@ export const URL_STATE_KEYS: Record = {
};
export type LocationTypes =
- | CONSTANTS.detectionEnginePage
+ | CONSTANTS.detectionsPage
| CONSTANTS.hostsDetails
| CONSTANTS.hostsPage
| CONSTANTS.networkDetails
diff --git a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx
index f691219e446a8..2d9ac8b7645ca 100644
--- a/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/anomalies/anomalies_query_tab_body/index.tsx
@@ -39,6 +39,14 @@ export const AnomaliesQueryTabBody = ({
flowTarget,
ip,
}: AnomaliesQueryTabBodyProps) => {
+ useEffect(() => {
+ return () => {
+ if (deleteQuery) {
+ deleteQuery({ id: ID });
+ }
+ };
+ }, []);
+
const [, siemJobs] = useSiemJobs(true);
const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE);
@@ -51,21 +59,12 @@ export const AnomaliesQueryTabBody = ({
ip
);
- useEffect(() => {
- return () => {
- if (deleteQuery) {
- deleteQuery({ id: ID });
- }
- };
- }, []);
-
return (
<>
=> {
- const requests = rules.map(rule =>
- fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, {
+ const response = await fetch(
+ `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
+ {
method: 'POST',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-xsrf': 'true',
},
- body: JSON.stringify({
- ...rule,
- name: `${rule.name} [${i18n.DUPLICATE}]`,
- created_at: undefined,
- created_by: undefined,
- id: undefined,
- rule_id: undefined,
- updated_at: undefined,
- updated_by: undefined,
- enabled: rule.enabled,
- immutable: false,
- last_success_at: undefined,
- last_success_message: undefined,
- status: undefined,
- status_date: undefined,
- }),
- })
+ body: JSON.stringify(
+ rules.map(rule => ({
+ ...rule,
+ name: `${rule.name} [${i18n.DUPLICATE}]`,
+ created_at: undefined,
+ created_by: undefined,
+ id: undefined,
+ rule_id: undefined,
+ updated_at: undefined,
+ updated_by: undefined,
+ enabled: rule.enabled,
+ immutable: undefined,
+ last_success_at: undefined,
+ last_success_message: undefined,
+ status: undefined,
+ status_date: undefined,
+ }))
+ ),
+ }
);
- const responses = await Promise.all(requests);
- await responses.map(response => throwIfNotOk(response));
- return Promise.all(
- responses.map>(response => response.json())
- );
+ await throwIfNotOk(response);
+ return response.json();
};
/**
@@ -322,7 +322,7 @@ export const getRuleStatusById = async ({
}: {
id: string;
signal: AbortSignal;
-}): Promise> => {
+}): Promise> => {
const response = await fetch(
`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS}?ids=${encodeURIComponent(
JSON.stringify([id])
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts
index a61cbabd80626..e9a0f27b34696 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts
@@ -10,3 +10,4 @@ export * from './persist_rule';
export * from './types';
export * from './use_rule';
export * from './use_rules';
+export * from './use_rule_status';
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
index feef888c0d47f..0dcd0da5be8f6 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
@@ -146,7 +146,7 @@ export interface DeleteRulesProps {
}
export interface DuplicateRulesProps {
- rules: Rules;
+ rules: Rule[];
}
export interface BasicFetchProps {
@@ -181,9 +181,15 @@ export interface ExportRulesProps {
}
export interface RuleStatus {
+ current_status: RuleInfoStatus;
+ failures: RuleInfoStatus[];
+}
+
+export type RuleStatusType = 'executing' | 'failed' | 'going to run' | 'succeeded';
+export interface RuleInfoStatus {
alert_id: string;
status_date: string;
- status: string;
+ status: RuleStatusType | null;
last_failure_at: string | null;
last_success_at: string | null;
last_failure_message: string | null;
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx
new file mode 100644
index 0000000000000..592419f879011
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx
@@ -0,0 +1,81 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { useEffect, useState } from 'react';
+
+import { createPrepackagedRules } from './api';
+
+type Return = [boolean, boolean | null];
+
+interface UseCreatePackagedRules {
+ canUserCRUD: boolean | null;
+ hasIndexManage: boolean | null;
+ hasManageApiKey: boolean | null;
+ isAuthenticated: boolean | null;
+ isSignalIndexExists: boolean | null;
+}
+
+/**
+ * Hook for creating the packages rules
+ *
+ * @param canUserCRUD boolean
+ * @param hasIndexManage boolean
+ * @param hasManageApiKey boolean
+ * @param isAuthenticated boolean
+ * @param isSignalIndexExists boolean
+ *
+ * @returns [loading, hasCreatedPackageRules]
+ */
+export const useCreatePackagedRules = ({
+ canUserCRUD,
+ hasIndexManage,
+ hasManageApiKey,
+ isAuthenticated,
+ isSignalIndexExists,
+}: UseCreatePackagedRules): Return => {
+ const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ let isSubscribed = true;
+ const abortCtrl = new AbortController();
+ setLoading(true);
+
+ async function createRules() {
+ try {
+ await createPrepackagedRules({
+ signal: abortCtrl.signal,
+ });
+
+ if (isSubscribed) {
+ setHasCreatedPackageRules(true);
+ }
+ } catch (error) {
+ if (isSubscribed) {
+ setHasCreatedPackageRules(false);
+ }
+ }
+ if (isSubscribed) {
+ setLoading(false);
+ }
+ }
+ if (
+ canUserCRUD &&
+ hasIndexManage &&
+ hasManageApiKey &&
+ isAuthenticated &&
+ isSignalIndexExists
+ ) {
+ createRules();
+ }
+ return () => {
+ isSubscribed = false;
+ abortCtrl.abort();
+ };
+ }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]);
+
+ return [loading, hasCreatedPackageRules];
+};
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx
index 216fbcea861a3..466c2cddac97d 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { useStateToaster } from '../../../components/toasters';
import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
@@ -12,7 +12,8 @@ import { getRuleStatusById } from './api';
import * as i18n from './translations';
import { RuleStatus } from './types';
-type Return = [boolean, RuleStatus[] | null];
+type Func = (ruleId: string) => void;
+type Return = [boolean, RuleStatus | null, Func | null];
/**
* Hook for using to get a Rule from the Detection Engine API
@@ -21,7 +22,8 @@ type Return = [boolean, RuleStatus[] | null];
*
*/
export const useRuleStatus = (id: string | undefined | null): Return => {
- const [ruleStatus, setRuleStatus] = useState(null);
+ const [ruleStatus, setRuleStatus] = useState(null);
+ const fetchRuleStatus = useRef(null);
const [loading, setLoading] = useState(true);
const [, dispatchToaster] = useStateToaster();
@@ -29,7 +31,7 @@ export const useRuleStatus = (id: string | undefined | null): Return => {
let isSubscribed = true;
const abortCtrl = new AbortController();
- async function fetchData(idToFetch: string) {
+ const fetchData = async (idToFetch: string) => {
try {
setLoading(true);
const ruleStatusResponse = await getRuleStatusById({
@@ -49,15 +51,16 @@ export const useRuleStatus = (id: string | undefined | null): Return => {
if (isSubscribed) {
setLoading(false);
}
- }
+ };
if (id != null) {
fetchData(id);
}
+ fetchRuleStatus.current = fetchData;
return () => {
isSubscribed = false;
abortCtrl.abort();
};
}, [id]);
- return [loading, ruleStatus];
+ return [loading, ruleStatus, fetchRuleStatus.current];
};
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts
index 34cb7684a0399..ea4860dafd40f 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts
@@ -96,5 +96,5 @@ export interface Privilege {
write: boolean;
};
};
- isAuthenticated: boolean;
+ is_authenticated: boolean;
}
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx
index 792ff29ad2488..7d0e331200d55 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx
@@ -42,7 +42,7 @@ export const usePrivilegeUser = (): Return => {
});
if (isSubscribed && privilege != null) {
- setAuthenticated(privilege.isAuthenticated);
+ setAuthenticated(privilege.is_authenticated);
if (privilege.index != null && Object.keys(privilege.index).length > 0) {
const indexName = Object.keys(privilege.index)[0];
setHasIndexManage(privilege.index[indexName].manage);
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx
index 189d8a1bf3f75..c1ee5fd12b8c1 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx
@@ -8,7 +8,6 @@ import { useEffect, useState, useRef } from 'react';
import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
import { useStateToaster } from '../../../components/toasters';
-import { createPrepackagedRules } from '../rules';
import { createSignalIndex, getSignalIndex } from './api';
import * as i18n from './translations';
import { PostSignalError, SignalIndexError } from './types';
@@ -41,7 +40,6 @@ export const useSignalIndex = (): Return => {
if (isSubscribed && signal != null) {
setSignalIndexName(signal.name);
setSignalIndexExists(true);
- createPrepackagedRules({ signal: abortCtrl.signal });
}
} catch (error) {
if (isSubscribed) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx
index 5b1be4ca2c1dc..d5fd325bb9a26 100644
--- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/index.tsx
@@ -26,7 +26,6 @@ import { SetQuery } from '../../pages/hosts/navigation/types';
export interface OwnProps extends QueryTemplateProps {
dataKey: string | string[];
defaultStackByOption: MatrixHistogramOption;
- deleteQuery?: ({ id }: { id: string }) => void;
errorMessage: string;
headerChildren?: React.ReactNode;
hideHistogramIfEmpty?: boolean;
diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts
index 9cda9d8f6115f..1df1aec76627c 100644
--- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { getOr } from 'lodash/fp';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import {
MatrixHistogramDataTypes,
MatrixHistogramQueryProps,
@@ -35,7 +35,7 @@ export const useQuery = ({
}: MatrixHistogramQueryProps) => {
const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY);
const [, dispatchToaster] = useStateToaster();
- const [refetch, setRefetch] = useState();
+ const refetch = useRef();
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
const [inspect, setInspect] = useState(null);
@@ -71,7 +71,7 @@ export const useQuery = ({
return apolloClient
.query({
query,
- fetchPolicy: 'cache-first',
+ fetchPolicy: 'network-only',
variables: matrixHistogramVariables,
context: {
fetchOptions: {
@@ -103,9 +103,7 @@ export const useQuery = ({
}
);
}
- setRefetch(() => {
- fetchData();
- });
+ refetch.current = fetchData;
fetchData();
return () => {
isSubscribed = false;
@@ -122,5 +120,5 @@ export const useQuery = ({
endDate,
]);
- return { data, loading, inspect, totalCount, refetch };
+ return { data, loading, inspect, totalCount, refetch: refetch.current };
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx
index 8290da1ba3220..5f017a3a1f67f 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx
@@ -45,7 +45,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 1,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -55,7 +55,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 2,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -65,7 +65,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 3,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -76,7 +76,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 4,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -87,7 +87,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 5,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -98,7 +98,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 6,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -109,7 +109,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 7,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -120,7 +120,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 8,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -131,7 +131,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 9,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -142,7 +142,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 10,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -153,7 +153,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 11,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -164,7 +164,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 12,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -175,7 +175,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 13,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -186,7 +186,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 14,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -197,7 +197,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 15,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -208,7 +208,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 16,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -219,7 +219,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 17,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -230,7 +230,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 18,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -241,7 +241,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 19,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -252,7 +252,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 20,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
@@ -263,7 +263,7 @@ export const ActivityMonitor = React.memo(() => {
{
id: 21,
rule: {
- href: '#/detection-engine/rules/rule-details',
+ href: '#/detections/rules/rule-details',
name: 'Automated exfiltration',
},
ran: '2019-12-28 00:00:00.000-05:00',
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx
index 5c4795a819275..e00dfa5b84473 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx
@@ -188,13 +188,13 @@ export const getSignalsActions = ({
updateTimelineIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => void;
}): TimelineAction[] => [
{
- getAction: ({ eventId, ecsData }: TimelineActionProps): JSX.Element => (
+ getAction: ({ ecsData }: TimelineActionProps): JSX.Element => (
sendSignalToTimelineAction({
apolloClient,
@@ -203,7 +203,7 @@ export const getSignalsActions = ({
updateTimelineIsLoading,
})
}
- iconType="tableDensityNormal"
+ iconType="timeline"
aria-label="Next"
/>
@@ -228,7 +228,7 @@ export const getSignalsActions = ({
})
}
isDisabled={!canUserCRUD || !hasIndexWrite}
- iconType={status === FILTER_OPEN ? 'indexOpen' : 'indexClose'}
+ iconType={status === FILTER_OPEN ? 'securitySignalDetected' : 'securitySignalResolved'}
aria-label="Next"
/>
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx
index b756b2eb75a7a..bb45ff68cb01d 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx
@@ -61,7 +61,7 @@ export const getBatchItems = ({
{
closePopover();
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx
index 0af3635d4c473..13d77385c53d4 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx
@@ -80,7 +80,7 @@ const SignalsUtilityBarComponent: React.FC = ({
{isFilteredToOpen
@@ -89,7 +89,7 @@ const SignalsUtilityBarComponent: React.FC = ({
{
if (!showClearSelection) {
selectAll();
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts
index d1ba946be41de..c262f907c9876 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts
@@ -11,7 +11,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle',
});
export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', {
- defaultMessage: 'All signals',
+ defaultMessage: 'Signals',
});
export const SIGNALS_DOCUMENT_TYPE = i18n.translate(
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts
index f329780b075e3..d475fd155ea25 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts
@@ -4,18 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import * as i18n from './translations';
import { SignalsHistogramOption } from './types';
export const signalsHistogramOptions: SignalsHistogramOption[] = [
- { text: i18n.STACK_BY_RISK_SCORES, value: 'signal.rule.risk_score' },
- { text: i18n.STACK_BY_SEVERITIES, value: 'signal.rule.severity' },
- { text: i18n.STACK_BY_DESTINATION_IPS, value: 'destination.ip' },
- { text: i18n.STACK_BY_ACTIONS, value: 'event.action' },
- { text: i18n.STACK_BY_CATEGORIES, value: 'event.category' },
- { text: i18n.STACK_BY_HOST_NAMES, value: 'host.name' },
- { text: i18n.STACK_BY_RULE_TYPES, value: 'signal.rule.type' },
- { text: i18n.STACK_BY_RULE_NAMES, value: 'signal.rule.name' },
- { text: i18n.STACK_BY_SOURCE_IPS, value: 'source.ip' },
- { text: i18n.STACK_BY_USERS, value: 'user.name' },
+ { text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' },
+ { text: 'signal.rule.severity', value: 'signal.rule.severity' },
+ { text: 'destination.ip', value: 'destination.ip' },
+ { text: 'event.action', value: 'event.action' },
+ { text: 'event.category', value: 'event.category' },
+ { text: 'host.name', value: 'host.name' },
+ { text: 'signal.rule.type', value: 'signal.rule.type' },
+ { text: 'signal.rule.name', value: 'signal.rule.name' },
+ { text: 'source.ip', value: 'source.ip' },
+ { text: 'user.name', value: 'user.name' },
];
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx
index fda40f5f9fa5d..64bc7ba24c689 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx
@@ -46,7 +46,7 @@ export const SignalsHistogramPanel = memo(
filters,
query,
from,
- legendPosition = 'bottom',
+ legendPosition = 'right',
loadingInitial = false,
showLinkToSignals = false,
showTotalSignalsCount = false,
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx
index 218fcc3a70f79..d4db8cc7c37e8 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx
@@ -44,7 +44,7 @@ export const SignalsHistogram = React.memo(
from,
query,
filters,
- legendPosition = 'bottom',
+ legendPosition = 'right',
loadingInitial,
setTotalSignalsCount,
stackByField,
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts
index 0245b9968cc36..8c88fa4a5dae6 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts
@@ -86,7 +86,7 @@ export const STACK_BY_USERS = i18n.translate(
export const HISTOGRAM_HEADER = i18n.translate(
'xpack.siem.detectionEngine.signals.histogram.headerTitle',
{
- defaultMessage: 'Signal detection frequency',
+ defaultMessage: 'Signal count',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx
index bbaccb7882484..24e14473d40e9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx
@@ -10,6 +10,7 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro
import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user';
import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index';
import { useKibana } from '../../../../lib/kibana';
+import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules';
export interface State {
canUserCRUD: boolean | null;
@@ -161,6 +162,14 @@ export const useUserInfo = (): State => {
createSignalIndex,
] = useSignalIndex();
+ useCreatePackagedRules({
+ canUserCRUD,
+ hasIndexManage,
+ hasManageApiKey,
+ isAuthenticated,
+ isSignalIndexExists,
+ });
+
const uiCapabilities = useKibana().services.application.capabilities;
const capabilitiesCanUserCRUD: boolean =
typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false;
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx
index 388f667f47fe1..d9e0377b34060 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx
@@ -4,27 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiButton, EuiSpacer } from '@elastic/eui';
-import React, { useCallback } from 'react';
+import { EuiButton, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui';
+import React, { useCallback, useMemo } from 'react';
+import { useParams } from 'react-router-dom';
import { StickyContainer } from 'react-sticky';
-
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
+
+import { Query } from '../../../../../../../src/plugins/data/common/query';
+import { esFilters } from '../../../../../../../src/plugins/data/common/es_query';
+
+import { GlobalTime } from '../../containers/global_time';
+import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
+import { AlertsTable } from '../../components/alerts_viewer/alerts_table';
import { FiltersGlobal } from '../../components/filters_global';
import { HeaderPage } from '../../components/header_page';
+import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine';
import { SiemSearchBar } from '../../components/search_bar';
import { WrapperPage } from '../../components/wrapper_page';
-import { GlobalTime } from '../../containers/global_time';
-import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
-import { SpyRoute } from '../../utils/route/spy_routes';
-
-import { Query } from '../../../../../../../src/plugins/data/common/query';
-import { esFilters } from '../../../../../../../src/plugins/data/common/es_query';
import { State } from '../../store';
import { inputsSelectors } from '../../store/inputs';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
+import { SpyRoute } from '../../utils/route/spy_routes';
import { InputsModelId } from '../../store/inputs/constants';
import { InputsRange } from '../../store/inputs/model';
+import { AlertsByCategory } from '../overview/alerts_by_category';
import { useSignalInfo } from './components/signals_info';
import { SignalsTable } from './components/signals';
import { NoWriteSignalsCallOut } from './components/no_write_signals_callout';
@@ -35,6 +39,7 @@ import { DetectionEngineEmptyPage } from './detection_engine_empty_page';
import { DetectionEngineNoIndex } from './detection_engine_no_signal_index';
import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated';
import * as i18n from './translations';
+import { DetectionEngineTab } from './types';
interface ReduxProps {
filters: esFilters.Filter[];
@@ -51,8 +56,22 @@ export interface DispatchProps {
type DetectionEngineComponentProps = ReduxProps & DispatchProps;
+const detectionsTabs = [
+ {
+ id: DetectionEngineTab.signals,
+ name: i18n.SIGNAL,
+ disabled: false,
+ },
+ {
+ id: DetectionEngineTab.alerts,
+ name: i18n.ALERT,
+ disabled: false,
+ },
+];
+
const DetectionEngineComponent = React.memo(
({ filters, query, setAbsoluteRangeDatePicker }) => {
+ const { tabName = DetectionEngineTab.signals } = useParams();
const {
loading,
isSignalIndexExists,
@@ -87,6 +106,25 @@ const DetectionEngineComponent = React.memo(
);
}
+
+ const tabs = useMemo(
+ () => (
+
+ {detectionsTabs.map(tab => (
+
+ {tab.name}
+
+ ))}
+
+ ),
+ [detectionsTabs, tabName]
+ );
+
return (
<>
{hasIndexWrite != null && !hasIndexWrite && }
@@ -99,7 +137,6 @@ const DetectionEngineComponent = React.memo(
@@ -111,32 +148,55 @@ const DetectionEngineComponent = React.memo(
}
title={i18n.PAGE_TITLE}
>
-
+
{i18n.BUTTON_MANAGE_RULES}
- {({ to, from }) => (
+ {({ to, from, deleteQuery, setQuery }) => (
<>
-
+ {tabs}
-
+ {tabName === DetectionEngineTab.signals && (
+ <>
+
+
+
+ >
+ )}
+ {tabName === DetectionEngineTab.alerts && (
+ <>
+
+
+
+ >
+ )}
>
)}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx
index c4e83429aebdb..33186d2787d8a 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx
@@ -7,21 +7,26 @@
import React from 'react';
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
+import { ManageUserInfo } from './components/user_info';
import { CreateRuleComponent } from './rules/create';
import { DetectionEngine } from './detection_engine';
import { EditRuleComponent } from './rules/edit';
import { RuleDetails } from './rules/details';
import { RulesComponent } from './rules';
-import { ManageUserInfo } from './components/user_info';
+import { DetectionEngineTab } from './types';
-const detectionEnginePath = `/:pageName(detection-engine)`;
+const detectionEnginePath = `/:pageName(detections)`;
type Props = Partial> & { url: string };
export const DetectionEngineContainer = React.memo(() => (
-
+
@@ -30,16 +35,16 @@ export const DetectionEngineContainer = React.memo(() => (
-
+
(
-
+
)}
/>
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts
index 757c1fabfc9cd..b79b3ed091f16 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts
@@ -60,7 +60,7 @@ export const mockTableData: TableData[] = [
lastResponse: { type: '—' },
method: 'saved_query',
rule: {
- href: '#/detection-engine/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61',
+ href: '#/detections/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61',
name: 'Home Grown!',
status: 'Status Placeholder',
},
@@ -112,7 +112,7 @@ export const mockTableData: TableData[] = [
lastResponse: { type: '—' },
method: 'saved_query',
rule: {
- href: '#/detection-engine/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee',
+ href: '#/detections/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee',
name: 'Home Grown!',
status: 'Status Placeholder',
},
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx
index f83a19445acd6..435edcab433b6 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx
@@ -29,17 +29,25 @@ export const editRuleAction = (rule: Rule, history: H.History) => {
history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`);
};
-export const duplicateRuleAction = async (
- rule: Rule,
+export const duplicateRulesAction = async (
+ rules: Rule[],
dispatch: React.Dispatch,
dispatchToaster: Dispatch
) => {
try {
- dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true });
- const duplicatedRule = await duplicateRules({ rules: [rule] });
- dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false });
- dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id });
- displaySuccessToast(i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRule.length), dispatchToaster);
+ const ruleIds = rules.map(r => r.id);
+ dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: true });
+ const duplicatedRules = await duplicateRules({ rules });
+ dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: false });
+ dispatch({
+ type: 'updateRules',
+ rules: duplicatedRules,
+ appendRuleId: rules[rules.length - 1].id,
+ });
+ displaySuccessToast(
+ i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRules.length),
+ dispatchToaster
+ );
} catch (e) {
displayErrorToast(i18n.DUPLICATE_RULE_ERROR, [e.message], dispatchToaster);
}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx
index 06d4c709a32bf..8a10d4f7100b9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx
@@ -10,9 +10,13 @@ import * as H from 'history';
import * as i18n from '../translations';
import { TableData } from '../types';
import { Action } from './reducer';
-import { deleteRulesAction, enableRulesAction, exportRulesAction } from './actions';
+import {
+ deleteRulesAction,
+ duplicateRulesAction,
+ enableRulesAction,
+ exportRulesAction,
+} from './actions';
import { ActionToaster } from '../../../../components/toasters';
-import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine';
export const getBatchItems = (
selectedState: TableData[],
@@ -25,7 +29,6 @@ export const getBatchItems = (
const containsDisabled = selectedState.some(v => !v.activate);
const containsLoading = selectedState.some(v => v.isLoading);
const containsImmutable = selectedState.some(v => v.immutable);
- const containsMultipleRules = Array.from(new Set(selectedState.map(v => v.rule_id))).length > 1;
return [
,
{
closePopover();
- history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${selectedState[0].id}/edit`);
+ await duplicateRulesAction(
+ selectedState.map(s => s.sourceRule),
+ dispatch,
+ dispatchToaster
+ );
}}
>
- {i18n.BATCH_ACTION_EDIT_INDEX_PATTERNS}
+ {i18n.BATCH_ACTION_DUPLICATE_SELECTED}
,
{
closePopover();
await deleteRulesAction(
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx
index 91b018eb3078f..d546c4edb55d3 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx
@@ -18,7 +18,7 @@ import React, { Dispatch } from 'react';
import { getEmptyTagValue } from '../../../../components/empty_value';
import {
deleteRulesAction,
- duplicateRuleAction,
+ duplicateRulesAction,
editRuleAction,
exportRulesAction,
} from './actions';
@@ -30,6 +30,7 @@ import { FormattedDate } from '../../../../components/formatted_date';
import { RuleSwitch } from '../components/rule_switch';
import { SeverityBadge } from '../components/severity_badge';
import { ActionToaster } from '../../../../components/toasters';
+import { getStatusColor } from '../components/rule_status/helpers';
const getActions = (
dispatch: React.Dispatch,
@@ -48,7 +49,7 @@ const getActions = (
icon: 'copy',
name: i18n.DUPLICATE_RULE,
onClick: (rowItem: TableData) =>
- duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster),
+ duplicateRulesAction([rowItem.sourceRule], dispatch, dispatchToaster),
},
{
description: i18n.EXPORT_RULE,
@@ -62,7 +63,6 @@ const getActions = (
icon: 'trash',
name: i18n.DELETE_RULE,
onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster),
- enabled: (rowItem: TableData) => !rowItem.immutable,
},
];
@@ -87,7 +87,7 @@ export const getColumns = (
field: 'method',
name: i18n.COLUMN_METHOD,
truncateText: true,
- width: '16%',
+ width: '14%',
},
{
field: 'severity',
@@ -114,19 +114,11 @@ export const getColumns = (
field: 'status',
name: i18n.COLUMN_LAST_RESPONSE,
render: (value: TableData['status']) => {
- const color =
- value == null
- ? 'subdued'
- : value === 'succeeded'
- ? 'success'
- : value === 'failed'
- ? 'danger'
- : value === 'executing'
- ? 'warning'
- : 'subdued';
return (
<>
- {value ?? getEmptyTagValue()}
+
+ {value ?? getEmptyTagValue()}
+
>
);
},
@@ -162,7 +154,7 @@ export const getColumns = (
/>
),
sortable: true,
- width: '85px',
+ width: '95px',
},
];
const actions: RulesColumns[] = [
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts
index 9666b7a5688cf..07a2f2f278987 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts
@@ -24,7 +24,7 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[]
immutable: rule.immutable,
rule_id: rule.rule_id,
rule: {
- href: `#/detection-engine/rules/id/${encodeURIComponent(rule.id)}`,
+ href: `#/detections/rules/id/${encodeURIComponent(rule.id)}`,
name: rule.name,
status: 'Status Placeholder',
},
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx
index e8b6919165c8b..011c008c5b2d2 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx
@@ -125,7 +125,7 @@ export const buildThreatsDescription = ({
description: (
{threats.map((threat, index) => {
- const tactic = tacticsOptions.find(t => t.name === threat.tactic.name);
+ const tactic = tacticsOptions.find(t => t.id === threat.tactic.id);
return (
@@ -133,7 +133,7 @@ export const buildThreatsDescription = ({
{threat.techniques.map(technique => {
- const myTechnique = techniquesOptions.find(t => t.name === technique.name);
+ const myTechnique = techniquesOptions.find(t => t.id === technique.id);
return (
- theme.euiColorPrimary};
+ width: 40px;
+ height: 40px;
+ }
+`;
+
interface RuleActionsOverflowComponentProps {
rule: Rule | null;
userHasNoPermissions: boolean;
@@ -54,7 +66,7 @@ const RuleActionsOverflowComponent = ({
disabled={userHasNoPermissions}
onClick={async () => {
setIsPopoverOpen(false);
- await duplicateRuleAction(rule, noop, dispatchToaster);
+ await duplicateRulesAction([rule], noop, dispatchToaster);
}}
>
{i18nActions.DUPLICATE_RULE}
@@ -73,7 +85,7 @@ const RuleActionsOverflowComponent = ({
{
setIsPopoverOpen(false);
await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback);
@@ -86,20 +98,29 @@ const RuleActionsOverflowComponent = ({
[rule, userHasNoPermissions]
);
+ const handlePopoverOpen = useCallback(() => {
+ setIsPopoverOpen(!isPopoverOpen);
+ }, [setIsPopoverOpen, isPopoverOpen]);
+
+ const button = useMemo(
+ () => (
+
+
+
+ ),
+ [handlePopoverOpen, userHasNoPermissions]
+ );
+
return (
<>
- setIsPopoverOpen(!isPopoverOpen)}
- />
-
- }
+ button={button}
closePopover={() => setIsPopoverOpen(false)}
id="ruleActionsOverflow"
isOpen={isPopoverOpen}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.ts
new file mode 100644
index 0000000000000..263f602251ea7
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { RuleStatusType } from '../../../../../containers/detection_engine/rules';
+
+export const getStatusColor = (status: RuleStatusType | string | null) =>
+ status == null
+ ? 'subdued'
+ : status === 'succeeded'
+ ? 'success'
+ : status === 'failed'
+ ? 'danger'
+ : status === 'executing' || status === 'going to run'
+ ? 'warning'
+ : 'subdued';
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx
new file mode 100644
index 0000000000000..2c9173cbeb694
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx
@@ -0,0 +1,99 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHealth,
+ EuiLoadingSpinner,
+ EuiText,
+} from '@elastic/eui';
+import { isEqual } from 'lodash/fp';
+import React, { memo, useCallback, useEffect, useState } from 'react';
+
+import { useRuleStatus, RuleInfoStatus } from '../../../../../containers/detection_engine/rules';
+import { FormattedDate } from '../../../../../components/formatted_date';
+import { getEmptyTagValue } from '../../../../../components/empty_value';
+import { getStatusColor } from './helpers';
+import * as i18n from './translations';
+
+interface RuleStatusProps {
+ ruleId: string | null;
+ ruleEnabled?: boolean | null;
+}
+
+const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled }) => {
+ const [loading, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId);
+ const [myRuleEnabled, setMyRuleEnabled] = useState(ruleEnabled ?? null);
+ const [currentStatus, setCurrentStatus] = useState(
+ ruleStatus?.current_status ?? null
+ );
+
+ useEffect(() => {
+ if (myRuleEnabled !== ruleEnabled && fetchRuleStatus != null && ruleId != null) {
+ fetchRuleStatus(ruleId);
+ if (myRuleEnabled !== ruleEnabled) {
+ setMyRuleEnabled(ruleEnabled ?? null);
+ }
+ }
+ }, [fetchRuleStatus, myRuleEnabled, ruleId, ruleEnabled, setMyRuleEnabled]);
+
+ useEffect(() => {
+ if (!isEqual(currentStatus, ruleStatus?.current_status)) {
+ setCurrentStatus(ruleStatus?.current_status ?? null);
+ }
+ }, [currentStatus, ruleStatus, setCurrentStatus]);
+
+ const handleRefresh = useCallback(() => {
+ if (fetchRuleStatus != null && ruleId != null) {
+ fetchRuleStatus(ruleId);
+ }
+ }, [fetchRuleStatus, ruleId]);
+
+ return (
+
+
+ {i18n.STATUS}
+ {':'}
+
+ {loading && (
+
+
+
+ )}
+ {!loading && (
+ <>
+
+
+ {currentStatus?.status ?? getEmptyTagValue()}
+
+
+ {currentStatus?.status_date != null && currentStatus?.status != null && (
+ <>
+
+ <>{i18n.STATUS_AT}>
+
+
+
+
+ >
+ )}
+
+
+
+ >
+ )}
+
+ );
+};
+
+export const RuleStatus = memo(RuleStatusComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/translations.ts
new file mode 100644
index 0000000000000..e03cc252ad729
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/translations.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const STATUS = i18n.translate('xpack.siem.detectionEngine.ruleStatus.statusDescription', {
+ defaultMessage: 'Status',
+});
+
+export const STATUS_AT = i18n.translate(
+ 'xpack.siem.detectionEngine.ruleStatus.statusAtDescription',
+ {
+ defaultMessage: 'at',
+ }
+);
+
+export const STATUS_DATE = i18n.translate(
+ 'xpack.siem.detectionEngine.ruleStatus.statusDateDescription',
+ {
+ defaultMessage: 'Status date',
+ }
+);
+
+export const REFRESH = i18n.translate('xpack.siem.detectionEngine.ruleStatus.refreshButton', {
+ defaultMessage: 'Refresh',
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx
index 9cb0323ed8987..09b7ecc9df982 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx
@@ -36,6 +36,7 @@ export interface RuleSwitchProps {
isDisabled?: boolean;
isLoading?: boolean;
optionLabel?: string;
+ onChange?: (enabled: boolean) => void;
}
/**
@@ -48,6 +49,7 @@ export const RuleSwitchComponent = ({
isLoading,
enabled,
optionLabel,
+ onChange,
}: RuleSwitchProps) => {
const [myIsLoading, setMyIsLoading] = useState(false);
const [myEnabled, setMyEnabled] = useState(enabled ?? false);
@@ -65,6 +67,9 @@ export const RuleSwitchComponent = ({
enabled: event.target.checked!,
});
setMyEnabled(updatedRules[0].enabled);
+ if (onChange != null) {
+ onChange(updatedRules[0].enabled);
+ }
} catch {
setMyIsLoading(false);
}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx
index 0ef104e6891df..3bde2087f26b1 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx
@@ -150,7 +150,13 @@ export const ScheduleItem = ({
/>
}
>
-
+
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts
index 328c4a0f96066..92aca1cecf9f3 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts
@@ -5,6 +5,7 @@
*/
import { AboutStepRule } from '../../types';
+import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/search_super_select/translations';
export const threatsDefault = [
{
@@ -25,7 +26,7 @@ export const stepAboutDefaultValue: AboutStepRule = {
tags: [],
timeline: {
id: null,
- title: null,
+ title: DEFAULT_TIMELINE_TITLE,
},
threats: threatsDefault,
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx
index 0e03a11776fb7..73c07673a82f4 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx
@@ -5,10 +5,11 @@
*/
import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { isEqual, get } from 'lodash/fp';
+import { isEqual } from 'lodash/fp';
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
+import { setFieldValue } from '../../helpers';
import { RuleStepProps, RuleStep, AboutStepRule } from '../../types';
import * as RuleI18n from '../../translations';
import { AddItem } from '../add_item_form';
@@ -71,14 +72,7 @@ const StepAboutRuleComponent: FC = ({
isNew: false,
};
setMyStepData(myDefaultValues);
- if (!isReadOnlyView) {
- Object.keys(schema).forEach(key => {
- const val = get(key, myDefaultValues);
- if (val != null) {
- form.setFieldValue(key, val);
- }
- });
- }
+ setFieldValue(form, schema, myDefaultValues);
}
}, [defaultValues]);
@@ -88,7 +82,7 @@ const StepAboutRuleComponent: FC = ({
}
}, [form]);
- return isReadOnlyView && myStepData != null ? (
+ return isReadOnlyView && myStepData.name != null ? (
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
index 6bdef4a69af1e..5409a5f161bba 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx
@@ -11,7 +11,7 @@ import {
EuiFlexItem,
EuiButton,
} from '@elastic/eui';
-import { isEmpty, isEqual, get } from 'lodash/fp';
+import { isEmpty, isEqual } from 'lodash/fp';
import React, { FC, memo, useCallback, useState, useEffect } from 'react';
import styled from 'styled-components';
@@ -19,6 +19,7 @@ import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/pu
import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules';
import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants';
import { useUiSetting$ } from '../../../../../lib/kibana';
+import { setFieldValue } from '../../helpers';
import * as RuleI18n from '../../translations';
import { DefineStepRule, RuleStep, RuleStepProps } from '../../types';
import { StepRuleDescription } from '../description_step';
@@ -121,14 +122,7 @@ const StepDefineRuleComponent: FC = ({
if (!isEqual(myDefaultValues, myStepData)) {
setMyStepData(myDefaultValues);
setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig));
- if (!isReadOnlyView) {
- Object.keys(schema).forEach(key => {
- const val = get(key, myDefaultValues);
- if (val != null) {
- form.setFieldValue(key, val);
- }
- });
- }
+ setFieldValue(form, schema, myDefaultValues);
}
}
}, [defaultValues, indicesConfig]);
@@ -152,7 +146,7 @@ const StepDefineRuleComponent: FC = ({
setOpenTimelineSearch(false);
}, []);
- return isReadOnlyView && myStepData != null ? (
+ return isReadOnlyView && myStepData?.queryBar != null ? (
= ({
@@ -67,14 +68,7 @@ const StepScheduleRuleComponent: FC = ({
isNew: false,
};
setMyStepData(myDefaultValues);
- if (!isReadOnlyView) {
- Object.keys(schema).forEach(key => {
- const val = get(key, myDefaultValues);
- if (val != null) {
- form.setFieldValue(key, val);
- }
- });
- }
+ setFieldValue(form, schema, myDefaultValues);
}
}, [defaultValues]);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx
index 4da17b88b9ad0..a951c1fab7cc8 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx
@@ -14,13 +14,14 @@ export const schema: FormSchema = {
label: i18n.translate(
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalLabel',
{
- defaultMessage: 'Rule run interval & look-back',
+ defaultMessage: 'Runs every',
}
),
helpText: i18n.translate(
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText',
{
- defaultMessage: 'How often and how far back this rule will search specified indices.',
+ defaultMessage:
+ 'Rules run periodically and detect signals within the specified time frame.',
}
),
},
@@ -28,15 +29,14 @@ export const schema: FormSchema = {
label: i18n.translate(
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackLabel',
{
- defaultMessage: 'Additional look-back',
+ defaultMessage: 'Additional look-back time',
}
),
labelAppend: OptionalFieldLabel,
helpText: i18n.translate(
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText',
{
- defaultMessage:
- 'Add more time to the look-back range in order to prevent potential gaps in signal reporting.',
+ defaultMessage: 'Adds time to the look-back period to prevent missed signals.',
}
),
},
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx
index feaaf4e85b2af..67bcc1af8150b 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx
@@ -9,13 +9,13 @@ import { i18n } from '@kbn/i18n';
export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate(
'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle',
{
- defaultMessage: 'Complete rule without activating',
+ defaultMessage: 'Create rule without activating it',
}
);
export const COMPLETE_WITH_ACTIVATING = i18n.translate(
'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle',
{
- defaultMessage: 'Complete rule & activate',
+ defaultMessage: 'Create & activate rule',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx
index e5656f5b081fb..cbc60015d9c87 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx
@@ -9,10 +9,11 @@ import React, { useCallback, useRef, useState } from 'react';
import { Redirect } from 'react-router-dom';
import styled from 'styled-components';
+import { usePersistRule } from '../../../../containers/detection_engine/rules';
import { HeaderPage } from '../../../../components/header_page';
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine';
import { WrapperPage } from '../../../../components/wrapper_page';
-import { usePersistRule } from '../../../../containers/detection_engine/rules';
+import { displaySuccessToast, useStateToaster } from '../../../../components/toasters';
import { SpyRoute } from '../../../../utils/route/spy_routes';
import { useUserInfo } from '../../components/user_info';
import { AccordionTitle } from '../components/accordion_title';
@@ -55,6 +56,7 @@ export const CreateRuleComponent = React.memo(() => {
canUserCRUD,
hasManageApiKey,
} = useUserInfo();
+ const [, dispatchToaster] = useStateToaster();
const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule);
const defineRuleRef = useRef(null);
const aboutRuleRef = useRef(null);
@@ -95,6 +97,7 @@ export const CreateRuleComponent = React.memo(() => {
const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item);
if ([0, 1].includes(stepRuleIdx)) {
if (isStepRuleInReadOnlyView[stepsRuleOrder[stepRuleIdx + 1]]) {
+ setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]);
setIsStepRuleInEditView({
...isStepRuleInReadOnlyView,
[step]: true,
@@ -203,12 +206,15 @@ export const CreateRuleComponent = React.memo(() => {
async (id: RuleStep) => {
const activeForm = await stepsForm.current[openAccordionId]?.submit();
if (activeForm != null && activeForm?.isValid) {
+ stepsData.current[openAccordionId] = {
+ ...stepsData.current[openAccordionId],
+ data: activeForm.data,
+ isValid: activeForm.isValid,
+ };
setOpenAccordionId(id);
- openCloseAccordion(openAccordionId);
-
setIsStepRuleInEditView({
...isStepRuleInReadOnlyView,
- [openAccordionId]: openAccordionId === RuleStep.scheduleRule ? false : true,
+ [openAccordionId]: true,
[id]: false,
});
}
@@ -217,6 +223,8 @@ export const CreateRuleComponent = React.memo(() => {
);
if (isSaved) {
+ const ruleName = (stepsData.current[RuleStep.aboutRule].data as AboutStepRule).name;
+ displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster);
return ;
}
@@ -224,7 +232,7 @@ export const CreateRuleComponent = React.memo(() => {
<>
{
{
{
+ i18n.translate('xpack.siem.detectionEngine.rules.create.successfullyCreatedRuleTitle', {
+ values: { ruleName },
+ defaultMessage: '{ruleName} was created',
+ });
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx
index 3b49cd30c9aab..f660c1763d5e0 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx
@@ -15,8 +15,7 @@ import {
} from '@elastic/eui';
import React, { memo } from 'react';
-import { useRuleStatus } from '../../../../containers/detection_engine/rules/use_rule_status';
-import { RuleStatus } from '../../../../containers/detection_engine/rules';
+import { useRuleStatus, RuleInfoStatus } from '../../../../containers/detection_engine/rules';
import { HeaderSection } from '../../../../components/header_section';
import * as i18n from './translations';
import { FormattedDate } from '../../../../components/formatted_date';
@@ -35,7 +34,7 @@ const FailureHistoryComponent: React.FC = ({ id }) => {
);
}
- const columns: Array> = [
+ const columns: Array> = [
{
name: i18n.COLUMN_STATUS_TYPE,
render: () => {i18n.TYPE_FAILED} ,
@@ -65,7 +64,9 @@ const FailureHistoryComponent: React.FC = ({ id }) => {
rs.last_failure_at != null) : []}
+ items={
+ ruleStatus != null ? ruleStatus?.failures.filter(rs => rs.last_failure_at != null) : []
+ }
sorting={{ sort: { field: 'status_date', direction: 'desc' } }}
/>
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx
index 099006a34920c..a23c681a5aab2 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx
@@ -10,8 +10,8 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
- EuiHealth,
EuiTab,
+ EuiTabs,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { memo, useCallback, useMemo, useState } from 'react';
@@ -60,10 +60,10 @@ import { inputsSelectors } from '../../../../store/inputs';
import { State } from '../../../../store';
import { InputsRange } from '../../../../store/inputs/model';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../../store/inputs/actions';
-import { getEmptyTagValue } from '../../../../components/empty_value';
+import { RuleActionsOverflow } from '../components/rule_actions_overflow';
import { RuleStatusFailedCallOut } from './status_failed_callout';
import { FailureHistory } from './failure_history';
-import { RuleActionsOverflow } from '../components/rule_actions_overflow';
+import { RuleStatus } from '../components/rule_status';
interface ReduxProps {
filters: esFilters.Filter[];
@@ -78,14 +78,19 @@ export interface DispatchProps {
}>;
}
+enum RuleDetailTabs {
+ signals = 'signals',
+ failures = 'failures',
+}
+
const ruleDetailTabs = [
{
- id: 'signal',
+ id: RuleDetailTabs.signals,
name: detectionI18n.SIGNAL,
disabled: false,
},
{
- id: 'failure',
+ id: RuleDetailTabs.failures,
name: i18n.FAILURE_HISTORY_TAB,
disabled: false,
},
@@ -106,7 +111,9 @@ const RuleDetailsComponent = memo(
} = useUserInfo();
const { ruleId } = useParams();
const [isLoading, rule] = useRule(ruleId);
- const [ruleDetailTab, setRuleDetailTab] = useState('signal');
+ // This is used to re-trigger api rule status when user de/activate rule
+ const [ruleEnabled, setRuleEnabled] = useState(null);
+ const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals);
const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({
rule,
detailsView: true,
@@ -175,34 +182,28 @@ const RuleDetailsComponent = memo(
filters,
]);
- const statusColor =
- rule?.status == null
- ? 'subdued'
- : rule?.status === 'succeeded'
- ? 'success'
- : rule?.status === 'failed'
- ? 'danger'
- : rule?.status === 'executing'
- ? 'warning'
- : 'subdued';
-
const tabs = useMemo(
- () =>
- ruleDetailTabs.map(tab => (
- setRuleDetailTab(tab.id)}
- isSelected={tab.id === ruleDetailTab}
- disabled={tab.disabled}
- key={tab.name}
- >
- {tab.name}
-
- )),
+ () => (
+
+ {ruleDetailTabs.map(tab => (
+ setRuleDetailTab(tab.id)}
+ isSelected={tab.id === ruleDetailTab}
+ disabled={tab.disabled}
+ key={tab.id}
+ >
+ {tab.name}
+
+ ))}
+
+ ),
[ruleDetailTabs, ruleDetailTab, setRuleDetailTab]
);
const ruleError = useMemo(
() =>
- rule?.status === 'failed' && ruleDetailTab === 'signal' && rule?.last_failure_at != null ? (
+ rule?.status === 'failed' &&
+ ruleDetailTab === RuleDetailTabs.signals &&
+ rule?.last_failure_at != null ? (
(
[setAbsoluteRangeDatePicker]
);
+ const handleOnChangeEnabledRule = useCallback(
+ (enabled: boolean) => {
+ if (ruleEnabled == null || enabled !== ruleEnabled) {
+ setRuleEnabled(enabled);
+ }
+ },
+ [ruleEnabled, setRuleEnabled]
+ );
+
return (
<>
{hasIndexWrite != null && !hasIndexWrite && }
@@ -238,7 +248,6 @@ const RuleDetailsComponent = memo(
href: `#${DETECTION_ENGINE_PAGE_NAME}/rules`,
text: i18n.BACK_TO_RULES,
}}
- badgeOptions={{ text: i18n.EXPERIMENTAL }}
border
subtitle={subTitle}
subtitle2={[
@@ -251,34 +260,7 @@ const RuleDetailsComponent = memo(
>,
]
: []),
-
-
- {i18n.STATUS}
- {':'}
-
-
-
- {rule?.status ?? getEmptyTagValue()}
-
-
- {rule?.status_date && (
- <>
-
- <>{i18n.STATUS_AT}>
-
-
-
-
- >
- )}
- ,
+ ,
]}
title={title}
>
@@ -289,6 +271,7 @@ const RuleDetailsComponent = memo(
isDisabled={userHasNoPermissions}
enabled={rule?.enabled ?? false}
optionLabel={i18n.ACTIVATE_RULE}
+ onChange={handleOnChangeEnabledRule}
/>
@@ -316,7 +299,7 @@ const RuleDetailsComponent = memo(
{ruleError}
{tabs}
- {ruleDetailTab === 'signal' && (
+ {ruleDetailTab === RuleDetailTabs.signals && (
<>
@@ -381,7 +364,9 @@ const RuleDetailsComponent = memo(
)}
>
)}
- {ruleDetailTab === 'failure' && }
+ {ruleDetailTab === RuleDetailTabs.failures && (
+
+ )}
)}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts
index 9976abc8412bf..46b6984ab323f 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts
@@ -13,7 +13,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.ruleDetails
export const BACK_TO_RULES = i18n.translate(
'xpack.siem.detectionEngine.ruleDetails.backToRulesDescription',
{
- defaultMessage: 'Back to rules',
+ defaultMessage: 'Back to signal detection rules',
}
);
@@ -35,24 +35,6 @@ export const UNKNOWN = i18n.translate('xpack.siem.detectionEngine.ruleDetails.un
defaultMessage: 'Unknown',
});
-export const STATUS = i18n.translate('xpack.siem.detectionEngine.ruleDetails.statusDescription', {
- defaultMessage: 'Status',
-});
-
-export const STATUS_AT = i18n.translate(
- 'xpack.siem.detectionEngine.ruleDetails.statusAtDescription',
- {
- defaultMessage: 'at',
- }
-);
-
-export const STATUS_DATE = i18n.translate(
- 'xpack.siem.detectionEngine.ruleDetails.statusDateDescription',
- {
- defaultMessage: 'Status date',
- }
-);
-
export const ERROR_CALLOUT_TITLE = i18n.translate(
'xpack.siem.detectionEngine.ruleDetails.errorCalloutTitle',
{
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
index e583461f52439..9b7833afd7f4d 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx
@@ -17,11 +17,12 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Redirect, useParams } from 'react-router-dom';
+import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules';
import { HeaderPage } from '../../../../components/header_page';
import { WrapperPage } from '../../../../components/wrapper_page';
-import { SpyRoute } from '../../../../utils/route/spy_routes';
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine';
-import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules';
+import { displaySuccessToast, useStateToaster } from '../../../../components/toasters';
+import { SpyRoute } from '../../../../utils/route/spy_routes';
import { useUserInfo } from '../../components/user_info';
import { FormHook, FormData } from '../components/shared_imports';
import { StepPanel } from '../components/step_panel';
@@ -48,6 +49,7 @@ interface ScheduleStepRuleForm extends StepRuleForm {
}
export const EditRuleComponent = memo(() => {
+ const [, dispatchToaster] = useStateToaster();
const {
loading: initLoading,
isSignalIndexExists,
@@ -271,6 +273,7 @@ export const EditRuleComponent = memo(() => {
}, []);
if (isSaved || (rule != null && rule.immutable)) {
+ displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster);
return ;
}
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts
index b81ae58e565f0..f6e56dca19c21 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts
@@ -28,3 +28,9 @@ export const SORRY_ERRORS = i18n.translate(
export const BACK_TO = i18n.translate('xpack.siem.detectionEngine.editRule.backToDescription', {
defaultMessage: 'Back to',
});
+
+export const SUCCESSFULLY_SAVED_RULE = (ruleName: string) =>
+ i18n.translate('xpack.siem.detectionEngine.rules.update.successfullySavedRuleTitle', {
+ values: { ruleName },
+ defaultMessage: '{ruleName} was saved',
+ });
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
index cc0882dd7e426..cfe6cb8da1cb0 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx
@@ -4,11 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { pick } from 'lodash/fp';
+import { get, pick } from 'lodash/fp';
import { useLocation } from 'react-router-dom';
import { esFilters } from '../../../../../../../../src/plugins/data/public';
import { Rule } from '../../../containers/detection_engine/rules';
+import { FormData, FormHook, FormSchema } from './components/shared_imports';
import { AboutStepRule, DefineStepRule, IMitreEnterpriseAttack, ScheduleStepRule } from './types';
interface GetStepsData {
@@ -67,3 +68,15 @@ export const getStepsData = ({
};
export const useQuery = () => new URLSearchParams(useLocation().search);
+
+export const setFieldValue = (
+ form: FormHook,
+ schema: FormSchema,
+ defaultValues: unknown
+) =>
+ Object.keys(schema).forEach(key => {
+ const val = get(key, defaultValues);
+ if (val != null) {
+ form.setFieldValue(key, val);
+ }
+ });
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts
index aeeef925d60e5..e1257007d44a3 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts
@@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
export const BACK_TO_DETECTION_ENGINE = i18n.translate(
'xpack.siem.detectionEngine.rules.backOptionsHeader',
{
- defaultMessage: 'Back to detection engine',
+ defaultMessage: 'Back to detections',
}
);
@@ -18,11 +18,11 @@ export const IMPORT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.impo
});
export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.addNewRuleTitle', {
- defaultMessage: 'Add new rule',
+ defaultMessage: 'Create new rule',
});
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', {
- defaultMessage: 'Rules',
+ defaultMessage: 'Signal detection rules',
});
export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', {
@@ -32,7 +32,7 @@ export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules
export const BATCH_ACTIONS = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.batchActionsTitle',
{
- defaultMessage: 'Batch actions',
+ defaultMessage: 'Bulk actions',
}
);
@@ -75,10 +75,10 @@ export const BATCH_ACTION_EXPORT_SELECTED = i18n.translate(
}
);
-export const BATCH_ACTION_EDIT_INDEX_PATTERNS = i18n.translate(
- 'xpack.siem.detectionEngine.rules.allRules.batchActions.editIndexPatternsTitle',
+export const BATCH_ACTION_DUPLICATE_SELECTED = i18n.translate(
+ 'xpack.siem.detectionEngine.rules.allRules.batchActions.duplicateSelectedTitle',
{
- defaultMessage: 'Edit selected index patterns…',
+ defaultMessage: 'Duplicate selected…',
}
);
@@ -243,7 +243,7 @@ export const COLUMN_TAGS = i18n.translate(
export const COLUMN_ACTIVATE = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.columns.activateTitle',
{
- defaultMessage: 'Activate',
+ defaultMessage: 'Activated',
}
);
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts
index e5f830d3a49b0..ab785a8ad2c6d 100644
--- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts
@@ -6,8 +6,8 @@
import { i18n } from '@kbn/i18n';
-export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', {
- defaultMessage: 'Detection engine',
+export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.detectionsPageTitle', {
+ defaultMessage: 'Detections',
});
export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', {
@@ -22,8 +22,12 @@ export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', {
defaultMessage: 'Signals',
});
+export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', {
+ defaultMessage: 'Third-party alerts',
+});
+
export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', {
- defaultMessage: 'Manage rules',
+ defaultMessage: 'Manage signal detection rules',
});
export const PANEL_SUBTITLE_SHOWING = i18n.translate(
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts
new file mode 100644
index 0000000000000..d529d99ad3ad4
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export enum DetectionEngineTab {
+ signals = 'signals',
+ alerts = 'alerts',
+}
diff --git a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx
index 220f8a958aa43..c0e959c5e97fa 100644
--- a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx
@@ -36,12 +36,12 @@ export const navTabs: SiemNavTab = {
disabled: false,
urlKey: 'network',
},
- [SiemPageName.detectionEngine]: {
- id: SiemPageName.detectionEngine,
+ [SiemPageName.detections]: {
+ id: SiemPageName.detections,
name: i18n.DETECTION_ENGINE,
href: getDetectionEngineUrl(),
disabled: false,
- urlKey: 'detection-engine',
+ urlKey: 'detections',
},
[SiemPageName.timelines]: {
id: SiemPageName.timelines,
diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx
index a545be447796d..b5bfdbde306ca 100644
--- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx
@@ -105,7 +105,7 @@ export const HomePage: React.FC = () => (
)}
/>
(
)}
diff --git a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts
index b87ea1c17a117..80800a3bd4198 100644
--- a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts
@@ -19,7 +19,7 @@ export const NETWORK = i18n.translate('xpack.siem.navigation.network', {
});
export const DETECTION_ENGINE = i18n.translate('xpack.siem.navigation.detectionEngine', {
- defaultMessage: 'Detection engine',
+ defaultMessage: 'Detections',
});
export const TIMELINES = i18n.translate('xpack.siem.navigation.timelines', {
diff --git a/x-pack/legacy/plugins/siem/public/pages/home/types.ts b/x-pack/legacy/plugins/siem/public/pages/home/types.ts
index 101c6a69b08d1..678de6dbcc128 100644
--- a/x-pack/legacy/plugins/siem/public/pages/home/types.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/home/types.ts
@@ -10,7 +10,7 @@ export enum SiemPageName {
overview = 'overview',
hosts = 'hosts',
network = 'network',
- detectionEngine = 'detection-engine',
+ detections = 'detections',
timelines = 'timelines',
}
@@ -18,7 +18,7 @@ export type SiemNavTabKey =
| SiemPageName.overview
| SiemPageName.hosts
| SiemPageName.network
- | SiemPageName.detectionEngine
+ | SiemPageName.detections
| SiemPageName.timelines;
export type SiemNavTab = Record;
diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx
index 0bb9563296316..0109eeef91463 100644
--- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx
@@ -25,7 +25,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable);
const ID = 'authenticationsOverTimeQuery';
const authStackByOptions: MatrixHistogramOption[] = [
{
- text: i18n.NAVIGATION_AUTHENTICATIONS_STACK_BY_EVENT_TYPE,
+ text: 'event.type',
value: 'event.type',
},
];
@@ -71,7 +71,6 @@ export const AuthenticationsQueryTabBody = ({
isAuthenticationsHistogram={true}
dataKey="AuthenticationsHistogram"
defaultStackByOption={authStackByOptions[0]}
- deleteQuery={deleteQuery}
endDate={endDate}
errorMessage={i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA}
filterQuery={filterQuery}
diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx
index a07cbc8484a1b..85bca90cc8e04 100644
--- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx
@@ -20,11 +20,11 @@ const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
export const eventsStackByOptions: MatrixHistogramOption[] = [
{
- text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION,
+ text: 'event.action',
value: 'event.action',
},
{
- text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET,
+ text: 'event.dataset',
value: 'event.dataset',
},
];
@@ -50,7 +50,6 @@ export const EventsQueryTabBody = ({
void;
filters?: esFilters.Filter[];
from: number;
+ hideHeaderChildren?: boolean;
indexPattern: IIndexPattern;
query?: Query;
setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker;
@@ -60,14 +60,24 @@ export const AlertsByCategory = React.memo(
deleteQuery,
filters = NO_FILTERS,
from,
+ hideHeaderChildren = false,
indexPattern,
query = DEFAULT_QUERY,
setAbsoluteRangeDatePicker,
setQuery,
to,
}) => {
+ useEffect(() => {
+ return () => {
+ if (deleteQuery) {
+ deleteQuery({ id: ID });
+ }
+ };
+ }, []);
+
const kibana = useKibana();
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
+
const updateDateRangeCallback = useCallback(
(min: number, max: number) => {
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
@@ -76,17 +86,11 @@ export const AlertsByCategory = React.memo(
);
const alertsCountViewAlertsButton = useMemo(
() => (
-
- {i18n.VIEW_ALERTS}
-
+ {i18n.VIEW_ALERTS}
),
[]
);
- const getTitle = useCallback(
- (option: MatrixHistogramOption) => i18n.ALERTS_COUNT_BY(option.text),
- []
- );
const getSubtitle = useCallback(
(totalCount: number) =>
`${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`,
@@ -96,7 +100,6 @@ export const AlertsByCategory = React.memo(
return (
(
queries: [query],
filters,
})}
- headerChildren={alertsCountViewAlertsButton}
+ headerChildren={hideHeaderChildren ? null : alertsCountViewAlertsButton}
id={ID}
isAlertsHistogram={true}
legendPosition={'right'}
@@ -115,7 +118,7 @@ export const AlertsByCategory = React.memo(
sourceId="default"
stackByOptions={alertsStackByOptions}
startDate={from}
- title={getTitle}
+ title={i18n.ALERTS_GRAPH_TITLE}
subtitle={getSubtitle}
type={HostsType.page}
updateDateRange={updateDateRangeCallback}
diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx
index 52084c4bfc280..191b4a2592695 100644
--- a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx
@@ -6,7 +6,7 @@
import { EuiButton } from '@elastic/eui';
import numeral from '@elastic/numeral';
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useEffect, useMemo } from 'react';
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
import styled from 'styled-components';
@@ -66,8 +66,17 @@ export const EventsByDataset = React.memo(
setQuery,
to,
}) => {
+ useEffect(() => {
+ return () => {
+ if (deleteQuery) {
+ deleteQuery({ id: ID });
+ }
+ };
+ }, []);
+
const kibana = useKibana();
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
+
const updateDateRangeCallback = useCallback(
(min: number, max: number) => {
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
@@ -96,7 +105,6 @@ export const EventsByDataset = React.memo(
return (
defaultMessage: 'Alerts count by {groupByField}',
});
+export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.overview.alertsGraphTitle', {
+ defaultMessage: 'Alert detection frequency',
+});
+
export const EVENTS_COUNT_BY = (groupByField: string) =>
i18n.translate('xpack.siem.overview.eventsCountByTitle', {
values: { groupByField },
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts
index 30a8d9d935128..a84fcb64d9ff7 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts
@@ -18,9 +18,9 @@ import {
DETECTION_ENGINE_PREPACKAGED_URL,
} from '../../../../../common/constants';
import { RuleAlertType, IRuleSavedAttributesSavedObjectAttributes } from '../../rules/types';
-import { RuleAlertParamsRest } from '../../types';
+import { RuleAlertParamsRest, PrepackagedRules } from '../../types';
-export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({
+export const mockPrepackagedRule = (): PrepackagedRules => ({
rule_id: 'rule-1',
description: 'Detecting root and admin users',
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
@@ -51,8 +51,6 @@ export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({
false_positives: [],
saved_id: 'some-id',
max_signals: 100,
- created_at: '2019-12-13T16:40:33.400Z',
- updated_at: '2019-12-13T16:40:33.400Z',
timeline_id: 'timeline-id',
timeline_title: 'timeline-title',
});
@@ -393,7 +391,7 @@ export const getMockPrivileges = () => ({
},
},
application: {},
- isAuthenticated: false,
+ is_authenticated: false,
});
export const getFindResultStatus = (): SavedObjectsFindResponse => ({
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts
index 240200af8b585..803d9d645aadb 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts
@@ -30,7 +30,7 @@ export const createReadPrivilegesRulesRoute = (server: ServerFacade): Hapi.Serve
const index = getIndex(request, server);
const permissions = await readPrivileges(callWithRequest, index);
return merge(permissions, {
- isAuthenticated: request?.auth?.isAuthenticated ?? false,
+ is_authenticated: request?.auth?.isAuthenticated ?? false,
});
} catch (err) {
return transformError(err);
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts
index 5ceecdb058e5f..3c9cad8dc4d4b 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts
@@ -36,8 +36,10 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR
const actionsClient = isFunction(request.getActionsClient)
? request.getActionsClient()
: null;
-
- if (!alertsClient || !actionsClient) {
+ const savedObjectsClient = isFunction(request.getSavedObjectsClient)
+ ? request.getSavedObjectsClient()
+ : null;
+ if (!alertsClient || !actionsClient || !savedObjectsClient) {
return headers.response().code(404);
}
@@ -59,7 +61,13 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR
}
}
await installPrepackagedRules(alertsClient, actionsClient, rulesToInstall, spaceIndex);
- await updatePrepackagedRules(alertsClient, actionsClient, rulesToUpdate, spaceIndex);
+ await updatePrepackagedRules(
+ alertsClient,
+ actionsClient,
+ savedObjectsClient,
+ rulesToUpdate,
+ spaceIndex
+ );
return {
rules_installed: rulesToInstall.length,
rules_updated: rulesToUpdate.length,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
index 9c18f9040008c..00a1d2eb980ec 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
@@ -55,7 +55,6 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
enabled,
false_positives: falsePositives,
from,
- immutable,
query,
language,
output_index: outputIndex,
@@ -109,7 +108,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
enabled,
falsePositives,
from,
- immutable,
+ immutable: false,
query,
language,
outputIndex: finalIndex,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts
index aa535d325f4b9..23acd12d341ed 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts
@@ -39,7 +39,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute =
enabled,
false_positives: falsePositives,
from,
- immutable,
query,
language,
output_index: outputIndex,
@@ -96,7 +95,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute =
enabled,
falsePositives,
from,
- immutable,
+ immutable: false,
query,
language,
outputIndex: finalIndex,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts
index e56c440f5a415..545c2e488b1c8 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts
@@ -13,10 +13,16 @@ import { findRulesStatusesSchema } from '../schemas/find_rules_statuses_schema';
import {
FindRulesStatusesRequest,
IRuleSavedAttributesSavedObjectAttributes,
+ RuleStatusResponse,
+ IRuleStatusAttributes,
} from '../../rules/types';
import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings';
-const convertToSnakeCase = (obj: IRuleSavedAttributesSavedObjectAttributes) => {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const convertToSnakeCase = >(obj: T): Partial | null => {
+ if (!obj) {
+ return null;
+ }
return Object.keys(obj).reduce((acc, item) => {
const newKey = snakeCase(item);
return { ...acc, [newKey]: obj[item] };
@@ -53,7 +59,7 @@ export const createFindRulesStatusRoute: Hapi.ServerRoute = {
"anotherAlertId": ...
}
*/
- const statuses = await query.ids.reduce(async (acc, id) => {
+ const statuses = await query.ids.reduce>(async (acc, id) => {
const lastFiveErrorsForId = await savedObjectsClient.find<
IRuleSavedAttributesSavedObjectAttributes
>({
@@ -64,15 +70,21 @@ export const createFindRulesStatusRoute: Hapi.ServerRoute = {
search: id,
searchFields: ['alertId'],
});
- const toDisplay =
- lastFiveErrorsForId.saved_objects.length <= 5
- ? lastFiveErrorsForId.saved_objects
- : lastFiveErrorsForId.saved_objects.slice(1);
+ const accumulated = await acc;
+ const currentStatus = convertToSnakeCase(
+ lastFiveErrorsForId.saved_objects[0]?.attributes
+ );
+ const failures = lastFiveErrorsForId.saved_objects
+ .slice(1)
+ .map(errorItem => convertToSnakeCase(errorItem.attributes));
return {
- ...(await acc),
- [id]: toDisplay.map(errorItem => convertToSnakeCase(errorItem.attributes)),
+ ...accumulated,
+ [id]: {
+ current_status: currentStatus,
+ failures,
+ },
};
- }, {});
+ }, Promise.resolve({}));
return statuses;
},
};
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts
index e312b5fc6bb10..6efaa1fea60d0 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts
@@ -52,8 +52,10 @@ export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute =
const actionsClient = isFunction(request.getActionsClient)
? request.getActionsClient()
: null;
-
- if (!alertsClient || !actionsClient) {
+ const savedObjectsClient = isFunction(request.getSavedObjectsClient)
+ ? request.getSavedObjectsClient()
+ : null;
+ if (!alertsClient || !actionsClient || !savedObjectsClient) {
return headers.response().code(404);
}
const { filename } = request.payload.file.hapi;
@@ -161,6 +163,7 @@ export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute =
const updatedRule = await updateRules({
alertsClient,
actionsClient,
+ savedObjectsClient,
description,
enabled,
falsePositives,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
index 180a75bdaaeea..e0d2672cf356a 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
@@ -7,12 +7,16 @@
import Hapi from 'hapi';
import { isFunction } from 'lodash/fp';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
-import { BulkUpdateRulesRequest } from '../../rules/types';
+import {
+ BulkUpdateRulesRequest,
+ IRuleSavedAttributesSavedObjectAttributes,
+} from '../../rules/types';
import { ServerFacade } from '../../../../types';
import { transformOrBulkError, getIdBulkError } from './utils';
import { transformBulkError } from '../utils';
import { updateRulesBulkSchema } from '../schemas/update_rules_bulk_schema';
import { updateRules } from '../../rules/update_rules';
+import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings';
export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRoute => {
return {
@@ -32,8 +36,10 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
const actionsClient = isFunction(request.getActionsClient)
? request.getActionsClient()
: null;
-
- if (!alertsClient || !actionsClient) {
+ const savedObjectsClient = isFunction(request.getSavedObjectsClient)
+ ? request.getSavedObjectsClient()
+ : null;
+ if (!alertsClient || !actionsClient || !savedObjectsClient) {
return headers.response().code(404);
}
@@ -44,7 +50,6 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
enabled,
false_positives: falsePositives,
from,
- immutable,
query,
language,
output_index: outputIndex,
@@ -77,11 +82,11 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
enabled,
falsePositives,
from,
- immutable,
query,
language,
outputIndex,
savedId,
+ savedObjectsClient,
timelineId,
timelineTitle,
meta,
@@ -102,7 +107,17 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou
version,
});
if (rule != null) {
- return transformOrBulkError(rule.id, rule);
+ const ruleStatuses = await savedObjectsClient.find<
+ IRuleSavedAttributesSavedObjectAttributes
+ >({
+ type: ruleStatusSavedObjectType,
+ perPage: 1,
+ sortField: 'statusDate',
+ sortOrder: 'desc',
+ search: rule.id,
+ searchFields: ['alertId'],
+ });
+ return transformOrBulkError(rule.id, rule, ruleStatuses.saved_objects[0]);
} else {
return getIdBulkError({ id, ruleId });
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts
index 147f3f9afa549..49c9304ae2d25 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts
@@ -33,7 +33,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = {
enabled,
false_positives: falsePositives,
from,
- immutable,
query,
language,
output_index: outputIndex,
@@ -75,11 +74,11 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = {
enabled,
falsePositives,
from,
- immutable,
query,
language,
outputIndex,
savedId,
+ savedObjectsClient,
timelineId,
timelineTitle,
meta,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts
index 1993948808ef4..abdd5a0c7b508 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts
@@ -4,20 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { UpdateRuleAlertParamsRest } from '../../rules/types';
-import { ThreatParams, RuleAlertParamsRest } from '../../types';
+import { ThreatParams, PrepackagedRules } from '../../types';
import { addPrepackagedRulesSchema } from './add_prepackaged_rules_schema';
describe('add prepackaged rules schema', () => {
test('empty objects do not validate', () => {
- expect(
- addPrepackagedRulesSchema.validate>({}).error
- ).toBeTruthy();
+ expect(addPrepackagedRulesSchema.validate>({}).error).toBeTruthy();
});
test('made up values do not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
madeUp: 'hi',
}).error
).toBeTruthy();
@@ -25,7 +22,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
}).error
).toBeTruthy();
@@ -33,7 +30,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
}).error
@@ -42,7 +39,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -52,7 +49,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -63,7 +60,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, name] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -75,7 +72,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, name, severity] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -88,7 +85,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, name, severity, type] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -102,7 +99,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -117,7 +114,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -133,7 +130,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, name, severity, type, query, index, interval, version] does validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -152,7 +149,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -170,7 +167,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, version] does validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -190,7 +187,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does not validate because output_index is not allowed', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
output_index: '.siem-signals',
risk_score: 50,
@@ -211,7 +208,7 @@ describe('add prepackaged rules schema', () => {
test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
@@ -229,7 +226,7 @@ describe('add prepackaged rules schema', () => {
test('You can send in an empty array to threats', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -251,7 +248,7 @@ describe('add prepackaged rules schema', () => {
});
test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version, threats] does validate', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -286,7 +283,7 @@ describe('add prepackaged rules schema', () => {
test('allows references to be sent as valid', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -307,7 +304,7 @@ describe('add prepackaged rules schema', () => {
test('defaults references to an array', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -327,7 +324,7 @@ describe('add prepackaged rules schema', () => {
test('defaults immutable to true', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -347,7 +344,7 @@ describe('add prepackaged rules schema', () => {
test('immutable cannot be false', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -368,7 +365,7 @@ describe('add prepackaged rules schema', () => {
test('immutable can be true', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -389,7 +386,7 @@ describe('add prepackaged rules schema', () => {
test('defaults enabled to false', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -409,7 +406,7 @@ describe('add prepackaged rules schema', () => {
test('rule_id is required', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
risk_score: 50,
description: 'some description',
from: 'now-5m',
@@ -429,7 +426,7 @@ describe('add prepackaged rules schema', () => {
test('references cannot be numbers', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial> & { references: number[] }
+ Partial> & { references: number[] }
>({
rule_id: 'rule-1',
risk_score: 50,
@@ -454,7 +451,7 @@ describe('add prepackaged rules schema', () => {
test('indexes cannot be numbers', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial> & { index: number[] }
+ Partial> & { index: number[] }
>({
rule_id: 'rule-1',
risk_score: 50,
@@ -477,7 +474,7 @@ describe('add prepackaged rules schema', () => {
test('defaults interval to 5 min', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -494,7 +491,7 @@ describe('add prepackaged rules schema', () => {
test('defaults max signals to 100', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -512,7 +509,7 @@ describe('add prepackaged rules schema', () => {
test('saved_id is required when type is saved_query and will not validate without out', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -530,7 +527,7 @@ describe('add prepackaged rules schema', () => {
test('saved_id is required when type is saved_query and validates with it', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -549,7 +546,7 @@ describe('add prepackaged rules schema', () => {
test('saved_query type can have filters with it', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -570,7 +567,7 @@ describe('add prepackaged rules schema', () => {
test('filters cannot be a string', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial & { filters: string }>
+ Partial & { filters: string }>
>({
rule_id: 'rule-1',
risk_score: 50,
@@ -591,7 +588,7 @@ describe('add prepackaged rules schema', () => {
test('language validates with kuery', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -612,7 +609,7 @@ describe('add prepackaged rules schema', () => {
test('language validates with lucene', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -633,7 +630,7 @@ describe('add prepackaged rules schema', () => {
test('language does not validate with something made up', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -654,7 +651,7 @@ describe('add prepackaged rules schema', () => {
test('max_signals cannot be negative', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -676,7 +673,7 @@ describe('add prepackaged rules schema', () => {
test('max_signals cannot be zero', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -698,7 +695,7 @@ describe('add prepackaged rules schema', () => {
test('max_signals can be 1', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -720,7 +717,7 @@ describe('add prepackaged rules schema', () => {
test('You can optionally send in an array of tags', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -744,7 +741,7 @@ describe('add prepackaged rules schema', () => {
test('You cannot send in an array of tags that are numbers', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial> & { tags: number[] }
+ Partial> & { tags: number[] }
>({
rule_id: 'rule-1',
risk_score: 50,
@@ -771,7 +768,7 @@ describe('add prepackaged rules schema', () => {
test('You cannot send in an array of threats that are missing "framework"', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial> & {
+ Partial> & {
threats: Array>>;
}
>({
@@ -815,7 +812,7 @@ describe('add prepackaged rules schema', () => {
test('You cannot send in an array of threats that are missing "tactic"', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial> & {
+ Partial> & {
threats: Array>>;
}
>({
@@ -855,7 +852,7 @@ describe('add prepackaged rules schema', () => {
test('You cannot send in an array of threats that are missing "techniques"', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial> & {
+ Partial> & {
threats: Array>>;
}
>({
@@ -892,7 +889,7 @@ describe('add prepackaged rules schema', () => {
test('You can optionally send in an array of false positives', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -916,7 +913,7 @@ describe('add prepackaged rules schema', () => {
test('You cannot send in an array of false positives that are numbers', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial> & { false_positives: number[] }
+ Partial> & { false_positives: number[] }
>({
rule_id: 'rule-1',
risk_score: 50,
@@ -942,7 +939,7 @@ describe('add prepackaged rules schema', () => {
test('You can optionally set the immutable to be true', () => {
expect(
- addPrepackagedRulesSchema.validate>({
+ addPrepackagedRulesSchema.validate>({
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
@@ -966,7 +963,7 @@ describe('add prepackaged rules schema', () => {
test('You cannot set the immutable to be a number', () => {
expect(
addPrepackagedRulesSchema.validate<
- Partial