Legacy url conflict UI improvements (#114172) (#114382)

# Conflicts:
#	docs/development/core/public/kibana-plugin-core-public.doclinksstart.md

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Joe Portner 2021-10-09 18:50:51 -04:00 committed by GitHub
parent 7f70d40e91
commit 7053f5b16b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 364 additions and 372 deletions

View file

@ -216,6 +216,10 @@ readonly links: {
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;

File diff suppressed because one or more lines are too long

View file

@ -362,6 +362,10 @@ export class DocLinksService {
mappingRolesFieldRules: `${ELASTICSEARCH_DOCS}role-mapping-resources.html#mapping-roles-rule-field`,
runAsPrivilege: `${ELASTICSEARCH_DOCS}security-privileges.html#_run_as_privilege`,
},
spaces: {
kibanaLegacyUrlAliases: `${KIBANA_DOCS}legacy-url-aliases.html`,
kibanaDisableLegacyUrlAliasesApi: `${KIBANA_DOCS}spaces-api-disable-legacy-url-aliases.html`,
},
watcher: {
jiraAction: `${ELASTICSEARCH_DOCS}actions-jira.html`,
pagerDutyAction: `${ELASTICSEARCH_DOCS}actions-pagerduty.html`,
@ -712,6 +716,10 @@ export interface DocLinksStart {
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;

View file

@ -686,6 +686,10 @@ export interface DocLinksStart {
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;

View file

@ -236,7 +236,7 @@ describe('embeddable', () => {
...savedVis,
sharingSavedObjectProps: {
outcome: 'conflict',
errorJSON: '{targetType: "lens", sourceId: "1", targetSpace: "space"}',
sourceId: '1',
aliasTargetId: '2',
},
} as ResolvedLensSavedObjectAttributes);

View file

@ -286,8 +286,9 @@ export class Embeddable
defaultMessage: `You've encountered a URL conflict`,
}),
longMessage: (
<this.deps.spaces.ui.components.getSavedObjectConflictMessage
json={sharingSavedObjectProps.errorJSON!}
<this.deps.spaces.ui.components.getEmbeddableLegacyUrlConflict
targetType={DOC_TYPE}
sourceId={sharingSavedObjectProps.sourceId!}
/>
),
};

View file

@ -48,7 +48,7 @@ export function getLensAttributeService(
outcome,
alias_target_id: aliasTargetId,
} = await savedObjectStore.load(savedObjectId);
const { attributes, references, type, id } = savedObject;
const { attributes, references, id } = savedObject;
const document = {
...attributes,
references,
@ -57,14 +57,7 @@ export function getLensAttributeService(
const sharingSavedObjectProps = {
aliasTargetId,
outcome,
errorJSON:
outcome === 'conflict'
? JSON.stringify({
targetType: type,
sourceId: id,
targetSpace: (await startDependencies.spaces.getActiveSpace()).id,
})
: undefined,
sourceId: id,
};
return {

View file

@ -27,7 +27,7 @@ export const getPersisted = async ({
lensServices: LensAppServices;
history?: History<unknown>;
}): Promise<
{ doc: Document; sharingSavedObjectProps: Omit<SharingSavedObjectProps, 'errorJSON'> } | undefined
{ doc: Document; sharingSavedObjectProps: Omit<SharingSavedObjectProps, 'sourceId'> } | undefined
> => {
const { notifications, spaces, attributeService } = lensServices;
let doc: Document;

View file

@ -43,7 +43,7 @@ export interface LensAppState extends EditorFrameState {
savedQuery?: SavedQuery;
searchSessionId: string;
resolvedDateRange: DateRange;
sharingSavedObjectProps?: Omit<SharingSavedObjectProps, 'errorJSON'>;
sharingSavedObjectProps?: Omit<SharingSavedObjectProps, 'sourceId'>;
}
export type DispatchSetState = (state: Partial<LensAppState>) => {

View file

@ -834,5 +834,5 @@ export interface ILensInterpreterRenderHandlers extends IInterpreterRenderHandle
export interface SharingSavedObjectProps {
outcome?: 'aliasMatch' | 'exactMatch' | 'conflict';
aliasTargetId?: string;
errorJSON?: string;
sourceId?: string;
}

View file

@ -349,8 +349,9 @@ export class MapEmbeddable
iconType="alert"
iconColor="danger"
data-test-subj="embeddable-maps-failure"
body={spaces.ui.components.getSavedObjectConflictMessage({
json: sharingSavedObjectProps.errorJSON!,
body={spaces.ui.components.getEmbeddableLegacyUrlConflict({
targetType: MAP_SAVED_OBJECT_TYPE,
sourceId: sharingSavedObjectProps.sourceId!,
})}
/>
</div>

View file

@ -14,12 +14,11 @@ 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';
export interface SharingSavedObjectProps {
outcome?: 'aliasMatch' | 'exactMatch' | 'conflict';
aliasTargetId?: string;
errorJSON?: string;
sourceId?: string;
}
type MapDoc = MapSavedObjectAttributes & {
@ -88,14 +87,7 @@ export function getMapAttributeService(): MapAttributeService {
sharingSavedObjectProps: {
aliasTargetId,
outcome,
errorJSON:
outcome === 'conflict' && getSpacesApi()
? JSON.stringify({
targetType: MAP_SAVED_OBJECT_TYPE,
sourceId: savedObjectId,
targetSpace: (await getSpacesApi()!.getActiveSpace()).id,
})
: undefined,
sourceId: savedObjectId,
},
};
},

View file

@ -19,3 +19,7 @@ export const getSpacesFeatureDescription = () => {
return spacesFeatureDescription;
};
export const DEFAULT_OBJECT_NOUN = i18n.translate('xpack.spaces.shareToSpace.objectNoun', {
defaultMessage: 'object',
});

View file

@ -20,8 +20,9 @@ export type {
CopyToSpaceSavedObjectTarget,
} from './copy_saved_objects_to_space';
export type { LegacyUrlConflictProps, EmbeddableLegacyUrlConflictProps } from './legacy_urls';
export type {
LegacyUrlConflictProps,
ShareToSpaceFlyoutProps,
ShareToSpaceSavedObjectTarget,
} from './share_saved_objects_to_space';

View file

@ -0,0 +1,22 @@
/*
* 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 { EmbeddableLegacyUrlConflictProps } from '../types';
import type { InternalProps } from './embeddable_legacy_url_conflict_internal';
export const getEmbeddableLegacyUrlConflict = async (
internalProps: InternalProps
): Promise<React.FC<EmbeddableLegacyUrlConflictProps>> => {
const { EmbeddableLegacyUrlConflictInternal } = await import(
'./embeddable_legacy_url_conflict_internal'
);
return (props: EmbeddableLegacyUrlConflictProps) => {
return <EmbeddableLegacyUrlConflictInternal {...{ ...internalProps, ...props }} />;
};
};

View file

@ -0,0 +1,92 @@
/*
* 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,
EuiCodeBlock,
EuiLink,
EuiSpacer,
EuiTextAlign,
} from '@elastic/eui';
import React, { useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import type { StartServicesAccessor } from 'src/core/public';
import type { PluginsStart } from '../../plugin';
import type { SpacesManager } from '../../spaces_manager';
import type { EmbeddableLegacyUrlConflictProps } from '../types';
export interface InternalProps {
spacesManager: SpacesManager;
getStartServices: StartServicesAccessor<PluginsStart>;
}
export const EmbeddableLegacyUrlConflictInternal = (
props: InternalProps & EmbeddableLegacyUrlConflictProps
) => {
const { spacesManager, getStartServices, targetType, sourceId } = props;
const [expandError, setExpandError] = useState(false);
const { value: asyncParams } = useAsync(async () => {
const [{ docLinks }] = await getStartServices();
const { id: targetSpace } = await spacesManager.getActiveSpace();
const docLink = docLinks.links.spaces.kibanaDisableLegacyUrlAliasesApi;
const aliasJsonString = JSON.stringify({ targetSpace, targetType, sourceId }, null, 2);
return { docLink, aliasJsonString };
}, [getStartServices, spacesManager]);
const { docLink, aliasJsonString } = asyncParams ?? {};
if (!aliasJsonString || !docLink) {
return null;
}
return (
<>
<FormattedMessage
id="xpack.spaces.embeddableLegacyUrlConflict.messageText"
defaultMessage="We found 2 saved objects for this panel. Disable the legacy URL alias to fix this error."
/>
<EuiSpacer />
{expandError ? (
<EuiTextAlign textAlign="left">
<EuiCallOut
title={
<FormattedMessage
id="xpack.spaces.embeddableLegacyUrlConflict.calloutTitle"
defaultMessage="Copy this JSON and use it with the {documentationLink}"
values={{
documentationLink: (
<EuiLink external href={docLink} target="_blank">
{'_disable_legacy_url_aliases API'}
</EuiLink>
),
}}
/>
}
color="danger"
iconType="alert"
>
<EuiCodeBlock fontSize="s" language="json" isCopyable={true} paddingSize="none">
{aliasJsonString}
</EuiCodeBlock>
</EuiCallOut>
</EuiTextAlign>
) : (
<EuiButtonEmpty onClick={() => setExpandError(true)}>
{i18n.translate('xpack.spaces.embeddableLegacyUrlConflict.detailsButton', {
defaultMessage: `View details`,
})}
</EuiButtonEmpty>
)}
</>
);
};

View file

@ -5,4 +5,5 @@
* 2.0.
*/
export { DocumentationLinksService } from './documentation_links';
export { getEmbeddableLegacyUrlConflict } from './embeddable_legacy_url_conflict';
export { getLegacyUrlConflict } from './legacy_url_conflict';

View file

@ -0,0 +1,135 @@
/*
* 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,
EuiButtonEmpty,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiSpacer,
EuiToolTip,
} from '@elastic/eui';
import React, { useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { first } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import type { StartServicesAccessor } from 'src/core/public';
import { DEFAULT_OBJECT_NOUN } from '../../constants';
import type { PluginsStart } from '../../plugin';
import type { LegacyUrlConflictProps } from '../types';
export interface InternalProps {
getStartServices: StartServicesAccessor<PluginsStart>;
}
export const LegacyUrlConflictInternal = (props: InternalProps & LegacyUrlConflictProps) => {
const {
getStartServices,
objectNoun = DEFAULT_OBJECT_NOUN,
currentObjectId,
otherObjectId,
otherObjectPath,
} = props;
const [isDismissed, setIsDismissed] = useState(false);
const { value: asyncParams } = useAsync(async () => {
const [{ application: applicationStart, docLinks }] = await getStartServices();
const appId = await applicationStart.currentAppId$.pipe(first()).toPromise(); // retrieve the most recent value from the BehaviorSubject
const docLink = docLinks.links.spaces.kibanaLegacyUrlAliases;
return { applicationStart, appId, docLink };
}, [getStartServices]);
const { docLink, applicationStart, appId } = asyncParams ?? {};
if (!applicationStart || !appId || !docLink || isDismissed) {
return null;
}
function clickLinkButton() {
applicationStart!.navigateToApp(appId!, { path: otherObjectPath });
}
function clickDismissButton() {
setIsDismissed(true);
}
return (
<EuiCallOut
color="warning"
iconType="help"
title={
<FormattedMessage
id="xpack.spaces.legacyUrlConflict.calloutTitle"
defaultMessage="2 saved objects use this URL"
/>
}
>
<FormattedMessage
id="xpack.spaces.legacyUrlConflict.calloutBodyText"
defaultMessage="Check that this is the {objectNoun} that you are looking for. Otherwise, go to the other one. {documentationLink}"
values={{
objectNoun,
documentationLink: (
<EuiLink external href={docLink} target="_blank">
{i18n.translate('xpack.spaces.legacyUrlConflict.documentationLinkText', {
defaultMessage: 'Learn more',
})}
</EuiLink>
),
}}
/>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiToolTip
position="bottom"
delay="long"
content={i18n.translate('xpack.spaces.legacyURLConflict.toolTipText', {
defaultMessage:
'This {objectNoun} has [id={currentObjectId}]. The other {objectNoun} has [id={otherObjectId}].',
values: { objectNoun, currentObjectId, otherObjectId },
})}
>
<EuiButton
color="warning"
size="s"
onClick={clickLinkButton}
data-test-subj="legacy-url-conflict-go-to-other-button"
>
<FormattedMessage
id="xpack.spaces.legacyUrlConflict.linkButton"
defaultMessage="Go to other {objectNoun}"
values={{ objectNoun }}
/>
</EuiButton>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="warning"
size="s"
onClick={clickDismissButton}
data-test-subj="legacy-url-conflict-dismiss-button"
>
<FormattedMessage
id="xpack.spaces.legacyUrlConflict.dismissButton"
defaultMessage="Dismiss"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
};

View file

@ -5,4 +5,7 @@
* 2.0.
*/
export { getEmbeddableLegacyUrlConflict, getLegacyUrlConflict } from './components';
export { createRedirectLegacyUrl } from './redirect_legacy_url';
export type { EmbeddableLegacyUrlConflictProps, LegacyUrlConflictProps } from './types';

View file

@ -10,9 +10,9 @@ import { first } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
import type { StartServicesAccessor } from 'src/core/public';
import type { PluginsStart } from '../../plugin';
import type { SpacesApiUi } from '../../ui_api';
import { DEFAULT_OBJECT_NOUN } from '../components/constants';
import { DEFAULT_OBJECT_NOUN } from '../constants';
import type { PluginsStart } from '../plugin';
import type { SpacesApiUi } from '../ui_api';
export function createRedirectLegacyUrl(
getStartServices: StartServicesAccessor<PluginsStart>
@ -22,10 +22,10 @@ export function createRedirectLegacyUrl(
const { currentAppId$, navigateToApp } = application;
const appId = await currentAppId$.pipe(first()).toPromise(); // retrieve the most recent value from the BehaviorSubject
const title = i18n.translate('xpack.spaces.shareToSpace.redirectLegacyUrlToast.title', {
const title = i18n.translate('xpack.spaces.redirectLegacyUrlToast.title', {
defaultMessage: `We redirected you to a new URL`,
});
const text = i18n.translate('xpack.spaces.shareToSpace.redirectLegacyUrlToast.text', {
const text = i18n.translate('xpack.spaces.redirectLegacyUrlToast.text', {
defaultMessage: `The {objectNoun} you're looking for has a new location. Use this URL from now on.`,
values: { objectNoun },
});

View file

@ -0,0 +1,46 @@
/*
* 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.
*/
/**
* Properties for the LegacyUrlConflict component.
*/
export interface LegacyUrlConflictProps {
/**
* The string that is used to describe the object in the callout, e.g., _There is a legacy URL for this page that points to a different
* **object**_.
*
* Default value is 'object'.
*/
objectNoun?: string;
/**
* The ID of the object that is currently shown on the page.
*/
currentObjectId: string;
/**
* The ID of the other object that the legacy URL alias points to.
*/
otherObjectId: string;
/**
* The path within your application to use for the new URL, optionally including `search` and/or `hash` URL components. Do not include
* `/app/my-app` or the current base path.
*/
otherObjectPath: string;
}
/**
* Properties for the EmbeddableLegacyUrlConflict component.
*/
export interface EmbeddableLegacyUrlConflictProps {
/**
* The target type of the legacy URL alias.
*/
targetType: string;
/**
* The source ID of the legacy URL alias.
*/
sourceId: string;
}

View file

@ -1,27 +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 { docLinksServiceMock } from 'src/core/public/mocks';
import { DocumentationLinksService } from './documentation_links';
describe('DocumentationLinksService', () => {
const setup = () => {
const docLinks = docLinksServiceMock.createStartContract();
const service = new DocumentationLinksService(docLinks);
return { docLinks, service };
};
describe('#getKibanaPrivilegesDocUrl', () => {
it('returns expected value', () => {
const { service } = setup();
expect(service.getKibanaPrivilegesDocUrl()).toMatchInlineSnapshot(
`"https://www.elastic.co/guide/en/kibana/mocked-test-branch/kibana-privileges.html"`
);
});
});
});

View file

@ -1,20 +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 type { DocLinksStart } from 'src/core/public';
export class DocumentationLinksService {
private readonly kbnPrivileges: string;
constructor(docLinks: DocLinksStart) {
this.kbnPrivileges = `${docLinks.links.security.kibanaPrivileges}`;
}
public getKibanaPrivilegesDocUrl() {
return `${this.kbnPrivileges}`;
}
}

View file

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

View file

@ -1,12 +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 { i18n } from '@kbn/i18n';
export const DEFAULT_OBJECT_NOUN = i18n.translate('xpack.spaces.shareToSpace.objectNoun', {
defaultMessage: 'object',
});

View file

@ -1,19 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 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,5 +6,3 @@
*/
export { getShareToSpaceFlyoutComponent } from './share_to_space_flyout';
export { getSavedObjectConflictMessage } from './get_saved_object_conflict_message';
export { getLegacyUrlConflict } from './legacy_url_conflict';

View file

@ -1,116 +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,
EuiButtonEmpty,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
} from '@elastic/eui';
import React, { useEffect, useState } from 'react';
import { first } from 'rxjs/operators';
import { FormattedMessage } from '@kbn/i18n/react';
import type { ApplicationStart, StartServicesAccessor } from 'src/core/public';
import type { PluginsStart } from '../../plugin';
import type { LegacyUrlConflictProps } from '../types';
import { DEFAULT_OBJECT_NOUN } from './constants';
export interface InternalProps {
getStartServices: StartServicesAccessor<PluginsStart>;
}
export const LegacyUrlConflictInternal = (props: InternalProps & LegacyUrlConflictProps) => {
const {
getStartServices,
objectNoun = DEFAULT_OBJECT_NOUN,
currentObjectId,
otherObjectId,
otherObjectPath,
} = props;
const [applicationStart, setApplicationStart] = useState<ApplicationStart>();
const [isDismissed, setIsDismissed] = useState(false);
const [appId, setAppId] = useState<string>();
useEffect(() => {
async function setup() {
const [{ application }] = await getStartServices();
const appIdValue = await application.currentAppId$.pipe(first()).toPromise(); // retrieve the most recent value from the BehaviorSubject
setApplicationStart(application);
setAppId(appIdValue);
}
setup();
}, [getStartServices]);
if (!applicationStart || !appId || isDismissed) {
return null;
}
function clickLinkButton() {
applicationStart!.navigateToApp(appId!, { path: otherObjectPath });
}
function clickDismissButton() {
setIsDismissed(true);
}
return (
<EuiCallOut
color="warning"
iconType="help"
title={
<FormattedMessage
id="xpack.spaces.shareToSpace.legacyUrlConflictTitle"
defaultMessage="2 objects are associated with this URL"
/>
}
>
<FormattedMessage
id="xpack.spaces.shareToSpace.legacyUrlConflictBody"
defaultMessage="You're currently looking at {objectNoun} [id={currentObjectId}]. A legacy URL for this page shows a different {objectNoun} [id={otherObjectId}]."
values={{ objectNoun, currentObjectId, otherObjectId }}
/>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButton
color="warning"
size="s"
onClick={clickLinkButton}
data-test-subj="legacy-url-conflict-go-to-other-button"
>
<FormattedMessage
id="xpack.spaces.shareToSpace.legacyUrlConflictLinkButton"
defaultMessage="Go to other {objectNoun}"
values={{ objectNoun }}
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="warning"
size="s"
onClick={clickDismissButton}
data-test-subj="legacy-url-conflict-dismiss-button"
>
<FormattedMessage
id="xpack.spaces.shareToSpace.legacyUrlConflictDismissButton"
defaultMessage="Dismiss"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
};

View file

@ -1,56 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 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

@ -26,7 +26,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common';
import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../../common/constants';
import { DocumentationLinksService } from '../../lib';
import { getSpaceAvatarComponent } from '../../space_avatar';
import { useSpaces } from '../../spaces_context';
import type { SpacesDataEntry } from '../../types';
@ -135,9 +134,7 @@ export const SelectableSpacesControl = (props: Props) => {
return null;
}
const kibanaPrivilegesUrl = new DocumentationLinksService(
docLinks!
).getKibanaPrivilegesDocUrl();
const docLink = docLinks?.links.security.kibanaPrivileges;
return (
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
@ -146,7 +143,7 @@ export const SelectableSpacesControl = (props: Props) => {
defaultMessage="To view hidden spaces, you need {additionalPrivilegesLink}."
values={{
additionalPrivilegesLink: (
<EuiLink href={kibanaPrivilegesUrl} target="_blank">
<EuiLink href={docLink} target="_blank">
<FormattedMessage
id="xpack.spaces.shareToSpace.unknownSpacesLabel.additionalPrivilegesLink"
defaultMessage="additional privileges"

View file

@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { ALL_SPACES_ID } from '../../../common/constants';
import { DocumentationLinksService } from '../../lib';
import { useSpaces } from '../../spaces_context';
import type { SpacesDataEntry } from '../../types';
import type { ShareOptions } from '../types';
@ -85,10 +84,7 @@ export const ShareModeControl = (props: Props) => {
return null;
}
const kibanaPrivilegesUrl = new DocumentationLinksService(
docLinks!
).getKibanaPrivilegesDocUrl();
const docLink = docLinks?.links.security.kibanaPrivileges;
return (
<>
<EuiCallOut
@ -108,7 +104,7 @@ export const ShareModeControl = (props: Props) => {
values={{
objectNoun,
readAndWritePrivilegesLink: (
<EuiLink href={kibanaPrivilegesUrl} target="_blank">
<EuiLink href={docLink} target="_blank">
<FormattedMessage
id="xpack.spaces.shareToSpace.privilegeWarningLink"
defaultMessage="read and write privileges"

View file

@ -28,6 +28,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import type { SavedObjectReferenceWithContext, ToastsStart } from 'src/core/public';
import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../../common/constants';
import { DEFAULT_OBJECT_NOUN } from '../../constants';
import { getCopyToSpaceFlyoutComponent } from '../../copy_saved_objects_to_space';
import { useSpaces } from '../../spaces_context';
import type { SpacesManager } from '../../spaces_manager';
@ -38,7 +39,6 @@ import type {
ShareToSpaceSavedObjectTarget,
} from '../types';
import { AliasTable } from './alias_table';
import { DEFAULT_OBJECT_NOUN } from './constants';
import { RelativesFooter } from './relatives_footer';
import { ShareToSpaceForm } from './share_to_space_form';
import type { InternalLegacyUrlAliasTarget } from './types';

View file

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

View file

@ -18,31 +18,6 @@ export interface ShareSavedObjectsToSpaceResponse {
[spaceId: string]: SavedObjectsImportResponse;
}
/**
* Properties for the LegacyUrlConflict component.
*/
export interface LegacyUrlConflictProps {
/**
* The string that is used to describe the object in the callout, e.g., _There is a legacy URL for this page that points to a different
* **object**_.
*
* Default value is 'object'.
*/
objectNoun?: string;
/**
* The ID of the object that is currently shown on the page.
*/
currentObjectId: string;
/**
* The ID of the other object that the legacy URL alias points to.
*/
otherObjectId: string;
/**
* The path to use for the new URL, optionally including `search` and/or `hash` URL components.
*/
otherObjectPath: string;
}
/**
* Properties for the ShareToSpaceFlyout.
*/
@ -140,10 +115,3 @@ export interface ShareToSpaceSavedObjectTarget {
*/
noun?: string;
}
/**
* Properties for the SavedObjectConflictMessage component.
*/
export interface SavedObjectConflictMessageProps {
json: string;
}

View file

@ -11,12 +11,9 @@ import React from 'react';
import type { StartServicesAccessor } from 'src/core/public';
import { getCopyToSpaceFlyoutComponent } from '../copy_saved_objects_to_space';
import { getEmbeddableLegacyUrlConflict, getLegacyUrlConflict } from '../legacy_urls';
import type { PluginsStart } from '../plugin';
import {
getLegacyUrlConflict,
getSavedObjectConflictMessage,
getShareToSpaceFlyoutComponent,
} from '../share_saved_objects_to_space';
import { getShareToSpaceFlyoutComponent } from '../share_saved_objects_to_space';
import { getSpaceAvatarComponent } from '../space_avatar';
import { getSpaceListComponent } from '../space_list';
import { getSpacesContextProviderWrapper } from '../spaces_context';
@ -55,8 +52,10 @@ export const getComponents = ({
getShareToSpaceFlyout: wrapLazy(getShareToSpaceFlyoutComponent, { showLoadingSpinner: false }),
getCopyToSpaceFlyout: wrapLazy(getCopyToSpaceFlyoutComponent, { showLoadingSpinner: false }),
getSpaceList: wrapLazy(getSpaceListComponent),
getEmbeddableLegacyUrlConflict: wrapLazy(() =>
getEmbeddableLegacyUrlConflict({ spacesManager, getStartServices })
),
getLegacyUrlConflict: wrapLazy(() => getLegacyUrlConflict({ getStartServices })),
getSpaceAvatar: wrapLazy(getSpaceAvatarComponent),
getSavedObjectConflictMessage: wrapLazy(() => getSavedObjectConflictMessage()),
};
};

View file

@ -7,8 +7,8 @@
import type { StartServicesAccessor } from 'src/core/public';
import { createRedirectLegacyUrl } from '../legacy_urls';
import type { PluginsStart } from '../plugin';
import { createRedirectLegacyUrl } from '../share_saved_objects_to_space';
import { useSpaces } from '../spaces_context';
import type { SpacesManager } from '../spaces_manager';
import { getComponents } from './components';

View file

@ -10,11 +10,8 @@ import type { ReactElement } from 'react';
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 { EmbeddableLegacyUrlConflictProps, LegacyUrlConflictProps } from '../legacy_urls';
import type { ShareToSpaceFlyoutProps } from '../share_saved_objects_to_space';
import type { SpaceAvatarProps } from '../space_avatar';
import type { SpaceListProps } from '../space_list';
import type { SpacesContextProps, SpacesReactContextValue } from '../spaces_context';
@ -88,6 +85,12 @@ export interface SpacesApiUiComponent {
* Note: must be rendered inside of a SpacesContext.
*/
getSpaceList: LazyComponentFn<SpaceListProps>;
/**
* Displays a callout that needs to be used if an embeddable component call to `SavedObjectsClient.resolve()` results in an `"conflict"`
* outcome, which indicates that the user has loaded an embeddable which is associated directly with one object (A), *and* with a legacy
* URL that points to a different object (B).
*/
getEmbeddableLegacyUrlConflict: LazyComponentFn<EmbeddableLegacyUrlConflictProps>;
/**
* Displays a callout that needs to be used if a call to `SavedObjectsClient.resolve()` results in an `"conflict"` outcome, which
* indicates that the user has loaded the page which is associated directly with one object (A), *and* with a legacy URL that points to a
@ -110,8 +113,4 @@ 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>;
}

View file

@ -24375,10 +24375,6 @@
"xpack.spaces.shareToSpace.currentSpaceBadge": "現在",
"xpack.spaces.shareToSpace.featureIsDisabledTooltip": "この機能はこのスペースでは無効です。",
"xpack.spaces.shareToSpace.flyoutTitle": "{objectNoun}をスペースに割り当てる",
"xpack.spaces.shareToSpace.legacyUrlConflictBody": "現在、{objectNoun} [id={currentObjectId}]を表示しています。このページのレガシーURLは別の{objectNoun} [id={otherObjectId}]を示しています。",
"xpack.spaces.shareToSpace.legacyUrlConflictDismissButton": "閉じる",
"xpack.spaces.shareToSpace.legacyUrlConflictLinkButton": "他の{objectNoun}に移動",
"xpack.spaces.shareToSpace.legacyUrlConflictTitle": "2つのオブジェクトがこのURLに関連付けられています",
"xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.linkText": "新しいスペースを作成",
"xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.text": "オブジェクトを共有するには、{createANewSpaceLink}できます。",
"xpack.spaces.shareToSpace.objectNoun": "オブジェクト",
@ -24387,8 +24383,6 @@
"xpack.spaces.shareToSpace.privilegeWarningBody": "この{objectNoun}のスペースを編集するには、すべてのスペースで{readAndWritePrivilegesLink}が必要です。",
"xpack.spaces.shareToSpace.privilegeWarningLink": "読み書き権限",
"xpack.spaces.shareToSpace.privilegeWarningTitle": "追加の権限が必要です",
"xpack.spaces.shareToSpace.redirectLegacyUrlToast.text": "検索している{objectNoun}は新しい場所にあります。今後はこのURLを使用してください。",
"xpack.spaces.shareToSpace.redirectLegacyUrlToast.title": "新しいURLに移動しました",
"xpack.spaces.shareToSpace.saveButton": "保存して閉じる",
"xpack.spaces.shareToSpace.shareErrorTitle": "{objectNoun}の更新エラー",
"xpack.spaces.shareToSpace.shareModeControl.buttonGroupLegend": "この共有方法を選択",

View file

@ -24781,10 +24781,6 @@
"xpack.spaces.shareToSpace.currentSpaceBadge": "当前",
"xpack.spaces.shareToSpace.featureIsDisabledTooltip": "此功能在此工作区中已禁用。",
"xpack.spaces.shareToSpace.flyoutTitle": "将 {objectNoun} 分配给工作区",
"xpack.spaces.shareToSpace.legacyUrlConflictBody": "当前您正在查看 {objectNoun} [id={currentObjectId}]。此页面的旧 URL 显示不同的 {objectNoun} [id={otherObjectId}]。",
"xpack.spaces.shareToSpace.legacyUrlConflictDismissButton": "关闭",
"xpack.spaces.shareToSpace.legacyUrlConflictLinkButton": "前往其他 {objectNoun}",
"xpack.spaces.shareToSpace.legacyUrlConflictTitle": "2 个对象与此 URL 关联",
"xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.linkText": "创建新工作区",
"xpack.spaces.shareToSpace.noAvailableSpaces.canCreateNewSpace.text": "您可以{createANewSpaceLink},用于共享您的对象。",
"xpack.spaces.shareToSpace.objectNoun": "对象",
@ -24793,8 +24789,6 @@
"xpack.spaces.shareToSpace.privilegeWarningBody": "要编辑此 {objectNoun} 的工作区,您在所有工作区中都需要{readAndWritePrivilegesLink}。",
"xpack.spaces.shareToSpace.privilegeWarningLink": "读写权限",
"xpack.spaces.shareToSpace.privilegeWarningTitle": "需要其他权限",
"xpack.spaces.shareToSpace.redirectLegacyUrlToast.text": "您正在寻找的{objectNoun}具有新的位置。从现在开始使用此 URL。",
"xpack.spaces.shareToSpace.redirectLegacyUrlToast.title": "我们已将您重定向到新 URL",
"xpack.spaces.shareToSpace.relativesControl.description": "{relativesCount} 个相关{relativesCount, plural, other {对象}}也将更改。",
"xpack.spaces.shareToSpace.saveButton": "保存并关闭",
"xpack.spaces.shareToSpace.shareErrorTitle": "更新 {objectNoun} 时出错",