[Lens] Switch to SavedObjectClient.resolve (#110059)
* Step 2: Update client code to use resolve() method instead of get() Following sharing Saved Objects developer guide: Step 2 This step demonstrates the changes to update client code to use the new SavedObjectsClient `resolve()` method instead of `get()`. * Step 3 Lens
This commit is contained in:
parent
9a459806ad
commit
d4c03eb9b4
|
@ -24,7 +24,8 @@
|
|||
"usageCollection",
|
||||
"taskManager",
|
||||
"globalSearch",
|
||||
"savedObjectsTagging"
|
||||
"savedObjectsTagging",
|
||||
"spaces"
|
||||
],
|
||||
"configPath": [
|
||||
"xpack",
|
||||
|
|
|
@ -383,6 +383,9 @@ describe('Lens App', () => {
|
|||
savedObjectId: savedObjectId || 'aaa',
|
||||
}));
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'exactMatch',
|
||||
},
|
||||
savedObjectId: initialSavedObjectId ?? 'aaa',
|
||||
references: [],
|
||||
state: {
|
||||
|
@ -1256,4 +1259,32 @@ describe('Lens App', () => {
|
|||
expect(defaultLeave).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it('should display a conflict callout if saved object conflicts', async () => {
|
||||
const history = createMemoryHistory();
|
||||
const { services } = await mountWith({
|
||||
props: {
|
||||
...makeDefaultProps(),
|
||||
history: {
|
||||
...history,
|
||||
location: {
|
||||
...history.location,
|
||||
search: '?_g=test',
|
||||
},
|
||||
},
|
||||
},
|
||||
preloadedState: {
|
||||
persistedDoc: defaultDoc,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'conflict',
|
||||
aliasTargetId: '2',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(services.spaces.ui.components.getLegacyUrlConflict).toHaveBeenCalledWith({
|
||||
currentObjectId: '1234',
|
||||
objectNoun: 'Lens visualization',
|
||||
otherObjectId: '2',
|
||||
otherObjectPath: '#/edit/2?_g=test',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
runSaveLensVisualization,
|
||||
} from './save_modal_container';
|
||||
import { getLensInspectorService, LensInspector } from '../lens_inspector_service';
|
||||
import { getEditPath } from '../../common';
|
||||
|
||||
export type SaveProps = Omit<OnSaveProps, 'onTitleDuplicate' | 'newDescription'> & {
|
||||
returnToOrigin: boolean;
|
||||
|
@ -70,6 +71,8 @@ export function App({
|
|||
notifications,
|
||||
savedObjectsTagging,
|
||||
getOriginatingAppName,
|
||||
spaces,
|
||||
http,
|
||||
// Temporarily required until the 'by value' paradigm is default.
|
||||
dashboardFeatureFlag,
|
||||
} = lensAppServices;
|
||||
|
@ -82,6 +85,7 @@ export function App({
|
|||
|
||||
const {
|
||||
persistedDoc,
|
||||
sharingSavedObjectProps,
|
||||
isLinkedToOriginatingApp,
|
||||
searchSessionId,
|
||||
isLoading,
|
||||
|
@ -166,6 +170,28 @@ export function App({
|
|||
});
|
||||
}, [onAppLeave, lastKnownDoc, isSaveable, persistedDoc, application.capabilities.visualize.save]);
|
||||
|
||||
const getLegacyUrlConflictCallout = useCallback(() => {
|
||||
// This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario
|
||||
if (spaces && sharingSavedObjectProps?.outcome === 'conflict' && persistedDoc?.savedObjectId) {
|
||||
// We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a
|
||||
// callout with a warning for the user, and provide a way for them to navigate to the other object.
|
||||
const currentObjectId = persistedDoc.savedObjectId;
|
||||
const otherObjectId = sharingSavedObjectProps?.aliasTargetId!; // This is always defined if outcome === 'conflict'
|
||||
const otherObjectPath = http.basePath.prepend(
|
||||
`${getEditPath(otherObjectId)}${history.location.search}`
|
||||
);
|
||||
return spaces.ui.components.getLegacyUrlConflict({
|
||||
objectNoun: i18n.translate('xpack.lens.appName', {
|
||||
defaultMessage: 'Lens visualization',
|
||||
}),
|
||||
currentObjectId,
|
||||
otherObjectId,
|
||||
otherObjectPath,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, [persistedDoc, sharingSavedObjectProps, spaces, http, history]);
|
||||
|
||||
// Sync Kibana breadcrumbs any time the saved document's title changes
|
||||
useEffect(() => {
|
||||
const isByValueMode = getIsByValueMode();
|
||||
|
@ -273,6 +299,8 @@ export function App({
|
|||
title={persistedDoc?.title}
|
||||
lensInspector={lensInspector}
|
||||
/>
|
||||
|
||||
{getLegacyUrlConflictCallout()}
|
||||
{(!isLoading || persistedDoc) && (
|
||||
<MemoizedEditorFrameWrapper
|
||||
editorFrame={editorFrame}
|
||||
|
|
|
@ -55,6 +55,7 @@ export async function getLensServices(
|
|||
savedObjectsTagging,
|
||||
usageCollection,
|
||||
fieldFormats,
|
||||
spaces,
|
||||
} = startDependencies;
|
||||
|
||||
const storage = new Storage(localStorage);
|
||||
|
@ -87,6 +88,7 @@ export async function getLensServices(
|
|||
},
|
||||
// Temporarily required until the 'by value' paradigm is default.
|
||||
dashboardFeatureFlag: startDependencies.dashboard.dashboardFeatureFlagConfig,
|
||||
spaces,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -203,7 +205,9 @@ export async function mountApp(
|
|||
trackUiEvent('loaded');
|
||||
const initialInput = getInitialInput(props.id, props.editByValue);
|
||||
|
||||
lensStore.dispatch(loadInitial({ redirectCallback, initialInput, emptyState }));
|
||||
lensStore.dispatch(
|
||||
loadInitial({ redirectCallback, initialInput, emptyState, history: props.history })
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider store={lensStore}>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { History } from 'history';
|
||||
import type { OnSaveProps } from 'src/plugins/saved_objects/public';
|
||||
import { SpacesApi } from '../../../spaces/public';
|
||||
import type {
|
||||
ApplicationStart,
|
||||
AppMountParameters,
|
||||
|
@ -116,6 +117,8 @@ export interface LensAppServices {
|
|||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||
getOriginatingAppName: () => string | undefined;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
spaces: SpacesApi;
|
||||
|
||||
// Temporarily required until the 'by value' paradigm is default.
|
||||
dashboardFeatureFlag: DashboardFeatureFlagConfig;
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ export interface WorkspacePanelProps {
|
|||
interface WorkspaceState {
|
||||
expressionBuildError?: Array<{
|
||||
shortMessage: string;
|
||||
longMessage: string;
|
||||
longMessage: React.ReactNode;
|
||||
fixAction?: DatasourceFixAction<unknown>;
|
||||
}>;
|
||||
expandError: boolean;
|
||||
|
@ -416,10 +416,10 @@ export const VisualizationWrapper = ({
|
|||
localState: WorkspaceState & {
|
||||
configurationValidationError?: Array<{
|
||||
shortMessage: string;
|
||||
longMessage: string;
|
||||
longMessage: React.ReactNode;
|
||||
fixAction?: DatasourceFixAction<unknown>;
|
||||
}>;
|
||||
missingRefsErrors?: Array<{ shortMessage: string; longMessage: string }>;
|
||||
missingRefsErrors?: Array<{ shortMessage: string; longMessage: React.ReactNode }>;
|
||||
};
|
||||
ExpressionRendererComponent: ReactExpressionRendererType;
|
||||
application: ApplicationStart;
|
||||
|
@ -454,7 +454,7 @@ export const VisualizationWrapper = ({
|
|||
validationError:
|
||||
| {
|
||||
shortMessage: string;
|
||||
longMessage: string;
|
||||
longMessage: React.ReactNode;
|
||||
fixAction?: DatasourceFixAction<unknown>;
|
||||
}
|
||||
| undefined
|
||||
|
@ -499,7 +499,7 @@ export const VisualizationWrapper = ({
|
|||
.map((validationError) => (
|
||||
<>
|
||||
<p
|
||||
key={validationError.longMessage}
|
||||
key={validationError.shortMessage}
|
||||
className="eui-textBreakWord"
|
||||
data-test-subj="configuration-failure-error"
|
||||
>
|
||||
|
|
|
@ -11,6 +11,6 @@ export type TableInspectorAdapter = Record<string, Datatable>;
|
|||
|
||||
export interface ErrorMessage {
|
||||
shortMessage: string;
|
||||
longMessage: string;
|
||||
longMessage: React.ReactNode;
|
||||
type?: 'fixable' | 'critical';
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
LensByReferenceInput,
|
||||
LensSavedObjectAttributes,
|
||||
LensEmbeddableInput,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
} from './embeddable';
|
||||
import { ReactExpressionRendererProps } from 'src/plugins/expressions/public';
|
||||
import { Query, TimeRange, Filter, IndexPatternsContract } from 'src/plugins/data/public';
|
||||
|
@ -68,12 +69,17 @@ const options = {
|
|||
const attributeServiceMockFromSavedVis = (document: Document): LensAttributeService => {
|
||||
const core = coreMock.createStart();
|
||||
const service = new AttributeService<
|
||||
LensSavedObjectAttributes,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
LensByValueInput,
|
||||
LensByReferenceInput
|
||||
>('lens', jest.fn(), core.i18n.Context, core.notifications.toasts, options);
|
||||
service.unwrapAttributes = jest.fn((input: LensByValueInput | LensByReferenceInput) => {
|
||||
return Promise.resolve({ ...document } as LensSavedObjectAttributes);
|
||||
return Promise.resolve({
|
||||
...document,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'exactMatch',
|
||||
},
|
||||
} as ResolvedLensSavedObjectAttributes);
|
||||
});
|
||||
service.wrapAttributes = jest.fn();
|
||||
return service;
|
||||
|
@ -86,7 +92,7 @@ describe('embeddable', () => {
|
|||
let trigger: { exec: jest.Mock };
|
||||
let basePath: IBasePath;
|
||||
let attributeService: AttributeService<
|
||||
LensSavedObjectAttributes,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
LensByValueInput,
|
||||
LensByReferenceInput
|
||||
>;
|
||||
|
@ -223,6 +229,50 @@ describe('embeddable', () => {
|
|||
expect(expressionRenderer).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should not render the vis if loaded saved object conflicts', async () => {
|
||||
attributeService.unwrapAttributes = jest.fn(
|
||||
(input: LensByValueInput | LensByReferenceInput) => {
|
||||
return Promise.resolve({
|
||||
...savedVis,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'conflict',
|
||||
errorJSON: '{targetType: "lens", sourceId: "1", targetSpace: "space"}',
|
||||
aliasTargetId: '2',
|
||||
},
|
||||
} as ResolvedLensSavedObjectAttributes);
|
||||
}
|
||||
);
|
||||
const embeddable = new Embeddable(
|
||||
{
|
||||
timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter,
|
||||
attributeService,
|
||||
inspector: inspectorPluginMock.createStartContract(),
|
||||
expressionRenderer,
|
||||
basePath,
|
||||
indexPatternService: {} as IndexPatternsContract,
|
||||
capabilities: {
|
||||
canSaveDashboards: true,
|
||||
canSaveVisualizations: true,
|
||||
},
|
||||
getTrigger,
|
||||
documentToExpression: () =>
|
||||
Promise.resolve({
|
||||
ast: {
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{ type: 'function', function: 'my', arguments: {} },
|
||||
{ type: 'function', function: 'expression', arguments: {} },
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
}),
|
||||
},
|
||||
{} as LensEmbeddableInput
|
||||
);
|
||||
await embeddable.initializeSavedVis({} as LensEmbeddableInput);
|
||||
expect(expressionRenderer).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should initialize output with deduped list of index patterns', async () => {
|
||||
attributeService = attributeServiceMockFromSavedVis({
|
||||
...savedVis,
|
||||
|
|
|
@ -41,7 +41,11 @@ import {
|
|||
ReferenceOrValueEmbeddable,
|
||||
} from '../../../../../src/plugins/embeddable/public';
|
||||
import { Document, injectFilterReferences } from '../persistence';
|
||||
import { ExpressionWrapper, ExpressionWrapperProps } from './expression_wrapper';
|
||||
import {
|
||||
ExpressionWrapper,
|
||||
ExpressionWrapperProps,
|
||||
savedObjectConflictError,
|
||||
} from './expression_wrapper';
|
||||
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
|
||||
import {
|
||||
isLensBrushEvent,
|
||||
|
@ -58,8 +62,12 @@ import { IBasePath } from '../../../../../src/core/public';
|
|||
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';
|
||||
|
||||
export type LensSavedObjectAttributes = Omit<Document, 'savedObjectId' | 'type'>;
|
||||
export interface ResolvedLensSavedObjectAttributes extends LensSavedObjectAttributes {
|
||||
sharingSavedObjectProps?: SharingSavedObjectProps;
|
||||
}
|
||||
|
||||
interface LensBaseEmbeddableInput extends EmbeddableInput {
|
||||
filters?: Filter[];
|
||||
|
@ -76,7 +84,7 @@ interface LensBaseEmbeddableInput extends EmbeddableInput {
|
|||
}
|
||||
|
||||
export type LensByValueInput = {
|
||||
attributes: LensSavedObjectAttributes;
|
||||
attributes: ResolvedLensSavedObjectAttributes;
|
||||
} & LensBaseEmbeddableInput;
|
||||
|
||||
export type LensByReferenceInput = SavedObjectEmbeddableInput & LensBaseEmbeddableInput;
|
||||
|
@ -253,15 +261,18 @@ export class Embeddable
|
|||
}
|
||||
|
||||
async initializeSavedVis(input: LensEmbeddableInput) {
|
||||
const attributes:
|
||||
| LensSavedObjectAttributes
|
||||
const attrs:
|
||||
| ResolvedLensSavedObjectAttributes
|
||||
| false = await this.deps.attributeService.unwrapAttributes(input).catch((e: Error) => {
|
||||
this.onFatalError(e);
|
||||
return false;
|
||||
});
|
||||
if (!attributes || this.isDestroyed) {
|
||||
if (!attrs || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { sharingSavedObjectProps, ...attributes } = attrs;
|
||||
|
||||
this.savedVis = {
|
||||
...attributes,
|
||||
type: this.type,
|
||||
|
@ -269,8 +280,12 @@ export class Embeddable
|
|||
};
|
||||
const { ast, errors } = await this.deps.documentToExpression(this.savedVis);
|
||||
this.errors = errors;
|
||||
if (sharingSavedObjectProps?.outcome === 'conflict') {
|
||||
const conflictError = savedObjectConflictError(sharingSavedObjectProps.errorJSON!);
|
||||
this.errors = this.errors ? [...this.errors, conflictError] : [conflictError];
|
||||
}
|
||||
this.expression = ast ? toExpression(ast) : null;
|
||||
if (errors) {
|
||||
if (this.errors) {
|
||||
this.logError('validation');
|
||||
}
|
||||
await this.initializeOutput();
|
||||
|
|
|
@ -5,10 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiIcon,
|
||||
EuiEmptyPrompt,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiSpacer,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
ExpressionRendererEvent,
|
||||
ReactExpressionRendererType,
|
||||
|
@ -18,6 +28,7 @@ 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';
|
||||
|
@ -158,3 +169,52 @@ 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} />,
|
||||
});
|
||||
|
|
|
@ -9,47 +9,68 @@ import { CoreStart } from '../../../../src/core/public';
|
|||
import { LensPluginStartDependencies } from './plugin';
|
||||
import { AttributeService } from '../../../../src/plugins/embeddable/public';
|
||||
import {
|
||||
LensSavedObjectAttributes,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
LensByValueInput,
|
||||
LensByReferenceInput,
|
||||
} from './embeddable/embeddable';
|
||||
import { SavedObjectIndexStore, Document } from './persistence';
|
||||
import { SavedObjectIndexStore } from './persistence';
|
||||
import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public';
|
||||
import { DOC_TYPE } from '../common';
|
||||
|
||||
export type LensAttributeService = AttributeService<
|
||||
LensSavedObjectAttributes,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
LensByValueInput,
|
||||
LensByReferenceInput
|
||||
>;
|
||||
|
||||
function documentToAttributes(doc: Document): LensSavedObjectAttributes {
|
||||
delete doc.savedObjectId;
|
||||
delete doc.type;
|
||||
return { ...doc };
|
||||
}
|
||||
|
||||
export function getLensAttributeService(
|
||||
core: CoreStart,
|
||||
startDependencies: LensPluginStartDependencies
|
||||
): LensAttributeService {
|
||||
const savedObjectStore = new SavedObjectIndexStore(core.savedObjects.client);
|
||||
return startDependencies.embeddable.getAttributeService<
|
||||
LensSavedObjectAttributes,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
LensByValueInput,
|
||||
LensByReferenceInput
|
||||
>(DOC_TYPE, {
|
||||
saveMethod: async (attributes: LensSavedObjectAttributes, savedObjectId?: string) => {
|
||||
saveMethod: async (attributes: ResolvedLensSavedObjectAttributes, savedObjectId?: string) => {
|
||||
const { sharingSavedObjectProps, ...attributesToSave } = attributes;
|
||||
const savedDoc = await savedObjectStore.save({
|
||||
...attributes,
|
||||
...attributesToSave,
|
||||
savedObjectId,
|
||||
type: DOC_TYPE,
|
||||
});
|
||||
return { id: savedDoc.savedObjectId };
|
||||
},
|
||||
unwrapMethod: async (savedObjectId: string): Promise<LensSavedObjectAttributes> => {
|
||||
const attributes = documentToAttributes(await savedObjectStore.load(savedObjectId));
|
||||
return attributes;
|
||||
unwrapMethod: async (savedObjectId: string): Promise<ResolvedLensSavedObjectAttributes> => {
|
||||
const {
|
||||
saved_object: savedObject,
|
||||
outcome,
|
||||
alias_target_id: aliasTargetId,
|
||||
} = await savedObjectStore.load(savedObjectId);
|
||||
const { attributes, references, type, id } = savedObject;
|
||||
const document = {
|
||||
...attributes,
|
||||
references,
|
||||
};
|
||||
|
||||
const sharingSavedObjectProps = {
|
||||
aliasTargetId,
|
||||
outcome,
|
||||
errorJSON:
|
||||
outcome === 'conflict'
|
||||
? JSON.stringify({
|
||||
targetType: type,
|
||||
sourceId: id,
|
||||
targetSpace: (await startDependencies.spaces.getActiveSpace()).id,
|
||||
})
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return {
|
||||
sharingSavedObjectProps,
|
||||
...document,
|
||||
};
|
||||
},
|
||||
checkForDuplicateTitle: (props: OnSaveProps) => {
|
||||
const savedObjectsClient = core.savedObjects.client;
|
||||
|
|
|
@ -24,11 +24,12 @@ import { LensAppServices } from './app_plugin/types';
|
|||
import { DOC_TYPE, layerTypes } from '../common';
|
||||
import { DataPublicPluginStart, esFilters, UI_SETTINGS } from '../../../../src/plugins/data/public';
|
||||
import { inspectorPluginMock } from '../../../../src/plugins/inspector/public/mocks';
|
||||
import { spacesPluginMock } from '../../spaces/public/mocks';
|
||||
import { dashboardPluginMock } from '../../../../src/plugins/dashboard/public/mocks';
|
||||
import type {
|
||||
LensByValueInput,
|
||||
LensSavedObjectAttributes,
|
||||
LensByReferenceInput,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
} from './embeddable/embeddable';
|
||||
import {
|
||||
mockAttributeService,
|
||||
|
@ -352,7 +353,7 @@ export function makeDefaultServices(
|
|||
|
||||
function makeAttributeService(): LensAttributeService {
|
||||
const attributeServiceMock = mockAttributeService<
|
||||
LensSavedObjectAttributes,
|
||||
ResolvedLensSavedObjectAttributes,
|
||||
LensByValueInput,
|
||||
LensByReferenceInput
|
||||
>(
|
||||
|
@ -365,7 +366,12 @@ export function makeDefaultServices(
|
|||
core
|
||||
);
|
||||
|
||||
attributeServiceMock.unwrapAttributes = jest.fn().mockResolvedValue(doc);
|
||||
attributeServiceMock.unwrapAttributes = jest.fn().mockResolvedValue({
|
||||
...doc,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'exactMatch',
|
||||
},
|
||||
});
|
||||
attributeServiceMock.wrapAttributes = jest.fn().mockResolvedValue({
|
||||
savedObjectId: ((doc as unknown) as LensByReferenceInput).savedObjectId,
|
||||
});
|
||||
|
@ -404,6 +410,7 @@ export function makeDefaultServices(
|
|||
remove: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
},
|
||||
spaces: spacesPluginMock.createStartContract(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ describe('LensStore', () => {
|
|||
bulkUpdate: jest.fn(([{ id }]: SavedObjectsBulkUpdateObject[]) =>
|
||||
Promise.resolve({ savedObjects: [{ id }, { id }] })
|
||||
),
|
||||
get: jest.fn(),
|
||||
resolve: jest.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -142,15 +142,18 @@ describe('LensStore', () => {
|
|||
describe('load', () => {
|
||||
test('throws if an error is returned', async () => {
|
||||
const { client, store } = testStore();
|
||||
client.get = jest.fn(async () => ({
|
||||
id: 'Paul',
|
||||
type: 'lens',
|
||||
attributes: {
|
||||
title: 'Hope clouds observation.',
|
||||
visualizationType: 'dune',
|
||||
state: '{ "datasource": { "giantWorms": true } }',
|
||||
client.resolve = jest.fn(async () => ({
|
||||
outcome: 'exactMatch',
|
||||
saved_object: {
|
||||
id: 'Paul',
|
||||
type: 'lens',
|
||||
attributes: {
|
||||
title: 'Hope clouds observation.',
|
||||
visualizationType: 'dune',
|
||||
state: '{ "datasource": { "giantWorms": true } }',
|
||||
},
|
||||
error: new Error('shoot dang!'),
|
||||
},
|
||||
error: new Error('shoot dang!'),
|
||||
}));
|
||||
|
||||
await expect(store.load('Paul')).rejects.toThrow('shoot dang!');
|
||||
|
|
|
@ -9,9 +9,11 @@ import {
|
|||
SavedObjectAttributes,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectReference,
|
||||
ResolvedSimpleSavedObject,
|
||||
} from 'kibana/public';
|
||||
import { Query } from '../../../../../src/plugins/data/public';
|
||||
import { DOC_TYPE, PersistableFilter } from '../../common';
|
||||
import { LensSavedObjectAttributes } from '../async_services';
|
||||
|
||||
export interface Document {
|
||||
savedObjectId?: string;
|
||||
|
@ -37,7 +39,7 @@ export interface DocumentSaver {
|
|||
}
|
||||
|
||||
export interface DocumentLoader {
|
||||
load: (savedObjectId: string) => Promise<Document>;
|
||||
load: (savedObjectId: string) => Promise<ResolvedSimpleSavedObject>;
|
||||
}
|
||||
|
||||
export type SavedObjectStore = DocumentLoader & DocumentSaver;
|
||||
|
@ -87,18 +89,16 @@ export class SavedObjectIndexStore implements SavedObjectStore {
|
|||
).savedObjects[1];
|
||||
}
|
||||
|
||||
async load(savedObjectId: string): Promise<Document> {
|
||||
const { type, attributes, references, error } = await this.client.get(DOC_TYPE, savedObjectId);
|
||||
async load(savedObjectId: string): Promise<ResolvedSimpleSavedObject<LensSavedObjectAttributes>> {
|
||||
const resolveResult = await this.client.resolve<LensSavedObjectAttributes>(
|
||||
DOC_TYPE,
|
||||
savedObjectId
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
if (resolveResult.saved_object.error) {
|
||||
throw resolveResult.saved_object.error;
|
||||
}
|
||||
|
||||
return {
|
||||
...(attributes as SavedObjectAttributes),
|
||||
references,
|
||||
savedObjectId,
|
||||
type,
|
||||
} as Document;
|
||||
return resolveResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
|
|||
import type { Start as InspectorStartContract } from 'src/plugins/inspector/public';
|
||||
import type { FieldFormatsSetup, FieldFormatsStart } from 'src/plugins/field_formats/public';
|
||||
import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_collection/public';
|
||||
import { SpacesPluginStart } from '../../spaces/public';
|
||||
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
||||
import { DashboardStart } from '../../../../src/plugins/dashboard/public';
|
||||
|
@ -100,6 +101,7 @@ export interface LensPluginStartDependencies {
|
|||
presentationUtil: PresentationUtilPluginStart;
|
||||
indexPatternFieldEditor: IndexPatternFieldEditorStart;
|
||||
inspector: InspectorStartContract;
|
||||
spaces: SpacesPluginStart;
|
||||
usageCollection?: UsageCollectionStart;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,13 +19,7 @@ export const initMiddleware = (storeDeps: LensStoreDeps) => (store: MiddlewareAP
|
|||
);
|
||||
return (next: Dispatch) => (action: PayloadAction) => {
|
||||
if (lensSlice.actions.loadInitial.match(action)) {
|
||||
return loadInitial(
|
||||
store,
|
||||
storeDeps,
|
||||
action.payload.redirectCallback,
|
||||
action.payload.initialInput,
|
||||
action.payload.emptyState
|
||||
);
|
||||
return loadInitial(store, storeDeps, action.payload);
|
||||
} else if (lensSlice.actions.navigateAway.match(action)) {
|
||||
return unsubscribeFromExternalContext();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
createMockDatasource,
|
||||
DatasourceMock,
|
||||
} from '../../mocks';
|
||||
import { Location, History } from 'history';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { loadInitial } from './load_initial';
|
||||
import { LensEmbeddableInput } from '../../embeddable';
|
||||
|
@ -65,7 +66,12 @@ describe('Mounter', () => {
|
|||
|
||||
it('should initialize initial datasource', async () => {
|
||||
const services = makeDefaultServices();
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc);
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({
|
||||
...defaultDoc,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'exactMatch',
|
||||
},
|
||||
});
|
||||
|
||||
const lensStore = await makeLensStore({
|
||||
data: services.data,
|
||||
|
@ -79,8 +85,10 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn(),
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
{
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput,
|
||||
}
|
||||
);
|
||||
});
|
||||
expect(mockDatasource.initialize).toHaveBeenCalled();
|
||||
|
@ -88,7 +96,12 @@ describe('Mounter', () => {
|
|||
|
||||
it('should have initialized only the initial datasource and visualization', async () => {
|
||||
const services = makeDefaultServices();
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc);
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({
|
||||
...defaultDoc,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'exactMatch',
|
||||
},
|
||||
});
|
||||
|
||||
const lensStore = await makeLensStore({ data: services.data, preloadedState });
|
||||
await act(async () => {
|
||||
|
@ -99,7 +112,7 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn()
|
||||
{ redirectCallback: jest.fn() }
|
||||
);
|
||||
});
|
||||
expect(mockDatasource.initialize).toHaveBeenCalled();
|
||||
|
@ -129,7 +142,7 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn()
|
||||
{ redirectCallback: jest.fn() }
|
||||
);
|
||||
expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -170,7 +183,11 @@ describe('Mounter', () => {
|
|||
const emptyState = getPreloadedState(storeDeps) as LensAppState;
|
||||
services.attributeService.unwrapAttributes = jest.fn();
|
||||
await act(async () => {
|
||||
await loadInitial(lensStore, storeDeps, jest.fn(), undefined, emptyState);
|
||||
await loadInitial(lensStore, storeDeps, {
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: undefined,
|
||||
emptyState,
|
||||
});
|
||||
});
|
||||
|
||||
expect(lensStore.getState()).toEqual({
|
||||
|
@ -189,20 +206,28 @@ describe('Mounter', () => {
|
|||
|
||||
it('loads a document and uses query and filters if initial input is provided', async () => {
|
||||
const services = makeDefaultServices();
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc);
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({
|
||||
...defaultDoc,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'exactMatch',
|
||||
},
|
||||
});
|
||||
const storeDeps = {
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
};
|
||||
const emptyState = getPreloadedState(storeDeps) as LensAppState;
|
||||
|
||||
const lensStore = await makeLensStore({ data: services.data, preloadedState });
|
||||
await act(async () => {
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn(),
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
);
|
||||
await loadInitial(lensStore, storeDeps, {
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: ({
|
||||
savedObjectId: defaultSavedObjectId,
|
||||
} as unknown) as LensEmbeddableInput,
|
||||
emptyState,
|
||||
});
|
||||
});
|
||||
|
||||
expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({
|
||||
|
@ -235,8 +260,12 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn(),
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
{
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: ({
|
||||
savedObjectId: defaultSavedObjectId,
|
||||
} as unknown) as LensEmbeddableInput,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -248,8 +277,12 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn(),
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
{
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: ({
|
||||
savedObjectId: defaultSavedObjectId,
|
||||
} as unknown) as LensEmbeddableInput,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -263,8 +296,10 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn(),
|
||||
({ savedObjectId: '5678' } as unknown) as LensEmbeddableInput
|
||||
{
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: ({ savedObjectId: '5678' } as unknown) as LensEmbeddableInput,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -287,8 +322,12 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
redirectCallback,
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
{
|
||||
redirectCallback,
|
||||
initialInput: ({
|
||||
savedObjectId: defaultSavedObjectId,
|
||||
} as unknown) as LensEmbeddableInput,
|
||||
}
|
||||
);
|
||||
});
|
||||
expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({
|
||||
|
@ -298,6 +337,50 @@ describe('Mounter', () => {
|
|||
expect(redirectCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('redirects if saved object is an aliasMatch', async () => {
|
||||
const services = makeDefaultServices();
|
||||
|
||||
const lensStore = makeLensStore({ data: services.data, preloadedState });
|
||||
|
||||
services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({
|
||||
...defaultDoc,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'aliasMatch',
|
||||
aliasTargetId: 'id2',
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await loadInitial(
|
||||
lensStore,
|
||||
{
|
||||
lensServices: services,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
{
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: ({
|
||||
savedObjectId: defaultSavedObjectId,
|
||||
} as unknown) as LensEmbeddableInput,
|
||||
history: {
|
||||
location: {
|
||||
search: '?search',
|
||||
} as Location,
|
||||
} as History,
|
||||
}
|
||||
);
|
||||
});
|
||||
expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({
|
||||
savedObjectId: defaultSavedObjectId,
|
||||
});
|
||||
|
||||
expect(services.spaces.ui.redirectLegacyUrl).toHaveBeenCalledWith(
|
||||
'#/edit/id2?search',
|
||||
'Lens visualization'
|
||||
);
|
||||
});
|
||||
|
||||
it('adds to the recently accessed list on load', async () => {
|
||||
const services = makeDefaultServices();
|
||||
const lensStore = makeLensStore({ data: services.data, preloadedState });
|
||||
|
@ -309,8 +392,12 @@ describe('Mounter', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
},
|
||||
jest.fn(),
|
||||
({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput
|
||||
{
|
||||
redirectCallback: jest.fn(),
|
||||
initialInput: ({
|
||||
savedObjectId: defaultSavedObjectId,
|
||||
} as unknown) as LensEmbeddableInput,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
import { MiddlewareAPI } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { History } from 'history';
|
||||
import { LensAppState, setState } from '..';
|
||||
import { updateLayer, updateVisualizationState, LensStoreDeps } from '..';
|
||||
import { SharingSavedObjectProps } from '../../types';
|
||||
import { LensEmbeddableInput, LensByReferenceInput } from '../../embeddable/embeddable';
|
||||
import { getInitialDatasourceId } from '../../utils';
|
||||
import { initializeDatasources } from '../../editor_frame_service/editor_frame';
|
||||
|
@ -19,22 +21,50 @@ import {
|
|||
switchToSuggestion,
|
||||
} from '../../editor_frame_service/editor_frame/suggestion_helpers';
|
||||
import { LensAppServices } from '../../app_plugin/types';
|
||||
import { getFullPath, LENS_EMBEDDABLE_TYPE } from '../../../common/constants';
|
||||
import { getEditPath, getFullPath, LENS_EMBEDDABLE_TYPE } from '../../../common/constants';
|
||||
import { Document, injectFilterReferences } from '../../persistence';
|
||||
|
||||
export const getPersisted = async ({
|
||||
initialInput,
|
||||
lensServices,
|
||||
history,
|
||||
}: {
|
||||
initialInput: LensEmbeddableInput;
|
||||
lensServices: LensAppServices;
|
||||
}): Promise<{ doc: Document } | undefined> => {
|
||||
const { notifications, attributeService } = lensServices;
|
||||
history?: History<unknown>;
|
||||
}): Promise<
|
||||
{ doc: Document; sharingSavedObjectProps: Omit<SharingSavedObjectProps, 'errorJSON'> } | undefined
|
||||
> => {
|
||||
const { notifications, spaces, attributeService } = lensServices;
|
||||
let doc: Document;
|
||||
|
||||
try {
|
||||
const attributes = await attributeService.unwrapAttributes(initialInput);
|
||||
|
||||
const result = await attributeService.unwrapAttributes(initialInput);
|
||||
if (!result) {
|
||||
return {
|
||||
doc: ({
|
||||
...initialInput,
|
||||
type: LENS_EMBEDDABLE_TYPE,
|
||||
} as unknown) as Document,
|
||||
sharingSavedObjectProps: {
|
||||
outcome: 'exactMatch',
|
||||
},
|
||||
};
|
||||
}
|
||||
const { sharingSavedObjectProps, ...attributes } = result;
|
||||
if (spaces && sharingSavedObjectProps?.outcome === 'aliasMatch' && history) {
|
||||
// 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 = lensServices.http.basePath.prepend(
|
||||
`${getEditPath(newObjectId)}${history.location.search}`
|
||||
);
|
||||
await spaces.ui.redirectLegacyUrl(
|
||||
newPath,
|
||||
i18n.translate('xpack.lens.legacyUrlConflict.objectNoun', {
|
||||
defaultMessage: 'Lens visualization',
|
||||
})
|
||||
);
|
||||
}
|
||||
doc = {
|
||||
...initialInput,
|
||||
...attributes,
|
||||
|
@ -43,6 +73,10 @@ export const getPersisted = async ({
|
|||
|
||||
return {
|
||||
doc,
|
||||
sharingSavedObjectProps: {
|
||||
aliasTargetId: sharingSavedObjectProps?.aliasTargetId,
|
||||
outcome: sharingSavedObjectProps?.outcome,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
notifications.toasts.addDanger(
|
||||
|
@ -62,9 +96,17 @@ export function loadInitial(
|
|||
embeddableEditorIncomingState,
|
||||
initialContext,
|
||||
}: LensStoreDeps,
|
||||
redirectCallback: (savedObjectId?: string) => void,
|
||||
initialInput?: LensEmbeddableInput,
|
||||
emptyState?: LensAppState
|
||||
{
|
||||
redirectCallback,
|
||||
initialInput,
|
||||
emptyState,
|
||||
history,
|
||||
}: {
|
||||
redirectCallback: (savedObjectId?: string) => void;
|
||||
initialInput?: LensEmbeddableInput;
|
||||
emptyState?: LensAppState;
|
||||
history?: History<unknown>;
|
||||
}
|
||||
) {
|
||||
const { getState, dispatch } = store;
|
||||
const { attributeService, notifications, data, dashboardFeatureFlag } = lensServices;
|
||||
|
@ -146,11 +188,11 @@ export function loadInitial(
|
|||
redirectCallback();
|
||||
});
|
||||
}
|
||||
getPersisted({ initialInput, lensServices })
|
||||
getPersisted({ initialInput, lensServices, history })
|
||||
.then(
|
||||
(persisted) => {
|
||||
if (persisted) {
|
||||
const { doc } = persisted;
|
||||
const { doc, sharingSavedObjectProps } = persisted;
|
||||
if (attributeService.inputIsRefType(initialInput)) {
|
||||
lensServices.chrome.recentlyAccessed.add(
|
||||
getFullPath(initialInput.savedObjectId),
|
||||
|
@ -190,6 +232,7 @@ export function loadInitial(
|
|||
|
||||
dispatch(
|
||||
setState({
|
||||
sharingSavedObjectProps,
|
||||
query: doc.state.query,
|
||||
searchSessionId:
|
||||
dashboardFeatureFlag.allowByValueEmbeddables &&
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { createSlice, current, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { History } from 'history';
|
||||
import { LensEmbeddableInput } from '..';
|
||||
import { TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
import { getInitialDatasourceId, getResolvedDateRange } from '../utils';
|
||||
|
@ -301,6 +302,7 @@ export const lensSlice = createSlice({
|
|||
initialInput?: LensEmbeddableInput;
|
||||
redirectCallback: (savedObjectId?: string) => void;
|
||||
emptyState: LensAppState;
|
||||
history: History<unknown>;
|
||||
}>
|
||||
) => state,
|
||||
},
|
||||
|
|
|
@ -13,8 +13,7 @@ import { Document } from '../persistence';
|
|||
import { TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
import { DateRange } from '../../common';
|
||||
import { LensAppServices } from '../app_plugin/types';
|
||||
import { DatasourceMap, VisualizationMap } from '../types';
|
||||
|
||||
import { DatasourceMap, VisualizationMap, SharingSavedObjectProps } from '../types';
|
||||
export interface VisualizationState {
|
||||
activeId: string | null;
|
||||
state: unknown;
|
||||
|
@ -44,6 +43,7 @@ export interface LensAppState extends EditorFrameState {
|
|||
savedQuery?: SavedQuery;
|
||||
searchSessionId: string;
|
||||
resolvedDateRange: DateRange;
|
||||
sharingSavedObjectProps?: Omit<SharingSavedObjectProps, 'errorJSON'>;
|
||||
}
|
||||
|
||||
export type DispatchSetState = (
|
||||
|
|
|
@ -256,7 +256,7 @@ export interface Datasource<T = unknown, P = unknown> {
|
|||
) =>
|
||||
| Array<{
|
||||
shortMessage: string;
|
||||
longMessage: string;
|
||||
longMessage: React.ReactNode;
|
||||
fixAction?: { label: string; newState: () => Promise<T> };
|
||||
}>
|
||||
| undefined;
|
||||
|
@ -729,7 +729,7 @@ export interface Visualization<T = unknown> {
|
|||
) =>
|
||||
| Array<{
|
||||
shortMessage: string;
|
||||
longMessage: string;
|
||||
longMessage: React.ReactNode;
|
||||
}>
|
||||
| undefined;
|
||||
|
||||
|
@ -813,3 +813,9 @@ export interface ILensInterpreterRenderHandlers extends IInterpreterRenderHandle
|
|||
| LensTableRowContextMenuEvent
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface SharingSavedObjectProps {
|
||||
outcome?: 'aliasMatch' | 'exactMatch' | 'conflict';
|
||||
aliasTargetId?: string;
|
||||
errorJSON?: string;
|
||||
}
|
||||
|
|
|
@ -383,7 +383,7 @@ export const getXyVisualization = ({
|
|||
|
||||
const errors: Array<{
|
||||
shortMessage: string;
|
||||
longMessage: string;
|
||||
longMessage: React.ReactNode;
|
||||
}> = [];
|
||||
|
||||
// check if the layers in the state are compatible with this type of chart
|
||||
|
@ -488,7 +488,7 @@ function validateLayersForDimension(
|
|||
| { valid: true }
|
||||
| {
|
||||
valid: false;
|
||||
payload: { shortMessage: string; longMessage: string };
|
||||
payload: { shortMessage: string; longMessage: React.ReactNode };
|
||||
} {
|
||||
// Multiple layers must be consistent:
|
||||
// * either a dimension is missing in ALL of them
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"../../../typings/**/*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../spaces/tsconfig.json" },
|
||||
{ "path": "../../../src/core/tsconfig.json" },
|
||||
{ "path": "../task_manager/tsconfig.json" },
|
||||
{ "path": "../global_search/tsconfig.json"},
|
||||
|
|
Loading…
Reference in a new issue