{
- const [items, setItems] = useState
>(() =>
+export const FieldStatusFilterGroup = ({ onChange }: { onChange: TControlsChangeHandler }) => {
+ const [items, setItems] = useState(() =>
Object.entries(FIELD_STATUS_MAP).map(([key, value]) => {
return {
label: value.label,
@@ -37,13 +32,13 @@ export const FieldStatusFilterGroup = ({
const onChangeItems = useCallback['onChange']>(
(nextItems) => {
setItems(nextItems);
- onChangeFilterGroup({
+ onChange({
status: nextItems
.filter((nextItem) => nextItem.checked === 'on')
- .map((item) => item.key as string),
+ .map((item) => item.key as SchemaFieldStatus),
});
},
- [onChangeFilterGroup]
+ [onChange]
);
return (
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/filters/type_filter_group.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/filters/type_filter_group.tsx
similarity index 66%
rename from x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/filters/type_filter_group.tsx
rename to x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/filters/type_filter_group.tsx
index 13f0657d0b133..05afd60590d2b 100644
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/filters/type_filter_group.tsx
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/filters/type_filter_group.tsx
@@ -8,24 +8,19 @@
import { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { EuiSelectableProps } from '@elastic/eui';
-import { FIELD_TYPE_MAP } from '../configuration_maps';
+import { EuiSelectableOption, EuiSelectableProps } from '@elastic/eui';
import { FilterGroup } from './filter_group';
-import { ChangeFilterGroups } from '../hooks/use_query_and_filters';
+import { FIELD_TYPE_MAP } from '../constants';
+import { TControlsChangeHandler } from '../hooks/use_controls';
+import { SchemaFieldType } from '../types';
const BUTTON_LABEL = i18n.translate(
'xpack.streams.streamDetailSchemaEditor.fieldTypeFilterGroupButtonLabel',
- {
- defaultMessage: 'Type',
- }
+ { defaultMessage: 'Type' }
);
-export const FieldTypeFilterGroup = ({
- onChangeFilterGroup,
-}: {
- onChangeFilterGroup: ChangeFilterGroups;
-}) => {
- const [items, setItems] = useState>(() =>
+export const FieldTypeFilterGroup = ({ onChange }: { onChange: TControlsChangeHandler }) => {
+ const [items, setItems] = useState(() =>
Object.entries(FIELD_TYPE_MAP).map(([key, value]) => {
return {
label: value.label,
@@ -37,13 +32,13 @@ export const FieldTypeFilterGroup = ({
const onChangeItems = useCallback['onChange']>(
(nextItems) => {
setItems(nextItems);
- onChangeFilterGroup({
+ onChange({
type: nextItems
.filter((nextItem) => nextItem.checked === 'on')
- .map((item) => item.key as string),
+ .map((item) => item.key as SchemaFieldType),
});
},
- [onChangeFilterGroup]
+ [onChange]
);
return (
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/children_affected_callout.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/children_affected_callout.tsx
similarity index 100%
rename from x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/children_affected_callout.tsx
rename to x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/children_affected_callout.tsx
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/ecs_recommendation.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/ecs_recommendation.tsx
similarity index 87%
rename from x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/ecs_recommendation.tsx
rename to x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/ecs_recommendation.tsx
index d8f7c977a4b37..beccd59b8a875 100644
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/ecs_recommendation.tsx
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/ecs_recommendation.tsx
@@ -11,23 +11,17 @@ import { i18n } from '@kbn/i18n';
const EcsRecommendationText = i18n.translate(
'xpack.streams.streamDetailSchemaEditor.ecsRecommendationText',
- {
- defaultMessage: 'ECS recommendation',
- }
+ { defaultMessage: 'ECS recommendation' }
);
const UknownEcsFieldText = i18n.translate(
'xpack.streams.streamDetailSchemaEditor.uknownEcsFieldText',
- {
- defaultMessage: 'Not an ECS field',
- }
+ { defaultMessage: 'Not an ECS field' }
);
const LoadingText = i18n.translate(
'xpack.streams.streamDetailSchemaEditor.ecsRecommendationLoadingText',
- {
- defaultMessage: 'Loading...',
- }
+ { defaultMessage: 'Loading...' }
);
export const EcsRecommendation = ({
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_format.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_form_format.tsx
similarity index 77%
rename from x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_format.tsx
rename to x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_form_format.tsx
index 9b69a68034170..535bdc01f13b0 100644
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_format.tsx
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_form_format.tsx
@@ -17,12 +17,12 @@ import React, { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { FieldDefinitionConfig } from '@kbn/streams-schema';
import useToggle from 'react-use/lib/useToggle';
-import { SchemaEditorEditingState } from '../hooks/use_editing_state';
+import { SchemaField } from '../types';
-type FieldFormFormatProps = Pick<
- SchemaEditorEditingState,
- 'nextFieldType' | 'nextFieldFormat' | 'setNextFieldFormat'
->;
+interface FieldFormFormatProps {
+ field: SchemaField;
+ onChange: (format: SchemaField['format']) => void;
+}
const DEFAULT_FORMAT = 'strict_date_optional_time||epoch_millis';
@@ -42,7 +42,7 @@ export const typeSupportsFormat = (type?: FieldDefinitionConfig['type']) => {
};
export const FieldFormFormat = (props: FieldFormFormatProps) => {
- if (!typeSupportsFormat(props.nextFieldType)) {
+ if (!typeSupportsFormat(props.field.type)) {
return null;
}
return ;
@@ -50,13 +50,13 @@ export const FieldFormFormat = (props: FieldFormFormatProps) => {
const FieldFormFormatSelection = (props: FieldFormFormatProps) => {
const [isFreeform, toggleIsFreeform] = useToggle(
- props.nextFieldFormat !== undefined && !isPopularFormat(props.nextFieldFormat)
+ props.field.format !== undefined && !isPopularFormat(props.field.format)
);
const onToggle = useCallback(
(e: EuiSwitchEvent) => {
- if (!e.target.checked && !isPopularFormat(props.nextFieldFormat)) {
- props.setNextFieldFormat(undefined);
+ if (!e.target.checked && !isPopularFormat(props.field.format)) {
+ props.onChange(undefined);
}
toggleIsFreeform();
},
@@ -85,13 +85,13 @@ const PopularFormatsSelector = (props: FieldFormFormatProps) => {
return (
{
- props.setNextFieldFormat(event.target.value as PopularFormatOption);
+ props.onChange(event.target.value as PopularFormatOption);
}}
- value={props.nextFieldFormat}
+ value={props.field.format}
options={POPULAR_FORMATS.map((format) => ({
text: format,
value: format,
@@ -105,8 +105,8 @@ const FreeformFormatInput = (props: FieldFormFormatProps) => {
props.setNextFieldFormat(e.target.value)}
+ value={props.field.format ?? ''}
+ onChange={(e) => props.onChange(e.target.value)}
/>
);
};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_form_type.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_form_type.tsx
new file mode 100644
index 0000000000000..85701cff5ec8a
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_form_type.tsx
@@ -0,0 +1,86 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui';
+import React, { useEffect } from 'react';
+import { EcsRecommendation } from './ecs_recommendation';
+import { FieldType } from '../field_type';
+import { useKibana } from '../../../hooks/use_kibana';
+import { EMPTY_CONTENT, FIELD_TYPE_MAP, FieldTypeOption } from '../constants';
+import { MappedSchemaField, SchemaField } from '../types';
+
+export const FieldFormType = ({
+ field,
+ isEditing,
+ onTypeChange,
+}: {
+ field: SchemaField;
+ isEditing: boolean;
+ onTypeChange: FieldTypeSelectorProps['onChange'];
+}) => {
+ const { useFieldsMetadata } = useKibana().dependencies.start.fieldsMetadata;
+
+ const { fieldsMetadata, loading } = useFieldsMetadata(
+ { attributes: ['type'], fieldNames: [field.name] },
+ [field]
+ );
+
+ // Propagate recommendation to state if a type is not already set
+ const recommendation = fieldsMetadata?.[field.name]?.type;
+
+ useEffect(() => {
+ if (
+ !loading &&
+ recommendation !== undefined &&
+ // Supported type
+ recommendation in FIELD_TYPE_MAP &&
+ !field.type
+ ) {
+ onTypeChange(recommendation as MappedSchemaField['type']);
+ }
+ }, [field, loading, recommendation, onTypeChange]);
+
+ return (
+
+
+ {isEditing ? (
+
+ ) : field.type ? (
+
+ ) : (
+ EMPTY_CONTENT
+ )}
+
+
+
+
+
+ );
+};
+
+interface FieldTypeSelectorProps {
+ isLoading?: boolean;
+ onChange: (value: FieldTypeOption) => void;
+ value?: FieldTypeOption;
+}
+
+const FieldTypeSelector = ({ value, onChange, isLoading = false }: FieldTypeSelectorProps) => {
+ return (
+ {
+ onChange(event.target.value as FieldTypeOption);
+ }}
+ value={value}
+ options={Object.entries(FIELD_TYPE_MAP).map(([optionKey, optionConfig]) => ({
+ text: optionConfig.label,
+ value: optionKey,
+ }))}
+ />
+ );
+};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_summary.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_summary.tsx
new file mode 100644
index 0000000000000..adb98b2946dfb
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/field_summary.tsx
@@ -0,0 +1,215 @@
+/*
+ * 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 {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHorizontalRule,
+ EuiIconTip,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import useToggle from 'react-use/lib/useToggle';
+import { WiredStreamDefinition } from '@kbn/streams-schema';
+import { useStreamsAppRouter } from '../../../hooks/use_streams_app_router';
+import { FieldParent } from '../field_parent';
+import { FieldStatusBadge } from '../field_status';
+import { FieldFormFormat, typeSupportsFormat } from './field_form_format';
+import { FieldFormType } from './field_form_type';
+import { ChildrenAffectedCallout } from './children_affected_callout';
+import { EMPTY_CONTENT } from '../constants';
+import { SchemaField } from '../types';
+
+const title = i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryTitle', {
+ defaultMessage: 'Field summary',
+});
+
+const FIELD_SUMMARIES = {
+ fieldStatus: {
+ label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldNameHeader', {
+ defaultMessage: 'Status',
+ }),
+ },
+ fieldType: {
+ label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldTypeHeader', {
+ defaultMessage: 'Type',
+ }),
+ },
+ fieldFormat: {
+ label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldFormatHeader', {
+ defaultMessage: 'Format',
+ }),
+ },
+ fieldParent: {
+ label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldParentHeader', {
+ defaultMessage: 'Field Parent',
+ }),
+ },
+};
+
+interface FieldSummaryProps {
+ field: SchemaField;
+ isEditingByDefault: boolean;
+ stream: WiredStreamDefinition;
+ onChange: (field: Partial) => void;
+}
+
+export const FieldSummary = (props: FieldSummaryProps) => {
+ const { field, isEditingByDefault, onChange, stream } = props;
+
+ const router = useStreamsAppRouter();
+
+ const [isEditing, toggleEditMode] = useToggle(isEditingByDefault);
+
+ return (
+ <>
+
+
+
+
+ {title}
+
+
+ {field.status !== 'inherited' && !isEditing ? (
+
+
+
+
+ {i18n.translate('xpack.streams.fieldSummary.editButtonLabel', {
+ defaultMessage: 'Edit',
+ })}
+
+
+
+
+ ) : field.status === 'inherited' ? (
+
+
+
+
+ {i18n.translate('xpack.streams.fieldSummary.editInParentButtonLabel', {
+ defaultMessage: 'Edit in parent stream',
+ })}
+
+
+
+
+ ) : null}
+
+
+
+
+
+
+
+
+ {FIELD_SUMMARIES.fieldStatus.label}{' '}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {FIELD_SUMMARIES.fieldType.label}
+
+
+
+ onChange({ type })}
+ />
+
+
+
+
+
+ {typeSupportsFormat(field.type) && (
+ <>
+
+
+
+ {FIELD_SUMMARIES.fieldFormat.label}
+
+
+
+ {isEditing ? (
+ onChange({ format })} />
+ ) : (
+ `${field.format ?? EMPTY_CONTENT}`
+ )}
+
+
+
+ >
+ )}
+
+
+
+
+ {FIELD_SUMMARIES.fieldParent.label}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isEditing && stream.ingest.routing.length > 0 ? (
+
+
+
+ ) : null}
+ >
+ );
+};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/index.tsx
new file mode 100644
index 0000000000000..8124672853553
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/index.tsx
@@ -0,0 +1,106 @@
+/*
+ * 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 {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiFlyoutFooter,
+ EuiTitle,
+ EuiButton,
+} from '@elastic/eui';
+import React, { useReducer } from 'react';
+import { i18n } from '@kbn/i18n';
+import { WiredStreamDefinition } from '@kbn/streams-schema';
+import useAsyncFn from 'react-use/lib/useAsyncFn';
+import { SamplePreviewTable } from './sample_preview_table';
+import { FieldSummary } from './field_summary';
+import { SchemaField } from '../types';
+
+export interface SchemaEditorFlyoutProps {
+ field: SchemaField;
+ isEditingByDefault?: boolean;
+ onClose?: () => void;
+ onSave: (field: SchemaField) => void;
+ stream: WiredStreamDefinition;
+ withFieldSimulation?: boolean;
+}
+
+export const SchemaEditorFlyout = ({
+ field,
+ stream,
+ onClose,
+ onSave,
+ isEditingByDefault = false,
+ withFieldSimulation = false,
+}: SchemaEditorFlyoutProps) => {
+ const [nextField, setNextField] = useReducer(
+ (prev: SchemaField, updated: Partial) =>
+ ({
+ ...prev,
+ ...updated,
+ } as SchemaField),
+ field
+ );
+
+ const [{ loading: isSaving }, saveChanges] = useAsyncFn(async () => {
+ await onSave(nextField);
+ if (onClose) onClose();
+ }, [nextField, onClose, onSave]);
+
+ return (
+ <>
+
+
+ {field.name}
+
+
+
+
+
+
+ {withFieldSimulation && (
+
+
+
+ )}
+
+
+
+
+
+
+ {i18n.translate('xpack.streams.schemaEditorFlyout.closeButtonLabel', {
+ defaultMessage: 'Cancel',
+ })}
+
+
+ {i18n.translate('xpack.streams.fieldForm.saveButtonLabel', {
+ defaultMessage: 'Save changes',
+ })}
+
+
+
+ >
+ );
+};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/sample_preview_table.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/sample_preview_table.tsx
similarity index 71%
rename from x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/sample_preview_table.tsx
rename to x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/sample_preview_table.tsx
index 4ebad532c130d..7ba282fad1be3 100644
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/sample_preview_table.tsx
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/flyout/sample_preview_table.tsx
@@ -6,27 +6,26 @@
*/
import React, { useMemo } from 'react';
-import { StreamsRepositoryClient } from '@kbn/streams-plugin/public/api';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { EuiCallOut } from '@elastic/eui';
-import { NamedFieldDefinitionConfig, WiredStreamGetResponse } from '@kbn/streams-schema';
+import { NamedFieldDefinitionConfig, WiredStreamDefinition } from '@kbn/streams-schema';
+import { useKibana } from '../../../hooks/use_kibana';
import { getFormattedError } from '../../../util/errors';
import { useStreamsAppFetch } from '../../../hooks/use_streams_app_fetch';
import { PreviewTable } from '../../preview_table';
-import { isFullFieldDefinition } from '../hooks/use_editing_state';
import { LoadingPanel } from '../../loading_panel';
+import { SchemaField, isSchemaFieldTyped } from '../types';
interface SamplePreviewTableProps {
- definition: WiredStreamGetResponse;
- nextFieldDefinition?: Partial;
- streamsRepositoryClient: StreamsRepositoryClient;
+ stream: WiredStreamDefinition;
+ nextField: SchemaField;
}
export const SamplePreviewTable = (props: SamplePreviewTableProps) => {
- const { nextFieldDefinition, ...rest } = props;
- if (isFullFieldDefinition(nextFieldDefinition)) {
- return ;
+ const { nextField, ...rest } = props;
+ if (isSchemaFieldTyped(nextField)) {
+ return ;
} else {
return null;
}
@@ -35,33 +34,32 @@ export const SamplePreviewTable = (props: SamplePreviewTableProps) => {
const SAMPLE_DOCUMENTS_TO_SHOW = 20;
const SamplePreviewTableContent = ({
- definition,
- nextFieldDefinition,
- streamsRepositoryClient,
-}: SamplePreviewTableProps & { nextFieldDefinition: NamedFieldDefinitionConfig }) => {
+ stream,
+ nextField,
+}: SamplePreviewTableProps & { nextField: NamedFieldDefinitionConfig }) => {
+ const { streamsRepositoryClient } = useKibana().dependencies.start.streams;
+
const { value, loading, error } = useStreamsAppFetch(
({ signal }) => {
return streamsRepositoryClient.fetch('POST /api/streams/{id}/schema/fields_simulation', {
signal,
params: {
path: {
- id: definition.stream.name,
+ id: stream.name,
},
body: {
- field_definitions: [nextFieldDefinition],
+ field_definitions: [nextField],
},
},
});
},
- [definition.stream.name, nextFieldDefinition, streamsRepositoryClient],
- {
- disableToastOnError: true,
- }
+ [stream.name, nextField, streamsRepositoryClient],
+ { disableToastOnError: true }
);
const columns = useMemo(() => {
- return [nextFieldDefinition.name];
- }, [nextFieldDefinition.name]);
+ return [nextField.name];
+ }, [nextField.name]);
if (loading) {
return ;
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/hooks/use_controls.ts b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/hooks/use_controls.ts
new file mode 100644
index 0000000000000..952b6e708b091
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/hooks/use_controls.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { useReducer } from 'react';
+import { EuiSearchBar } from '@elastic/eui';
+import { SchemaFieldStatus, MappedSchemaField } from '../types';
+
+const defaultControls = {
+ query: EuiSearchBar.Query.MATCH_ALL,
+ status: [] as SchemaFieldStatus[],
+ type: [] as Array,
+} as const;
+
+export type TControls = typeof defaultControls;
+
+const mergeReducer = (prev: TControls, updated: Partial) => ({ ...prev, ...updated });
+
+export const useControls = () => useReducer(mergeReducer, defaultControls);
+
+export type TControlsChangeHandler = (update: Partial) => void;
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/hooks/use_schema_fields.ts b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/hooks/use_schema_fields.ts
new file mode 100644
index 0000000000000..5ed9a571d3e75
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/hooks/use_schema_fields.ts
@@ -0,0 +1,218 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { useAbortController } from '@kbn/observability-utils-browser/hooks/use_abort_controller';
+import {
+ FieldDefinitionConfig,
+ NamedFieldDefinitionConfig,
+ WiredStreamGetResponse,
+} from '@kbn/streams-schema';
+import { isEqual, omit } from 'lodash';
+import { useMemo, useCallback } from 'react';
+import { useStreamsAppFetch } from '../../../hooks/use_streams_app_fetch';
+import { useKibana } from '../../../hooks/use_kibana';
+import { MappedSchemaField, SchemaField, isSchemaFieldTyped } from '../types';
+
+export const useSchemaFields = ({
+ definition,
+ refreshDefinition,
+}: {
+ definition: WiredStreamGetResponse;
+ refreshDefinition: () => void;
+}) => {
+ const {
+ dependencies: {
+ start: {
+ streams: { streamsRepositoryClient },
+ },
+ },
+ core: {
+ notifications: { toasts },
+ },
+ } = useKibana();
+
+ const abortController = useAbortController();
+
+ const {
+ value: unmappedFieldsValue,
+ loading: isLoadingUnmappedFields,
+ refresh: refreshUnmappedFields,
+ } = useStreamsAppFetch(
+ ({ signal }) => {
+ return streamsRepositoryClient.fetch('GET /api/streams/{id}/schema/unmapped_fields', {
+ signal,
+ params: {
+ path: {
+ id: definition.stream.name,
+ },
+ },
+ });
+ },
+ [definition.stream.name, streamsRepositoryClient]
+ );
+
+ const fields = useMemo(() => {
+ const inheritedFields: SchemaField[] = Object.entries(definition.inherited_fields).map(
+ ([name, field]) => ({
+ name,
+ type: field.type,
+ format: field.format,
+ parent: field.from,
+ status: 'inherited',
+ })
+ );
+
+ const mappedFields: SchemaField[] = Object.entries(definition.stream.ingest.wired.fields).map(
+ ([name, field]) => ({
+ name,
+ type: field.type,
+ format: field.format,
+ parent: definition.stream.name,
+ status: 'mapped',
+ })
+ );
+
+ const unmappedFields: SchemaField[] =
+ unmappedFieldsValue?.unmappedFields.map((field) => ({
+ name: field,
+ parent: definition.stream.name,
+ status: 'unmapped',
+ })) ?? [];
+
+ return [...inheritedFields, ...mappedFields, ...unmappedFields];
+ }, [definition, unmappedFieldsValue]);
+
+ const refreshFields = useCallback(() => {
+ refreshDefinition();
+ refreshUnmappedFields();
+ }, [refreshDefinition, refreshUnmappedFields]);
+
+ const updateField = useCallback(
+ async (field: SchemaField) => {
+ try {
+ if (!isSchemaFieldTyped(field)) {
+ throw new Error('The field is not complete or fully mapped.');
+ }
+
+ const nextFieldDefinitionConfig = convertToFieldDefinitionConfig(field);
+ const persistedFieldDefinitionConfig = definition.stream.ingest.wired.fields[field.name];
+
+ if (!hasChanges(persistedFieldDefinitionConfig, nextFieldDefinitionConfig)) {
+ throw new Error('The field is not different, hence updating is not necessary.');
+ }
+
+ await streamsRepositoryClient.fetch(`PUT /api/streams/{id}/_ingest`, {
+ signal: abortController.signal,
+ params: {
+ path: {
+ id: definition.stream.name,
+ },
+ body: {
+ ingest: {
+ ...definition.stream.ingest,
+ wired: {
+ fields: {
+ ...definition.stream.ingest.wired.fields,
+ [field.name]: nextFieldDefinitionConfig,
+ },
+ },
+ },
+ },
+ },
+ });
+
+ toasts.addSuccess(
+ i18n.translate('xpack.streams.streamDetailSchemaEditorEditSuccessToast', {
+ defaultMessage: '{field} was successfully edited',
+ values: { field: field.name },
+ })
+ );
+
+ refreshFields();
+ } catch (error) {
+ toasts.addError(error, {
+ title: i18n.translate('xpack.streams.streamDetailSchemaEditorEditErrorToast', {
+ defaultMessage: 'Something went wrong editing the {field} field',
+ values: { field: field.name },
+ }),
+ toastMessage: error.message,
+ toastLifeTimeMs: 5000,
+ });
+ }
+ },
+ [abortController.signal, definition, refreshFields, streamsRepositoryClient, toasts]
+ );
+
+ const unmapField = useCallback(
+ async (fieldName: SchemaField['name']) => {
+ try {
+ const persistedFieldDefinitionConfig = definition.stream.ingest.wired.fields[fieldName];
+
+ if (!persistedFieldDefinitionConfig) {
+ throw new Error('The field is not mapped, hence it cannot be unmapped.');
+ }
+
+ await streamsRepositoryClient.fetch(`PUT /api/streams/{id}/_ingest`, {
+ signal: abortController.signal,
+ params: {
+ path: {
+ id: definition.stream.name,
+ },
+ body: {
+ ingest: {
+ ...definition.stream.ingest,
+ wired: {
+ fields: omit(definition.stream.ingest.wired.fields, fieldName),
+ },
+ },
+ },
+ },
+ });
+
+ toasts.addSuccess(
+ i18n.translate('xpack.streams.streamDetailSchemaEditorUnmapSuccessToast', {
+ defaultMessage: '{field} was successfully unmapped',
+ values: { field: fieldName },
+ })
+ );
+
+ refreshFields();
+ } catch (error) {
+ toasts.addError(error, {
+ title: i18n.translate('xpack.streams.streamDetailSchemaEditorUnmapErrorToast', {
+ defaultMessage: 'Something went wrong unmapping the {field} field',
+ values: { field: fieldName },
+ }),
+ toastMessage: error.message,
+ toastLifeTimeMs: 5000,
+ });
+ }
+ },
+ [abortController.signal, definition, refreshFields, streamsRepositoryClient, toasts]
+ );
+
+ return {
+ fields,
+ isLoadingUnmappedFields,
+ refreshFields,
+ unmapField,
+ updateField,
+ };
+};
+
+const convertToFieldDefinitionConfig = (field: MappedSchemaField): FieldDefinitionConfig => ({
+ type: field.type,
+ ...(field.format && field.type === 'date' ? { format: field.format } : {}),
+});
+
+const hasChanges = (
+ field: Partial,
+ fieldUpdate: Partial
+) => {
+ return !isEqual(field, fieldUpdate);
+};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/index.tsx
new file mode 100644
index 0000000000000..0a6cc2d562b3d
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/index.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiFlexGroup, EuiPortal, EuiProgress } from '@elastic/eui';
+import { useControls } from './hooks/use_controls';
+import { SchemaEditorProps } from './types';
+import { SchemaEditorContextProvider } from './schema_editor_context';
+import { Controls } from './schema_editor_controls';
+import { FieldsTable } from './schema_editor_table';
+
+export function SchemaEditor({
+ fields,
+ isLoading,
+ onFieldUnmap,
+ onFieldUpdate,
+ onRefreshData,
+ stream,
+ withControls = false,
+ withFieldSimulation = false,
+ withTableActions = false,
+}: SchemaEditorProps) {
+ const [controls, updateControls] = useControls();
+
+ return (
+
+
+ {isLoading ? (
+
+
+
+ ) : null}
+ {withControls && (
+
+ )}
+
+
+
+ );
+}
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_context.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_context.tsx
new file mode 100644
index 0000000000000..4fd5015eba21b
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_context.tsx
@@ -0,0 +1,15 @@
+/*
+ * 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 createContainer from 'constate';
+
+import { SchemaEditorProps } from './types';
+
+const useSchemaEditor = (props: SchemaEditorProps) => props;
+
+export const [SchemaEditorContextProvider, useSchemaEditorContext] =
+ createContainer(useSchemaEditor);
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_controls.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_controls.tsx
new file mode 100644
index 0000000000000..e3043f1ebfb2b
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_controls.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiFlexItem, EuiFlexGroup, EuiSearchBar, EuiButton } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FieldStatusFilterGroup } from './filters/status_filter_group';
+import { FieldTypeFilterGroup } from './filters/type_filter_group';
+import { TControls } from './hooks/use_controls';
+import { SchemaEditorProps } from './types';
+
+interface ControlsProps {
+ controls: TControls;
+ onChange: (nextControls: Partial) => void;
+ onRefreshData: SchemaEditorProps['onRefreshData'];
+}
+
+export function Controls({ controls, onChange, onRefreshData }: ControlsProps) {
+ return (
+
+
+
+ onChange({ query: nextQuery.query ?? undefined })}
+ box={{
+ incremental: true,
+ }}
+ />
+
+
+
+
+
+
+
+ {onRefreshData && (
+
+
+ {i18n.translate('xpack.streams.schemaEditor.refreshDataButtonLabel', {
+ defaultMessage: 'Refresh',
+ })}
+
+
+ )}
+
+
+ );
+}
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_table.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_table.tsx
new file mode 100644
index 0000000000000..9a086c49a9355
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/schema_editor_table.tsx
@@ -0,0 +1,151 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState, useMemo } from 'react';
+import {
+ EuiDataGridColumnSortingConfig,
+ EuiSearchBar,
+ EuiScreenReaderOnly,
+ EuiDataGrid,
+ EuiDataGridCellProps,
+ EuiDataGridControlColumn,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { WiredStreamDefinition } from '@kbn/streams-schema';
+import { isEmpty } from 'lodash';
+import { TABLE_COLUMNS, EMPTY_CONTENT } from './constants';
+import { FieldActionsCell } from './field_actions';
+import { FieldParent } from './field_parent';
+import { FieldStatusBadge } from './field_status';
+import { TControls } from './hooks/use_controls';
+import { SchemaField } from './types';
+import { FieldType } from './field_type';
+
+export function FieldsTable({
+ fields,
+ controls,
+ stream,
+ withTableActions,
+}: {
+ fields: SchemaField[];
+ controls: TControls;
+ stream: WiredStreamDefinition;
+ withTableActions: boolean;
+}) {
+ // Column visibility
+ const [visibleColumns, setVisibleColumns] = useState(Object.keys(TABLE_COLUMNS));
+ // Column sorting
+ const [sortingColumns, setSortingColumns] = useState([]);
+
+ const filteredFields = useMemo(
+ () => filterFieldsByControls(fields, controls),
+ [fields, controls]
+ );
+
+ const trailingColumns = useMemo(() => {
+ if (!withTableActions) return undefined;
+
+ return [createFieldActionsCellRenderer(filteredFields)];
+ }, [withTableActions, filteredFields]);
+
+ const RenderCellValue = useMemo(
+ () => createCellRenderer(filteredFields, stream),
+ [filteredFields, stream]
+ );
+
+ return (
+ ({
+ id: columnId,
+ ...value,
+ }))}
+ columnVisibility={{
+ visibleColumns,
+ setVisibleColumns,
+ canDragAndDropColumns: false,
+ }}
+ sorting={{ columns: sortingColumns, onSort: setSortingColumns }}
+ toolbarVisibility={true}
+ rowCount={filteredFields.length}
+ renderCellValue={RenderCellValue}
+ trailingControlColumns={trailingColumns}
+ gridStyle={{
+ border: 'none',
+ rowHover: 'none',
+ header: 'underline',
+ }}
+ inMemory={{ level: 'sorting' }}
+ />
+ );
+}
+
+const createCellRenderer =
+ (fields: SchemaField[], stream: WiredStreamDefinition): EuiDataGridCellProps['renderCellValue'] =>
+ ({ rowIndex, columnId }) => {
+ const field = fields[rowIndex];
+ if (!field) return null;
+ const { parent, status } = field;
+
+ if (columnId === 'type') {
+ if (!field.type) return EMPTY_CONTENT;
+ return ;
+ }
+
+ if (columnId === 'parent') {
+ return ;
+ }
+
+ if (columnId === 'status') {
+ return ;
+ }
+
+ return field[columnId as keyof SchemaField] || EMPTY_CONTENT;
+ };
+
+const createFieldActionsCellRenderer = (fields: SchemaField[]): EuiDataGridControlColumn => ({
+ id: 'field-actions',
+ width: 40,
+ headerCellRender: () => (
+
+
+ {i18n.translate('xpack.streams.streamDetailSchemaEditorFieldsTableActionsTitle', {
+ defaultMessage: 'Field actions',
+ })}
+
+
+ ),
+ rowCellRender: ({ rowIndex }) => {
+ const field = fields[rowIndex];
+
+ if (!field) return null;
+
+ return ;
+ },
+});
+
+const filterFieldsByControls = (fields: SchemaField[], controls: TControls) => {
+ if (!controls.query && isEmpty(controls.type) && isEmpty(controls.status)) {
+ return fields;
+ }
+
+ const matchingQueryFields = EuiSearchBar.Query.execute(controls.query, fields, {
+ defaultFields: ['name', 'type'],
+ });
+
+ const filteredByGroupsFields = matchingQueryFields.filter((field) => {
+ return (
+ (isEmpty(controls.type) || (field.type && controls.type.includes(field.type))) && // Filter by applied type
+ (isEmpty(controls.status) || controls.status.includes(field.status)) // Filter by applied status
+ );
+ });
+
+ return filteredByGroupsFields;
+};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/types.ts b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/types.ts
new file mode 100644
index 0000000000000..36cbc11b4a545
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/types.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { FieldDefinitionConfig, WiredStreamDefinition } from '@kbn/streams-schema';
+
+export type SchemaFieldStatus = 'inherited' | 'mapped' | 'unmapped';
+export type SchemaFieldType = FieldDefinitionConfig['type'];
+
+export interface BaseSchemaField extends Omit {
+ name: string;
+ parent: string;
+}
+
+export interface MappedSchemaField extends BaseSchemaField {
+ status: 'inherited' | 'mapped';
+ type: SchemaFieldType;
+}
+
+export interface UnmappedSchemaField extends BaseSchemaField {
+ status: 'unmapped';
+ type?: SchemaFieldType | undefined;
+}
+
+export type SchemaField = MappedSchemaField | UnmappedSchemaField;
+
+export interface SchemaEditorProps {
+ fields: SchemaField[];
+ isLoading?: boolean;
+ onFieldUnmap: (fieldName: SchemaField['name']) => void;
+ onFieldUpdate: (field: SchemaField) => void;
+ onRefreshData?: () => void;
+ stream: WiredStreamDefinition;
+ withControls?: boolean;
+ withFieldSimulation?: boolean;
+ withTableActions?: boolean;
+}
+
+export const isSchemaFieldTyped = (field: SchemaField): field is MappedSchemaField => {
+ return !!field && !!field.name && !!field.type;
+};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/unpromote_field_modal.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/unpromote_field_modal.tsx
new file mode 100644
index 0000000000000..750bac00d8070
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/schema_editor/unpromote_field_modal.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 { EuiConfirmModal } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import useAsyncFn from 'react-use/lib/useAsyncFn';
+import { SchemaEditorProps, SchemaField } from './types';
+
+export const UnpromoteFieldModal = ({
+ field,
+ onClose,
+ onFieldUnmap,
+}: {
+ field: SchemaField;
+ onClose: () => void;
+ onFieldUnmap: SchemaEditorProps['onFieldUnmap'];
+}) => {
+ const [{ loading }, unmapField] = useAsyncFn(async () => {
+ await onFieldUnmap(field.name);
+ if (onClose) onClose();
+ }, [field, onClose, onFieldUnmap]);
+
+ return (
+
+ {i18n.translate('xpack.streams.unpromoteFieldModal.unpromoteFieldWarning', {
+ defaultMessage: 'Are you sure you want to unmap this field from template mappings?',
+ })}
+
+ );
+};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/fields_table.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/fields_table.tsx
deleted file mode 100644
index 960b25ef39795..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/fields_table.tsx
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React, { useMemo, useState } from 'react';
-import {
- EuiButtonIcon,
- EuiContextMenu,
- EuiDataGrid,
- EuiPopover,
- EuiSearchBar,
- useGeneratedHtmlId,
-} from '@elastic/eui';
-import type {
- EuiContextMenuPanelDescriptor,
- EuiContextMenuPanelItemDescriptor,
- EuiDataGridColumnSortingConfig,
- EuiDataGridProps,
- Query,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import useToggle from 'react-use/lib/useToggle';
-import { isRootStreamDefinition, WiredStreamGetResponse } from '@kbn/streams-schema';
-import { FieldType } from './field_type';
-import { FieldStatusBadge } from './field_status';
-import { FieldEntry, SchemaEditorEditingState } from './hooks/use_editing_state';
-import { SchemaEditorUnpromotingState } from './hooks/use_unpromoting_state';
-import { FieldParent } from './field_parent';
-import { SchemaEditorQueryAndFiltersState } from './hooks/use_query_and_filters';
-
-interface FieldsTableContainerProps {
- definition: WiredStreamGetResponse;
- unmappedFieldsResult?: string[];
- isLoadingUnmappedFields: boolean;
- query?: Query;
- editingState: SchemaEditorEditingState;
- unpromotingState: SchemaEditorUnpromotingState;
- queryAndFiltersState: SchemaEditorQueryAndFiltersState;
-}
-
-const COLUMNS = {
- name: {
- display: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldsTablenameHeader', {
- defaultMessage: 'Field',
- }),
- },
- type: {
- display: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldsTabletypeHeader', {
- defaultMessage: 'Type',
- }),
- },
- format: {
- display: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldsTableformatHeader', {
- defaultMessage: 'Format',
- }),
- },
- parent: {
- display: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldsTableFieldParentHeader', {
- defaultMessage: 'Field Parent (Stream)',
- }),
- },
- status: {
- display: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldsTablestatusHeader', {
- defaultMessage: 'Status',
- }),
- },
-};
-
-export const EMPTY_CONTENT = '-----';
-
-export const FieldsTableContainer = ({
- definition,
- unmappedFieldsResult,
- query,
- editingState,
- unpromotingState,
- queryAndFiltersState,
-}: FieldsTableContainerProps) => {
- const inheritedFields = useMemo(() => {
- return Object.entries(definition.inherited_fields).map(([name, field]) => ({
- name,
- type: field.type,
- format: field.format,
- parent: field.from,
- status: 'inherited' as const,
- }));
- }, [definition]);
-
- const filteredInheritedFields = useMemo(() => {
- if (!query) return inheritedFields;
- return EuiSearchBar.Query.execute(query, inheritedFields, {
- defaultFields: ['name', 'type'],
- });
- }, [inheritedFields, query]);
-
- const mappedFields = useMemo(() => {
- return Object.entries(definition.stream.ingest.wired.fields).map(([name, field]) => ({
- name,
- type: field.type,
- format: field.format,
- parent: definition.stream.name,
- status: 'mapped' as const,
- }));
- return [];
- }, [definition]);
-
- const filteredMappedFields = useMemo(() => {
- if (!query) return mappedFields;
- return EuiSearchBar.Query.execute(query, mappedFields, {
- defaultFields: ['name', 'type'],
- });
- }, [mappedFields, query]);
-
- const unmappedFields = useMemo(() => {
- return unmappedFieldsResult
- ? unmappedFieldsResult.map((field) => ({
- name: field,
- parent: definition.stream.name,
- status: 'unmapped' as const,
- }))
- : [];
- }, [definition.stream.name, unmappedFieldsResult]);
-
- const filteredUnmappedFields = useMemo(() => {
- if (!unmappedFieldsResult) return [];
- if (!query) return unmappedFields;
- return EuiSearchBar.Query.execute(query, unmappedFields, {
- defaultFields: ['name'],
- });
- }, [unmappedFieldsResult, query, unmappedFields]);
-
- const allFilteredFields = useMemo(() => {
- return [...filteredInheritedFields, ...filteredMappedFields, ...filteredUnmappedFields];
- }, [filteredInheritedFields, filteredMappedFields, filteredUnmappedFields]);
-
- const filteredFieldsWithFilterGroupsApplied = useMemo(() => {
- const filterGroups = queryAndFiltersState.filterGroups;
- let fieldsWithFilterGroupsApplied = allFilteredFields;
-
- if (filterGroups.type && filterGroups.type.length > 0) {
- fieldsWithFilterGroupsApplied = fieldsWithFilterGroupsApplied.filter(
- (field) => 'type' in field && filterGroups.type.includes(field.type)
- );
- }
-
- if (filterGroups.status && filterGroups.status.length > 0) {
- fieldsWithFilterGroupsApplied = fieldsWithFilterGroupsApplied.filter(
- (field) => 'status' in field && filterGroups.status.includes(field.status)
- );
- }
-
- return fieldsWithFilterGroupsApplied;
- }, [allFilteredFields, queryAndFiltersState.filterGroups]);
-
- return (
-
- );
-};
-
-interface FieldsTableProps {
- definition: WiredStreamGetResponse;
- fields: FieldEntry[];
- editingState: SchemaEditorEditingState;
- unpromotingState: SchemaEditorUnpromotingState;
-}
-
-const FieldsTable = ({ definition, fields, editingState, unpromotingState }: FieldsTableProps) => {
- // Column visibility
- const [visibleColumns, setVisibleColumns] = useState(Object.keys(COLUMNS));
-
- // Column sorting
- const [sortingColumns, setSortingColumns] = useState([]);
-
- const trailingColumns = useMemo(() => {
- return !isRootStreamDefinition(definition.stream)
- ? ([
- {
- id: 'actions',
- width: 40,
- headerCellRender: () => null,
- rowCellRender: ({ rowIndex }) => {
- const field = fields[rowIndex];
-
- if (!field) return null;
-
- let actions: ActionsCellActionsDescriptor[] = [];
-
- switch (field.status) {
- case 'mapped':
- actions = [
- {
- name: i18n.translate('xpack.streams.actions.viewFieldLabel', {
- defaultMessage: 'View field',
- }),
- disabled: editingState.isSaving,
- onClick: (fieldEntry: FieldEntry) => {
- editingState.selectField(fieldEntry, false);
- },
- },
- {
- name: i18n.translate('xpack.streams.actions.editFieldLabel', {
- defaultMessage: 'Edit field',
- }),
- disabled: editingState.isSaving,
- onClick: (fieldEntry: FieldEntry) => {
- editingState.selectField(fieldEntry, true);
- },
- },
- {
- name: i18n.translate('xpack.streams.actions.unpromoteFieldLabel', {
- defaultMessage: 'Unmap field',
- }),
- disabled: unpromotingState.isUnpromotingField,
- onClick: (fieldEntry: FieldEntry) => {
- unpromotingState.setSelectedField(fieldEntry.name);
- },
- },
- ];
- break;
- case 'unmapped':
- actions = [
- {
- name: i18n.translate('xpack.streams.actions.viewFieldLabel', {
- defaultMessage: 'View field',
- }),
- disabled: editingState.isSaving,
- onClick: (fieldEntry: FieldEntry) => {
- editingState.selectField(fieldEntry, false);
- },
- },
- {
- name: i18n.translate('xpack.streams.actions.mapFieldLabel', {
- defaultMessage: 'Map field',
- }),
- disabled: editingState.isSaving,
- onClick: (fieldEntry: FieldEntry) => {
- editingState.selectField(fieldEntry, true);
- },
- },
- ];
- break;
- case 'inherited':
- actions = [
- {
- name: i18n.translate('xpack.streams.actions.viewFieldLabel', {
- defaultMessage: 'View field',
- }),
- disabled: editingState.isSaving,
- onClick: (fieldEntry: FieldEntry) => {
- editingState.selectField(fieldEntry, false);
- },
- },
- ];
- break;
- }
-
- return (
- ({
- name: action.name,
- icon: action.icon,
- onClick: (event) => {
- action.onClick(field);
- },
- })),
- },
- ]}
- />
- );
- },
- },
- ] as EuiDataGridProps['trailingControlColumns'])
- : undefined;
- }, [definition, editingState, fields, unpromotingState]);
-
- return (
- ({
- id: columnId,
- ...value,
- }))}
- columnVisibility={{
- visibleColumns,
- setVisibleColumns,
- canDragAndDropColumns: false,
- }}
- sorting={{ columns: sortingColumns, onSort: setSortingColumns }}
- toolbarVisibility={true}
- rowCount={fields.length}
- renderCellValue={({ rowIndex, columnId }) => {
- const field = fields[rowIndex];
- if (!field) return null;
-
- if (columnId === 'type') {
- const fieldType = field.type;
- if (!fieldType) return EMPTY_CONTENT;
- return ;
- } else if (columnId === 'parent') {
- return (
-
- );
- } else if (columnId === 'status') {
- return ;
- } else {
- return field[columnId as keyof FieldEntry] || EMPTY_CONTENT;
- }
- }}
- trailingControlColumns={trailingColumns}
- gridStyle={{
- border: 'none',
- rowHover: 'none',
- header: 'underline',
- }}
- inMemory={{ level: 'sorting' }}
- />
- );
-};
-
-export const ActionsCell = ({ panels }: { panels: EuiContextMenuPanelDescriptor[] }) => {
- const contextMenuPopoverId = useGeneratedHtmlId({
- prefix: 'fieldsTableContextMenuPopover',
- });
-
- const [popoverIsOpen, togglePopoverIsOpen] = useToggle(false);
-
- return (
-
- );
-};
-
-export type ActionsCellActionsDescriptor = Omit & {
- onClick: (field: FieldEntry) => void;
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_type.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_type.tsx
deleted file mode 100644
index ce03d709ce2b5..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_type.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiSelect } from '@elastic/eui';
-import React from 'react';
-import { SchemaEditorEditingState } from '../hooks/use_editing_state';
-
-type FieldFormTypeProps = Pick & {
- isLoadingRecommendation: boolean;
- recommendation?: string;
-};
-
-const TYPE_OPTIONS = {
- long: 'Long',
- double: 'Double',
- keyword: 'Keyword',
- match_only_text: 'Text (match_only_text)',
- boolean: 'Boolean',
- ip: 'IP',
- date: 'Date',
-} as const;
-
-type FieldTypeOption = keyof typeof TYPE_OPTIONS;
-
-export const FieldFormType = ({
- nextFieldType: value,
- setNextFieldType: onChange,
- isLoadingRecommendation,
- recommendation,
-}: FieldFormTypeProps) => {
- return (
- {
- onChange(event.target.value as FieldTypeOption);
- }}
- value={value}
- options={Object.entries(TYPE_OPTIONS).map(([optionKey, optionValue]) => ({
- text: optionValue,
- value: optionKey,
- }))}
- />
- );
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_type_wrapper.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_type_wrapper.tsx
deleted file mode 100644
index 671e6625112c8..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_form_type_wrapper.tsx
+++ /dev/null
@@ -1,84 +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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import React, { useEffect } from 'react';
-import { EMPTY_CONTENT } from '../fields_table';
-import { EcsRecommendation } from './ecs_recommendation';
-import { FieldFormType } from './field_form_type';
-import { FieldEntry, SchemaEditorEditingState } from '../hooks/use_editing_state';
-import { FieldType } from '../field_type';
-import { useKibana } from '../../../hooks/use_kibana';
-import { FIELD_TYPE_MAP } from '../configuration_maps';
-
-export const FieldFormTypeWrapper = ({
- isEditing,
- nextFieldType,
- setNextFieldType,
- selectedFieldType,
- selectedFieldName,
-}: {
- isEditing: boolean;
- nextFieldType: SchemaEditorEditingState['nextFieldType'];
- setNextFieldType: SchemaEditorEditingState['setNextFieldType'];
- selectedFieldType: FieldEntry['type'];
- selectedFieldName: FieldEntry['name'];
-}) => {
- const {
- dependencies: {
- start: {
- fieldsMetadata: { useFieldsMetadata },
- },
- },
- } = useKibana();
-
- const { fieldsMetadata, loading } = useFieldsMetadata(
- {
- attributes: ['type'],
- fieldNames: [selectedFieldName],
- },
- [selectedFieldName]
- );
-
- // Propagate recommendation to state if a type is not already set
- useEffect(() => {
- const recommendation = fieldsMetadata?.[selectedFieldName]?.type;
- if (
- !loading &&
- recommendation !== undefined &&
- // Supported type
- recommendation in FIELD_TYPE_MAP &&
- !nextFieldType
- ) {
- setNextFieldType(recommendation as FieldEntry['type']);
- }
- }, [fieldsMetadata, loading, nextFieldType, selectedFieldName, setNextFieldType]);
-
- return (
-
-
- {isEditing ? (
-
- ) : selectedFieldType ? (
-
- ) : (
- `${EMPTY_CONTENT}`
- )}
-
-
-
-
-
- );
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_summary.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_summary.tsx
deleted file mode 100644
index 55a1f67fc308b..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/field_summary.tsx
+++ /dev/null
@@ -1,215 +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 {
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiHorizontalRule,
- EuiIconTip,
- EuiSpacer,
- EuiTitle,
-} from '@elastic/eui';
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { useStreamsAppRouter } from '../../../hooks/use_streams_app_router';
-import { FieldParent } from '../field_parent';
-import { FieldStatusBadge } from '../field_status';
-import { FieldFormFormat, typeSupportsFormat } from './field_form_format';
-import { SchemaEditorFlyoutProps } from '.';
-import { FieldFormTypeWrapper } from './field_form_type_wrapper';
-
-const EMPTY_CONTENT = '-----';
-
-const title = i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryTitle', {
- defaultMessage: 'Field summary',
-});
-
-const FIELD_SUMMARIES = {
- fieldStatus: {
- label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldNameHeader', {
- defaultMessage: 'Status',
- }),
- },
- fieldType: {
- label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldTypeHeader', {
- defaultMessage: 'Type',
- }),
- },
- fieldFormat: {
- label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldFormatHeader', {
- defaultMessage: 'Format',
- }),
- },
- fieldParent: {
- label: i18n.translate('xpack.streams.streamDetailSchemaEditorFieldSummaryFieldParentHeader', {
- defaultMessage: 'Field Parent',
- }),
- },
-};
-
-export const FieldSummary = (props: SchemaEditorFlyoutProps) => {
- const {
- selectedField,
- isEditing,
- nextFieldType,
- setNextFieldType,
- nextFieldFormat,
- setNextFieldFormat,
- toggleIsEditing,
- } = props;
-
- const router = useStreamsAppRouter();
-
- if (!selectedField) {
- return null;
- }
-
- return (
-
-
-
-
- {title}
-
-
- {selectedField.status !== 'inherited' && !isEditing ? (
-
-
-
- toggleIsEditing()}
- iconType="pencil"
- >
- {i18n.translate('xpack.streams.fieldSummary.editButtonLabel', {
- defaultMessage: 'Edit',
- })}
-
-
-
-
- ) : selectedField.status === 'inherited' ? (
-
-
-
-
- {i18n.translate('xpack.streams.fieldSummary.editInParentButtonLabel', {
- defaultMessage: 'Edit in parent stream',
- })}
-
-
-
-
- ) : null}
-
-
-
-
-
-
-
-
- {FIELD_SUMMARIES.fieldStatus.label}{' '}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {FIELD_SUMMARIES.fieldType.label}
-
-
-
-
-
-
-
-
-
- {typeSupportsFormat(nextFieldType) && (
- <>
-
-
-
- {FIELD_SUMMARIES.fieldFormat.label}
-
-
-
- {isEditing ? (
-
- ) : (
- `${selectedField.format ?? EMPTY_CONTENT}`
- )}
-
-
-
- >
- )}
-
-
-
-
- {FIELD_SUMMARIES.fieldParent.label}
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/index.tsx
deleted file mode 100644
index 834ee2f5a33d2..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/flyout/index.tsx
+++ /dev/null
@@ -1,104 +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 { StreamsRepositoryClient } from '@kbn/streams-plugin/public/api';
-import {
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiFlyout,
- EuiFlyoutBody,
- EuiFlyoutHeader,
- EuiFlyoutFooter,
- EuiTitle,
- EuiButton,
-} from '@elastic/eui';
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { WiredStreamGetResponse } from '@kbn/streams-schema';
-import { SchemaEditorEditingState } from '../hooks/use_editing_state';
-import { ChildrenAffectedCallout } from './children_affected_callout';
-import { SamplePreviewTable } from './sample_preview_table';
-import { FieldSummary } from './field_summary';
-
-export type SchemaEditorFlyoutProps = {
- streamsRepositoryClient: StreamsRepositoryClient;
- definition: WiredStreamGetResponse;
-} & SchemaEditorEditingState;
-
-export const SchemaEditorFlyout = (props: SchemaEditorFlyoutProps) => {
- const {
- definition,
- streamsRepositoryClient,
- selectedField,
- reset,
- nextFieldDefinition,
- isEditing,
- isSaving,
- saveChanges,
- } = props;
-
- return (
- reset()} maxWidth="500px">
-
-
-
-
- {selectedField?.name}
-
-
-
-
-
-
-
-
- {isEditing && definition.stream.ingest.routing.length > 0 ? (
-
-
-
- ) : null}
-
-
-
-
-
-
-
-
-
- reset()}
- flush="left"
- >
- {i18n.translate('xpack.streams.schemaEditorFlyout.closeButtonLabel', {
- defaultMessage: 'Cancel',
- })}
-
-
-
- saveChanges && saveChanges()}
- >
- {i18n.translate('xpack.streams.fieldForm.saveButtonLabel', {
- defaultMessage: 'Save changes',
- })}
-
-
-
-
-
- );
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_editing_state.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_editing_state.tsx
deleted file mode 100644
index 6134b6cb0e2e0..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_editing_state.tsx
+++ /dev/null
@@ -1,185 +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 { NamedFieldDefinitionConfig, WiredStreamGetResponse } from '@kbn/streams-schema';
-import { StreamsRepositoryClient } from '@kbn/streams-plugin/public/api';
-import { useCallback, useMemo, useState } from 'react';
-import useToggle from 'react-use/lib/useToggle';
-import { useAbortController } from '@kbn/observability-utils-browser/hooks/use_abort_controller';
-import { ToastsStart } from '@kbn/core-notifications-browser';
-import { i18n } from '@kbn/i18n';
-import { omit } from 'lodash';
-import { FieldStatus } from '../configuration_maps';
-
-export type SchemaEditorEditingState = ReturnType;
-
-export interface FieldEntry {
- name: NamedFieldDefinitionConfig['name'];
- type?: NamedFieldDefinitionConfig['type'];
- format?: NamedFieldDefinitionConfig['format'];
- parent: string;
- status: FieldStatus;
-}
-
-export type EditableFieldDefinition = FieldEntry;
-
-export const useEditingState = ({
- streamsRepositoryClient,
- definition,
- refreshDefinition,
- refreshUnmappedFields,
- toastsService,
-}: {
- streamsRepositoryClient: StreamsRepositoryClient;
- definition: WiredStreamGetResponse;
- refreshDefinition: () => void;
- refreshUnmappedFields: () => void;
- toastsService: ToastsStart;
-}) => {
- const abortController = useAbortController();
- /* Whether the field is being edited, otherwise it's just displayed. */
- const [isEditing, toggleIsEditing] = useToggle(false);
- /* Whether changes are being persisted */
- const [isSaving, toggleIsSaving] = useToggle(false);
- /* Holds errors from saving changes */
- const [error, setError] = useState();
-
- /* Represents the currently selected field. This should not be edited directly. */
- const [selectedField, setSelectedField] = useState();
-
- /** Dirty state */
- /* Dirty state of the field type */
- const [nextFieldType, setNextFieldType] = useState();
- /* Dirty state of the field format */
- const [nextFieldFormat, setNextFieldFormat] = useState<
- EditableFieldDefinition['format'] | undefined
- >();
- /* Full dirty definition entry that can be persisted against a stream */
- const nextFieldDefinition = useMemo(() => {
- return selectedField
- ? {
- name: selectedField.name,
- type: nextFieldType,
- ...(nextFieldFormat && nextFieldType === 'date' ? { format: nextFieldFormat } : {}),
- }
- : undefined;
- }, [nextFieldFormat, nextFieldType, selectedField]);
-
- const selectField = useCallback(
- (field: EditableFieldDefinition, selectAndEdit?: boolean) => {
- setSelectedField(field);
- setNextFieldType(field.type);
- setNextFieldFormat(field.format);
- toggleIsEditing(selectAndEdit !== undefined ? selectAndEdit : false);
- },
- [toggleIsEditing]
- );
-
- const reset = useCallback(() => {
- setSelectedField(undefined);
- setNextFieldType(undefined);
- setNextFieldFormat(undefined);
- toggleIsEditing(false);
- toggleIsSaving(false);
- setError(undefined);
- }, [toggleIsEditing, toggleIsSaving]);
-
- const saveChanges = useMemo(() => {
- return selectedField &&
- isFullFieldDefinition(nextFieldDefinition) &&
- hasChanges(selectedField, nextFieldDefinition)
- ? async () => {
- toggleIsSaving(true);
- try {
- await streamsRepositoryClient.fetch(`PUT /api/streams/{id}/_ingest`, {
- signal: abortController.signal,
- params: {
- path: {
- id: definition.stream.name,
- },
- body: {
- ingest: {
- ...definition.stream.ingest,
- wired: {
- fields: {
- ...Object.fromEntries(
- Object.entries(definition.stream.ingest.wired.fields).filter(
- ([name, _field]) => name !== nextFieldDefinition.name
- )
- ),
- [nextFieldDefinition.name]: omit(nextFieldDefinition, 'name'),
- },
- },
- },
- },
- },
- });
- toastsService.addSuccess(
- i18n.translate('xpack.streams.streamDetailSchemaEditorEditSuccessToast', {
- defaultMessage: '{field} was successfully edited',
- values: { field: nextFieldDefinition.name },
- })
- );
- reset();
- refreshDefinition();
- refreshUnmappedFields();
- } catch (e) {
- toggleIsSaving(false);
- setError(e);
- toastsService.addError(e, {
- title: i18n.translate('xpack.streams.streamDetailSchemaEditorEditErrorToast', {
- defaultMessage: 'Something went wrong editing the {field} field',
- values: { field: nextFieldDefinition.name },
- }),
- });
- }
- }
- : undefined;
- }, [
- abortController.signal,
- definition,
- nextFieldDefinition,
- refreshDefinition,
- refreshUnmappedFields,
- reset,
- selectedField,
- streamsRepositoryClient,
- toastsService,
- toggleIsSaving,
- ]);
-
- return {
- selectedField,
- selectField,
- isEditing,
- toggleIsEditing,
- nextFieldType,
- setNextFieldType,
- nextFieldFormat,
- setNextFieldFormat,
- isSaving,
- saveChanges,
- reset,
- error,
- nextFieldDefinition,
- };
-};
-
-export const isFullFieldDefinition = (
- value?: Partial
-): value is NamedFieldDefinitionConfig => {
- return !!value && !!value.name && !!value.type;
-};
-
-const hasChanges = (
- selectedField: Partial,
- nextFieldEntry: Partial
-) => {
- return (
- selectedField.type !== nextFieldEntry.type || selectedField.format !== nextFieldEntry.format
- );
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_query_and_filters.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_query_and_filters.tsx
deleted file mode 100644
index 96bd1b417244a..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_query_and_filters.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiSearchBar, Query } from '@elastic/eui';
-import { useCallback, useState } from 'react';
-
-export type FilterGroups = Record;
-
-export const useQueryAndFilters = () => {
- const [query, setQuery] = useState(EuiSearchBar.Query.MATCH_ALL);
- const [filterGroups, setFilterGroups] = useState({});
-
- const changeFilterGroups = useCallback(
- (nextFilterGroups: FilterGroups) => {
- setFilterGroups({
- ...filterGroups,
- ...nextFilterGroups,
- });
- },
- [filterGroups]
- );
-
- return {
- query,
- setQuery,
- filterGroups,
- changeFilterGroups,
- };
-};
-
-export type SchemaEditorQueryAndFiltersState = ReturnType;
-export type ChangeFilterGroups = ReturnType['changeFilterGroups'];
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_unpromoting_state.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_unpromoting_state.tsx
deleted file mode 100644
index 2eeb0fc90877a..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/hooks/use_unpromoting_state.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { StreamsRepositoryClient } from '@kbn/streams-plugin/public/api';
-import { useCallback, useState } from 'react';
-import useToggle from 'react-use/lib/useToggle';
-import { useAbortController } from '@kbn/observability-utils-browser/hooks/use_abort_controller';
-import { ToastsStart } from '@kbn/core-notifications-browser';
-import { i18n } from '@kbn/i18n';
-import { omit } from 'lodash';
-import { WiredStreamGetResponse } from '@kbn/streams-schema';
-
-export type SchemaEditorUnpromotingState = ReturnType;
-
-export const useUnpromotingState = ({
- streamsRepositoryClient,
- definition,
- refreshDefinition,
- refreshUnmappedFields,
- toastsService,
-}: {
- streamsRepositoryClient: StreamsRepositoryClient;
- definition: WiredStreamGetResponse;
- refreshDefinition: () => void;
- refreshUnmappedFields: () => void;
- toastsService: ToastsStart;
-}) => {
- const abortController = useAbortController();
- /* Represents the currently persisted state of the selected field. This should not be edited directly. */
- const [selectedField, setSelectedField] = useState();
- /* Whether changes are being persisted */
- const [isUnpromotingField, toggleIsUnpromotingField] = useToggle(false);
- /* Holds errors from saving changes */
- const [error, setError] = useState();
-
- const unpromoteField = useCallback(async () => {
- if (!selectedField) {
- return;
- }
- toggleIsUnpromotingField(true);
- try {
- await streamsRepositoryClient.fetch(`PUT /api/streams/{id}/_ingest`, {
- signal: abortController.signal,
- params: {
- path: {
- id: definition.stream.name,
- },
- body: {
- ingest: {
- ...definition.stream.ingest,
- wired: {
- fields: omit(definition.stream.ingest.wired.fields, selectedField),
- },
- },
- },
- },
- });
- toggleIsUnpromotingField(false);
- setSelectedField(undefined);
- refreshDefinition();
- refreshUnmappedFields();
- toastsService.addSuccess(
- i18n.translate('xpack.streams.streamDetailSchemaEditorUnmapSuccessToast', {
- defaultMessage: '{field} was successfully unmapped',
- values: { field: selectedField },
- })
- );
- } catch (e) {
- toggleIsUnpromotingField(false);
- setError(e);
- toastsService.addError(e, {
- title: i18n.translate('xpack.streams.streamDetailSchemaEditorUnmapErrorToast', {
- defaultMessage: 'Something went wrong unmapping the {field} field',
- values: { field: selectedField },
- }),
- });
- }
- }, [
- abortController.signal,
- definition.stream.ingest,
- definition.stream.name,
- refreshDefinition,
- refreshUnmappedFields,
- selectedField,
- streamsRepositoryClient,
- toastsService,
- toggleIsUnpromotingField,
- ]);
-
- return { selectedField, setSelectedField, isUnpromotingField, unpromoteField, error };
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/index.tsx
index 870d7268be7ed..50c88f4ae0185 100644
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/index.tsx
+++ b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/index.tsx
@@ -4,22 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React, { useCallback, useEffect } from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiPortal, EuiButton } from '@elastic/eui';
-import { css } from '@emotion/css';
-import { i18n } from '@kbn/i18n';
-import { WiredStreamGetResponse } from '@kbn/streams-schema';
-import { useEditingState } from './hooks/use_editing_state';
-import { SchemaEditorFlyout } from './flyout';
-import { useKibana } from '../../hooks/use_kibana';
-import { useUnpromotingState } from './hooks/use_unpromoting_state';
-import { SimpleSearchBar } from './simple_search_bar';
-import { UnpromoteFieldModal } from './unpromote_field_modal';
-import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch';
-import { FieldsTableContainer } from './fields_table';
-import { FieldTypeFilterGroup } from './filters/type_filter_group';
-import { useQueryAndFilters } from './hooks/use_query_and_filters';
-import { FieldStatusFilterGroup } from './filters/status_filter_group';
+import React from 'react';
+import { WiredStreamGetResponse, isRootStreamDefinition } from '@kbn/streams-schema';
+import { SchemaEditor } from '../schema_editor';
+import { useSchemaFields } from '../schema_editor/hooks/use_schema_fields';
interface SchemaEditorProps {
definition?: WiredStreamGetResponse;
@@ -37,133 +25,23 @@ const Content = ({
refreshDefinition,
isLoadingDefinition,
}: Required) => {
- const {
- dependencies: {
- start: {
- streams: { streamsRepositoryClient },
- },
- },
- core: {
- notifications: { toasts },
- },
- } = useKibana();
-
- const queryAndFiltersState = useQueryAndFilters();
-
- const {
- value: unmappedFieldsValue,
- loading: isLoadingUnmappedFields,
- refresh: refreshUnmappedFields,
- } = useStreamsAppFetch(
- ({ signal }) => {
- return streamsRepositoryClient.fetch('GET /api/streams/{id}/schema/unmapped_fields', {
- signal,
- params: {
- path: {
- id: definition.stream.name,
- },
- },
- });
- },
- [definition.stream.name, streamsRepositoryClient]
- );
-
- const editingState = useEditingState({
- definition,
- streamsRepositoryClient,
- refreshDefinition,
- refreshUnmappedFields,
- toastsService: toasts,
- });
-
- const unpromotingState = useUnpromotingState({
- definition,
- streamsRepositoryClient,
- refreshDefinition,
- refreshUnmappedFields,
- toastsService: toasts,
- });
-
- const { reset } = editingState;
-
- // If the definition changes (e.g. navigating to parent stream), reset the entire editing state.
- useEffect(() => {
- reset();
- }, [definition.stream.name, reset]);
-
- const refreshData = useCallback(() => {
- refreshDefinition();
- refreshUnmappedFields();
- }, [refreshDefinition, refreshUnmappedFields]);
+ const { fields, isLoadingUnmappedFields, refreshFields, unmapField, updateField } =
+ useSchemaFields({
+ definition,
+ refreshDefinition,
+ });
return (
-
-
- {isLoadingDefinition || isLoadingUnmappedFields ? (
-
-
-
- ) : null}
-
-
-
-
- queryAndFiltersState.setQuery(nextQuery.query ?? undefined)
- }
- />
-
-
-
-
-
-
-
-
-
- {i18n.translate('xpack.streams.schemaEditor.refreshDataButtonLabel', {
- defaultMessage: 'Refresh',
- })}
-
-
-
-
-
-
-
-
- {editingState.selectedField && (
-
- )}
-
- {unpromotingState.selectedField && (
-
- )}
-
-
+
);
};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/simple_search_bar.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/simple_search_bar.tsx
deleted file mode 100644
index 93e972c4b999a..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/simple_search_bar.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiSearchBar, EuiSearchBarProps } from '@elastic/eui';
-import React from 'react';
-
-/* Simple search bar that doesn't attempt to integrate with unified search */
-export const SimpleSearchBar = ({
- query,
- onChange,
-}: {
- query: EuiSearchBarProps['query'];
- onChange: Required['onChange'];
-}) => {
- return (
- {
- onChange(nextQuery);
- }}
- />
- );
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/unpromote_field_modal.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/unpromote_field_modal.tsx
deleted file mode 100644
index 59d66b44eec44..0000000000000
--- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_schema_editor/unpromote_field_modal.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-import {
- EuiButton,
- EuiModal,
- EuiModalBody,
- EuiModalFooter,
- EuiModalHeader,
- EuiModalHeaderTitle,
- useGeneratedHtmlId,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import { SchemaEditorUnpromotingState } from './hooks/use_unpromoting_state';
-
-export const UnpromoteFieldModal = ({
- unpromotingState,
-}: {
- unpromotingState: SchemaEditorUnpromotingState;
-}) => {
- const { setSelectedField, selectedField, unpromoteField, isUnpromotingField } = unpromotingState;
-
- const modalTitleId = useGeneratedHtmlId();
-
- if (!selectedField) return null;
-
- return (
- setSelectedField(undefined)}>
-
- {selectedField}
-
-
-
- {i18n.translate('xpack.streams.unpromoteFieldModal.unpromoteFieldWarning', {
- defaultMessage: 'Are you sure you want to unmap this field from template mappings?',
- })}
-
-
-
- unpromoteField()}
- disabled={isUnpromotingField}
- color="danger"
- fill
- >
- {i18n.translate('xpack.streams.unpromoteFieldModal.unpromoteFieldButtonLabel', {
- defaultMessage: 'Unmap field',
- })}
-
-
-
- );
-};
diff --git a/x-pack/solutions/observability/plugins/streams_app/tsconfig.json b/x-pack/solutions/observability/plugins/streams_app/tsconfig.json
index 8e8ab46705baa..6bfe3e0e6a4d9 100644
--- a/x-pack/solutions/observability/plugins/streams_app/tsconfig.json
+++ b/x-pack/solutions/observability/plugins/streams_app/tsconfig.json
@@ -31,7 +31,6 @@
"@kbn/observability-utils-server",
"@kbn/ui-theme",
"@kbn/calculate-auto",
- "@kbn/core-notifications-browser",
"@kbn/kibana-react-plugin",
"@kbn/es-query",
"@kbn/server-route-repository-client",
@@ -45,7 +44,6 @@
"@kbn/code-editor",
"@kbn/ui-theme",
"@kbn/navigation-plugin",
- "@kbn/core-notifications-browser",
"@kbn/index-lifecycle-management-common-shared",
"@kbn/streams-schema",
"@kbn/react-hooks",