diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_renderer/settings/settings_flyout.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_renderer/settings/settings_flyout.tsx
index 0f4e747023cd4..173558323b2fe 100644
--- a/src/platform/plugins/shared/dashboard/public/dashboard_renderer/settings/settings_flyout.tsx
+++ b/src/platform/plugins/shared/dashboard/public/dashboard_renderer/settings/settings_flyout.tsx
@@ -126,19 +126,10 @@ export const DashboardSettingsFlyout = ({ onClose, ariaLabelledBy }: DashboardSe
if (!savedObjectsTaggingApi) return;
return (
-
- }
- >
- updateDashboardSetting({ tags: selectedTags })}
- />
-
+ updateDashboardSetting({ tags: selectedTags })}
+ />
);
};
diff --git a/x-pack/platform/plugins/private/translations/translations/de-DE.json b/x-pack/platform/plugins/private/translations/translations/de-DE.json
index e644a583bb6ed..c3e9195f0d817 100644
--- a/x-pack/platform/plugins/private/translations/translations/de-DE.json
+++ b/x-pack/platform/plugins/private/translations/translations/de-DE.json
@@ -1471,7 +1471,6 @@
"dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel": "Farbpaletten zwischen den Bedienfeldern synchronisieren",
"dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel": "Cursor über alle Felder hinweg synchronisieren",
"dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel": "Synchrone Tooltips für alle Bedienfelder",
- "dashboard.embeddableApi.showSettings.flyout.form.tagsFormRowLabel": "Tags",
"dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel": "Verwenden Sie Abstände zwischen den Panels",
"dashboard.embeddableApi.showSettings.flyout.formRow.syncAcrossPanelsLabel": "Über alle Felder hinweg synchronisieren",
"dashboard.embeddableApi.showSettings.flyout.title": "Dashboard-Einstellungen",
diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
index 773c556dfe997..bd37817442fca 100644
--- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json
+++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
@@ -1488,7 +1488,6 @@
"dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel": "Synchroniser les palettes de couleur de tous les panneaux",
"dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel": "Synchroniser le curseur de tous les panneaux",
"dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel": "Synchroniser les infobulles de tous les panneaux",
- "dashboard.embeddableApi.showSettings.flyout.form.tagsFormRowLabel": "Balises",
"dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel": "Utiliser des marges entre les panneaux",
"dashboard.embeddableApi.showSettings.flyout.formRow.syncAcrossPanelsLabel": "Synchroniser sur tous les panneaux",
"dashboard.embeddableApi.showSettings.flyout.title": "Paramètres du tableau de bord",
diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
index 5f3b1c2c55ec0..f46f9e68b4771 100644
--- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json
+++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
@@ -1491,7 +1491,6 @@
"dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel": "パネル全体でカラーパレットを同期",
"dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel": "パネル全体でカーソルを同期",
"dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel": "パネル間でツールチップを同期",
- "dashboard.embeddableApi.showSettings.flyout.form.tagsFormRowLabel": "タグ",
"dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel": "パネルの間に余白を使用",
"dashboard.embeddableApi.showSettings.flyout.formRow.syncAcrossPanelsLabel": "パネル全体で同期",
"dashboard.embeddableApi.showSettings.flyout.title": "ダッシュボード設定",
diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
index 9b5b2e0bf8d52..38bcf10f73546 100644
--- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json
+++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
@@ -1485,7 +1485,6 @@
"dashboard.embeddableApi.showSettings.flyout.form.syncColorsBetweenPanelsSwitchLabel": "在面板之间同步调色板",
"dashboard.embeddableApi.showSettings.flyout.form.syncCursorBetweenPanelsSwitchLabel": "在面板之间同步光标",
"dashboard.embeddableApi.showSettings.flyout.form.syncTooltipsBetweenPanelsSwitchLabel": "在面板之间同步工具提示",
- "dashboard.embeddableApi.showSettings.flyout.form.tagsFormRowLabel": "标签",
"dashboard.embeddableApi.showSettings.flyout.form.useMarginsBetweenPanelsSwitchLabel": "在面板间使用边距",
"dashboard.embeddableApi.showSettings.flyout.formRow.syncAcrossPanelsLabel": "跨面板同步",
"dashboard.embeddableApi.showSettings.flyout.title": "仪表板设置",
diff --git a/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/base/tag_selector.tsx b/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/base/tag_selector.tsx
index b42f7e41939b2..529da7ef5f9a3 100644
--- a/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/base/tag_selector.tsx
+++ b/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/base/tag_selector.tsx
@@ -113,7 +113,7 @@ export const TagSelector: FC = ({
allowCreate,
openCreateModal,
fullWidth = true,
- ...otherProps
+ ...comboBoxProps
}) => {
const [currentSearch, setCurrentSearch] = useState('');
@@ -175,16 +175,26 @@ export const TagSelector: FC = ({
[selected, onTagsSelected, openCreateModal, currentSearch]
);
+ const { onSearchChange: onSearchChangeProp, ...restProps } = comboBoxProps;
+
+ const handleOnSearchChange = useCallback(
+ (searchValue: string) => {
+ setCurrentSearch(searchValue);
+ onSearchChangeProp?.(searchValue);
+ },
+ [onSearchChangeProp]
+ );
+
return (
placeholder={''}
options={options}
selectedOptions={selectedOptions}
- onSearchChange={setCurrentSearch}
+ onSearchChange={handleOnSearchChange}
onChange={onChange}
renderOption={renderOption}
fullWidth={fullWidth}
- {...otherProps}
+ {...restProps}
/>
);
};
diff --git a/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/connected/saved_object_save_modal_tag_selector.tsx b/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/connected/saved_object_save_modal_tag_selector.tsx
index 3113e7dbb18a6..1e8481b054265 100644
--- a/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/connected/saved_object_save_modal_tag_selector.tsx
+++ b/x-pack/platform/plugins/shared/saved_objects_tagging/public/components/connected/saved_object_save_modal_tag_selector.tsx
@@ -11,6 +11,7 @@ import useObservable from 'react-use/lib/useObservable';
import { EuiFormRow, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { SavedObjectSaveModalTagSelectorComponentProps } from '@kbn/saved-objects-tagging-oss-plugin/public';
+import { i18n } from '@kbn/i18n';
import type { TagsCapabilities } from '../../../common';
import { TagSelector } from '../base';
import type { ITagsCache } from '../../services';
@@ -35,6 +36,44 @@ export const getConnectedSavedObjectModalTagSelectorComponent = ({
}: SavedObjectSaveModalTagSelectorComponentProps) => {
const tags = useObservable(cache.getState$(), cache.getState());
const [selected, setSelected] = useState(initialSelection);
+ const [searchValue, setSearchValue] = useState('');
+ const [touched, setTouched] = useState(false);
+
+ const normalizedSearchValue = searchValue.trim().toLowerCase();
+
+ const exactTagMatch = !!normalizedSearchValue
+ ? tags.find((tag) => tag.name.toLowerCase() === normalizedSearchValue)
+ : undefined;
+
+ const isTagAlreadyCreated = !!exactTagMatch;
+ const isTagAlreadySelected = isTagAlreadyCreated && selected.includes(exactTagMatch.id);
+ const noMatchingTag = !!normalizedSearchValue && !isTagAlreadyCreated;
+ const isInvalid = touched && (noMatchingTag || isTagAlreadyCreated || isTagAlreadySelected);
+
+ const getErrorMessage = () => {
+ if (isTagAlreadySelected) {
+ return i18n.translate('xpack.savedObjectsTagging.uiApi.saveModal.alreadySelectedHint', {
+ defaultMessage: 'Tag "{searchValue}" is already selected.',
+ values: { searchValue },
+ });
+ }
+ if (isTagAlreadyCreated) {
+ return i18n.translate('xpack.savedObjectsTagging.uiApi.saveModal.exactTagMatchHint', {
+ defaultMessage: 'Tag "{searchValue}" already exists. Select it from the existing tags.',
+ values: { searchValue },
+ });
+ }
+ return capabilities.create
+ ? i18n.translate('xpack.savedObjectsTagging.uiApi.saveModal.noMatchingTagCreateHint', {
+ defaultMessage:
+ 'No tags match "{searchValue}". Select an existing tag or create a new one.',
+ values: { searchValue },
+ })
+ : i18n.translate('xpack.savedObjectsTagging.uiApi.saveModal.noMatchingTagHint', {
+ defaultMessage: 'No tags match "{searchValue}".',
+ values: { searchValue },
+ });
+ };
const setSelectedInternal = useCallback(
(newSelection: string[]) => {
@@ -63,6 +102,8 @@ export const getConnectedSavedObjectModalTagSelectorComponent = ({
)
}
+ isInvalid={isInvalid}
+ error={isInvalid ? getErrorMessage() : undefined}
>
setTouched(true)}
+ isInvalid={isInvalid}
{...rest}
/>