Skip to content
Closed
3 changes: 2 additions & 1 deletion x-pack/plugins/maps/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"savedObjectsTagging",
"charts",
"security",
"usageCollection"
"usageCollection",
"spaces"
],
"ui": true,
"server": true,
Expand Down
18 changes: 18 additions & 0 deletions x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import { MapContainer } from '../connected_components/map_container';
import { SavedMap } from '../routes/map_page';
import { getIndexPatternsFromIds } from '../index_pattern_util';
import { getMapAttributeService } from '../map_attribute_service';
import { resolveSavedObject } from '../resolve_saved_object';
import { isUrlDrilldown, toValueClickDataFormat } from '../trigger_actions/trigger_utils';
import { waitUntilTimeLayersLoad$ } from '../routes/map_page/map_app/wait_until_time_layers_load';

Expand Down Expand Up @@ -152,6 +153,23 @@ export class MapEmbeddable
this.onFatalError(e);
return;
}
try {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of handing saved object check seperatly in MapEmbeddable and MapApp, how about moving logic into a new SavedMap called something like "hasSavedObjectIdConflict"? That way all checking logic can just be part of SavedMap and consolidated into a single location.

await this._initializeOutput();
const savedObjectId = this._savedMap.getSavedObjectId();
if (savedObjectId) {
const { resolvedSavedObject } = await resolveSavedObject(savedObjectId);
if (resolvedSavedObject?.outcome === 'conflict') {
const currentObjectId = resolvedSavedObject.saved_object.id;
const otherObjectId = resolvedSavedObject.alias_target_id!;
throw new Error(
`This object "${currentObjectId}" has the same URL as a legacy alias "${otherObjectId}". Disable the alias to resolve this error. Reference: https://www.elastic.co/guide/en/kibana/master/legacy-url-aliases.html}`
);
}
}
} catch (e) {
this.onFatalError(e);
return;
}

// deferred loading of this embeddable is complete
this.setInitializationFinished();
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/maps/public/embeddable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface MapEmbeddableConfig {
}

interface MapEmbeddableState {
savedObjectId?: string;
isLayerTOCOpen?: boolean;
openTOCDetails?: string[];
mapCenter?: MapCenterAndZoom;
Expand Down
10 changes: 8 additions & 2 deletions x-pack/plugins/maps/public/lazy_load_bundle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { IndexPatternsContract } from 'src/plugins/data/public';
import { AppMountParameters } from 'kibana/public';
import { AppMountParameters, HttpStart } from 'kibana/public';
import { IContainer } from '../../../../../src/plugins/embeddable/public';
import { LayerDescriptor } from '../../common/descriptor_types';
import type {
Expand All @@ -20,6 +20,7 @@ import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_s
import type { EMSTermJoinConfig, SampleValuesConfig } from '../ems_autosuggest';
import type { CreateTileMapLayerDescriptorParams } from '../classes/layers/create_tile_map_layer_descriptor';
import type { CreateRegionMapLayerDescriptorParams } from '../classes/layers/create_region_map_layer_descriptor';
import { SpacesPluginStart } from '../../../spaces/public';

let loadModulesPromise: Promise<LazyLoadedMapModules>;

Expand All @@ -31,7 +32,12 @@ export interface LazyLoadedMapModules {
) => MapEmbeddableType;
getIndexPatternService: () => IndexPatternsContract;
getMapsCapabilities: () => any;
renderApp: (params: AppMountParameters, AppUsageTracker: React.FC) => Promise<() => void>;
renderApp: (
params: AppMountParameters,
AppUsageTracker: React.FC,
http: HttpStart,
spacesApi?: SpacesPluginStart
) => Promise<() => void>;
createSecurityLayerDescriptors: (
indexPatternId: string,
indexPatternTitle: string
Expand Down
21 changes: 7 additions & 14 deletions x-pack/plugins/maps/public/map_attribute_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import { MAP_SAVED_OBJECT_TYPE } from '../common/constants';
import { getMapEmbeddableDisplayName } from '../common/i18n_getters';
import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public';
import { getCoreOverlays, getEmbeddableService, getSavedObjectsClient } from './kibana_services';
import { extractReferences, injectReferences } from '../common/migrations/references';
import { extractReferences } from '../common/migrations/references';
import { MapByValueInput, MapByReferenceInput } from './embeddable/types';
import { resolveSavedObject } from './resolve_saved_object';

type MapDoc = MapSavedObjectAttributes & { references?: SavedObjectReference[] };
type MapDoc = MapSavedObjectAttributes & {
references?: SavedObjectReference[];
};

export type MapAttributeService = AttributeService<MapDoc, MapByValueInput, MapByReferenceInput>;

let mapAttributeService: MapAttributeService | null = null;

export function getMapAttributeService(): MapAttributeService {
if (mapAttributeService) {
return mapAttributeService;
Expand Down Expand Up @@ -58,17 +60,8 @@ export function getMapAttributeService(): MapAttributeService {
return { id: savedObject.id };
},
unwrapMethod: async (savedObjectId: string): Promise<MapDoc> => {
const savedObject = await getSavedObjectsClient().get<MapSavedObjectAttributes>(
MAP_SAVED_OBJECT_TYPE,
savedObjectId
);

if (savedObject.error) {
throw savedObject.error;
}

const { attributes } = injectReferences(savedObject);
return { ...attributes, references: savedObject.references };
const { mapDoc } = await resolveSavedObject(savedObjectId);
return mapDoc;
},
checkForDuplicateTitle: (props: OnSaveProps) => {
return checkForDuplicateTitle(
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/maps/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import {
tileMapVisType,
} from './legacy_visualizations';
import { SecurityPluginStart } from '../../security/public';
import { SpacesPluginStart } from '../../spaces/public';

export interface MapsPluginSetupDependencies {
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
Expand Down Expand Up @@ -112,6 +113,7 @@ export interface MapsPluginStartDependencies {
savedObjectsTagging?: SavedObjectTaggingPluginStart;
presentationUtil: PresentationUtilPluginStart;
security: SecurityPluginStart;
spaces?: SpacesPluginStart;
}

/**
Expand Down Expand Up @@ -179,10 +181,13 @@ export class MapsPlugin
euiIconType: APP_ICON_SOLUTION,
category: DEFAULT_APP_CATEGORIES.kibana,
async mount(params: AppMountParameters) {
const [coreStart, pluginsStart] = await core.getStartServices();
const { http } = coreStart;
const { spaces: spacesApi } = pluginsStart as MapsPluginStartDependencies;
const UsageTracker =
plugins.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
const { renderApp } = await lazyLoadMapModules();
return renderApp(params, UsageTracker);
return renderApp(params, UsageTracker, http, spacesApi);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure passing spaces into renderApp this way is needed. Spaces is a start dependency and can just be grabbed from kibana_services when needed to align with how all start dependencies are obtained.

},
});

Expand Down
9 changes: 7 additions & 2 deletions x-pack/plugins/maps/public/render_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 { AppMountParameters, HttpStart } from 'kibana/public';
import {
getCoreChrome,
getCoreI18n,
Expand All @@ -26,6 +26,7 @@ import {
import { ListPage, MapPage } from './routes';
import { MapByValueInput, MapByReferenceInput } from './embeddable/types';
import { APP_ID } from '../common/constants';
import { SpacesPluginStart } from '../../spaces/public';

export let goToSpecifiedPath: (path: string) => void;
export let kbnUrlStateStorage: IKbnUrlStateStorage;
Expand Down Expand Up @@ -63,7 +64,9 @@ function setAppChrome() {

export async function renderApp(
{ element, history, onAppLeave, setHeaderActionMenu }: AppMountParameters,
AppUsageTracker: React.FC
AppUsageTracker: React.FC,
http: HttpStart,
spacesApi?: SpacesPluginStart
) {
goToSpecifiedPath = (path) => history.push(path);
kbnUrlStateStorage = createKbnUrlStateStorage({
Expand Down Expand Up @@ -98,6 +101,8 @@ export async function renderApp(
setHeaderActionMenu={setHeaderActionMenu}
stateTransfer={stateTransfer}
originatingApp={originatingApp}
spacesApi={spacesApi}
http={http}
key={routeProps.match.params.savedMapId ? routeProps.match.params.savedMapId : 'new'}
/>
);
Expand Down
47 changes: 47 additions & 0 deletions x-pack/plugins/maps/public/resolve_saved_object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 { SavedObjectReference } from 'src/core/types';
import { ResolvedSimpleSavedObject } from 'kibana/public';
import { AttributeService } from '../../../../src/plugins/embeddable/public';
import { MapSavedObjectAttributes } from '../common/map_saved_object_type';
import { MAP_SAVED_OBJECT_TYPE } from '../common/constants';
import { getSavedObjectsClient } from './kibana_services';
import { injectReferences } from '../common/migrations/references';
import { MapByValueInput, MapByReferenceInput } from './embeddable/types';

type MapDoc = MapSavedObjectAttributes & {
references?: SavedObjectReference[];
};

export type MapAttributeService = AttributeService<MapDoc, MapByValueInput, MapByReferenceInput>;

let resolveResult: ResolvedSimpleSavedObject<MapSavedObjectAttributes> | undefined;
export async function resolveSavedObject(
savedObjectId: string
): Promise<{
mapDoc: MapDoc;
resolvedSavedObject: ResolvedSimpleSavedObject<MapSavedObjectAttributes>;
}> {
if (!resolveResult || resolveResult.saved_object.id !== savedObjectId) {
resolveResult = await getSavedObjectsClient().resolve<MapSavedObjectAttributes>(
MAP_SAVED_OBJECT_TYPE,
savedObjectId
);
}
const savedObject = resolveResult.saved_object;

if (savedObject.error) {
throw savedObject.error;
}

const { attributes } = injectReferences(savedObject);
return {
mapDoc: { ...attributes, references: savedObject.references },
resolvedSavedObject: resolveResult,
};
}
35 changes: 33 additions & 2 deletions x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import React from 'react';
import _ from 'lodash';
import { finalize, switchMap, tap } from 'rxjs/operators';
import { EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AppLeaveAction, AppMountParameters } from 'kibana/public';
import { AppLeaveAction, AppMountParameters, HttpStart } from 'kibana/public';
import { Adapters } from 'src/plugins/embeddable/public';
import { Subscription } from 'rxjs';
import type { Query, Filter, TimeRange, IndexPattern } from 'src/plugins/data/common';
Expand Down Expand Up @@ -40,7 +41,7 @@ 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 { getFullPath, getEditPath, APP_ID } from '../../../../common/constants';
import {
getInitialQuery,
getInitialRefreshConfig,
Expand All @@ -50,13 +51,17 @@ import {
unsavedChangesWarning,
} from '../saved_map';
import { waitUntilTimeLayersLoad$ } from './wait_until_time_layers_load';
import { SpacesPluginStart } from '../../../../../spaces/public';
import { resolveSavedObject } from '../../../resolve_saved_object';

interface MapRefreshConfig {
isPaused: boolean;
interval: number;
}

export interface Props {
spacesApi?: SpacesPluginStart;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get spacesStartApi from kibana_services instead of passing in to be consistent with how other dependencies are provided

http: HttpStart;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http does not need to be passed to MapApp, its never used. This dependency chain starts all the way in RenderApp. No need to pass http to RenderApp in public/plugin

savedMap: SavedMap;
// saveCounter used to trigger MapApp render after SaveMap.save
saveCounter: number;
Expand Down Expand Up @@ -93,6 +98,7 @@ export interface State {
savedQuery?: SavedQuery;
isRefreshPaused: boolean;
refreshInterval: number;
savedObjectWarning: unknown;
}

export class MapApp extends React.Component<Props, State> {
Expand All @@ -111,6 +117,7 @@ export class MapApp extends React.Component<Props, State> {
initialized: false,
isRefreshPaused: true,
refreshInterval: 0,
savedObjectWarning: null,
};
}

Expand Down Expand Up @@ -356,6 +363,7 @@ export class MapApp extends React.Component<Props, State> {
this.props.savedMap.getTitle(),
savedObjectId
);
await this.getLegacyUrlConflictCallout(savedObjectId);
}

this._initMapAndLayerSettings(this.props.savedMap.getAttributes());
Expand Down Expand Up @@ -430,6 +438,28 @@ export class MapApp extends React.Component<Props, State> {
);
}

async getLegacyUrlConflictCallout(savedObjectId: string) {
const { resolvedSavedObject } = await resolveSavedObject(savedObjectId);
if (this.props.spacesApi && resolvedSavedObject?.outcome === 'conflict') {
const currentObjectId = resolvedSavedObject.saved_object.id;
const otherObjectId = resolvedSavedObject.alias_target_id!;
const otherObjectPath = getEditPath(otherObjectId);
this.setState({
savedObjectWarning: (
<>
{this.props.spacesApi.ui.components.getLegacyUrlConflict({
objectNoun: 'saved map',
currentObjectId,
otherObjectId,
otherObjectPath,
})}
<EuiSpacer />
</>
),
});
}
}

_addFilter = async (newFilters: Filter[]) => {
newFilters.forEach((filter) => {
filter.$state = { store: esFilters.FilterStateStore.APP_STATE };
Expand All @@ -447,6 +477,7 @@ export class MapApp extends React.Component<Props, State> {
{this._renderTopNav()}
<h1 className="euiScreenReaderOnly">{`screenTitle placeholder`}</h1>
<div id="react-maps-root">
{this.state.savedObjectWarning}
<MapContainer
addFilters={this._addFilter}
title={this.props.savedMap.getAttributes().title}
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/maps/public/routes/map_page/map_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { AppMountParameters } from 'kibana/public';
import { AppMountParameters, HttpStart } from 'kibana/public';
import { EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
import { MapApp } from './map_app';
import { SavedMap, getInitialLayersFromUrlParam } from './saved_map';
import { MapEmbeddableInput } from '../../embeddable/types';
import { SpacesPluginStart } from '../../../../spaces/public';

interface Props {
mapEmbeddableInput?: MapEmbeddableInput;
Expand All @@ -20,6 +21,8 @@ interface Props {
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
stateTransfer: EmbeddableStateTransfer;
originatingApp?: string;
spacesApi?: SpacesPluginStart;
http: HttpStart;
}

interface State {
Expand Down Expand Up @@ -69,6 +72,8 @@ export class MapPage extends Component<Props, State> {
return (
<Provider store={this.state.savedMap.getStore()}>
<MapApp
spacesApi={this.props.spacesApi}
http={this.props.http}
savedMap={this.state.savedMap}
onAppLeave={this.props.onAppLeave}
setHeaderActionMenu={this.props.setHeaderActionMenu}
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/maps/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
]
}