From 4701edfbab0dd63b083d7080f5f53d496455b1a0 Mon Sep 17 00:00:00 2001
From: christineweng
Date: Tue, 15 Jul 2025 16:59:58 -0500
Subject: [PATCH 01/12] managed data view
---
.../kbn-content-management-utils/src/types.ts | 4 +
.../logs/utils/get_all_logs_data_view_spec.ts | 1 +
.../public/components/data_view_editor.tsx | 4 +
.../data_view_editor_flyout_content.tsx | 32 ++++--
.../data_view_flyout_content_container.tsx | 4 +
.../public/components/footer/footer.tsx | 14 ++-
.../data_view_editor/public/open_editor.tsx | 4 +
.../shared/data_view_editor/public/types.ts | 8 ++
.../common/content_management/v1/types.ts | 1 +
.../common/data_views/abstract_data_views.ts | 7 ++
.../common/data_views/data_views.ts | 10 +-
.../plugins/shared/data_views/common/types.ts | 7 +-
.../schema/v1/cm_services.ts | 1 +
.../public/create_data_view.ts | 1 +
.../sidebar/discover_sidebar_responsive.tsx | 5 +-
.../components/top_nav/discover_topnav.tsx | 13 +--
.../main/state_management/redux/hooks.tsx | 12 +-
.../dataview_picker/change_dataview.test.tsx | 18 ++-
.../dataview_picker/change_dataview.tsx | 38 ++++--
.../dataview_picker/data_view_picker.tsx | 6 -
.../public/logs_explorer_url_schema.ts | 1 +
.../components/data_view_picker/index.tsx | 5 +-
.../hooks/use_managed_data_views.test.ts | 108 ------------------
.../hooks/use_managed_data_views.ts | 35 ------
.../hooks/use_saved_data_views.test.ts | 23 ++--
.../hooks/use_saved_data_views.ts | 17 ++-
.../utils/create_explore_data_view.ts | 1 +
.../containers/create_sourcerer_data_view.ts | 2 +
28 files changed, 162 insertions(+), 220 deletions(-)
delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts
delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.ts
diff --git a/src/platform/packages/shared/kbn-content-management-utils/src/types.ts b/src/platform/packages/shared/kbn-content-management-utils/src/types.ts
index 3a6c286a404aa..2d76a737d4069 100644
--- a/src/platform/packages/shared/kbn-content-management-utils/src/types.ts
+++ b/src/platform/packages/shared/kbn-content-management-utils/src/types.ts
@@ -75,6 +75,10 @@ export interface SavedObjectCreateOptions {
* * For global object types (registered with `namespaceType: 'agnostic'`): this option cannot be used.
*/
initialNamespaces?: string[];
+ /**
+ * Whether the object is managed by the application.
+ */
+ managed?: boolean;
}
/** Saved Object search options - Pick and Omit to customize */
diff --git a/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts b/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts
index 41a72405698c6..eb36e1a05fbe4 100644
--- a/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts
+++ b/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts
@@ -22,4 +22,5 @@ export const getAllLogsDataViewSpec = ({
}),
title: allLogsIndexPattern,
timeFieldName: '@timestamp',
+ managed: true,
});
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
index ec193823a561e..2b32201781a99 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
@@ -25,6 +25,8 @@ export const DataViewEditor = ({
requireTimestampField = false,
editData,
allowAdHocDataView,
+ onDuplicate,
+ isEdit,
getDataViewHelpText,
}: DataViewEditorPropsWithServices) => {
const { Provider: KibanaReactContextProvider } =
@@ -45,6 +47,8 @@ export const DataViewEditor = ({
requireTimestampField={requireTimestampField}
editData={editData}
allowAdHocDataView={allowAdHocDataView}
+ onDuplicate={onDuplicate}
+ isEdit={isEdit}
getDataViewHelpText={getDataViewHelpText}
/>
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
index ab70360e68a9c..295deb41bf4e0 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
@@ -8,7 +8,7 @@
*/
import type { ReactNode } from 'react';
-import React, { useEffect, useCallback, useMemo } from 'react';
+import React, { useEffect, useCallback, useMemo, useMemo } from 'react';
import { css } from '@emotion/react';
import {
EuiTitle,
@@ -72,6 +72,8 @@ export interface Props {
showManagementLink?: boolean;
allowAdHoc: boolean;
dataViewEditorService: DataViewEditorService;
+ isEdit: boolean;
+ onDuplicate?: () => void;
getDataViewHelpText?: (dataView: DataView) => ReactNode | string | undefined;
}
@@ -92,6 +94,8 @@ const IndexPatternEditorFlyoutContentComponent = ({
showManagementLink,
getDataViewHelpText,
dataViewEditorService,
+ onDuplicate,
+ isEdit,
}: Props) => {
const styles = useMemoCss(componentStyles);
const isMobile = useIsWithinBreakpoints(['s', 'xs']);
@@ -101,6 +105,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
} = useKibana();
const canSave = dataViews.getCanSaveSync();
+ const isManaged = isEdit ? !!editData?.managed : false;
const { form } = useForm({
// Prefill with data if editData exists
@@ -113,8 +118,8 @@ const IndexPatternEditorFlyoutContentComponent = ({
...(editData
? {
title: editData.getIndexPattern(),
- id: editData.id,
- name: editData.name,
+ id: isEdit ? editData.id : undefined,
+ name: isEdit ? editData.name : undefined,
allowHidden: editData.getAllowHidden(),
...(editData.timeFieldName === noTimeFieldValue
? { timestampField: { label: noTimeFieldLabel, value: noTimeFieldValue } }
@@ -183,6 +188,15 @@ const IndexPatternEditorFlyoutContentComponent = ({
const rollupCaps = useObservable(dataViewEditorService.rollupCaps$);
const rollupIndicesCapabilities = useObservable(dataViewEditorService.rollupIndicesCaps$, {});
+ const namesNotAllowed = useMemo(() => {
+ // if form is popoluated but not editing an existing data view
+ // add the editData data view name to the not allowed names
+ if (!isEdit && editData) {
+ return [editData.name, ...(existingDataViewNames || [])];
+ }
+ return existingDataViewNames || [];
+ }, [existingDataViewNames, isEdit, editData]);
+
useDebounce(
() => {
dataViewEditorService.setIndexPattern(title);
@@ -285,10 +299,12 @@ const IndexPatternEditorFlyoutContentComponent = ({
: SubmittingType.persisting
: undefined
}
- isEdit={!!editData}
+ isEdit={isEdit}
isPersisted={Boolean(editData && editData.isPersisted())}
allowAdHoc={allowAdHoc}
canSave={canSave}
+ isManaged={isManaged}
+ onDuplicate={onDuplicate}
/>
);
@@ -297,9 +313,9 @@ const IndexPatternEditorFlyoutContentComponent = ({
- {editData ? editorTitleEditMode : editorTitle}
+ {editData && isEdit ? editorTitleEditMode : editorTitle}
- {showManagementLink && editData && editData.id && (
+ {showManagementLink && isEdit && editData && editData.id && (
-
+
@@ -352,7 +368,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
{
form.getFields().title.validate();
}}
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
index fdf4df7647ca2..0039ce6e3c55a 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
@@ -25,6 +25,8 @@ const DataViewFlyoutContentContainer = ({
editData,
allowAdHocDataView,
showManagementLink,
+ isEdit,
+ onDuplicate,
getDataViewHelpText,
}: DataViewEditorProps) => {
const {
@@ -101,6 +103,8 @@ const DataViewFlyoutContentContainer = ({
showManagementLink={showManagementLink}
allowAdHoc={allowAdHocDataView || false}
dataViewEditorService={dataViewEditorService}
+ onDuplicate={onDuplicate}
+ isEdit={isEdit || false}
getDataViewHelpText={getDataViewHelpText}
/>
);
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
index de73a0c7d5f47..f1e819b82b898 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
@@ -26,12 +26,14 @@ export enum SubmittingType {
interface FooterProps {
onCancel: () => void;
onSubmit: (isAdHoc?: boolean) => void;
+ onDuplicate?: () => void;
submittingType: SubmittingType | undefined;
submitDisabled: boolean;
isEdit: boolean;
isPersisted: boolean;
allowAdHoc: boolean;
canSave: boolean;
+ isManaged: boolean;
}
const closeButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutCloseButtonLabel', {
@@ -66,6 +68,8 @@ export const Footer = ({
allowAdHoc,
isPersisted,
canSave,
+ isManaged,
+ onDuplicate,
}: FooterProps) => {
const isEditingAdHoc = isEdit && !isPersisted;
const submitPersisted = () => {
@@ -108,7 +112,7 @@ export const Footer = ({
)}
- {(canSave || isEditingAdHoc) && (
+ {(canSave || isEditingAdHoc) && !isManaged && (
)}
+
+ {(canSave || isEditingAdHoc) && isManaged && onDuplicate && (
+
+
+ {'Duplicate'}
+
+
+ )}
diff --git a/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx b/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
index 28c70cc0b4084..5037e89ab4b2c 100644
--- a/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
@@ -50,6 +50,8 @@ export const getEditorOpener =
requireTimestampField = false,
allowAdHocDataView = false,
editData,
+ onDuplicate,
+ isEdit,
getDataViewHelpText,
}: DataViewEditorProps): CloseEditor => {
const closeEditor = () => {
@@ -81,6 +83,8 @@ export const getEditorOpener =
requireTimestampField={requireTimestampField}
allowAdHocDataView={allowAdHocDataView}
showManagementLink={Boolean(editData && editData.isPersisted())}
+ onDuplicate={onDuplicate}
+ isEdit={isEdit}
getDataViewHelpText={getDataViewHelpText}
/>
,
diff --git a/src/platform/plugins/shared/data_view_editor/public/types.ts b/src/platform/plugins/shared/data_view_editor/public/types.ts
index 67c914d512f05..49550d10922fe 100644
--- a/src/platform/plugins/shared/data_view_editor/public/types.ts
+++ b/src/platform/plugins/shared/data_view_editor/public/types.ts
@@ -71,6 +71,14 @@ export interface DataViewEditorProps {
* if set to true a link to the management page is shown
*/
showManagementLink?: boolean;
+ /**
+ * if set to true a duplicate button is shown
+ */
+ onDuplicate?: () => void;
+ /**
+ * if set to true an existing data view is being edited
+ */
+ isEdit?: boolean;
/**
* Optional callback to get help text based on the active data view
*/
diff --git a/src/platform/plugins/shared/data_views/common/content_management/v1/types.ts b/src/platform/plugins/shared/data_views/common/content_management/v1/types.ts
index 367e91fec7b06..1bfce15b747b3 100644
--- a/src/platform/plugins/shared/data_views/common/content_management/v1/types.ts
+++ b/src/platform/plugins/shared/data_views/common/content_management/v1/types.ts
@@ -20,6 +20,7 @@ interface DataViewCreateOptions {
id?: SavedObjectCreateOptions['id'];
initialNamespaces?: SavedObjectCreateOptions['initialNamespaces'];
overwrite?: SavedObjectCreateOptions['overwrite'];
+ managed?: SavedObjectCreateOptions['managed'];
}
interface DataViewUpdateOptions {
diff --git a/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts b/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts
index 96c38bdb69025..4f42aeab87556 100644
--- a/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts
+++ b/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts
@@ -127,6 +127,10 @@ export abstract class AbstractDataView {
* list of indices that the index pattern matched
*/
public matchedIndices: string[] = [];
+ /**
+ * Whether the data view is managed by the application.
+ */
+ public managed: boolean = false;
protected scriptedFieldsMap: DataViewFieldBaseSpecMap;
@@ -196,6 +200,7 @@ export abstract class AbstractDataView {
this.namespaces = spec.namespaces || [];
this.name = spec.name || '';
this.allowHidden = spec.allowHidden || false;
+ this.managed = spec.managed || false;
}
getAllowHidden = () => this.allowHidden;
@@ -388,6 +393,7 @@ export abstract class AbstractDataView {
runtimeFieldMap: stringifyOrUndefined(this.runtimeFieldMap),
name: this.name,
allowHidden: this.allowHidden,
+ // managed: this.managed,
};
}
@@ -419,6 +425,7 @@ export abstract class AbstractDataView {
allowNoIndex: this.allowNoIndex,
name: this.name,
allowHidden: this.getAllowHidden(),
+ managed: this.managed,
};
// Filter undefined values from the spec
diff --git a/src/platform/plugins/shared/data_views/common/data_views/data_views.ts b/src/platform/plugins/shared/data_views/common/data_views/data_views.ts
index 03bf350a2bb55..d4fad32b15c43 100644
--- a/src/platform/plugins/shared/data_views/common/data_views/data_views.ts
+++ b/src/platform/plugins/shared/data_views/common/data_views/data_views.ts
@@ -90,6 +90,10 @@ export interface DataViewListItem {
* Time field name if applicable
*/
timeFieldName?: string;
+ /**
+ * Whether the data view is managed by the application.
+ */
+ managed?: boolean;
}
/**
@@ -497,6 +501,7 @@ export class DataViewsService {
typeMeta: obj?.attributes?.typeMeta && JSON.parse(obj?.attributes?.typeMeta),
name: obj?.attributes?.name,
timeFieldName: obj?.attributes?.timeFieldName,
+ managed: obj?.managed,
}));
};
@@ -827,6 +832,7 @@ export class DataViewsService {
name,
allowHidden,
},
+ managed,
} = savedObject;
const parsedSourceFilters = sourceFilters ? JSON.parse(sourceFilters) : undefined;
@@ -864,6 +870,7 @@ export class DataViewsService {
runtimeFieldMap: parsedRuntimeFieldMap,
name,
allowHidden,
+ managed,
};
};
@@ -965,6 +972,7 @@ export class DataViewsService {
spec.fieldFormats = savedObject.attributes.fieldFormatMap
? JSON.parse(savedObject.attributes.fieldFormatMap)
: {};
+ spec.managed = savedObject.managed;
const indexPattern = await this.createFromSpec(spec, true, displayErrors);
indexPattern.setEtag(etag);
@@ -1262,13 +1270,13 @@ export class DataViewsService {
throw new DuplicateDataViewError(`Duplicate data view: ${dataView.getName()}`);
}
}
-
const body = dataView.getAsSavedObjectBody();
const response: SavedObject = (await this.savedObjectsClient.create(body, {
id: dataView.id,
initialNamespaces: dataView.namespaces.length > 0 ? dataView.namespaces : undefined,
overwrite,
+ managed: dataView.managed,
})) as SavedObject;
if (this.savedObjectsCache) {
diff --git a/src/platform/plugins/shared/data_views/common/types.ts b/src/platform/plugins/shared/data_views/common/types.ts
index 25cde7ef80e02..1e1f72754381d 100644
--- a/src/platform/plugins/shared/data_views/common/types.ts
+++ b/src/platform/plugins/shared/data_views/common/types.ts
@@ -301,7 +301,7 @@ export interface PersistenceAPI {
create: (
attributes: DataViewAttributes,
// SavedObjectsCreateOptions
- options: { id?: string; initialNamespaces?: string[]; overwrite?: boolean }
+ options: { id?: string; initialNamespaces?: string[]; overwrite?: boolean; managed?: boolean }
) => Promise;
/**
* Delete a saved object by id
@@ -326,6 +326,7 @@ export interface GetFieldsOptions {
includeEmptyFields?: boolean;
abortSignal?: AbortSignal;
runtimeMappings?: estypes.MappingRuntimeFields;
+ // managed?: boolean;
}
/**
@@ -562,6 +563,10 @@ export type DataViewSpec = {
* Allow hidden and system indices when loading field list
*/
allowHidden?: boolean;
+ /**
+ * Whether the data view is managed by the application.
+ */
+ managed?: boolean;
};
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
diff --git a/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts b/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts
index 7e1d35a1179e1..058149c68c88c 100644
--- a/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts
+++ b/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts
@@ -65,6 +65,7 @@ const dataViewCreateOptionsSchema = schema.object({
id: createOptionsSchemas.id,
initialNamespaces: createOptionsSchemas.initialNamespaces,
overwrite: schema.maybe(createOptionsSchemas.overwrite),
+ managed: schema.maybe(schema.boolean()),
});
const dataViewSearchOptionsSchema = schema.object({
diff --git a/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts b/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts
index eb1bc9590ee4b..38078687bec97 100644
--- a/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts
+++ b/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts
@@ -80,6 +80,7 @@ const registerCreateDataViewRouteFactory =
data_view: serviceKey === SERVICE_KEY ? dataViewSpecSchema : schema.never(),
index_pattern:
serviceKey === SERVICE_KEY_LEGACY ? dataViewSpecSchema : schema.never(),
+ managed: schema.maybe(schema.boolean({ defaultValue: false })),
}),
},
response: {
diff --git a/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
index 04186920ef0ff..b24f50e3a5253 100644
--- a/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
+++ b/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
@@ -181,7 +181,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
);
const selectedDataViewRef = useRef(selectedDataView);
const showFieldList = sidebarState.status !== DiscoverSidebarReducerStatus.INITIAL;
- const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker();
+ const { savedDataViews, adHocDataViews } = useDataViewsForPicker();
useEffect(() => {
const subscription = props.documents$.subscribe((documentState) => {
@@ -314,6 +314,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
onSave: async (dataView) => {
onDataViewCreated(dataView);
},
+ isEdit: true,
});
if (setDataViewEditorRef) {
setDataViewEditorRef(ref);
@@ -336,7 +337,6 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
state.query);
- const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker();
+ const { savedDataViews, adHocDataViews } = useDataViewsForPicker();
const dataView = useCurrentDataView();
const isESQLToDataViewTransitionModalVisible = useInternalStateSelector(
(state) => state.isESQLToDataViewTransitionModalVisible
@@ -191,19 +191,10 @@ export const DiscoverTopNav = ({
onCreateDefaultAdHocDataView: stateContainer.actions.createAndAppendAdHocDataView,
onChangeDataView: stateContainer.actions.onChangeDataView,
adHocDataViews,
- managedDataViews,
savedDataViews,
onEditDataView: stateContainer.actions.onDataViewEdited,
};
- }, [
- adHocDataViews,
- addField,
- createNewDataView,
- dataView,
- managedDataViews,
- savedDataViews,
- stateContainer,
- ]);
+ }, [adHocDataViews, addField, createNewDataView, dataView, savedDataViews, stateContainer]);
const onESQLDocsFlyoutVisibilityChanged = useCallback((isOpen: boolean) => {
if (isOpen) {
diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx
index a034da956177e..95a05c19c8ba5 100644
--- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx
+++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx
@@ -102,16 +102,10 @@ export const useCurrentChartPortalNode = () => useCurrentTabContext().currentCha
export const useDataViewsForPicker = () => {
const originalAdHocDataViews = useAdHocDataViews();
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
- const defaultProfileAdHocDataViewIds = useInternalStateSelector(
- (state) => state.defaultProfileAdHocDataViewIds
- );
return useMemo(() => {
- const managedDataViews = originalAdHocDataViews.filter(
- ({ id }) => id && defaultProfileAdHocDataViewIds.includes(id)
- );
- const adHocDataViews = differenceBy(originalAdHocDataViews, managedDataViews, 'id');
+ const adHocDataViews = differenceBy(originalAdHocDataViews, savedDataViews, 'id');
- return { savedDataViews, managedDataViews, adHocDataViews };
- }, [defaultProfileAdHocDataViewIds, originalAdHocDataViews, savedDataViews]);
+ return { savedDataViews, adHocDataViews };
+ }, [originalAdHocDataViews, savedDataViews]);
};
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
index bd111145435a4..f68695926f517 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
@@ -20,6 +20,9 @@ import { DataViewSelector } from './data_view_selector';
import { dataViewMock, dataViewMockEsql } from './mocks/dataview';
import type { DataViewPickerProps } from './data_view_picker';
+const dataViewEditorMock = dataViewEditorPluginMock.createStartContract();
+const openEditorMock = jest.fn();
+
describe('DataView component', () => {
const createMockWebStorage = () => ({
clear: jest.fn(),
@@ -48,8 +51,8 @@ describe('DataView component', () => {
storageValue: boolean,
uiSettingValue: boolean = false
) {
- const dataViewEditorMock = dataViewEditorPluginMock.createStartContract();
(dataViewEditorMock.userPermissions.editDataView as jest.Mock).mockReturnValue(true);
+ (dataViewEditorMock.openEditor as jest.Mock).mockImplementation(openEditorMock);
let dataMock = dataPluginMock.createStartContract();
dataMock = {
...dataMock,
@@ -66,6 +69,9 @@ describe('DataView component', () => {
uiSettings: {
get: jest.fn(() => uiSettingValue),
},
+ application: {
+ navigateToApp: jest.fn(),
+ },
};
return (
@@ -78,6 +84,7 @@ describe('DataView component', () => {
}
let props: DataViewPickerProps;
beforeEach(() => {
+ jest.clearAllMocks();
props = {
currentDataViewId: 'dataview-1',
trigger: {
@@ -209,8 +216,14 @@ describe('DataView component', () => {
id: 'dataview-1',
title: 'dataview-1',
},
+ {
+ id: 'the-data-view-id',
+ title: 'the-data-view-title',
+ name: 'the-data-view',
+ type: 'default',
+ managed: true,
+ },
],
- managedDataViews: [dataViewMock],
},
false
)
@@ -226,6 +239,7 @@ describe('DataView component', () => {
title: 'the-data-view-title',
name: 'the-data-view',
type: 'default',
+ managed: true,
isManaged: true,
},
]);
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
index 9aa3c3fea0e1c..a9223b8cefb4c 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
@@ -26,7 +26,7 @@ import {
EuiButtonEmpty,
} from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
-import type { DataView } from '@kbn/data-views-plugin/public';
+import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public';
import type { IUnifiedSearchPluginServices } from '../types';
import { type DataViewPickerProps } from './data_view_picker';
import type { DataViewListItemEnhanced } from './dataview_list';
@@ -45,11 +45,16 @@ const mapDataViewListItem = (
...partial,
});
-const mapAdHocDataView = (adHocDataView: DataView) =>
- mapDataViewListItem(adHocDataView, { isAdhoc: true });
+const mapAdHocDataView = (adHocDataView: DataView) => {
+ if (adHocDataView.managed) {
+ return mapDataViewListItem(adHocDataView, { isManaged: true });
+ }
+ return mapDataViewListItem(adHocDataView, { isAdhoc: true });
+};
-const mapManagedDataView = (managedDataView: DataView) =>
- mapDataViewListItem(managedDataView, { isManaged: true });
+const mapManagedDataView = (managedDataView: DataViewListItem) => {
+ return { ...managedDataView, isManaged: true };
+};
const shrinkableContainerCss = css`
min-width: 0;
@@ -59,7 +64,6 @@ export function ChangeDataView({
isMissingCurrent,
currentDataViewId,
adHocDataViews,
- managedDataViews,
savedDataViews,
onChangeDataView,
onAddField,
@@ -100,16 +104,16 @@ export function ChangeDataView({
useEffect(() => {
const fetchDataViews = async () => {
- const savedDataViewRefs = savedDataViews
+ const availableDataViewRefs = savedDataViews
? savedDataViews
: (await data.dataViews.getIdsWithTitle()) ?? [];
+ const savedDataViewRefs = availableDataViewRefs.filter((dataView) => !dataView.managed);
+ const managedDataViewRefs = savedDataViews?.filter((dataView) => dataView.managed === true).map(mapManagedDataView) ?? [];
const adHocDataViewRefs = adHocDataViews?.map(mapAdHocDataView) ?? [];
- const managedDataViewRefs = managedDataViews?.map(mapManagedDataView) ?? [];
-
setDataViewsList([...savedDataViewRefs, ...adHocDataViewRefs, ...managedDataViewRefs]);
};
fetchDataViews();
- }, [data, currentDataViewId, adHocDataViews, savedDataViews, managedDataViews]);
+ }, [data, currentDataViewId, adHocDataViews, savedDataViews]);
const isAdHocSelected = useMemo(() => {
return adHocDataViews?.some((dataView) => dataView.id === currentDataViewId);
@@ -148,6 +152,18 @@ export function ChangeDataView({
);
};
+ const onDuplicate = useCallback(async () => {
+ const dataView = await dataViews.get(currentDataViewId!);
+ if (onEditDataView) {
+ dataViewEditor.openEditor({
+ editData: dataView,
+ onSave: (updatedDataView) => {
+ onEditDataView(updatedDataView);
+ },
+ allowAdHocDataView: true,
+ });
+ }
+ }, [currentDataViewId, dataViews, onEditDataView, dataViewEditor]);
const items = useMemo(() => {
const panelItems: EuiContextMenuPanelProps['items'] = [];
@@ -179,6 +195,8 @@ export function ChangeDataView({
onSave: (updatedDataView) => {
onEditDataView(updatedDataView);
},
+ onDuplicate,
+ isEdit: true,
getDataViewHelpText,
});
} else {
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
index f5b310f04c14d..8abc62cb4b321 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
@@ -45,10 +45,6 @@ export interface DataViewPickerProps {
* The adHocDataviews.
*/
adHocDataViews?: DataView[];
- /**
- * Data views managed by the application
- */
- managedDataViews?: DataView[];
/**
* Saved data views
*/
@@ -87,7 +83,6 @@ export const DataViewPicker = ({
isMissingCurrent,
currentDataViewId,
adHocDataViews,
- managedDataViews,
savedDataViews,
onChangeDataView,
onEditDataView,
@@ -112,7 +107,6 @@ export const DataViewPicker = ({
onCreateDefaultAdHocDataView={onCreateDefaultAdHocDataView}
trigger={trigger}
adHocDataViews={adHocDataViews}
- managedDataViews={managedDataViews}
savedDataViews={savedDataViews}
selectableProps={selectableProps}
isDisabled={isDisabled}
diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.ts
index 524641ea0ba6c..929860a1ec505 100644
--- a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.ts
+++ b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.ts
@@ -85,6 +85,7 @@ export const hydrateDataSourceSelection = (
name: 'All logs',
title: 'logs-*,dataset-logs-*-*',
timeFieldName: '@timestamp',
+ managed: true,
};
};
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
index c2ce011e511d4..812fddb9deb8b 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
@@ -20,7 +20,6 @@ import { sharedStateSelector } from '../../redux/selectors';
import { sharedDataViewManagerSlice } from '../../redux/slices';
import { useSelectDataView } from '../../hooks/use_select_data_view';
import { DataViewManagerScopeName } from '../../constants';
-import { useManagedDataViews } from '../../hooks/use_managed_data_views';
import { useSavedDataViews } from '../../hooks/use_saved_data_views';
import { LOADING } from './translations';
import { DATA_VIEW_PICKER_TEST_ID } from './constants';
@@ -68,7 +67,6 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie
.map((spec) => new DataView({ spec, fieldFormats }));
}, [adhocDataViewSpecs, fieldFormats]);
- const managedDataViews = useManagedDataViews();
const savedDataViews = useSavedDataViews();
const isDefaultSourcerer = scope === DataViewManagerScopeName.default;
@@ -98,7 +96,7 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie
const createNewDataView = useCallback(() => {
closeDataViewEditor.current = dataViewEditor.openEditor({
- onSave: async (newDataView) => {
+ onSave: async (newDataView: DataView) => {
if (!newDataView.id) {
return;
}
@@ -194,7 +192,6 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie
onDataViewCreated={createNewDataView}
adHocDataViews={adhocDataViews}
savedDataViews={savedDataViews}
- managedDataViews={managedDataViews}
onClosePopover={onClosePopover}
getDataViewHelpText={getDataViewHelpText}
/>
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts
deleted file mode 100644
index 33ce7a1d3b5fb..0000000000000
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { renderHook } from '@testing-library/react';
-import { DataView } from '@kbn/data-views-plugin/public';
-import { useManagedDataViews } from './use_managed_data_views';
-import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../constants';
-import { DEFAULT_ALERT_DATA_VIEW_ID } from '../../../common/constants';
-
-// Mock dependencies
-jest.mock('react-redux', () => ({
- useSelector: jest.fn(),
-}));
-
-jest.mock('../../common/lib/kibana', () => ({
- useKibana: jest.fn(),
-}));
-
-jest.mock('@kbn/data-views-plugin/public', () => ({
- DataView: jest.fn(),
-}));
-
-import { useSelector } from 'react-redux';
-import { useKibana } from '../../common/lib/kibana';
-
-describe('useManagedDataViews', () => {
- const mockFieldFormats = {};
-
- beforeEach(() => {
- jest.clearAllMocks();
-
- // Mock DataView constructor
- (DataView as jest.Mock).mockImplementation(({ spec }) => ({
- ...spec,
- }));
-
- // Mock useKibana
- (useKibana as jest.Mock).mockReturnValue({
- services: {
- fieldFormats: mockFieldFormats,
- },
- });
- });
-
- it('should filter data views to only include those with Alert Data View ID', () => {
- // Create mock data views with a mix of IDs
- const mockDataViews = [
- { id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, title: 'Security solution data view' },
- { id: 'some-other-id', title: 'Other data view' },
- { id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, title: 'Another security solution data view' },
- { id: DEFAULT_ALERT_DATA_VIEW_ID, title: 'Security alert data view' },
- ];
-
- // Mock the Redux selector
- (useSelector as jest.Mock).mockReturnValue({
- dataViews: mockDataViews,
- adhocDataViews: [],
- defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID,
- alertDataViewId: DEFAULT_ALERT_DATA_VIEW_ID,
- });
-
- // Render the hook
- const { result } = renderHook(() => useManagedDataViews());
-
- // Expect only data views with matching ID to be included
- expect(result.current.length).toBe(1);
-
- // Verify the IDs of the filtered data views
- result.current.forEach((dataView, i) => {
- if (i <= 1) {
- expect(dataView.id).toBe(DEFAULT_ALERT_DATA_VIEW_ID);
- }
- });
-
- // Verify DataView constructor was called with correct arguments
- expect(DataView).toHaveBeenCalledTimes(1);
- expect(DataView).toHaveBeenCalledWith({
- spec: mockDataViews[3],
- fieldFormats: mockFieldFormats,
- });
- });
-
- it('should return an empty array when no data views match the filter criteria', () => {
- // Create mock data views with no matching IDs
- const mockDataViews = [
- { id: 'some-id', title: 'Some Data View' },
- { id: 'another-id', title: 'Another Data View' },
- ];
- (useSelector as jest.Mock).mockReturnValue({
- dataViews: mockDataViews,
- adhocDataViews: [],
- defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID,
- });
-
- const { result } = renderHook(() => useManagedDataViews());
-
- // Expect no data views to be included
- expect(result.current).toEqual([]);
- expect(result.current.length).toBe(0);
-
- // Verify DataView constructor was not called
- expect(DataView).not.toHaveBeenCalled();
- });
-});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.ts
deleted file mode 100644
index cf182c93d2fc0..0000000000000
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.ts
+++ /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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { DataView } from '@kbn/data-views-plugin/public';
-import { useSelector } from 'react-redux';
-import { useMemo } from 'react';
-import { EXPLORE_DATA_VIEW_PREFIX } from '../../../common/constants';
-import { useKibana } from '../../common/lib/kibana';
-import { sharedStateSelector } from '../redux/selectors';
-
-/**
- * Returns the default security solution data view and alert data view
- */
-export const useManagedDataViews = (): DataView[] => {
- const {
- dataViews: dataViewSpecs,
- adhocDataViews,
- alertDataViewId,
- } = useSelector(sharedStateSelector);
- const {
- services: { fieldFormats },
- } = useKibana();
-
- return useMemo(
- () =>
- [...dataViewSpecs, ...adhocDataViews]
- .filter((dv) => dv.id === alertDataViewId || dv.id?.startsWith(EXPLORE_DATA_VIEW_PREFIX))
- .map((spec) => new DataView({ spec, fieldFormats })),
- [dataViewSpecs, adhocDataViews, alertDataViewId, fieldFormats]
- );
-};
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts
index 94d3205c97de9..f1c2f7fd18522 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts
@@ -20,18 +20,20 @@ describe('useSavedDataViews', () => {
jest.clearAllMocks();
});
- it('should not filter out the default data view and transform the remaining ones', () => {
+ it('should not transform saved data views', () => {
// Mock data to be returned by the selector
const mockDataViews = [
{
id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, // This should not be filtered out
title: 'Default View',
name: 'default_view',
+ managed: true,
},
{
id: DEFAULT_ALERT_DATA_VIEW_ID, // This should be filtered out
title: 'Default Alert View',
name: 'default_alert_view',
+ managed: true,
},
{
id: 'custom-view-1',
@@ -48,19 +50,13 @@ describe('useSavedDataViews', () => {
// Mock the useSelector to return our test data
(useSelector as jest.Mock).mockReturnValue({
dataViews: mockDataViews,
- defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID,
- alertDataViewId: DEFAULT_ALERT_DATA_VIEW_ID,
});
// Render the hook
const { result } = renderHook(() => useSavedDataViews());
- // Expect the alert view to be filtered out
- expect(result.current).toHaveLength(3);
- expect(
- result.current.find((item) => item.id === DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID)
- ).not.toBeUndefined();
- expect(result.current.find((item) => item.id === DEFAULT_ALERT_DATA_VIEW_ID)).toBeUndefined();
+ // Expect the default view to be filtered out
+ expect(result.current).toHaveLength(4);
// Expect the custom views to be correctly transformed
expect(result.current).toEqual([
@@ -69,6 +65,12 @@ describe('useSavedDataViews', () => {
title: 'Default View',
name: 'default_view',
},
+ {
+ id: DEFAULT_ALERT_DATA_VIEW_ID,
+ title: 'Default Alert View',
+ name: 'default_alert_view',
+ managed: true,
+ },
{
id: 'custom-view-1',
title: 'Custom View 1',
@@ -86,7 +88,6 @@ describe('useSavedDataViews', () => {
// Mock the useSelector to return an empty array
(useSelector as jest.Mock).mockReturnValue({
dataViews: [],
- defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID,
});
// Render the hook
@@ -114,8 +115,6 @@ describe('useSavedDataViews', () => {
// Mock the useSelector
(useSelector as jest.Mock).mockReturnValue({
dataViews: mockDataViews,
- defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID,
- alertDataViewId: DEFAULT_ALERT_DATA_VIEW_ID,
});
// Render the hook
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts
index fdde44eb554bf..6ed389f8985c6 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts
@@ -15,17 +15,16 @@ import { sharedStateSelector } from '../redux/selectors';
* The list excludes managed data views (default security solution data view and alert data view)
*/
export const useSavedDataViews = (): DataViewListItem[] => {
- const { dataViews: dataViewSpecs, alertDataViewId } = useSelector(sharedStateSelector);
+ const { dataViews: dataViewSpecs } = useSelector(sharedStateSelector);
return useMemo(
() =>
- dataViewSpecs
- .filter((dv) => dv.id !== alertDataViewId)
- .map((spec) => ({
- id: spec.id ?? '',
- title: spec.title ?? '',
- name: spec.name,
- })),
- [dataViewSpecs, alertDataViewId]
+ dataViewSpecs.map((spec) => ({
+ id: spec.id ?? '',
+ title: spec.title ?? '',
+ name: spec.name,
+ managed: spec.managed,
+ })),
+ [dataViewSpecs]
);
};
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_explore_data_view.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_explore_data_view.ts
index c94bd887aee2b..5a0905f5fcdda 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_explore_data_view.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_explore_data_view.ts
@@ -26,5 +26,6 @@ export const createExploreDataView = async (
id: `${EXPLORE_DATA_VIEW_PREFIX}-${(await dependencies.spaces.getActiveSpace()).id}`,
name: SECURITY_SOLUTION_EXPLORE_DATA_VIEW,
title: exploreDataViewPattern,
+ managed: true,
});
};
diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts
index b23c8a7a993fc..29d0635672fd2 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts
@@ -63,6 +63,7 @@ export const createSourcererDataView = async ({
title: patternListAsTitle,
timeFieldName: DEFAULT_TIME_FIELD,
name: DEFAULT_SECURITY_DATA_VIEW,
+ managed: true,
},
// Override property - if a data view exists with the security solution pattern
// delete it and replace it with our data view
@@ -108,6 +109,7 @@ export const createSourcererDataView = async ({
title: signalIndexName,
timeFieldName: DEFAULT_TIME_FIELD,
name: DEFAULT_SECURITY_ALERT_DATA_VIEW,
+ managed: true,
},
// Override property - if a data view exists with the security solution pattern
// delete it and replace it with our data view
From b727b887a761c96a23c6806207e0a9043436844b Mon Sep 17 00:00:00 2001
From: christineweng
Date: Mon, 28 Jul 2025 21:39:51 -0500
Subject: [PATCH 02/12] comments and fixes
---
packages/kbn-optimizer/limits.yml | 2 +-
.../public/components/data_view_editor.tsx | 4 +-
.../data_view_editor_flyout_content.tsx | 42 ++++---
.../data_view_flyout_content_container.tsx | 6 +-
.../public/components/footer/footer.tsx | 29 +++--
.../components/form_fields/name_field.tsx | 4 +-
.../form_fields/timestamp_field.tsx | 10 +-
.../components/form_fields/title_field.tsx | 3 +
.../data_view_editor/public/open_editor.tsx | 4 +-
.../shared/data_view_editor/public/types.ts | 7 +-
.../field_editor_flyout_content.tsx | 30 +++--
.../__snapshots__/utils.test.ts.snap | 2 +
.../edit_index_pattern/edit_index_pattern.tsx | 4 +-
.../index_pattern_table.tsx | 4 +-
.../public/components/types.ts | 1 +
.../public/components/utils.ts | 17 ++-
.../__snapshots__/data_view.test.ts.snap | 3 +
.../__snapshots__/data_view_lazy.test.ts.snap | 3 +
.../__snapshots__/data_views.test.ts.snap | 1 +
.../common/data_views/abstract_data_views.ts | 1 -
.../common/data_views/data_views.ts | 1 -
.../plugins/shared/data_views/common/types.ts | 2 +-
.../server/rest_api_routes/schema.ts | 1 +
.../sidebar/discover_sidebar_responsive.tsx | 18 +--
.../components/top_nav/discover_topnav.tsx | 15 +--
.../main/state_management/redux/hooks.tsx | 8 +-
.../classic_nav_root_profile/profile.test.ts | 1 +
.../profile.test.ts | 1 +
.../dataview_picker/change_dataview.test.tsx | 8 +-
.../dataview_picker/change_dataview.tsx | 118 ++++++++++--------
.../dataview_picker/data_view_picker.tsx | 3 +-
.../public/dataview_picker/dataview_list.tsx | 13 +-
.../_get_default_ad_hoc_data_views.ts | 12 +-
.../lens/public/app_plugin/lens_top_nav.tsx | 44 +++----
.../log_views/log_views_client.test.ts | 1 +
.../lib/fetch_search_source_query.test.ts | 1 +
.../data_views/integration/integration.ts | 1 +
.../_get_default_ad_hoc_data_views.ts | 12 +-
.../public/logs_explorer_url_schema.test.ts | 1 +
.../custom_common/index_selection.tsx | 28 ++---
.../data_view_picker/index.test.tsx | 42 +------
.../components/data_view_picker/index.tsx | 29 ++---
.../hooks/use_saved_data_views.test.ts | 2 -
.../public/data_view_manager/redux/slices.ts | 8 +-
.../containers/create_sourcerer_data_view.ts | 1 -
.../components/fields_browser/index.tsx | 2 +-
46 files changed, 281 insertions(+), 269 deletions(-)
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index e2359a59dcbd1..4c251d93b45ee 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -174,7 +174,7 @@ pageLoadAssetSize:
uiActions: 24712
uiActionsEnhanced: 20189
unifiedDocViewer: 14513
- unifiedSearch: 23000
+ unifiedSearch: 23500
upgradeAssistant: 6898
uptime: 48184
urlDrilldown: 21563
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
index 2b32201781a99..79daa0c3b5181 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
@@ -26,7 +26,7 @@ export const DataViewEditor = ({
editData,
allowAdHocDataView,
onDuplicate,
- isEdit,
+ isDuplicatingManaged,
getDataViewHelpText,
}: DataViewEditorPropsWithServices) => {
const { Provider: KibanaReactContextProvider } =
@@ -48,7 +48,7 @@ export const DataViewEditor = ({
editData={editData}
allowAdHocDataView={allowAdHocDataView}
onDuplicate={onDuplicate}
- isEdit={isEdit}
+ isDuplicatingManaged={isDuplicatingManaged}
getDataViewHelpText={getDataViewHelpText}
/>
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
index 295deb41bf4e0..e8baaa1b0a30f 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
@@ -8,7 +8,7 @@
*/
import type { ReactNode } from 'react';
-import React, { useEffect, useCallback, useMemo, useMemo } from 'react';
+import React, { useEffect, useCallback, useMemo } from 'react';
import { css } from '@emotion/react';
import {
EuiTitle,
@@ -72,7 +72,7 @@ export interface Props {
showManagementLink?: boolean;
allowAdHoc: boolean;
dataViewEditorService: DataViewEditorService;
- isEdit: boolean;
+ isDuplicatingManaged: boolean;
onDuplicate?: () => void;
getDataViewHelpText?: (dataView: DataView) => ReactNode | string | undefined;
}
@@ -95,7 +95,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
getDataViewHelpText,
dataViewEditorService,
onDuplicate,
- isEdit,
+ isDuplicatingManaged,
}: Props) => {
const styles = useMemoCss(componentStyles);
const isMobile = useIsWithinBreakpoints(['s', 'xs']);
@@ -105,7 +105,10 @@ const IndexPatternEditorFlyoutContentComponent = ({
} = useKibana();
const canSave = dataViews.getCanSaveSync();
- const isManaged = isEdit ? !!editData?.managed : false;
+ // Edit form is populated and disabled if the current data view is managed
+ // and the data view is not being duplicated
+ const isFormDisabled = !!editData?.managed && !isDuplicatingManaged;
+ const isEditingExisting = editData && !editData.managed && !isDuplicatingManaged;
const { form } = useForm({
// Prefill with data if editData exists
@@ -118,8 +121,8 @@ const IndexPatternEditorFlyoutContentComponent = ({
...(editData
? {
title: editData.getIndexPattern(),
- id: isEdit ? editData.id : undefined,
- name: isEdit ? editData.name : undefined,
+ id: isDuplicatingManaged ? undefined : editData.id,
+ name: isDuplicatingManaged ? undefined : editData.name,
allowHidden: editData.getAllowHidden(),
...(editData.timeFieldName === noTimeFieldValue
? { timestampField: { label: noTimeFieldLabel, value: noTimeFieldValue } }
@@ -155,7 +158,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
};
}
- if (editData && editData.getIndexPattern() !== formData.title) {
+ if (isEditingExisting && editData.getIndexPattern() !== formData.title) {
await editDataViewModal({
dataViewName: formData.name || formData.title,
overlays,
@@ -189,13 +192,13 @@ const IndexPatternEditorFlyoutContentComponent = ({
const rollupIndicesCapabilities = useObservable(dataViewEditorService.rollupIndicesCaps$, {});
const namesNotAllowed = useMemo(() => {
- // if form is popoluated but not editing an existing data view
- // add the editData data view name to the not allowed names
- if (!isEdit && editData) {
+ // When duplicating a managed data view, add the existing name
+ // to the not allowed names list
+ if (isDuplicatingManaged && editData) {
return [editData.name, ...(existingDataViewNames || [])];
}
return existingDataViewNames || [];
- }, [existingDataViewNames, isEdit, editData]);
+ }, [existingDataViewNames, isDuplicatingManaged, editData]);
useDebounce(
() => {
@@ -299,12 +302,13 @@ const IndexPatternEditorFlyoutContentComponent = ({
: SubmittingType.persisting
: undefined
}
- isEdit={isEdit}
+ hasEditData={!!editData}
isPersisted={Boolean(editData && editData.isPersisted())}
allowAdHoc={allowAdHoc}
canSave={canSave}
- isManaged={isManaged}
+ isManaged={!!editData?.managed}
onDuplicate={onDuplicate}
+ isDuplicatingManaged={isDuplicatingManaged}
/>
);
@@ -313,9 +317,11 @@ const IndexPatternEditorFlyoutContentComponent = ({
- {editData && isEdit ? editorTitleEditMode : editorTitle}
+
+ {editData && !isDuplicatingManaged ? editorTitleEditMode : editorTitle}
+
- {showManagementLink && isEdit && editData && editData.id && (
+ {showManagementLink && !isDuplicatingManaged && editData && editData.id && (
-
+
@@ -353,6 +359,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
dataViewEditorService.indexPatternValidationProvider
}
titleHelpText={titleHelpText}
+ disabled={isFormDisabled}
/>
@@ -363,12 +370,13 @@ const IndexPatternEditorFlyoutContentComponent = ({
options$={dataViewEditorService.timestampFieldOptions$}
isLoadingOptions$={dataViewEditorService.loadingTimestampFields$}
matchedIndices$={dataViewEditorService.matchedIndices$}
+ disabled={isFormDisabled}
/>
{
form.getFields().title.validate();
}}
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
index 0039ce6e3c55a..db1943dd7c6a8 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
@@ -25,8 +25,8 @@ const DataViewFlyoutContentContainer = ({
editData,
allowAdHocDataView,
showManagementLink,
- isEdit,
onDuplicate,
+ isDuplicatingManaged,
getDataViewHelpText,
}: DataViewEditorProps) => {
const {
@@ -54,7 +54,7 @@ const DataViewFlyoutContentContainer = ({
const onSaveClick = async (dataViewSpec: DataViewSpec, persist: boolean = true) => {
try {
let saveResponse;
- if (editData) {
+ if (editData && !editData.managed && !isDuplicatingManaged) {
const { name = '', timeFieldName, title = '', allowHidden = false } = dataViewSpec;
editData.setIndexPattern(title);
editData.name = name;
@@ -104,7 +104,7 @@ const DataViewFlyoutContentContainer = ({
allowAdHoc={allowAdHocDataView || false}
dataViewEditorService={dataViewEditorService}
onDuplicate={onDuplicate}
- isEdit={isEdit || false}
+ isDuplicatingManaged={isDuplicatingManaged || false}
getDataViewHelpText={getDataViewHelpText}
/>
);
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
index f1e819b82b898..5b1de19497cfd 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
@@ -29,11 +29,12 @@ interface FooterProps {
onDuplicate?: () => void;
submittingType: SubmittingType | undefined;
submitDisabled: boolean;
- isEdit: boolean;
+ hasEditData: boolean;
isPersisted: boolean;
allowAdHoc: boolean;
canSave: boolean;
isManaged: boolean;
+ isDuplicatingManaged: boolean;
}
const closeButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutCloseButtonLabel', {
@@ -48,6 +49,13 @@ const editButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutEditButt
defaultMessage: 'Save',
});
+const duplicateButtonLabel = i18n.translate(
+ 'indexPatternEditor.editor.flyoutDuplicateButtonLabel',
+ {
+ defaultMessage: 'Duplicate',
+ }
+);
+
const editUnpersistedButtonLabel = i18n.translate(
'indexPatternEditor.editor.flyoutEditUnpersistedButtonLabel',
{
@@ -64,14 +72,19 @@ export const Footer = ({
onSubmit,
submittingType,
submitDisabled,
- isEdit,
+ hasEditData,
allowAdHoc,
isPersisted,
canSave,
- isManaged,
onDuplicate,
+ isManaged,
+ isDuplicatingManaged,
}: FooterProps) => {
- const isEditingAdHoc = isEdit && !isPersisted;
+ const isEditingAdHoc = hasEditData && !isPersisted;
+
+ const isEditing = (canSave || isEditingAdHoc) && !isManaged;
+ const showDuplicateButton = (canSave || isEditingAdHoc) && isManaged && onDuplicate;
+
const submitPersisted = () => {
onSubmit(false);
};
@@ -112,7 +125,7 @@ export const Footer = ({
)}
- {(canSave || isEditingAdHoc) && !isManaged && (
+ {isEditing && (
- {isEdit
+ {hasEditData && !isDuplicatingManaged
? isPersisted
? editButtonLabel
: editUnpersistedButtonLabel
@@ -134,10 +147,10 @@ export const Footer = ({
)}
- {(canSave || isEditingAdHoc) && isManaged && onDuplicate && (
+ {showDuplicateButton && (
- {'Duplicate'}
+ {duplicateButtonLabel}
)}
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/name_field.tsx b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/name_field.tsx
index 8b4a5236627a9..6759abf9f72f3 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/name_field.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/name_field.tsx
@@ -18,6 +18,7 @@ import { schema } from '../form_schema';
interface NameFieldProps {
namesNotAllowed: string[];
+ disabled?: boolean;
}
interface GetNameConfigArgs {
@@ -49,7 +50,7 @@ const getNameConfig = ({ namesNotAllowed }: GetNameConfigArgs): FieldConfig {
+export const NameField = ({ namesNotAllowed, disabled }: NameFieldProps) => {
const config = useMemo(
() =>
getNameConfig({
@@ -76,6 +77,7 @@ export const NameField = ({ namesNotAllowed }: NameFieldProps) => {
) => {
field.setValue(e.target.value);
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/timestamp_field.tsx b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/timestamp_field.tsx
index 7e82d87ea7547..6d6c64b24c518 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/timestamp_field.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/timestamp_field.tsx
@@ -25,6 +25,7 @@ interface Props {
options$: Observable;
isLoadingOptions$: Observable;
matchedIndices$: Observable;
+ disabled?: boolean;
}
export const requireTimestampOptionValidator = (
@@ -71,7 +72,12 @@ const timestampFieldHelp = i18n.translate('indexPatternEditor.editor.form.timeFi
defaultMessage: 'Select a timestamp field for use with the global time filter.',
});
-export const TimestampField = ({ options$, isLoadingOptions$, matchedIndices$ }: Props) => {
+export const TimestampField = ({
+ options$,
+ isLoadingOptions$,
+ matchedIndices$,
+ disabled,
+}: Props) => {
const options = useObservable(options$, []);
const isLoadingOptions = useObservable(isLoadingOptions$, false);
const hasMatchedIndices = !!useObservable(matchedIndices$, matchedIndiciesDefault)
@@ -98,7 +104,7 @@ export const TimestampField = ({ options$, isLoadingOptions$, matchedIndices$ }:
}
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
- const isDisabled = !optionsAsComboBoxOptions.length || isLoadingOptions;
+ const isDisabled = !optionsAsComboBoxOptions.length || isLoadingOptions || disabled;
// if the value isn't in the list then don't use it.
const valueInList = !!optionsAsComboBoxOptions.find(
(option) => option.value === value.value
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_field.tsx b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_field.tsx
index 596e6f6b1ae79..1a7874dc071be 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_field.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_field.tsx
@@ -30,6 +30,7 @@ interface TitleFieldProps {
matchedIndices: MatchedIndicesSet;
rollupIndex: string | null | undefined;
}>;
+ disabled?: boolean;
titleHelpText?: ReactNode | string;
}
@@ -146,6 +147,7 @@ export const TitleField = ({
matchedIndices$,
rollupIndicesCapabilities,
indexPatternValidationProvider,
+ disabled,
titleHelpText,
}: TitleFieldProps) => {
const [appendedWildcard, setAppendedWildcard] = useState(false);
@@ -188,6 +190,7 @@ export const TitleField = ({
>
) => {
let query = e.target.value;
diff --git a/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx b/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
index 5037e89ab4b2c..5f82559aaaf14 100644
--- a/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
@@ -51,7 +51,7 @@ export const getEditorOpener =
allowAdHocDataView = false,
editData,
onDuplicate,
- isEdit,
+ isDuplicatingManaged,
getDataViewHelpText,
}: DataViewEditorProps): CloseEditor => {
const closeEditor = () => {
@@ -84,7 +84,7 @@ export const getEditorOpener =
allowAdHocDataView={allowAdHocDataView}
showManagementLink={Boolean(editData && editData.isPersisted())}
onDuplicate={onDuplicate}
- isEdit={isEdit}
+ isDuplicatingManaged={isDuplicatingManaged}
getDataViewHelpText={getDataViewHelpText}
/>
,
diff --git a/src/platform/plugins/shared/data_view_editor/public/types.ts b/src/platform/plugins/shared/data_view_editor/public/types.ts
index 49550d10922fe..c20e70fdb7d8e 100644
--- a/src/platform/plugins/shared/data_view_editor/public/types.ts
+++ b/src/platform/plugins/shared/data_view_editor/public/types.ts
@@ -72,13 +72,13 @@ export interface DataViewEditorProps {
*/
showManagementLink?: boolean;
/**
- * if set to true a duplicate button is shown
+ * if editing a managed data view and onDuplicate is defined, a duplicate button is shown
*/
onDuplicate?: () => void;
/**
- * if set to true an existing data view is being edited
+ * if editing a managed data view and onDuplicate is defined, a duplicate button is shown
*/
- isEdit?: boolean;
+ isDuplicatingManaged?: boolean;
/**
* Optional callback to get help text based on the active data view
*/
@@ -148,6 +148,7 @@ export interface FormInternal extends Omit
export interface TimestampOption {
display: string;
fieldName?: string;
+ isDisabled?: boolean;
}
export interface MatchedIndicesSet {
diff --git a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
index c92fb21aa2778..e91c25da8c1aa 100644
--- a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
@@ -14,6 +14,7 @@ import {
EuiFlexItem,
EuiText,
EuiTitle,
+ EuiToolTip,
useIsWithinMaxBreakpoint,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -38,6 +39,13 @@ const i18nTexts = {
saveButtonLabel: i18n.translate('indexPatternFieldEditor.editor.flyoutSaveButtonLabel', {
defaultMessage: 'Save',
}),
+ disabledSaveButtonTooltip: i18n.translate(
+ 'indexPatternFieldEditor.editor.flyoutDisabledSaveButtonTooltip',
+ {
+ defaultMessage:
+ 'Fields cannot be edited on managed data views. Duplicate the data view in order to make changes.',
+ }
+ ),
};
const defaultModalVisibility = {
@@ -267,16 +275,18 @@ const FieldEditorFlyoutContentComponent = ({
-
- {i18nTexts.saveButtonLabel}
-
+
+
+ {i18nTexts.saveButtonLabel}
+
+
>
diff --git a/src/platform/plugins/shared/data_view_management/public/components/__snapshots__/utils.test.ts.snap b/src/platform/plugins/shared/data_view_management/public/components/__snapshots__/utils.test.ts.snap
index 5329da0bd89fb..1c094b8781c67 100644
--- a/src/platform/plugins/shared/data_view_management/public/components/__snapshots__/utils.test.ts.snap
+++ b/src/platform/plugins/shared/data_view_management/public/components/__snapshots__/utils.test.ts.snap
@@ -6,6 +6,7 @@ Array [
"default": true,
"getName": [Function],
"id": "test",
+ "managed": undefined,
"name": undefined,
"namespaces": undefined,
"sort": "0test name",
@@ -22,6 +23,7 @@ Array [
"default": false,
"getName": [Function],
"id": "test1",
+ "managed": undefined,
"name": "Test Name 1",
"namespaces": undefined,
"sort": "1Test Name 1",
diff --git a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx
index 08eb885232329..5e7628b279e15 100644
--- a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx
+++ b/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx
@@ -58,7 +58,7 @@ const mappingConflictHeader = i18n.translate(
const securityDataView = i18n.translate(
'indexPatternManagement.editIndexPattern.badge.securityDataViewTitle',
{
- defaultMessage: 'Security Data View',
+ defaultMessage: 'Security Solution',
}
);
@@ -210,7 +210,7 @@ export const EditIndexPattern = withRouter(
)}
{indexPattern.id && indexPattern.id.indexOf(securitySolution) === 0 && (
- {securityDataView}
+ {securityDataView}
)}
{tags.map((tag) => (
diff --git a/src/platform/plugins/shared/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/platform/plugins/shared/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx
index 89ebb218dfba6..3600c76089bf9 100644
--- a/src/platform/plugins/shared/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx
+++ b/src/platform/plugins/shared/data_view_management/public/components/index_pattern_table/index_pattern_table.tsx
@@ -59,7 +59,7 @@ const sorting = {
const securityDataView = i18n.translate(
'indexPatternManagement.indexPatternTable.badge.securityDataViewTitle',
{
- defaultMessage: 'Security Data View',
+ defaultMessage: 'Security Solution',
}
);
@@ -286,7 +286,7 @@ export const IndexPatternTable = ({ history, canSave, setShowCreateDialog, title
{dataView?.id?.indexOf(securitySolution) === 0 && (
<>
- {securityDataView}
+ {securityDataView}
>
)}
{dataView?.tags?.map(({ key: tagKey, name: tagName }) => (
diff --git a/src/platform/plugins/shared/data_view_management/public/components/types.ts b/src/platform/plugins/shared/data_view_management/public/components/types.ts
index cb6c48bd80481..07b6ec1395ba0 100644
--- a/src/platform/plugins/shared/data_view_management/public/components/types.ts
+++ b/src/platform/plugins/shared/data_view_management/public/components/types.ts
@@ -23,4 +23,5 @@ export interface IndexPatternTableItem {
sort: string;
namespaces?: string[];
getName: () => string;
+ managed?: boolean;
}
diff --git a/src/platform/plugins/shared/data_view_management/public/components/utils.ts b/src/platform/plugins/shared/data_view_management/public/components/utils.ts
index 8291711428bc1..a4dc348df1fed 100644
--- a/src/platform/plugins/shared/data_view_management/public/components/utils.ts
+++ b/src/platform/plugins/shared/data_view_management/public/components/utils.ts
@@ -24,6 +24,13 @@ const defaultIndexPatternListName = i18n.translate(
}
);
+const managedDataViewTagName = i18n.translate(
+ 'indexPatternManagement.editIndexPattern.list.managedDataViewTagName',
+ {
+ defaultMessage: 'Managed',
+ }
+);
+
export const isRollup = (indexPatternType: string = '') => {
return indexPatternType === DataViewType.ROLLUP;
};
@@ -31,7 +38,7 @@ export const isRollup = (indexPatternType: string = '') => {
export async function getIndexPatterns(defaultIndex: string, dataViewsService: DataViewsContract) {
const existingIndexPatterns = await dataViewsService.getIdsWithTitle(true);
const indexPatternsListItems = existingIndexPatterns.map((idxPattern) => {
- const { id, title, namespaces, name } = idxPattern;
+ const { id, title, namespaces, name, managed } = idxPattern;
const isDefault = defaultIndex === id;
const tags = getTags(idxPattern, isDefault, dataViewsService.getRollupsEnabled());
const displayName = name ? name : title;
@@ -48,6 +55,7 @@ export async function getIndexPatterns(defaultIndex: string, dataViewsService: D
// or on bottom of the table
sort: `${isDefault ? '0' : '1'}${displayName}`,
getName: () => displayName,
+ managed,
};
});
@@ -70,6 +78,13 @@ export const getTags = (
rollupsEnabled: boolean
) => {
const tags = [];
+ if (indexPattern.managed) {
+ tags.push({
+ key: DataViewType.MANAGED,
+ name: managedDataViewTagName,
+ 'data-test-subj': 'managed-tag',
+ });
+ }
if (isDefault) {
tags.push({
key: DataViewType.DEFAULT,
diff --git a/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view.test.ts.snap b/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view.test.ts.snap
index 3df45ed00e450..0a5e320862679 100644
--- a/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view.test.ts.snap
+++ b/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view.test.ts.snap
@@ -33,6 +33,7 @@ Object {
"fieldAttrs": undefined,
"fieldFormats": Object {},
"id": "test-pattern",
+ "managed": false,
"name": "Name",
"runtimeFieldMap": Object {
"runtime_field": Object {
@@ -57,6 +58,7 @@ Object {
"fieldAttrs": Object {},
"fieldFormats": Object {},
"id": "test-pattern",
+ "managed": false,
"name": "Name",
"runtimeFieldMap": Object {
"runtime_field": Object {
@@ -707,6 +709,7 @@ Object {
},
},
"id": "test-pattern",
+ "managed": false,
"name": "Name",
"runtimeFieldMap": Object {
"runtime_field": Object {
diff --git a/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view_lazy.test.ts.snap b/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view_lazy.test.ts.snap
index af25681927c6a..82afa0e3d29c2 100644
--- a/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view_lazy.test.ts.snap
+++ b/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_view_lazy.test.ts.snap
@@ -32,6 +32,7 @@ Object {
"allowNoIndex": false,
"fieldFormats": Object {},
"id": "test-pattern",
+ "managed": false,
"name": "Name",
"runtimeFieldMap": Object {
"runtime_field": Object {
@@ -56,6 +57,7 @@ Object {
"fieldAttrs": Object {},
"fieldFormats": Object {},
"id": "test-pattern",
+ "managed": false,
"name": "Name",
"runtimeFieldMap": Object {
"runtime_field": Object {
@@ -702,6 +704,7 @@ Object {
},
},
"id": "test-pattern",
+ "managed": false,
"name": "Name",
"runtimeFieldMap": Object {
"runtime_field": Object {
diff --git a/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_views.test.ts.snap b/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_views.test.ts.snap
index 7ea6cafaaacbc..5489dc9f3951e 100644
--- a/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_views.test.ts.snap
+++ b/src/platform/plugins/shared/data_views/common/data_views/__snapshots__/data_views.test.ts.snap
@@ -67,6 +67,7 @@ Object {
},
"fields": Object {},
"id": "id",
+ "managed": undefined,
"name": "Kibana *",
"namespaces": undefined,
"runtimeFieldMap": Object {
diff --git a/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts b/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts
index 4f42aeab87556..8c6d888a7a9a4 100644
--- a/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts
+++ b/src/platform/plugins/shared/data_views/common/data_views/abstract_data_views.ts
@@ -393,7 +393,6 @@ export abstract class AbstractDataView {
runtimeFieldMap: stringifyOrUndefined(this.runtimeFieldMap),
name: this.name,
allowHidden: this.allowHidden,
- // managed: this.managed,
};
}
diff --git a/src/platform/plugins/shared/data_views/common/data_views/data_views.ts b/src/platform/plugins/shared/data_views/common/data_views/data_views.ts
index d4fad32b15c43..2dd7f37072b18 100644
--- a/src/platform/plugins/shared/data_views/common/data_views/data_views.ts
+++ b/src/platform/plugins/shared/data_views/common/data_views/data_views.ts
@@ -972,7 +972,6 @@ export class DataViewsService {
spec.fieldFormats = savedObject.attributes.fieldFormatMap
? JSON.parse(savedObject.attributes.fieldFormatMap)
: {};
- spec.managed = savedObject.managed;
const indexPattern = await this.createFromSpec(spec, true, displayErrors);
indexPattern.setEtag(etag);
diff --git a/src/platform/plugins/shared/data_views/common/types.ts b/src/platform/plugins/shared/data_views/common/types.ts
index 1e1f72754381d..184098adc8848 100644
--- a/src/platform/plugins/shared/data_views/common/types.ts
+++ b/src/platform/plugins/shared/data_views/common/types.ts
@@ -326,7 +326,6 @@ export interface GetFieldsOptions {
includeEmptyFields?: boolean;
abortSignal?: AbortSignal;
runtimeMappings?: estypes.MappingRuntimeFields;
- // managed?: boolean;
}
/**
@@ -386,6 +385,7 @@ export type TypeMeta = {
export enum DataViewType {
DEFAULT = 'default',
ROLLUP = 'rollup',
+ MANAGED = 'managed',
}
export type FieldSpecConflictDescriptions = Record;
diff --git a/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts b/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
index 7b144e647f1f6..13f1473e5f4a4 100644
--- a/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
+++ b/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
@@ -54,6 +54,7 @@ export const dataViewSpecSchema = schema.object({
name: schema.maybe(schema.string()),
namespaces: schema.maybe(schema.arrayOf(schema.string())),
allowHidden: schema.maybe(schema.boolean()),
+ managed: schema.maybe(schema.boolean()),
});
export const dataViewsRuntimeResponseSchema = () =>
diff --git a/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
index b24f50e3a5253..96c75be95771a 100644
--- a/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
+++ b/src/platform/plugins/shared/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx
@@ -296,10 +296,6 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
};
}, []);
- const setDataViewEditorRef = useCallback((ref: () => void | undefined) => {
- closeDataViewEditor.current = ref;
- }, []);
-
const { dataViewEditor } = services;
const canEditDataView =
@@ -309,20 +305,12 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
const createNewDataView = useMemo(
() =>
canEditDataView
- ? () => {
- const ref = dataViewEditor.openEditor({
- onSave: async (dataView) => {
- onDataViewCreated(dataView);
- },
- isEdit: true,
- });
- if (setDataViewEditorRef) {
- setDataViewEditorRef(ref);
- }
+ ? (dataView: DataView) => {
+ onDataViewCreated(dataView);
closeFieldListFlyout?.();
}
: undefined,
- [canEditDataView, dataViewEditor, setDataViewEditorRef, onDataViewCreated, closeFieldListFlyout]
+ [canEditDataView, onDataViewCreated, closeFieldListFlyout]
);
const searchBarCustomization = useDiscoverCustomization('search_bar');
diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx
index b0efd8d08d5f0..8497fb179bfa6 100644
--- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx
+++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav.tsx
@@ -76,7 +76,6 @@ export const DiscoverTopNav = ({
}, [dataView, isEsqlMode]);
const closeFieldEditor = useRef<() => void | undefined>();
- const closeDataViewEditor = useRef<() => void | undefined>();
useEffect(() => {
return () => {
@@ -84,9 +83,6 @@ export const DiscoverTopNav = ({
if (closeFieldEditor.current) {
closeFieldEditor.current();
}
- if (closeDataViewEditor.current) {
- closeDataViewEditor.current();
- }
};
}, []);
@@ -119,13 +115,6 @@ export const DiscoverTopNav = ({
[editField, canEditDataView]
);
- const createNewDataView = useCallback(() => {
- closeDataViewEditor.current = dataViewEditor.openEditor({
- onSave: stateContainer.actions.onDataViewCreated,
- allowAdHocDataView: true,
- });
- }, [dataViewEditor, stateContainer]);
-
const updateSavedQueryId = (newSavedQueryId: string | undefined) => {
const { appState } = stateContainer;
if (newSavedQueryId) {
@@ -187,14 +176,14 @@ export const DiscoverTopNav = ({
},
currentDataViewId: dataView?.id,
onAddField: addField,
- onDataViewCreated: createNewDataView,
+ onDataViewCreated: stateContainer.actions.onDataViewCreated,
onCreateDefaultAdHocDataView: stateContainer.actions.createAndAppendAdHocDataView,
onChangeDataView: stateContainer.actions.onChangeDataView,
adHocDataViews,
savedDataViews,
onEditDataView: stateContainer.actions.onDataViewEdited,
};
- }, [adHocDataViews, addField, createNewDataView, dataView, savedDataViews, stateContainer]);
+ }, [adHocDataViews, addField, dataView, savedDataViews, stateContainer]);
const onESQLDocsFlyoutVisibilityChanged = useCallback((isOpen: boolean) => {
if (isOpen) {
diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx
index 95a05c19c8ba5..ce655b7857b65 100644
--- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx
+++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx
@@ -7,7 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { differenceBy } from 'lodash';
import {
type TypedUseSelectorHook,
type ReactReduxContextValue,
@@ -100,12 +99,9 @@ export const useCurrentTabAction = (
export const useCurrentChartPortalNode = () => useCurrentTabContext().currentChartPortalNode;
export const useDataViewsForPicker = () => {
- const originalAdHocDataViews = useAdHocDataViews();
+ const adHocDataViews = useAdHocDataViews();
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
-
return useMemo(() => {
- const adHocDataViews = differenceBy(originalAdHocDataViews, savedDataViews, 'id');
-
return { savedDataViews, adHocDataViews };
- }, [originalAdHocDataViews, savedDataViews]);
+ }, [adHocDataViews, savedDataViews]);
};
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts
index 22cf03cbc83c7..b44493ff65e79 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts
@@ -57,6 +57,7 @@ describe('classicNavRootProfileProvider', () => {
name: 'All logs',
timeFieldName: '@timestamp',
title: 'logs-*',
+ managed: true,
},
]);
});
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts
index cc31cc44bff48..e41dcfd2fa818 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts
@@ -70,6 +70,7 @@ describe('observabilityRootProfileProvider', () => {
name: 'All logs',
timeFieldName: '@timestamp',
title: 'logs-*',
+ managed: true,
},
]);
});
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
index f68695926f517..a00943488bc01 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
@@ -127,16 +127,13 @@ describe('DataView component', () => {
});
it('should render the add dataview menu if onDataViewCreated is given', async () => {
- const addDataViewSpy = jest.fn();
const component = mount(
- wrapDataViewComponentInContext({ ...props, onDataViewCreated: addDataViewSpy }, false)
+ wrapDataViewComponentInContext({ ...props, onDataViewCreated: jest.fn() }, false)
);
findTestSubject(component, 'dataview-trigger').simulate('click');
expect(component.find('[data-test-subj="dataview-create-new"]').at(0).text()).toContain(
'Create a data view'
);
- component.find('[data-test-subj="dataview-create-new"]').first().simulate('click');
- expect(addDataViewSpy).toHaveBeenCalled();
});
it('should properly handle ad hoc data views', async () => {
@@ -168,6 +165,7 @@ describe('DataView component', () => {
name: 'the-data-view',
type: 'default',
isAdhoc: true,
+ managed: undefined,
},
]);
});
@@ -201,6 +199,7 @@ describe('DataView component', () => {
name: 'the-data-view-esql',
type: 'esql',
isAdhoc: true,
+ managed: undefined,
},
]);
});
@@ -240,7 +239,6 @@ describe('DataView component', () => {
name: 'the-data-view',
type: 'default',
managed: true,
- isManaged: true,
},
]);
});
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
index a9223b8cefb4c..1f84380b54059 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
@@ -26,7 +26,8 @@ import {
EuiButtonEmpty,
} from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
-import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/public';
+import { DataView } from '@kbn/data-views-plugin/public';
+import type { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common';
import type { IUnifiedSearchPluginServices } from '../types';
import { type DataViewPickerProps } from './data_view_picker';
import type { DataViewListItemEnhanced } from './dataview_list';
@@ -34,28 +35,15 @@ import adhoc from './assets/adhoc.svg';
import { changeDataViewStyles } from './change_dataview.styles';
import { DataViewSelector } from './data_view_selector';
-const mapDataViewListItem = (
- dataView: DataView,
- partial: Partial
-): DataViewListItemEnhanced => ({
- title: dataView.title,
- name: dataView.name,
- id: dataView.id!,
- type: dataView.type,
- ...partial,
+const mapAdHocDataView = (adHocDataView: DataView): DataViewListItemEnhanced => ({
+ title: adHocDataView.title,
+ name: adHocDataView.name,
+ id: adHocDataView.id!,
+ type: adHocDataView.type,
+ isAdhoc: true,
+ managed: adHocDataView.managed,
});
-const mapAdHocDataView = (adHocDataView: DataView) => {
- if (adHocDataView.managed) {
- return mapDataViewListItem(adHocDataView, { isManaged: true });
- }
- return mapDataViewListItem(adHocDataView, { isAdhoc: true });
-};
-
-const mapManagedDataView = (managedDataView: DataViewListItem) => {
- return { ...managedDataView, isManaged: true };
-};
-
const shrinkableContainerCss = css`
min-width: 0;
`;
@@ -104,14 +92,13 @@ export function ChangeDataView({
useEffect(() => {
const fetchDataViews = async () => {
- const availableDataViewRefs = savedDataViews
+ const savedDataViewRefs = savedDataViews
? savedDataViews
: (await data.dataViews.getIdsWithTitle()) ?? [];
- const savedDataViewRefs = availableDataViewRefs.filter((dataView) => !dataView.managed);
- const managedDataViewRefs = savedDataViews?.filter((dataView) => dataView.managed === true).map(mapManagedDataView) ?? [];
const adHocDataViewRefs = adHocDataViews?.map(mapAdHocDataView) ?? [];
- setDataViewsList([...savedDataViewRefs, ...adHocDataViewRefs, ...managedDataViewRefs]);
+ setDataViewsList([...savedDataViewRefs, ...adHocDataViewRefs]);
};
+
fetchDataViews();
}, [data, currentDataViewId, adHocDataViews, savedDataViews]);
@@ -153,17 +140,64 @@ export function ChangeDataView({
);
};
const onDuplicate = useCallback(async () => {
- const dataView = await dataViews.get(currentDataViewId!);
+ if (!currentDataViewId || !onDataViewCreated) {
+ return;
+ }
+ const dataView = await dataViews.get(currentDataViewId);
+ const editData = new DataView({
+ spec: { title: dataView.getIndexPattern() },
+ fieldFormats: {} as FieldFormatsStartCommon,
+ });
+
+ dataViewEditor.openEditor({
+ editData,
+ onSave: (newDataView) => {
+ onDataViewCreated(newDataView);
+ },
+ allowAdHocDataView: true,
+ isDuplicatingManaged: true,
+ });
+ }, [currentDataViewId, dataViews, dataViewEditor, onDataViewCreated]);
+
+ const onEdit = useCallback(async () => {
if (onEditDataView) {
+ const dataView = await dataViews.get(currentDataViewId!);
dataViewEditor.openEditor({
editData: dataView,
onSave: (updatedDataView) => {
onEditDataView(updatedDataView);
},
+ onDuplicate,
+ getDataViewHelpText,
+ });
+ } else {
+ application.navigateToApp('management', {
+ path: `/kibana/indexPatterns/patterns/${currentDataViewId}`,
+ });
+ }
+ closePopover();
+ }, [
+ currentDataViewId,
+ dataViews,
+ onEditDataView,
+ dataViewEditor,
+ application,
+ closePopover,
+ onDuplicate,
+ getDataViewHelpText,
+ ]);
+
+ const onCreate = useCallback(() => {
+ if (onDataViewCreated) {
+ dataViewEditor.openEditor({
+ onSave: (newDataView) => {
+ onDataViewCreated(newDataView);
+ },
allowAdHocDataView: true,
});
}
- }, [currentDataViewId, dataViews, onEditDataView, dataViewEditor]);
+ closePopover();
+ }, [onDataViewCreated, dataViewEditor, closePopover]);
const items = useMemo(() => {
const panelItems: EuiContextMenuPanelProps['items'] = [];
@@ -187,25 +221,7 @@ export function ChangeDataView({
key="manage"
icon="indexSettings"
data-test-subj="indexPattern-manage-field"
- onClick={async () => {
- if (onEditDataView) {
- const dataView = await dataViews.get(currentDataViewId!);
- dataViewEditor.openEditor({
- editData: dataView,
- onSave: (updatedDataView) => {
- onEditDataView(updatedDataView);
- },
- onDuplicate,
- isEdit: true,
- getDataViewHelpText,
- });
- } else {
- application.navigateToApp('management', {
- path: `/kibana/indexPatterns/patterns/${currentDataViewId}`,
- });
- }
- closePopover();
- }}
+ onClick={onEdit}
>
{i18n.translate('unifiedSearch.query.queryBar.indexPattern.manageFieldButton', {
defaultMessage: 'Manage this data view',
@@ -245,10 +261,7 @@ export function ChangeDataView({
{
- closePopover();
- onDataViewCreated();
- }}
+ onClick={onCreate}
size="xs"
iconType="plusInCircleFilled"
iconSide="left"
@@ -278,21 +291,20 @@ export function ChangeDataView({
return panelItems;
}, [
- application,
closePopover,
currentDataViewId,
dataViewEditor,
- dataViews,
dataViewsList,
euiTheme.size.s,
onAddField,
onChangeDataView,
onCreateDefaultAdHocDataView,
+ onCreate,
onDataViewCreated,
+ onEdit,
onEditDataView,
searchListInputId,
selectableProps,
- getDataViewHelpText,
]);
return (
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
index 8abc62cb4b321..fca87dd69cf0d 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
@@ -60,9 +60,10 @@ export interface DataViewPickerProps {
onAddField?: () => void;
/**
* Callback that is called when the user clicks the create dataview option.
+ * The first parameter is the created data view
* Also works as a flag to show the create dataview button.
*/
- onDataViewCreated?: () => void;
+ onDataViewCreated?: (createdDataView: DataView) => void;
onCreateDefaultAdHocDataView?: (dataViewSpec: DataViewSpec) => void;
/**
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/dataview_list.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/dataview_list.tsx
index 629823b0b7cb3..6fa39ed431699 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/dataview_list.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/dataview_list.tsx
@@ -68,7 +68,6 @@ const strings = {
export interface DataViewListItemEnhanced extends DataViewListItem {
isAdhoc?: boolean;
- isManaged?: boolean;
}
export interface DataViewsListProps {
@@ -137,19 +136,19 @@ export function DataViewsList({
data-test-subj="indexPattern-switcher"
searchable
singleSelection="always"
- options={sortedDataViewsList?.map(({ title, id, name, isAdhoc, isManaged }) => ({
+ options={sortedDataViewsList?.map(({ title, id, name, isAdhoc, managed }) => ({
key: id,
label: name ? name : title,
value: id,
checked: id === currentDataViewId ? 'on' : undefined,
- append: isAdhoc ? (
-
- {strings.editorAndPopover.adhoc.getTemporaryDataviewLabel()}
-
- ) : isManaged ? (
+ append: managed ? (
{strings.editorAndPopover.managed.getManagedDataviewLabel()}
+ ) : isAdhoc ? (
+
+ {strings.editorAndPopover.adhoc.getTemporaryDataviewLabel()}
+
) : null,
}))}
onChange={(choices) => {
diff --git a/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts b/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
index 45c7d6d3a3e70..981469cf2ac02 100644
--- a/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
+++ b/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
@@ -36,7 +36,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await dataViews.getSelectedName()).not.to.be('Example profile data view');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -51,7 +52,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await discover.waitUntilSearchingHasFinished();
expect(await toasts.getCount({ timeout: 2000 })).to.be(0);
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
});
it('should create a copy of the profile data view when saving the Discover session', async () => {
@@ -70,7 +72,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
).to.be('2024-06-10T14:00:00.000Z');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -99,7 +102,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
diff --git a/x-pack/platform/plugins/shared/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/platform/plugins/shared/lens/public/app_plugin/lens_top_nav.tsx
index 6ed5d3f484088..f2c871189ffb7 100644
--- a/x-pack/platform/plugins/shared/lens/public/app_plugin/lens_top_nav.tsx
+++ b/x-pack/platform/plugins/shared/lens/public/app_plugin/lens_top_nav.tsx
@@ -394,7 +394,6 @@ export const LensTopNavMenu = ({
const canEditDataView =
Boolean(dataViewEditor?.userPermissions.editDataView()) || !currentIndexPattern?.isPersisted();
const closeFieldEditor = useRef<() => void | undefined>();
- const closeDataViewEditor = useRef<() => void | undefined>();
const allLoaded = Object.values(datasourceStates).every(({ isLoading }) => isLoading === false);
@@ -491,7 +490,6 @@ export const LensTopNavMenu = ({
return () => {
// Make sure to close the editors when unmounting
closeFieldEditor.current?.();
- closeDataViewEditor.current?.();
};
}, []);
@@ -1107,32 +1105,24 @@ export const LensTopNavMenu = ({
[editField, canEditDataView]
);
- const createNewDataView = useCallback(() => {
- closeDataViewEditor.current = dataViewEditor.openEditor({
- onSave: async (dataView) => {
- if (dataView.id) {
- if (isOnTextBasedMode) {
- dispatch(
- switchAndCleanDatasource({
- newDatasourceId: 'formBased',
- visualizationId: visualization?.activeId,
- currentIndexPatternId: dataView?.id,
- })
- );
- }
- dispatchChangeIndexPattern(dataView);
- setCurrentIndexPattern(dataView);
+ const createNewDataView = useCallback(
+ async (dataView: DataView) => {
+ if (dataView.id) {
+ if (isOnTextBasedMode) {
+ dispatch(
+ switchAndCleanDatasource({
+ newDatasourceId: 'formBased',
+ visualizationId: visualization?.activeId,
+ currentIndexPatternId: dataView?.id,
+ })
+ );
}
- },
- allowAdHocDataView: true,
- });
- }, [
- dataViewEditor,
- dispatch,
- dispatchChangeIndexPattern,
- isOnTextBasedMode,
- visualization?.activeId,
- ]);
+ dispatchChangeIndexPattern(dataView);
+ setCurrentIndexPattern(dataView);
+ }
+ },
+ [dispatch, dispatchChangeIndexPattern, isOnTextBasedMode, visualization?.activeId]
+ );
const onCreateDefaultAdHocDataView = useCallback(
async (dataViewSpec: DataViewSpec) => {
diff --git a/x-pack/platform/plugins/shared/logs_shared/server/services/log_views/log_views_client.test.ts b/x-pack/platform/plugins/shared/logs_shared/server/services/log_views/log_views_client.test.ts
index 206bb0d9f416f..f31a7d293e5e5 100644
--- a/x-pack/platform/plugins/shared/logs_shared/server/services/log_views/log_views_client.test.ts
+++ b/x-pack/platform/plugins/shared/logs_shared/server/services/log_views/log_views_client.test.ts
@@ -285,6 +285,7 @@ describe('LogViewsClient class', () => {
"getTimeField": [Function],
"id": "LOG_DATA_VIEW",
"isTimeBased": [Function],
+ "managed": false,
"matchedIndices": Array [],
"metaFields": Array [
"_id",
diff --git a/x-pack/platform/plugins/shared/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts b/x-pack/platform/plugins/shared/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts
index 43eb7d82a27ea..1d9bf8ce461a0 100644
--- a/x-pack/platform/plugins/shared/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts
+++ b/x-pack/platform/plugins/shared/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts
@@ -839,6 +839,7 @@ describe('fetchSearchSourceQuery', () => {
title: 'title',
type: 'index-pattern',
version: undefined,
+ managed: false,
},
true // skipFetchFields flag
);
diff --git a/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts b/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts
index c77b58b8e5cf8..d8ea9e5b20e63 100644
--- a/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts
+++ b/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts
@@ -48,6 +48,7 @@ export default function ({ getService }: FtrProviderContext) {
index_pattern: {
title,
},
+ managed: false,
});
const id = response1.body.index_pattern.id;
const response2 = await supertestWithoutAuth
diff --git a/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts b/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
index 8732101199528..8f079d01367e6 100644
--- a/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
+++ b/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
@@ -39,7 +39,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await dataViews.getSelectedName()).not.to.be('Example profile data view');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -54,7 +55,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await discover.waitUntilSearchingHasFinished();
expect(await toasts.getCount({ timeout: 2000 })).to.be(0);
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
});
it('should create a copy of the profile data view when saving the Discover session', async () => {
@@ -73,7 +75,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
).to.be('Jun 10, 2024 @ 14:00:00.000');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -105,7 +108,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isAdHoc()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.test.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.test.ts
index 5c9190dc12945..cb9b4a0f0ba32 100644
--- a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.test.ts
+++ b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/logs_explorer_url_schema.test.ts
@@ -129,6 +129,7 @@ describe('logs_explorer_url_schema', () => {
name: 'All logs',
title: 'logs-*,dataset-logs-*-*',
timeFieldName: '@timestamp',
+ managed: true,
});
});
});
diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/components/indicator_section/custom_common/index_selection.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/components/indicator_section/custom_common/index_selection.tsx
index d15a716a92c6f..14c7fc6984f3b 100644
--- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/components/indicator_section/custom_common/index_selection.tsx
+++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_edit/components/indicator_section/custom_common/index_selection.tsx
@@ -27,11 +27,8 @@ const SETTINGS_SYNC_FIELD = 'settings.syncField';
export function IndexSelection({ selectedDataView }: { selectedDataView?: DataView }) {
const { control, getFieldState, setValue, watch } = useFormContext();
- const {
- dataViews: dataViewsService,
- dataViewFieldEditor,
- dataViewEditor,
- } = useKibana().services;
+ const { dataViews: dataViewsService, dataViewFieldEditor } =
+ useKibana().services;
const currentIndexPattern = watch(INDEX_FIELD);
const currentDataViewId = watch(DATA_VIEW_FIELD);
@@ -122,20 +119,15 @@ export function IndexSelection({ selectedDataView }: { selectedDataView?: DataVi
adHocDataViews,
})
}
- onDataViewCreated={() => {
- dataViewEditor.openEditor({
- allowAdHocDataView: true,
- onSave: (dataView: DataView) => {
- if (!dataView.isPersisted()) {
- setAdHocDataViews((prev) => [...prev, dataView]);
- } else {
- refetchDataViewsList();
- }
+ onDataViewCreated={(dataView: DataView) => {
+ if (!dataView.isPersisted()) {
+ setAdHocDataViews((prev) => [...prev, dataView]);
+ } else {
+ refetchDataViewsList();
+ }
- field.onChange(dataView.id);
- updateDataViewDependantFields(dataView.getIndexPattern(), dataView.timeFieldName);
- },
- });
+ field.onChange(dataView.id);
+ updateDataViewDependantFields(dataView.getIndexPattern(), dataView.timeFieldName);
}}
/>
)}
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.test.tsx
index 2465e37a88704..3fbaec265927d 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.test.tsx
@@ -8,11 +8,9 @@
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { DataViewPicker } from '.';
-import { sharedDataViewManagerSlice } from '../../redux/slices';
import { useDispatch } from 'react-redux';
import { useKibana } from '../../../common/lib/kibana';
-import { DataView } from '@kbn/data-views-plugin/common';
-import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
+import type { DataView } from '@kbn/data-views-plugin/common';
import { TestProviders } from '../../../common/mock/test_providers';
import { useSelectDataView } from '../../hooks/use_select_data_view';
import { useUpdateUrlParam } from '../../../common/utils/global_query_string';
@@ -50,7 +48,11 @@ jest.mock('@kbn/unified-search-plugin/public', () => ({
>
{'Change Data View'}
-
)}
-
{showDuplicateButton && (
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
index 1f84380b54059..af732865bd751 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
@@ -160,8 +160,8 @@ export function ChangeDataView({
}, [currentDataViewId, dataViews, dataViewEditor, onDataViewCreated]);
const onEdit = useCallback(async () => {
- if (onEditDataView) {
- const dataView = await dataViews.get(currentDataViewId!);
+ if (onEditDataView && currentDataViewId) {
+ const dataView = await dataViews.get(currentDataViewId);
dataViewEditor.openEditor({
editData: dataView,
onSave: (updatedDataView) => {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
index 063171e266f93..deed67577561b 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
@@ -134,7 +134,7 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie
dv.id === defaultDataViewId ? (
{'securitySolution:defaultIndex'},
}}
From 1776b008e9c38c68bd266b97125bb5705e90e1cf Mon Sep 17 00:00:00 2001
From: christineweng
Date: Wed, 27 Aug 2025 17:03:58 -0500
Subject: [PATCH 04/12] comments and make duplicate available to all data views
---
.../logs/utils/get_all_logs_data_view_spec.ts | 1 -
.../advanced_params_content.tsx | 2 +-
.../public/components/data_view_editor.tsx | 4 +-
.../data_view_editor_flyout_content.tsx | 48 ++++++++++++-------
.../data_view_flyout_content_container.tsx | 6 +--
.../public/components/footer/footer.tsx | 8 ++--
.../data_view_editor/public/open_editor.tsx | 4 +-
.../shared/data_view_editor/public/types.ts | 6 +--
.../components/field_editor/field_detail.tsx | 7 ++-
.../components/field_editor/field_editor.tsx | 10 ++--
.../components/field_editor/form_row.tsx | 3 ++
.../field_editor_flyout_content.tsx | 38 +++++++++------
.../public/components/utils.ts | 2 +-
.../plugins/shared/data_views/common/types.ts | 1 -
.../public/create_data_view.ts | 1 -
.../server/rest_api_routes/schema.ts | 1 -
.../use_default_ad_hoc_data_views.test.tsx | 4 +-
.../hooks/use_default_ad_hoc_data_views.ts | 2 +-
.../classic_nav_root_profile/profile.test.ts | 1 -
.../profile.test.ts | 1 -
.../dataview_picker/change_dataview.test.tsx | 8 +---
.../dataview_picker/change_dataview.tsx | 12 ++---
.../dataview_picker/data_view_picker.tsx | 2 +-
.../_get_default_ad_hoc_data_views.ts | 12 ++---
.../data_views/integration/integration.ts | 1 -
.../public/data_view_manager/redux/slices.ts | 2 +-
26 files changed, 101 insertions(+), 86 deletions(-)
diff --git a/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts b/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts
index eb36e1a05fbe4..41a72405698c6 100644
--- a/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts
+++ b/src/platform/packages/shared/kbn-discover-utils/src/data_types/logs/utils/get_all_logs_data_view_spec.ts
@@ -22,5 +22,4 @@ export const getAllLogsDataViewSpec = ({
}),
title: allLogsIndexPattern,
timeFieldName: '@timestamp',
- managed: true,
});
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx b/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx
index a1aafb4f4b63b..3151005389295 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx
@@ -50,7 +50,7 @@ export const AdvancedParamsContent = ({
componentProps={{
euiFieldProps: {
'aria-label': allowHiddenAriaLabel,
- disabled: disableAllowHidden,
+ disabled: disableAllowHidden || disableId,
},
}}
onChange={onAllowHiddenChange}
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
index 79daa0c3b5181..43a3b72efb517 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.tsx
@@ -26,7 +26,7 @@ export const DataViewEditor = ({
editData,
allowAdHocDataView,
onDuplicate,
- isDuplicatingManaged,
+ isDuplicating,
getDataViewHelpText,
}: DataViewEditorPropsWithServices) => {
const { Provider: KibanaReactContextProvider } =
@@ -48,7 +48,7 @@ export const DataViewEditor = ({
editData={editData}
allowAdHocDataView={allowAdHocDataView}
onDuplicate={onDuplicate}
- isDuplicatingManaged={isDuplicatingManaged}
+ isDuplicating={isDuplicating}
getDataViewHelpText={getDataViewHelpText}
/>
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
index 8b1f1aa4976c4..0e1dc2e2e3382 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
@@ -18,6 +18,7 @@ import {
EuiLink,
EuiSkeletonRectangle,
EuiSkeletonTitle,
+ EuiCallOut,
type UseEuiTheme,
useIsWithinBreakpoints,
} from '@elastic/eui';
@@ -72,7 +73,7 @@ export interface Props {
showManagementLink?: boolean;
allowAdHoc: boolean;
dataViewEditorService: DataViewEditorService;
- isDuplicatingManaged: boolean;
+ isDuplicating: boolean;
onDuplicate?: () => void;
getDataViewHelpText?: (dataView: DataView) => ReactNode | string | undefined;
}
@@ -95,7 +96,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
getDataViewHelpText,
dataViewEditorService,
onDuplicate,
- isDuplicatingManaged,
+ isDuplicating,
}: Props) => {
const styles = useMemoCss(componentStyles);
const isMobile = useIsWithinBreakpoints(['s', 'xs']);
@@ -106,10 +107,9 @@ const IndexPatternEditorFlyoutContentComponent = ({
const canSave = dataViews.getCanSaveSync();
const isManaged = !!editData?.managed;
- // Edit form is populated and disabled if the current data view is managed
- // and the data view is not being duplicated
- const isFormDisabled = isManaged && !isDuplicatingManaged;
- const isEditingExisting = editData && !isManaged && !isDuplicatingManaged;
+ // onDuplicate is only provided when editing an existing data view
+ // if onDuplicate is undefined we are duplicating a new data view
+ const isEditingExisting = editData && !isDuplicating;
const { form } = useForm({
// Prefill with data if editData exists
@@ -122,8 +122,8 @@ const IndexPatternEditorFlyoutContentComponent = ({
...(editData
? {
title: editData.getIndexPattern(),
- id: isDuplicatingManaged ? undefined : editData.id,
- name: isDuplicatingManaged ? undefined : editData.name,
+ id: isDuplicating ? undefined : editData.id,
+ name: isDuplicating ? undefined : editData.name,
allowHidden: editData.getAllowHidden(),
...(editData.timeFieldName === noTimeFieldValue
? { timestampField: { label: noTimeFieldLabel, value: noTimeFieldValue } }
@@ -193,13 +193,13 @@ const IndexPatternEditorFlyoutContentComponent = ({
const rollupIndicesCapabilities = useObservable(dataViewEditorService.rollupIndicesCaps$, {});
const namesNotAllowed = useMemo(() => {
- // When duplicating a managed data view, add the existing name
+ // When duplicating a data view, add the existing name
// to the not allowed names list
- if (isDuplicatingManaged && editData) {
+ if (isDuplicating && editData) {
return [editData.name, ...(existingDataViewNames || [])];
}
return existingDataViewNames || [];
- }, [existingDataViewNames, isDuplicatingManaged, editData]);
+ }, [existingDataViewNames, isDuplicating, editData]);
useDebounce(
() => {
@@ -309,7 +309,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
canSave={canSave}
isManaged={isManaged}
onDuplicate={onDuplicate}
- isDuplicatingManaged={isDuplicatingManaged}
+ isDuplicating={isDuplicating}
/>
);
@@ -319,10 +319,10 @@ const IndexPatternEditorFlyoutContentComponent = ({
- {editData && !isDuplicatingManaged ? editorTitleEditMode : editorTitle}
+ {isEditingExisting ? editorTitleEditMode : editorTitle}
- {showManagementLink && !isDuplicatingManaged && editData && editData.id && (
+ {showManagementLink && isEditingExisting && editData && editData.id && (
)}
+ {isManaged && (
+
+ )}
-
+
@@ -360,7 +372,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
dataViewEditorService.indexPatternValidationProvider
}
titleHelpText={titleHelpText}
- disabled={isFormDisabled}
+ disabled={isManaged}
/>
@@ -371,13 +383,13 @@ const IndexPatternEditorFlyoutContentComponent = ({
options$={dataViewEditorService.timestampFieldOptions$}
isLoadingOptions$={dataViewEditorService.loadingTimestampFields$}
matchedIndices$={dataViewEditorService.matchedIndices$}
- disabled={isFormDisabled}
+ disabled={isManaged}
/>
{
form.getFields().title.validate();
}}
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
index 9659394bc812b..8f49ac4cb14f1 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
@@ -26,7 +26,7 @@ const DataViewFlyoutContentContainer = ({
allowAdHocDataView,
showManagementLink,
onDuplicate,
- isDuplicatingManaged = false,
+ isDuplicating = false,
getDataViewHelpText,
}: DataViewEditorProps) => {
const {
@@ -54,7 +54,7 @@ const DataViewFlyoutContentContainer = ({
const onSaveClick = async (dataViewSpec: DataViewSpec, persist: boolean = true) => {
try {
let saveResponse;
- if (editData && !editData.managed && !isDuplicatingManaged) {
+ if (editData && !editData.managed && !isDuplicating) {
const { name = '', timeFieldName, title = '', allowHidden = false } = dataViewSpec;
editData.setIndexPattern(title);
editData.name = name;
@@ -104,7 +104,7 @@ const DataViewFlyoutContentContainer = ({
allowAdHoc={allowAdHocDataView || false}
dataViewEditorService={dataViewEditorService}
onDuplicate={onDuplicate}
- isDuplicatingManaged={isDuplicatingManaged}
+ isDuplicating={isDuplicating}
getDataViewHelpText={getDataViewHelpText}
/>
);
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
index 46930a85f6ffd..5d633f4c2af66 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
@@ -34,7 +34,7 @@ interface FooterProps {
allowAdHoc: boolean;
canSave: boolean;
isManaged: boolean;
- isDuplicatingManaged: boolean;
+ isDuplicating: boolean;
}
const closeButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutCloseButtonLabel', {
@@ -78,12 +78,12 @@ export const Footer = ({
canSave,
onDuplicate,
isManaged,
- isDuplicatingManaged,
+ isDuplicating,
}: FooterProps) => {
const isEditingAdHoc = hasEditData && !isPersisted;
const isEditing = (canSave || isEditingAdHoc) && !isManaged;
- const showDuplicateButton = (canSave || isEditingAdHoc) && isManaged && onDuplicate;
+ const showDuplicateButton = (canSave || isEditingAdHoc) && onDuplicate;
const submitPersisted = () => {
onSubmit(false);
@@ -138,7 +138,7 @@ export const Footer = ({
(submittingType === SubmittingType.savingAsAdHoc && isEditingAdHoc)
}
>
- {hasEditData && !isDuplicatingManaged
+ {hasEditData && !isDuplicating
? isPersisted
? editButtonLabel
: editUnpersistedButtonLabel
diff --git a/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx b/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
index 5f82559aaaf14..f1e7189f6babe 100644
--- a/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/open_editor.tsx
@@ -51,7 +51,7 @@ export const getEditorOpener =
allowAdHocDataView = false,
editData,
onDuplicate,
- isDuplicatingManaged,
+ isDuplicating,
getDataViewHelpText,
}: DataViewEditorProps): CloseEditor => {
const closeEditor = () => {
@@ -84,7 +84,7 @@ export const getEditorOpener =
allowAdHocDataView={allowAdHocDataView}
showManagementLink={Boolean(editData && editData.isPersisted())}
onDuplicate={onDuplicate}
- isDuplicatingManaged={isDuplicatingManaged}
+ isDuplicating={isDuplicating}
getDataViewHelpText={getDataViewHelpText}
/>
,
diff --git a/src/platform/plugins/shared/data_view_editor/public/types.ts b/src/platform/plugins/shared/data_view_editor/public/types.ts
index c20e70fdb7d8e..af96f240393e6 100644
--- a/src/platform/plugins/shared/data_view_editor/public/types.ts
+++ b/src/platform/plugins/shared/data_view_editor/public/types.ts
@@ -72,13 +72,13 @@ export interface DataViewEditorProps {
*/
showManagementLink?: boolean;
/**
- * if editing a managed data view and onDuplicate is defined, a duplicate button is shown
+ * If editing a data view and onDuplicate is defined, a duplicate button is shown
*/
onDuplicate?: () => void;
/**
- * if editing a managed data view and onDuplicate is defined, a duplicate button is shown
+ * Optional boolean to indicate if the data view is being duplicated
*/
- isDuplicatingManaged?: boolean;
+ isDuplicating?: boolean;
/**
* Optional callback to get help text based on the active data view
*/
diff --git a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/field_detail.tsx b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/field_detail.tsx
index b0d78067d59f8..57b0e7e7c3cf1 100644
--- a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/field_detail.tsx
+++ b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/field_detail.tsx
@@ -79,7 +79,7 @@ const geti18nTexts = (): {
},
});
-export const FieldDetail = ({}) => {
+export const FieldDetail = ({ isDisabled }: { isDisabled?: boolean }) => {
const { links, fieldTypeToProcess } = useFieldEditorContext();
const i18nTexts = geti18nTexts();
return (
@@ -88,6 +88,7 @@ export const FieldDetail = ({}) => {
{
{
{
{
void;
/** Handler to receive update on the form "isModified" state */
onFormModifiedChange?: (isModified: boolean) => void;
+ /** If disabled, the field editor will not be editable */
+ isDisabled?: boolean;
}
const changeWarning = i18n.translate('indexPatternFieldEditor.editor.form.changeWarning', {
@@ -111,7 +113,7 @@ const formSerializer = (field: FieldFormInternal): Field => {
};
};
-const FieldEditorComponent = ({ field, onChange, onFormModifiedChange }: Props) => {
+const FieldEditorComponent = ({ field, onChange, onFormModifiedChange, isDisabled }: Props) => {
const { fieldTypeToProcess, fieldName$, subfields$, dataView } = useFieldEditorContext();
const {
params: { update: updatePreviewParams },
@@ -260,7 +262,7 @@ const FieldEditorComponent = ({ field, onChange, onFormModifiedChange }: Props)
data-test-subj="nameField"
componentProps={{
euiFieldProps: {
- disabled: fieldTypeToProcess === 'concrete',
+ disabled: fieldTypeToProcess === 'concrete' || isDisabled,
'aria-label': i18n.translate('indexPatternFieldEditor.editor.form.nameAriaLabel', {
defaultMessage: 'Name field',
}),
@@ -272,7 +274,7 @@ const FieldEditorComponent = ({ field, onChange, onFormModifiedChange }: Props)
{/* Type */}
@@ -304,7 +306,7 @@ const FieldEditorComponent = ({ field, onChange, onFormModifiedChange }: Props)
>
)}
{updatedType && updatedType[0].value !== 'composite' ? (
-
+
) : (
)}
diff --git a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/form_row.tsx b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/form_row.tsx
index ea12517e885ac..b66630e30ef8e 100644
--- a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/form_row.tsx
+++ b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor/form_row.tsx
@@ -26,6 +26,7 @@ interface Props {
children: React.ReactNode;
description?: string | JSX.Element;
withDividerRule?: boolean;
+ disabled?: boolean;
'data-test-subj'?: string;
}
@@ -34,6 +35,7 @@ export const FormRow = ({
description,
children,
formFieldPath,
+ disabled,
withDividerRule = false,
'data-test-subj': dataTestSubj,
}: Props) => {
@@ -52,6 +54,7 @@ export const FormRow = ({
label: title,
showLabel: false,
'data-test-subj': 'toggle',
+ disabled,
},
}}
/>
diff --git a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
index e91c25da8c1aa..0aae120b9881e 100644
--- a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
@@ -10,11 +10,11 @@
import {
EuiButton,
EuiButtonEmpty,
+ EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiTitle,
- EuiToolTip,
useIsWithinMaxBreakpoint,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -39,8 +39,8 @@ const i18nTexts = {
saveButtonLabel: i18n.translate('indexPatternFieldEditor.editor.flyoutSaveButtonLabel', {
defaultMessage: 'Save',
}),
- disabledSaveButtonTooltip: i18n.translate(
- 'indexPatternFieldEditor.editor.flyoutDisabledSaveButtonTooltip',
+ disabledSaveCalloutMessage: i18n.translate(
+ 'indexPatternFieldEditor.editor.flyoutDisabledSaveCalloutMessage',
{
defaultMessage:
'Fields cannot be edited on managed data views. Duplicate the data view in order to make changes.',
@@ -251,12 +251,22 @@ const FieldEditorFlyoutContentComponent = ({
/>
+ {dataView.managed && (
+
+ )}
@@ -275,18 +285,16 @@ const FieldEditorFlyoutContentComponent = ({
-
-
- {i18nTexts.saveButtonLabel}
-
-
+
+ {i18nTexts.saveButtonLabel}
+
>
diff --git a/src/platform/plugins/shared/data_view_management/public/components/utils.ts b/src/platform/plugins/shared/data_view_management/public/components/utils.ts
index a4dc348df1fed..4e118f2f5a642 100644
--- a/src/platform/plugins/shared/data_view_management/public/components/utils.ts
+++ b/src/platform/plugins/shared/data_view_management/public/components/utils.ts
@@ -80,7 +80,7 @@ export const getTags = (
const tags = [];
if (indexPattern.managed) {
tags.push({
- key: DataViewType.MANAGED,
+ key: 'managed',
name: managedDataViewTagName,
'data-test-subj': 'managed-tag',
});
diff --git a/src/platform/plugins/shared/data_views/common/types.ts b/src/platform/plugins/shared/data_views/common/types.ts
index 184098adc8848..402219f496836 100644
--- a/src/platform/plugins/shared/data_views/common/types.ts
+++ b/src/platform/plugins/shared/data_views/common/types.ts
@@ -385,7 +385,6 @@ export type TypeMeta = {
export enum DataViewType {
DEFAULT = 'default',
ROLLUP = 'rollup',
- MANAGED = 'managed',
}
export type FieldSpecConflictDescriptions = Record;
diff --git a/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts b/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts
index 38078687bec97..eb1bc9590ee4b 100644
--- a/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts
+++ b/src/platform/plugins/shared/data_views/server/rest_api_routes/public/create_data_view.ts
@@ -80,7 +80,6 @@ const registerCreateDataViewRouteFactory =
data_view: serviceKey === SERVICE_KEY ? dataViewSpecSchema : schema.never(),
index_pattern:
serviceKey === SERVICE_KEY_LEGACY ? dataViewSpecSchema : schema.never(),
- managed: schema.maybe(schema.boolean({ defaultValue: false })),
}),
},
response: {
diff --git a/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts b/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
index 13f1473e5f4a4..7b144e647f1f6 100644
--- a/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
+++ b/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
@@ -54,7 +54,6 @@ export const dataViewSpecSchema = schema.object({
name: schema.maybe(schema.string()),
namespaces: schema.maybe(schema.arrayOf(schema.string())),
allowHidden: schema.maybe(schema.boolean()),
- managed: schema.maybe(schema.boolean()),
});
export const dataViewsRuntimeResponseSchema = () =>
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.test.tsx
index 80dfff526415c..c471d70a7716e 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.test.tsx
+++ b/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.test.tsx
@@ -82,7 +82,9 @@ describe('useDefaultAdHocDataViews', () => {
);
await result.current.initializeProfileDataViews(rootProfileState);
expect(clearInstanceCache.mock.calls).toEqual(previousDataViews.map((dv) => [dv.id]));
- expect(createDataView.mock.calls).toEqual(newDataViews.map((dv) => [dv.toSpec(), true]));
+ expect(createDataView.mock.calls).toEqual(
+ newDataViews.map((dv) => [{ ...dv.toSpec(), managed: true }, true])
+ );
expect(
stateContainer.runtimeStateManager.adHocDataViews$.getValue().map((dv) => dv.id)
).toEqual([existingAdHocDataVew.id, ...newDataViews.map((dv) => dv.id)]);
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.ts b/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.ts
index 005d31aa5df73..79fb7158e9422 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.ts
+++ b/src/platform/plugins/shared/discover/public/context_awareness/hooks/use_default_ad_hoc_data_views.ts
@@ -40,7 +40,7 @@ export const useDefaultAdHocDataViews = () => {
const profileDataViewSpecs = rootProfileState.getDefaultAdHocDataViews();
const profileDataViews = await Promise.all(
- profileDataViewSpecs.map((spec) => dataViews.create(spec, true))
+ profileDataViewSpecs.map((spec) => dataViews.create({ ...spec, managed: true }, true))
);
dispatch(internalStateActions.setDefaultProfileAdHocDataViews(profileDataViews));
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts
index b44493ff65e79..22cf03cbc83c7 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/common/classic_nav_root_profile/profile.test.ts
@@ -57,7 +57,6 @@ describe('classicNavRootProfileProvider', () => {
name: 'All logs',
timeFieldName: '@timestamp',
title: 'logs-*',
- managed: true,
},
]);
});
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts
index e41dcfd2fa818..cc31cc44bff48 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts
@@ -70,7 +70,6 @@ describe('observabilityRootProfileProvider', () => {
name: 'All logs',
timeFieldName: '@timestamp',
title: 'logs-*',
- managed: true,
},
]);
});
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
index a00943488bc01..4607164bcb75f 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.test.tsx
@@ -20,9 +20,6 @@ import { DataViewSelector } from './data_view_selector';
import { dataViewMock, dataViewMockEsql } from './mocks/dataview';
import type { DataViewPickerProps } from './data_view_picker';
-const dataViewEditorMock = dataViewEditorPluginMock.createStartContract();
-const openEditorMock = jest.fn();
-
describe('DataView component', () => {
const createMockWebStorage = () => ({
clear: jest.fn(),
@@ -51,8 +48,8 @@ describe('DataView component', () => {
storageValue: boolean,
uiSettingValue: boolean = false
) {
+ const dataViewEditorMock = dataViewEditorPluginMock.createStartContract();
(dataViewEditorMock.userPermissions.editDataView as jest.Mock).mockReturnValue(true);
- (dataViewEditorMock.openEditor as jest.Mock).mockImplementation(openEditorMock);
let dataMock = dataPluginMock.createStartContract();
dataMock = {
...dataMock,
@@ -69,9 +66,6 @@ describe('DataView component', () => {
uiSettings: {
get: jest.fn(() => uiSettingValue),
},
- application: {
- navigateToApp: jest.fn(),
- },
};
return (
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
index af732865bd751..a6878346ac320 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/change_dataview.tsx
@@ -26,8 +26,7 @@ import {
EuiButtonEmpty,
} from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
-import { DataView } from '@kbn/data-views-plugin/public';
-import type { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common';
+import type { DataView } from '@kbn/data-views-plugin/public';
import type { IUnifiedSearchPluginServices } from '../types';
import { type DataViewPickerProps } from './data_view_picker';
import type { DataViewListItemEnhanced } from './dataview_list';
@@ -144,9 +143,10 @@ export function ChangeDataView({
return;
}
const dataView = await dataViews.get(currentDataViewId);
- const editData = new DataView({
- spec: { title: dataView.getIndexPattern() },
- fieldFormats: {} as FieldFormatsStartCommon,
+ const editData = await dataViews.create({
+ ...dataView.toSpec(),
+ id: undefined,
+ managed: false,
});
dataViewEditor.openEditor({
@@ -155,7 +155,7 @@ export function ChangeDataView({
onDataViewCreated(newDataView);
},
allowAdHocDataView: true,
- isDuplicatingManaged: true,
+ isDuplicating: true,
});
}, [currentDataViewId, dataViews, dataViewEditor, onDataViewCreated]);
diff --git a/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx b/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
index fca87dd69cf0d..5f4a98680fac3 100644
--- a/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
+++ b/src/platform/plugins/shared/unified_search/public/dataview_picker/data_view_picker.tsx
@@ -59,7 +59,7 @@ export interface DataViewPickerProps {
*/
onAddField?: () => void;
/**
- * Callback that is called when the user clicks the create dataview option.
+ * Callback that is called when the user creates a new data view through the picker menu.
* The first parameter is the created data view
* Also works as a flag to show the create dataview button.
*/
diff --git a/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts b/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
index 981469cf2ac02..45c7d6d3a3e70 100644
--- a/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
+++ b/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
@@ -36,8 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await dataViews.getSelectedName()).not.to.be('Example profile data view');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -52,8 +51,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await discover.waitUntilSearchingHasFinished();
expect(await toasts.getCount({ timeout: 2000 })).to.be(0);
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
});
it('should create a copy of the profile data view when saving the Discover session', async () => {
@@ -72,8 +70,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
).to.be('2024-06-10T14:00:00.000Z');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -102,8 +99,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
diff --git a/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts b/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts
index d8ea9e5b20e63..c77b58b8e5cf8 100644
--- a/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts
+++ b/x-pack/platform/test/serverless/api_integration/test_suites/data_views/integration/integration.ts
@@ -48,7 +48,6 @@ export default function ({ getService }: FtrProviderContext) {
index_pattern: {
title,
},
- managed: false,
});
const id = response1.body.index_pattern.id;
const response2 = await supertestWithoutAuth
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts
index 45c7ceb42ce42..f771f5e4883f3 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts
@@ -63,7 +63,7 @@ export const sharedDataViewManagerSlice = createSlice({
// NOTE: user is allowed to duplicate a managed data view and
// we want both to show up in the list
state.adhocDataViews.find(
- (dv) => dv.title === dataViewSpec.title && dv.name === dataViewSpec.managed
+ (dv) => dv.title === dataViewSpec.title && dv.managed === dataViewSpec.managed
)
) {
return;
From e9872f3f4fd8464e1e78805971c0ebd22dfe010b Mon Sep 17 00:00:00 2001
From: christineweng
Date: Thu, 28 Aug 2025 15:59:04 -0500
Subject: [PATCH 05/12] add unit tests
---
.../public/components/footer/footer.test.tsx | 161 ++++++++++++++++++
.../public/components/footer/footer.tsx | 2 +-
.../_get_default_ad_hoc_data_views.ts | 2 +-
.../_get_default_ad_hoc_data_views.ts | 5 +-
4 files changed, 165 insertions(+), 5 deletions(-)
create mode 100644 src/platform/plugins/shared/data_view_editor/public/components/footer/footer.test.tsx
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.test.tsx b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.test.tsx
new file mode 100644
index 0000000000000..6cd82e241b816
--- /dev/null
+++ b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.test.tsx
@@ -0,0 +1,161 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import React from 'react';
+import { fireEvent, render } from '@testing-library/react';
+import { Footer } from './footer';
+
+const onSubmit = jest.fn();
+const props = {
+ onCancel: jest.fn(),
+ onSubmit,
+ submitDisabled: false,
+ submittingType: undefined,
+ hasEditData: true,
+ allowAdHoc: false,
+ isPersisted: true,
+ canSave: true,
+ isManaged: false,
+ isDuplicating: false,
+};
+
+const SAVE_AS_AD_HOC_BUTTON_TEST_ID = 'exploreIndexPatternButton';
+const SAVE_AS_PERSISTED_BUTTON_TEST_ID = 'saveIndexPatternButton';
+const DUPLICATE_BUTTON_TEST_ID = 'duplicateButton';
+
+describe('Footer', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('Create new dataview', () => {
+ it('renders footer buttons correctly', () => {
+ const { getByTestId, queryByTestId } = render();
+ expect(getByTestId('closeFlyoutButton')).toBeInTheDocument();
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toHaveTextContent(
+ 'Save data view to Kibana'
+ );
+ expect(queryByTestId(DUPLICATE_BUTTON_TEST_ID)).not.toBeInTheDocument();
+
+ fireEvent.click(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID));
+ expect(onSubmit).toHaveBeenCalledWith(false);
+ });
+
+ it('renders Use without saving button when allowAdHoc is true', () => {
+ const { getByTestId } = render();
+
+ expect(getByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).toHaveTextContent('Use without saving');
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toHaveTextContent(
+ 'Save data view to Kibana'
+ );
+
+ fireEvent.click(getByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID));
+ expect(onSubmit).toHaveBeenCalledWith(true);
+ });
+
+ it('does not render any save buttons when canSave is false', () => {
+ const { queryByTestId } = render();
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(DUPLICATE_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ });
+ });
+
+ describe('Editing an unmanaged dataview', () => {
+ describe('ad hoc dataview', () => {
+ it('renders Continue to use without saving button', () => {
+ const { getByTestId, queryByTestId } = render();
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toHaveTextContent(
+ 'Continue to use without saving'
+ );
+ expect(queryByTestId(DUPLICATE_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ });
+
+ it('renders duplicate button when onDuplicate is provided', () => {
+ const onDuplicate = jest.fn();
+ const { getByTestId, queryByTestId } = render(
+
+ );
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toHaveTextContent(
+ 'Continue to use without saving'
+ );
+ expect(getByTestId(DUPLICATE_BUTTON_TEST_ID)).toBeInTheDocument();
+
+ fireEvent.click(getByTestId(DUPLICATE_BUTTON_TEST_ID));
+ expect(onDuplicate).toHaveBeenCalled();
+ });
+ });
+
+ describe('persisted dataview', () => {
+ it('renders save button', () => {
+ const { getByTestId, queryByTestId } = render();
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toHaveTextContent('Save');
+ });
+ });
+
+ it('renders duplicate button when onDuplicate is provided', () => {
+ const onDuplicate = jest.fn();
+ const { getByTestId, queryByTestId } = render(
+
+ );
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).toHaveTextContent('Save');
+ expect(getByTestId(DUPLICATE_BUTTON_TEST_ID)).toBeInTheDocument();
+
+ fireEvent.click(getByTestId(DUPLICATE_BUTTON_TEST_ID));
+ expect(onDuplicate).toHaveBeenCalled();
+ });
+ });
+
+ describe('Editing a managed dataview', () => {
+ describe('ad hoc dataview', () => {
+ it('does not render any button', () => {
+ const { queryByTestId } = render();
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(DUPLICATE_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ });
+
+ it('renders duplicate button when onDuplicate is provided', () => {
+ const { getByTestId, queryByTestId } = render(
+
+ );
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(DUPLICATE_BUTTON_TEST_ID)).toBeInTheDocument();
+ });
+ });
+
+ describe('persisted dataview', () => {
+ it('does not render any button', () => {
+ const { queryByTestId } = render();
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(DUPLICATE_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ });
+
+ it('renders duplicate button when onDuplicate is provided', () => {
+ const { getByTestId, queryByTestId } = render(
+
+ );
+ expect(queryByTestId(SAVE_AS_AD_HOC_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByTestId(SAVE_AS_PERSISTED_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(DUPLICATE_BUTTON_TEST_ID)).toBeInTheDocument();
+ });
+ });
+ });
+});
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
index 5d633f4c2af66..407eedbdf7032 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
@@ -148,7 +148,7 @@ export const Footer = ({
)}
{showDuplicateButton && (
-
+
{duplicateButtonLabel}
diff --git a/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts b/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
index 45c7d6d3a3e70..24e57eb29b085 100644
--- a/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
+++ b/src/platform/test/functional/apps/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
@@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await dataViews.getSelectedName()).to.be(
'Example profile data view (Default profile data view session)'
);
- expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
diff --git a/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts b/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
index 8f079d01367e6..e194c2fcf74a1 100644
--- a/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
+++ b/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
@@ -39,8 +39,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await dataViews.getSelectedName()).not.to.be('Example profile data view');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -65,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await dataViews.getSelectedName()).to.be(
'Example profile data view (Default profile data view session)'
);
- expect(await dataViews.isManaged()).to.be(false);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
From 8356f5fa6aab6e4a0314e9a8d4dbecbe17e7a6df Mon Sep 17 00:00:00 2001
From: christineweng
Date: Thu, 4 Sep 2025 13:49:15 -0500
Subject: [PATCH 06/12] copy updates and fix tests
---
.github/CODEOWNERS | 1 +
.../src/schema.ts | 1 +
.../data_view_editor_flyout_content.tsx | 6 +-
.../public/components/footer/footer.tsx | 15 ++--
.../field_editor_flyout_content.tsx | 6 +-
.../edit_index_pattern/edit_index_pattern.tsx | 8 ++-
.../schema/v1/cm_services.ts | 2 +-
.../server/rest_api_routes/schema.ts | 1 +
.../data_views/_managed_data_view.ts | 68 +++++++++++++++++++
.../test/functional/apps/management/index.ts | 1 +
.../kbn_archiver/managed_data_view.json | 1 +
.../functional/page_objects/settings_page.ts | 17 +++++
.../_get_default_ad_hoc_data_views.ts | 9 +--
13 files changed, 114 insertions(+), 22 deletions(-)
create mode 100644 src/platform/test/functional/apps/management/data_views/_managed_data_view.ts
create mode 100644 src/platform/test/functional/fixtures/kbn_archiver/managed_data_view.json
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 76cf164d58cb8..d4b749a3598b1 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1212,6 +1212,7 @@ x-pack/platform/test/serverless/api_integration/test_suites/platform_security @e
/src/platform/test/functional/fixtures/kbn_archiver/discover.json @elastic/kibana-data-discovery
/src/platform/test/functional/fixtures/kbn_archiver/date_nested.json @elastic/kibana-data-discovery
/src/platform/test/functional/fixtures/kbn_archiver/date_* @elastic/kibana-data-discovery
+/src/platform/test/functional/fixtures/kbn_archiver/managed_data_view.json @elastic/kibana-data-discovery
/src/platform/test/functional/fixtures/es_archiver/unmapped_fields @elastic/kibana-data-discovery # Assigned per the only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/group7/_indexpattern_with_unmapped_fields.ts#L26
/src/platform/test/functional/fixtures/es_archiver/message_with_newline @elastic/kibana-data-discovery # Assigned per the only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/classic/_doc_table_newline.ts#L24
/src/platform/test/functional/fixtures/es_archiver/hamlet @elastic/kibana-data-discovery # Assigned per the only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/group5/_large_string.ts#L30
diff --git a/src/platform/packages/shared/kbn-content-management-utils/src/schema.ts b/src/platform/packages/shared/kbn-content-management-utils/src/schema.ts
index 02d71541e5b3d..59f2fcabdf754 100644
--- a/src/platform/packages/shared/kbn-content-management-utils/src/schema.ts
+++ b/src/platform/packages/shared/kbn-content-management-utils/src/schema.ts
@@ -81,6 +81,7 @@ export const createOptionsSchemas = {
version: schema.maybe(schema.string()),
refresh: schema.maybe(schema.boolean()),
initialNamespaces: schema.maybe(schema.arrayOf(schema.string())),
+ managed: schema.maybe(schema.boolean()),
};
export const schemaAndOr = schema.oneOf([schema.literal('AND'), schema.literal('OR')]);
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
index 0e1dc2e2e3382..7b20a8033a812 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
@@ -337,10 +337,10 @@ const IndexPatternEditorFlyoutContentComponent = ({
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
index 407eedbdf7032..228c489333a1f 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/footer/footer.tsx
@@ -108,6 +108,14 @@ export const Footer = ({
+ {showDuplicateButton && (
+
+
+ {duplicateButtonLabel}
+
+
+ )}
+
{allowAdHoc && (
)}
- {showDuplicateButton && (
-
-
- {duplicateButtonLabel}
-
-
- )}
diff --git a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
index 0aae120b9881e..5d5eb0748cde7 100644
--- a/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_field_editor/public/components/field_editor_flyout_content.tsx
@@ -43,7 +43,7 @@ const i18nTexts = {
'indexPatternFieldEditor.editor.flyoutDisabledSaveCalloutMessage',
{
defaultMessage:
- 'Fields cannot be edited on managed data views. Duplicate the data view in order to make changes.',
+ "You can't edit managed data view fields. Instead, you can duplicate the data view and make changes to your newly created copy.",
}
),
};
@@ -254,8 +254,8 @@ const FieldEditorFlyoutContentComponent = ({
{dataView.managed && (
diff --git a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx
index 5e7628b279e15..9cd64fb0c6a77 100644
--- a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx
+++ b/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx
@@ -225,10 +225,14 @@ export const EditIndexPattern = withRouter(
) : tag.key === 'rollup' ? (
- {tag.name}
+
+ {tag.name}
+
) : (
- {tag.name}
+
+ {tag.name}
+
)}
))}
diff --git a/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts b/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts
index 058149c68c88c..ee65dc7dec6ab 100644
--- a/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts
+++ b/src/platform/plugins/shared/data_views/server/content_management/schema/v1/cm_services.ts
@@ -65,7 +65,7 @@ const dataViewCreateOptionsSchema = schema.object({
id: createOptionsSchemas.id,
initialNamespaces: createOptionsSchemas.initialNamespaces,
overwrite: schema.maybe(createOptionsSchemas.overwrite),
- managed: schema.maybe(schema.boolean()),
+ managed: createOptionsSchemas.managed,
});
const dataViewSearchOptionsSchema = schema.object({
diff --git a/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts b/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
index 7b144e647f1f6..13f1473e5f4a4 100644
--- a/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
+++ b/src/platform/plugins/shared/data_views/server/rest_api_routes/schema.ts
@@ -54,6 +54,7 @@ export const dataViewSpecSchema = schema.object({
name: schema.maybe(schema.string()),
namespaces: schema.maybe(schema.arrayOf(schema.string())),
allowHidden: schema.maybe(schema.boolean()),
+ managed: schema.maybe(schema.boolean()),
});
export const dataViewsRuntimeResponseSchema = () =>
diff --git a/src/platform/test/functional/apps/management/data_views/_managed_data_view.ts b/src/platform/test/functional/apps/management/data_views/_managed_data_view.ts
new file mode 100644
index 0000000000000..fd7e11f5c4502
--- /dev/null
+++ b/src/platform/test/functional/apps/management/data_views/_managed_data_view.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import expect from '@kbn/expect';
+import type { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
+ const testSubjects = getService('testSubjects');
+ const PageObjects = getPageObjects(['settings', 'common', 'header']);
+
+ describe('managed data view', function describeIndexTests() {
+ before(async function () {
+ await esArchiver.load('x-pack/platform/test/fixtures/es_archives/logstash_functional');
+ await kibanaServer.importExport.load(
+ 'src/platform/test/functional/fixtures/kbn_archiver/managed_data_view'
+ );
+ await kibanaServer.uiSettings.replace({});
+ await PageObjects.settings.navigateTo();
+ await PageObjects.settings.clickKibanaIndexPatterns();
+ });
+
+ after(async function () {
+ await esArchiver.unload('x-pack/platform/test/fixtures/es_archives/logstash_functional');
+ await kibanaServer.importExport.unload(
+ 'src/platform/test/functional/fixtures/kbn_archiver/managed_data_view'
+ );
+ });
+
+ describe('when viewing a managed data view', function () {
+ it('can see managed badge in data view page', async function () {
+ await PageObjects.settings.clickKibanaIndexPatterns();
+ await PageObjects.settings.clickIndexPatternLogstash();
+
+ const patternName = await PageObjects.settings.getIndexPageHeading();
+ expect(patternName).to.be('logstash-*');
+
+ const managedBadge = await PageObjects.settings.getManagedTag();
+ expect(managedBadge).to.be('Managed');
+ });
+
+ it('data view editor is disabled', async function () {
+ await PageObjects.settings.clickKibanaIndexPatterns();
+ await PageObjects.settings.clickIndexPatternLogstash();
+ await PageObjects.settings.clickEditIndexButton();
+
+ await PageObjects.settings.expectDisabledDataViewEditor();
+ await testSubjects.click('closeFlyoutButton');
+ });
+
+ it('field editor is disabled', async function () {
+ await PageObjects.settings.clickKibanaIndexPatterns();
+ await PageObjects.settings.clickIndexPatternLogstash();
+ await PageObjects.settings.clickAddField();
+
+ await PageObjects.settings.expectDisabledFieldEditor();
+ await testSubjects.click('closeFlyoutButton');
+ });
+ });
+ });
+}
diff --git a/src/platform/test/functional/apps/management/index.ts b/src/platform/test/functional/apps/management/index.ts
index aafe9cd606ed4..ed42c92e45f0b 100644
--- a/src/platform/test/functional/apps/management/index.ts
+++ b/src/platform/test/functional/apps/management/index.ts
@@ -51,5 +51,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_files'));
loadTestFile(require.resolve('./_data_view_field_filters'));
loadTestFile(require.resolve('./data_views/_cache'));
+ loadTestFile(require.resolve('./data_views/_managed_data_view'));
});
}
diff --git a/src/platform/test/functional/fixtures/kbn_archiver/managed_data_view.json b/src/platform/test/functional/fixtures/kbn_archiver/managed_data_view.json
new file mode 100644
index 0000000000000..94f7c3b447910
--- /dev/null
+++ b/src/platform/test/functional/fixtures/kbn_archiver/managed_data_view.json
@@ -0,0 +1 @@
+{"attributes":{"allowHidden":false,"fieldAttrs":"{}","fieldFormatMap":"{}","fields":"[]","name":"logstash-*","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"logstash-*"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:35:58.606Z","id":"5f863f70-4728-4e8d-b441-db08f8c33b28","managed":true,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-01-18T17:35:58.606Z","version":"WzI4LDFd"}
\ No newline at end of file
diff --git a/src/platform/test/functional/page_objects/settings_page.ts b/src/platform/test/functional/page_objects/settings_page.ts
index 18c7cbfd9b6a2..31c90e39939cd 100644
--- a/src/platform/test/functional/page_objects/settings_page.ts
+++ b/src/platform/test/functional/page_objects/settings_page.ts
@@ -191,6 +191,14 @@ export class SettingsPageObject extends FtrService {
await field.type(dataViewName);
}
+ async expectDisabledDataViewEditor() {
+ const nameField = await this.getNameField();
+ expect(await nameField.getAttribute('disabled')).to.equal('true');
+
+ const titleField = await this.getIndexPatternField();
+ expect(await titleField.getAttribute('disabled')).to.equal('true');
+ }
+
async getSaveIndexPatternButton() {
return await this.testSubjects.find('saveIndexPatternButton');
}
@@ -223,6 +231,10 @@ export class SettingsPageObject extends FtrService {
return await this.testSubjects.getVisibleText('indexPatternTitle');
}
+ async getManagedTag() {
+ return await this.testSubjects.getVisibleText('managed-tag');
+ }
+
async getTableHeader() {
return await this.find.allByCssSelector('table.euiTable thead tr th');
}
@@ -886,6 +898,11 @@ export class SettingsPageObject extends FtrService {
await this.header.waitUntilLoadingHasFinished();
}
+ async expectDisabledFieldEditor() {
+ expect(await this.testSubjects.getAttribute('input', 'disabled')).to.eql('true');
+ expect(await this.testSubjects.getAttribute('typeField', 'disabled')).to.eql('true');
+ }
+
async setFieldName(name: string) {
this.log.debug('set field name = ' + name);
await this.testSubjects.setValue('nameField', name);
diff --git a/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts b/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
index e194c2fcf74a1..942ef066bc3e4 100644
--- a/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
+++ b/x-pack/platform/test/serverless/functional/test_suites/discover/context_awareness/extensions/_get_default_ad_hoc_data_views.ts
@@ -54,8 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await discover.waitUntilSearchingHasFinished();
expect(await toasts.getCount({ timeout: 2000 })).to.be(0);
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
});
it('should create a copy of the profile data view when saving the Discover session', async () => {
@@ -74,8 +73,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
).to.be('Jun 10, 2024 @ 14:00:00.000');
await dataViews.switchTo('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
@@ -107,8 +105,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
expect(await dataViews.getSelectedName()).to.be('Example profile data view');
await discover.waitUntilSearchingHasFinished();
- expect(await dataViews.isManaged()).to.be(false);
- expect(await dataViews.isAdHoc()).to.be(true);
+ expect(await dataViews.isManaged()).to.be(true);
expect(await unifiedFieldList.getSidebarSectionFieldNames('available')).to.have.length(7);
expect(
await (await dataGrid.getCellElementByColumnName(0, '@timestamp')).getVisibleText()
From 3e3b037522c1ab149aa9103f4102828af21d917f Mon Sep 17 00:00:00 2001
From: christineweng
Date: Fri, 5 Sep 2025 16:45:50 -0500
Subject: [PATCH 07/12] rebase and fix run time field
---
.../components/data_view_flyout_content_container.tsx | 11 +++++++++++
.../components/data_view_picker/index.tsx | 9 +++++----
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
index 8f49ac4cb14f1..ed377226a33da 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_flyout_content_container.tsx
@@ -65,6 +65,17 @@ const DataViewFlyoutContentContainer = ({
}
saveResponse = editData;
} else {
+ if (editData && isDuplicating) {
+ const editDataSpec = editData.toSpec();
+ dataViewSpec = {
+ ...editDataSpec,
+ id: undefined,
+ name: dataViewSpec.name,
+ timeFieldName: dataViewSpec.timeFieldName,
+ title: dataViewSpec.title,
+ allowHidden: dataViewSpec.allowHidden,
+ };
+ }
saveResponse = persist
? await dataViews.createAndSave(dataViewSpec)
: await dataViews.create(dataViewSpec);
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
index deed67577561b..643dcc68b91b7 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
@@ -90,16 +90,17 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie
[isDefaultSourcerer, scope, selectDataView, updateUrlParam]
);
- const handleCreateNewDataView = useMemo(() => {
- return (newDataView: DataView) => {
+ const handleCreateNewDataView = useCallback(
+ (newDataView: DataView) => {
if (!newDataView.id) {
return;
}
dispatch(sharedDataViewManagerSlice.actions.addDataView(newDataView));
handleChangeDataView(newDataView.id, newDataView.getIndexPattern());
- };
- }, [dispatch, handleChangeDataView]);
+ },
+ [dispatch, handleChangeDataView]
+ );
const editField = useCallback(
async (fieldName?: string, _uiAction: 'edit' | 'add' = 'edit') => {
From 028a51ca5e31963ada919ceb6b2ae1e03f0f1ad5 Mon Sep 17 00:00:00 2001
From: christineweng
Date: Mon, 8 Sep 2025 12:03:24 -0500
Subject: [PATCH 08/12] fix browser field
---
.../components/data_view_picker/index.tsx | 16 ++++++++++++++--
.../hooks/use_browser_fields.ts | 4 ++--
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
index 643dcc68b91b7..bfcbc0937c827 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx
@@ -6,7 +6,7 @@
*/
import { DataViewPicker as UnifiedDataViewPicker } from '@kbn/unified-search-plugin/public';
-import React, { useCallback, useRef, useMemo, memo } from 'react';
+import React, { useCallback, useRef, useMemo, memo, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { DataView } from '@kbn/data-views-plugin/public';
import { EuiCode } from '@elastic/eui';
@@ -45,7 +45,7 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie
const selectDataView = useSelectDataView();
const {
- services: { dataViewEditor, data, dataViewFieldEditor, fieldFormats },
+ services: { dataViewEditor, data, dataViewFieldEditor, fieldFormats, onAppLeave },
} = useKibana();
const canEditDataView = useMemo(
@@ -130,6 +130,18 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie
[dataViewId, data.dataViews, scope, dataViewFieldEditor, handleChangeDataView]
);
+ // clearing browser fields cache when user leaves the app
+ // this is to account for any new fields added outside of security solution
+ useEffect(() => {
+ onAppLeave?.((actions) => {
+ browserFieldsManager.clearCache();
+ return actions.default();
+ });
+ return () => {
+ onAppLeave?.((actions) => actions.default());
+ };
+ }, [onAppLeave]);
+
const getDataViewHelpText = useCallback(
(dv: DataView) =>
dv.id === defaultDataViewId ? (
diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts
index 04950e13094be..5220aaa9246cf 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts
@@ -31,8 +31,8 @@ export const useBrowserFields = (
return emptyFields;
}
- const { browserFields } = browserFieldsManager.getBrowserFields(activeDataView, scope);
+ const { browserFields } = browserFieldsManager.getBrowserFields(activeDataView);
return browserFields;
- }, [activeDataView, scope]);
+ }, [activeDataView]);
};
From 4b4a35a73a4c9568522077902701f8ad0c6855b6 Mon Sep 17 00:00:00 2001
From: christineweng
Date: Thu, 11 Sep 2025 13:49:24 -0500
Subject: [PATCH 09/12] comments
---
.../advanced_params_content.tsx | 2 +-
.../data_view_editor_flyout_content.tsx | 8 +++++---
.../data_view_flyout_content_container.tsx | 2 +-
.../field_editor/composite_editor.tsx | 10 ++++++++--
.../components/field_editor/field_detail.tsx | 10 +++++-----
.../components/field_editor/field_editor.tsx | 2 +-
.../form_fields/custom_description_field.tsx | 10 ++++++++--
.../form_fields/custom_label_field.tsx | 10 ++++++++--
.../field_editor/form_fields/format_field.tsx | 7 ++++++-
.../form_fields/popularity_field.tsx | 8 ++++++--
.../field_editor/form_fields/script_field.tsx | 4 +++-
.../field_editor_flyout_content.tsx | 4 +++-
.../field_format_editor.tsx | 4 +++-
.../server/rest_api_routes/schema.ts | 1 -
.../sidebar/discover_sidebar_responsive.tsx | 14 --------------
.../redux/actions/save_discover_session.ts | 1 +
.../dataview_picker/change_dataview.tsx | 19 +++++++++++++++----
.../_get_default_ad_hoc_data_views.ts | 2 +-
.../_get_default_ad_hoc_data_views.ts | 2 +-
19 files changed, 76 insertions(+), 44 deletions(-)
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx b/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx
index 3151005389295..a1aafb4f4b63b 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/advanced_params_content/advanced_params_content.tsx
@@ -50,7 +50,7 @@ export const AdvancedParamsContent = ({
componentProps={{
euiFieldProps: {
'aria-label': allowHiddenAriaLabel,
- disabled: disableAllowHidden || disableId,
+ disabled: disableAllowHidden,
},
}}
onChange={onAllowHiddenChange}
diff --git a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
index 7b20a8033a812..c43ef9b17e508 100644
--- a/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
+++ b/src/platform/plugins/shared/data_view_editor/public/components/data_view_editor_flyout_content.tsx
@@ -21,6 +21,7 @@ import {
EuiCallOut,
type UseEuiTheme,
useIsWithinBreakpoints,
+ useEuiTheme,
} from '@elastic/eui';
import useDebounce from 'react-use/lib/useDebounce';
import { i18n } from '@kbn/i18n';
@@ -99,6 +100,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
isDuplicating,
}: Props) => {
const styles = useMemoCss(componentStyles);
+ const { euiTheme } = useEuiTheme();
const isMobile = useIsWithinBreakpoints(['s', 'xs']);
const {
@@ -342,7 +344,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
color="primary"
iconType="info"
size="s"
- style={{ marginTop: '10px' }}
+ css={{ marginTop: euiTheme.base }}
/>
)}