[Maps] Switch to SavedObjectClient.resolve (#112606)

* [Maps] Switch to SavedObjectClient.resolve

* spaces tslint

* aliasMatch

* maps eslint

* update lens to use spaces.ui.components.getSavedObjectConflictMessage

* lens eslint

* spaces eslint

* review feedback

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2021-10-05 13:45:51 -06:00 committed by GitHub
parent 31515e90fd
commit a9a923d5ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 243 additions and 95 deletions

View file

@ -7,6 +7,7 @@
import { isEqual, uniqBy } from 'lodash';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { render, unmountComponentAtNode } from 'react-dom';
import type {
ExecutionContextSearch,
@ -41,11 +42,7 @@ import {
ReferenceOrValueEmbeddable,
} from '../../../../../src/plugins/embeddable/public';
import { Document, injectFilterReferences } from '../persistence';
import {
ExpressionWrapper,
ExpressionWrapperProps,
savedObjectConflictError,
} from './expression_wrapper';
import { ExpressionWrapper, ExpressionWrapperProps } from './expression_wrapper';
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
import {
isLensBrushEvent,
@ -63,6 +60,7 @@ import { LensAttributeService } from '../lens_attribute_service';
import type { ErrorMessage } from '../editor_frame_service/types';
import { getLensInspectorService, LensInspector } from '../lens_inspector_service';
import { SharingSavedObjectProps } from '../types';
import type { SpacesPluginStart } from '../../../spaces/public';
export type LensSavedObjectAttributes = Omit<Document, 'savedObjectId' | 'type'>;
export interface ResolvedLensSavedObjectAttributes extends LensSavedObjectAttributes {
@ -108,6 +106,7 @@ export interface LensEmbeddableDeps {
getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions'];
capabilities: { canSaveVisualizations: boolean; canSaveDashboards: boolean };
usageCollection?: UsageCollectionSetup;
spaces?: SpacesPluginStart;
}
export class Embeddable
@ -281,8 +280,17 @@ export class Embeddable
};
const { ast, errors } = await this.deps.documentToExpression(this.savedVis);
this.errors = errors;
if (sharingSavedObjectProps?.outcome === 'conflict') {
const conflictError = savedObjectConflictError(sharingSavedObjectProps.errorJSON!);
if (sharingSavedObjectProps?.outcome === 'conflict' && this.deps.spaces) {
const conflictError = {
shortMessage: i18n.translate('xpack.lens.embeddable.legacyURLConflict.shortMessage', {
defaultMessage: `You've encountered a URL conflict`,
}),
longMessage: (
<this.deps.spaces.ui.components.getSavedObjectConflictMessage
json={sharingSavedObjectProps.errorJSON!}
/>
),
};
this.errors = this.errors ? [...this.errors, conflictError] : [conflictError];
}
this.expression = ast ? toExpression(ast) : null;

View file

@ -24,6 +24,7 @@ import { LensAttributeService } from '../lens_attribute_service';
import { DOC_TYPE } from '../../common/constants';
import { ErrorMessage } from '../editor_frame_service/types';
import { extract, inject } from '../../common/embeddable_factory';
import type { SpacesPluginStart } from '../../../spaces/public';
export interface LensEmbeddableStartServices {
timefilter: TimefilterContract;
@ -38,6 +39,7 @@ export interface LensEmbeddableStartServices {
documentToExpression: (
doc: Document
) => Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }>;
spaces?: SpacesPluginStart;
}
export class EmbeddableFactory implements EmbeddableFactoryDefinition {
@ -90,6 +92,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
capabilities,
usageCollection,
inspector,
spaces,
} = await this.getStartServices();
const { Embeddable } = await import('../async_services');
@ -110,6 +113,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
canSaveVisualizations: Boolean(capabilities.visualize.save),
},
usageCollection,
spaces,
},
input,
parent

View file

@ -5,20 +5,10 @@
* 2.0.
*/
import React, { useState } from 'react';
import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiIcon,
EuiEmptyPrompt,
EuiButtonEmpty,
EuiCallOut,
EuiSpacer,
EuiLink,
} from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon, EuiEmptyPrompt } from '@elastic/eui';
import {
ExpressionRendererEvent,
ReactExpressionRendererType,
@ -28,7 +18,6 @@ import type { KibanaExecutionContext } from 'src/core/public';
import { ExecutionContextSearch } from 'src/plugins/data/public';
import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions';
import classNames from 'classnames';
import { i18n } from '@kbn/i18n';
import { getOriginalRequestErrorMessages } from '../editor_frame_service/error_helper';
import { ErrorMessage } from '../editor_frame_service/types';
import { LensInspector } from '../lens_inspector_service';
@ -172,52 +161,3 @@ export function ExpressionWrapper({
</I18nProvider>
);
}
const SavedObjectConflictMessage = ({ json }: { json: string }) => {
const [expandError, setExpandError] = useState(false);
return (
<>
<FormattedMessage
id="xpack.lens.embeddable.legacyURLConflict.longMessage"
defaultMessage="Disable the {documentationLink} associated with this object."
values={{
documentationLink: (
<EuiLink
external
href="https://www.elastic.co/guide/en/kibana/master/legacy-url-aliases.html"
target="_blank"
>
{i18n.translate('xpack.lens.embeddable.legacyURLConflict.documentationLinkText', {
defaultMessage: 'legacy URL alias',
})}
</EuiLink>
),
}}
/>
<EuiSpacer />
{expandError ? (
<EuiCallOut
title={i18n.translate('xpack.lens.embeddable.legacyURLConflict.expandErrorText', {
defaultMessage: `This object has the same URL as a legacy alias. Disable the alias to resolve this error : {json}`,
values: { json },
})}
color="danger"
iconType="alert"
/>
) : (
<EuiButtonEmpty onClick={() => setExpandError(true)}>
{i18n.translate('xpack.lens.embeddable.legacyURLConflict.expandError', {
defaultMessage: `Show more`,
})}
</EuiButtonEmpty>
)}
</>
);
};
export const savedObjectConflictError = (json: string): ErrorMessage => ({
shortMessage: i18n.translate('xpack.lens.embeddable.legacyURLConflict.shortMessage', {
defaultMessage: `You've encountered a URL conflict`,
}),
longMessage: <SavedObjectConflictMessage json={json} />,
});

View file

@ -212,6 +212,7 @@ export class LensPlugin {
uiActions: plugins.uiActions,
usageCollection,
inspector: plugins.inspector,
spaces: plugins.spaces,
};
};

View file

@ -29,6 +29,7 @@
"savedObjectsTagging",
"charts",
"security",
"spaces",
"usageCollection"
],
"ui": true,

View file

@ -5,4 +5,12 @@
flex: 1 1 100%;
z-index: 1;
min-height: 0; // Absolute must for Firefox to scroll contents
}
.mapEmbeddedError {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: auto;
}

View file

@ -12,6 +12,7 @@ import { Provider } from 'react-redux';
import { render, unmountComponentAtNode } from 'react-dom';
import { Subscription } from 'rxjs';
import { Unsubscribe } from 'redux';
import { EuiEmptyPrompt } from '@elastic/eui';
import {
Embeddable,
IContainer,
@ -66,6 +67,7 @@ import {
getCoreI18n,
getHttp,
getChartsPaletteServiceGetColor,
getSpacesApi,
getSearchService,
} from '../kibana_services';
import { LayerDescriptor, MapExtent } from '../../common/descriptor_types';
@ -353,23 +355,38 @@ export class MapEmbeddable
return;
}
const I18nContext = getCoreI18n().Context;
const sharingSavedObjectProps = this._savedMap.getSharingSavedObjectProps();
const spaces = getSpacesApi();
const content =
sharingSavedObjectProps && spaces && sharingSavedObjectProps?.outcome === 'conflict' ? (
<div className="mapEmbeddedError">
<EuiEmptyPrompt
iconType="alert"
iconColor="danger"
data-test-subj="embeddable-maps-failure"
body={spaces.ui.components.getSavedObjectConflictMessage({
json: sharingSavedObjectProps.errorJSON!,
})}
/>
</div>
) : (
<MapContainer
onSingleValueTrigger={this.onSingleValueTrigger}
addFilters={this.input.hideFilterActions ? null : this.addFilters}
getFilterActions={this.getFilterActions}
getActionContext={this.getActionContext}
renderTooltipContent={this._renderTooltipContent}
title={this.getTitle()}
description={this.getDescription()}
waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this._savedMap.getStore())}
isSharable={this._isSharable}
/>
);
const I18nContext = getCoreI18n().Context;
render(
<Provider store={this._savedMap.getStore()}>
<I18nContext>
<MapContainer
onSingleValueTrigger={this.onSingleValueTrigger}
addFilters={this.input.hideFilterActions ? null : this.addFilters}
getFilterActions={this.getFilterActions}
getActionContext={this.getActionContext}
renderTooltipContent={this._renderTooltipContent}
title={this.getTitle()}
description={this.getDescription()}
waitUntilTimeLayersLoad$={waitUntilTimeLayersLoad$(this._savedMap.getStore())}
isSharable={this._isSharable}
/>
</I18nContext>
<I18nContext>{content}</I18nContext>
</Provider>,
this._domNode
);

View file

@ -53,6 +53,7 @@ export const getNavigateToApp = () => coreStart.application.navigateToApp;
export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging;
export const getPresentationUtilContext = () => pluginsStart.presentationUtil.ContextProvider;
export const getSecurityService = () => pluginsStart.security;
export const getSpacesApi = () => pluginsStart.spaces;
// xpack.maps.* kibana.yml settings from this plugin
let mapAppConfig: MapsConfigType;

View file

@ -14,8 +14,18 @@ import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/sav
import { getCoreOverlays, getEmbeddableService, getSavedObjectsClient } from './kibana_services';
import { extractReferences, injectReferences } from '../common/migrations/references';
import { MapByValueInput, MapByReferenceInput } from './embeddable/types';
import { getSpacesApi } from './kibana_services';
type MapDoc = MapSavedObjectAttributes & { references?: SavedObjectReference[] };
export interface SharingSavedObjectProps {
outcome?: 'aliasMatch' | 'exactMatch' | 'conflict';
aliasTargetId?: string;
errorJSON?: string;
}
type MapDoc = MapSavedObjectAttributes & {
sharingSavedObjectProps?: SharingSavedObjectProps;
references?: SavedObjectReference[];
};
export type MapAttributeService = AttributeService<MapDoc, MapByValueInput, MapByReferenceInput>;
@ -58,7 +68,11 @@ export function getMapAttributeService(): MapAttributeService {
return { id: savedObject.id };
},
unwrapMethod: async (savedObjectId: string): Promise<MapDoc> => {
const savedObject = await getSavedObjectsClient().get<MapSavedObjectAttributes>(
const {
saved_object: savedObject,
outcome,
alias_target_id: aliasTargetId,
} = await getSavedObjectsClient().resolve<MapSavedObjectAttributes>(
MAP_SAVED_OBJECT_TYPE,
savedObjectId
);
@ -68,7 +82,22 @@ export function getMapAttributeService(): MapAttributeService {
}
const { attributes } = injectReferences(savedObject);
return { ...attributes, references: savedObject.references };
return {
...attributes,
references: savedObject.references,
sharingSavedObjectProps: {
aliasTargetId,
outcome,
errorJSON:
outcome === 'conflict' && getSpacesApi()
? JSON.stringify({
targetType: MAP_SAVED_OBJECT_TYPE,
sourceId: savedObjectId,
targetSpace: (await getSpacesApi()!.getActiveSpace()).id,
})
: undefined,
},
};
},
checkForDuplicateTitle: (props: OnSaveProps) => {
return checkForDuplicateTitle(

View file

@ -82,7 +82,8 @@ import {
tileMapRenderer,
tileMapVisType,
} from './legacy_visualizations';
import { SecurityPluginStart } from '../../security/public';
import type { SecurityPluginStart } from '../../security/public';
import type { SpacesPluginStart } from '../../spaces/public';
export interface MapsPluginSetupDependencies {
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
@ -112,6 +113,7 @@ export interface MapsPluginStartDependencies {
savedObjectsTagging?: SavedObjectTaggingPluginStart;
presentationUtil: PresentationUtilPluginStart;
security: SecurityPluginStart;
spaces?: SpacesPluginStart;
}
/**

View file

@ -9,7 +9,7 @@ import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Router, Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { AppMountParameters } from 'kibana/public';
import type { AppMountParameters } from 'kibana/public';
import {
getCoreChrome,
getCoreI18n,
@ -98,6 +98,7 @@ export async function renderApp(
setHeaderActionMenu={setHeaderActionMenu}
stateTransfer={stateTransfer}
originatingApp={originatingApp}
history={history}
key={routeProps.match.params.savedMapId ? routeProps.match.params.savedMapId : 'new'}
/>
);

View file

@ -18,6 +18,7 @@ import {
getCoreChrome,
getMapsCapabilities,
getNavigation,
getSpacesApi,
getTimeFilter,
getToasts,
} from '../../../kibana_services';
@ -40,7 +41,8 @@ import { getIndexPatternsFromIds } from '../../../index_pattern_util';
import { getTopNavConfig } from '../top_nav_config';
import { goToSpecifiedPath } from '../../../render_app';
import { MapSavedObjectAttributes } from '../../../../common/map_saved_object_type';
import { getFullPath, APP_ID } from '../../../../common/constants';
import { getEditPath, getFullPath, APP_ID } from '../../../../common/constants';
import { getMapEmbeddableDisplayName } from '../../../../common/i18n_getters';
import {
getInitialQuery,
getInitialRefreshConfig,
@ -85,6 +87,7 @@ export interface Props {
isSaveDisabled: boolean;
query: Query | undefined;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
history: AppMountParameters['history'];
}
export interface State {
@ -347,6 +350,16 @@ export class MapApp extends React.Component<Props, State> {
return;
}
const sharingSavedObjectProps = this.props.savedMap.getSharingSavedObjectProps();
const spaces = getSpacesApi();
if (spaces && sharingSavedObjectProps?.outcome === 'aliasMatch') {
// We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash
const newObjectId = sharingSavedObjectProps?.aliasTargetId; // This is always defined if outcome === 'aliasMatch'
const newPath = `${getEditPath(newObjectId)}${this.props.history.location.hash}`;
await spaces.ui.redirectLegacyUrl(newPath, getMapEmbeddableDisplayName());
return;
}
this.props.savedMap.setBreadcrumbs();
getCoreChrome().docTitle.change(this.props.savedMap.getTitle());
const savedObjectId = this.props.savedMap.getSavedObjectId();
@ -437,6 +450,21 @@ export class MapApp extends React.Component<Props, State> {
this._onFiltersChange([...this.props.filters, ...newFilters]);
};
_renderLegacyUrlConflict() {
const sharingSavedObjectProps = this.props.savedMap.getSharingSavedObjectProps();
const spaces = getSpacesApi();
return spaces && sharingSavedObjectProps?.outcome === 'conflict'
? spaces.ui.components.getLegacyUrlConflict({
objectNoun: getMapEmbeddableDisplayName(),
currentObjectId: this.props.savedMap.getSavedObjectId()!,
otherObjectId: sharingSavedObjectProps.aliasTargetId!,
otherObjectPath: `${getEditPath(sharingSavedObjectProps.aliasTargetId!)}${
this.props.history.location.hash
}`,
})
: null;
}
render() {
if (!this.state.initialized) {
return null;
@ -447,6 +475,7 @@ export class MapApp extends React.Component<Props, State> {
{this._renderTopNav()}
<h1 className="euiScreenReaderOnly">{`screenTitle placeholder`}</h1>
<div id="react-maps-root">
{this._renderLegacyUrlConflict()}
<MapContainer
addFilters={this._addFilter}
title={this.props.savedMap.getAttributes().title}

View file

@ -7,8 +7,8 @@
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { AppMountParameters } from 'kibana/public';
import { EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
import type { AppMountParameters } from 'kibana/public';
import type { EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
import { MapApp } from './map_app';
import { SavedMap, getInitialLayersFromUrlParam } from './saved_map';
import { MapEmbeddableInput } from '../../embeddable/types';
@ -20,6 +20,7 @@ interface Props {
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
stateTransfer: EmbeddableStateTransfer;
originatingApp?: string;
history: AppMountParameters['history'];
}
interface State {
@ -69,6 +70,7 @@ export class MapPage extends Component<Props, State> {
return (
<Provider store={this.state.savedMap.getStore()}>
<MapApp
history={this.props.history}
savedMap={this.state.savedMap}
onAppLeave={this.props.onAppLeave}
setHeaderActionMenu={this.props.setHeaderActionMenu}

View file

@ -30,7 +30,7 @@ import {
setHiddenLayers,
} from '../../../actions';
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors';
import { getMapAttributeService } from '../../../map_attribute_service';
import { getMapAttributeService, SharingSavedObjectProps } from '../../../map_attribute_service';
import { OnSaveProps } from '../../../../../../../src/plugins/saved_objects/public';
import { MapByReferenceInput, MapEmbeddableInput } from '../../../embeddable/types';
import {
@ -50,6 +50,7 @@ import { whenLicenseInitialized } from '../../../licensed_features';
export class SavedMap {
private _attributes: MapSavedObjectAttributes | null = null;
private _sharingSavedObjectProps: SharingSavedObjectProps | null = null;
private readonly _defaultLayers: LayerDescriptor[];
private readonly _embeddableId?: string;
private _initialLayerListConfig: LayerDescriptor[] = [];
@ -98,8 +99,11 @@ export class SavedMap {
};
} else {
const doc = await getMapAttributeService().unwrapAttributes(this._mapEmbeddableInput);
const { references, ...savedObjectAttributes } = doc;
const { references, sharingSavedObjectProps, ...savedObjectAttributes } = doc;
this._attributes = savedObjectAttributes;
if (sharingSavedObjectProps) {
this._sharingSavedObjectProps = sharingSavedObjectProps;
}
const savedObjectsTagging = getSavedObjectsTagging();
if (savedObjectsTagging && references && references.length) {
this._tags = savedObjectsTagging.ui.getTagIdsFromReferences(references);
@ -274,6 +278,10 @@ export class SavedMap {
return this._attributes;
}
public getSharingSavedObjectProps(): SharingSavedObjectProps | null {
return this._sharingSavedObjectProps;
}
public isByValue(): boolean {
const hasSavedObjectId = !!this.getSavedObjectId();
return getIsAllowByValueEmbeddables() && !!this._originatingApp && !hasSavedObjectId;

View file

@ -36,6 +36,7 @@
{ "path": "../licensing/tsconfig.json" },
{ "path": "../file_upload/tsconfig.json" },
{ "path": "../saved_objects_tagging/tsconfig.json" },
{ "path": "../security/tsconfig.json" }
{ "path": "../security/tsconfig.json" },
{ "path": "../spaces/tsconfig.json" }
]
}

View file

@ -41,6 +41,7 @@ const createApiUiComponentsMock = () => {
getSpaceList: jest.fn(),
getLegacyUrlConflict: jest.fn(),
getSpaceAvatar: jest.fn(),
getSavedObjectConflictMessage: jest.fn(),
};
return mock;

View file

@ -0,0 +1,19 @@
/*
* 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 type { SavedObjectConflictMessageProps } from '../types';
export const getSavedObjectConflictMessage = async (): Promise<
React.FC<SavedObjectConflictMessageProps>
> => {
const { SavedObjectConflictMessage } = await import('./saved_object_conflict_message');
return (props: SavedObjectConflictMessageProps) => {
return <SavedObjectConflictMessage {...props} />;
};
};

View file

@ -6,4 +6,5 @@
*/
export { getShareToSpaceFlyoutComponent } from './share_to_space_flyout';
export { getSavedObjectConflictMessage } from './get_saved_object_conflict_message';
export { getLegacyUrlConflict } from './legacy_url_conflict';

View file

@ -0,0 +1,56 @@
/*
* 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, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import type { SavedObjectConflictMessageProps } from '../types';
export const SavedObjectConflictMessage = ({ json }: SavedObjectConflictMessageProps) => {
const [expandError, setExpandError] = useState(false);
return (
<>
<FormattedMessage
id="xpack.spaces.legacyURLConflict.longMessage"
defaultMessage="Disable the {documentationLink} associated with this object."
values={{
documentationLink: (
<EuiLink
external
href="https://www.elastic.co/guide/en/kibana/master/legacy-url-aliases.html"
target="_blank"
>
{i18n.translate('xpack.spaces.legacyURLConflict.documentationLinkText', {
defaultMessage: 'legacy URL alias',
})}
</EuiLink>
),
}}
/>
<EuiSpacer />
{expandError ? (
<EuiCallOut
title={i18n.translate('xpack.spaces.legacyURLConflict.expandErrorText', {
defaultMessage: `This object has the same URL as a legacy alias. Disable the alias to resolve this error : {json}`,
values: { json },
})}
color="danger"
iconType="alert"
/>
) : (
<EuiButtonEmpty onClick={() => setExpandError(true)}>
{i18n.translate('xpack.spaces.legacyURLConflict.expandError', {
defaultMessage: `Show more`,
})}
</EuiButtonEmpty>
)}
</>
);
};

View file

@ -5,10 +5,15 @@
* 2.0.
*/
export { getShareToSpaceFlyoutComponent, getLegacyUrlConflict } from './components';
export {
getShareToSpaceFlyoutComponent,
getLegacyUrlConflict,
getSavedObjectConflictMessage,
} from './components';
export { createRedirectLegacyUrl } from './utils';
export type {
LegacyUrlConflictProps,
ShareToSpaceFlyoutProps,
ShareToSpaceSavedObjectTarget,
SavedObjectConflictMessageProps,
} from './types';

View file

@ -140,3 +140,10 @@ export interface ShareToSpaceSavedObjectTarget {
*/
noun?: string;
}
/**
* Properties for the SavedObjectConflictMessage component.
*/
export interface SavedObjectConflictMessageProps {
json: string;
}

View file

@ -14,6 +14,7 @@ import { getCopyToSpaceFlyoutComponent } from '../copy_saved_objects_to_space';
import type { PluginsStart } from '../plugin';
import {
getLegacyUrlConflict,
getSavedObjectConflictMessage,
getShareToSpaceFlyoutComponent,
} from '../share_saved_objects_to_space';
import { getSpaceAvatarComponent } from '../space_avatar';
@ -56,5 +57,6 @@ export const getComponents = ({
getSpaceList: wrapLazy(getSpaceListComponent),
getLegacyUrlConflict: wrapLazy(() => getLegacyUrlConflict({ getStartServices })),
getSpaceAvatar: wrapLazy(getSpaceAvatarComponent),
getSavedObjectConflictMessage: wrapLazy(() => getSavedObjectConflictMessage()),
};
};

View file

@ -12,6 +12,7 @@ import type { CoreStart } from 'src/core/public';
import type { CopyToSpaceFlyoutProps } from '../copy_saved_objects_to_space';
import type {
LegacyUrlConflictProps,
SavedObjectConflictMessageProps,
ShareToSpaceFlyoutProps,
} from '../share_saved_objects_to_space';
import type { SpaceAvatarProps } from '../space_avatar';
@ -109,4 +110,8 @@ export interface SpacesApiUiComponent {
* Displays an avatar for the given space.
*/
getSpaceAvatar: LazyComponentFn<SpaceAvatarProps>;
/**
* Displays a saved object conflict message that directs user to disable legacy URL alias
*/
getSavedObjectConflictMessage: LazyComponentFn<SavedObjectConflictMessageProps>;
}