[Time to Visualize] Align Lens & Visualize Breadcrumbs (#86941) (#87395)

* Aligned Lens & Visualize Breadcrumbs
This commit is contained in:
Devon Thomson 2021-01-06 10:48:15 -05:00 committed by GitHub
parent 54a37115ba
commit 530095ea00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 92 additions and 60 deletions

View file

@ -45,7 +45,7 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => {
useEffect(() => {
const { originatingApp: value, embeddableId: embeddableIdValue, valueInput: valueInputValue } =
services.embeddable.getStateTransfer().getIncomingEditorState() || {};
services.stateTransferService.getIncomingEditorState() || {};
setOriginatingApp(value);
setValueInput(valueInputValue);
setEmbeddableId(embeddableIdValue);

View file

@ -46,6 +46,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
services,
eventEmitter,
isChromeVisible,
originatingApp,
visualizationIdFromUrl
);
const { appState, hasUnappliedChanges } = useVisualizeAppState(
@ -64,8 +65,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => {
useLinkedSearchUpdates(services, eventEmitter, appState, savedVisInstance);
useEffect(() => {
const { originatingApp: value } =
services.embeddable.getStateTransfer().getIncomingEditorState() || {};
const { originatingApp: value } = services.stateTransferService.getIncomingEditorState() || {};
setOriginatingApp(value);
}, [services]);

View file

@ -45,7 +45,7 @@ export const VisualizeListing = () => {
savedVisualizations,
toastNotifications,
visualizations,
embeddable,
stateTransferService,
savedObjects,
savedObjectsPublic,
savedObjectsTagging,
@ -74,7 +74,7 @@ export const VisualizeListing = () => {
useMount(() => {
// Reset editor state if the visualize listing page is loaded.
embeddable.getStateTransfer().clearEditorState();
stateTransferService.clearEditorState();
chrome.setBreadcrumbs([
{
text: i18n.translate('visualize.visualizeListingBreadcrumbsTitle', {

View file

@ -80,7 +80,6 @@ const TopNav = ({
},
[visInstance.embeddableHandler]
);
const stateTransfer = services.embeddable.getStateTransfer();
const savedObjectsClient = services.savedObjects.client;
const config = useMemo(() => {
@ -96,7 +95,7 @@ const TopNav = ({
visInstance,
stateContainer,
visualizationIdFromUrl,
stateTransfer,
stateTransfer: services.stateTransferService,
savedObjectsClient,
embeddableId,
onAppLeave,
@ -117,7 +116,6 @@ const TopNav = ({
visualizationIdFromUrl,
services,
embeddableId,
stateTransfer,
savedObjectsClient,
onAppLeave,
]);

View file

@ -43,7 +43,7 @@ import {
} from 'src/plugins/kibana_utils/public';
import { SharePluginStart } from 'src/plugins/share/public';
import { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/public';
import { EmbeddableStart } from 'src/plugins/embeddable/public';
import { EmbeddableStart, EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
import { UrlForwardingStart } from 'src/plugins/url_forwarding/public';
import { EventEmitter } from 'events';
import { DashboardStart } from '../../../dashboard/public';
@ -95,6 +95,7 @@ export interface EditorRenderProps {
}
export interface VisualizeServices extends CoreStart {
stateTransferService: EmbeddableStateTransfer;
embeddable: EmbeddableStart;
history: History;
kbnUrlStateStorage: IKbnUrlStateStorage;

View file

@ -21,16 +21,8 @@ import { i18n } from '@kbn/i18n';
import { VisualizeConstants } from '../visualize_constants';
const appPrefixes: Record<string, any> = {
dashboards: {
text: i18n.translate('visualize.dashboard.prefix.breadcrumb', {
defaultMessage: 'Dashboard',
}),
},
};
const defaultEditText = i18n.translate('visualize.editor.defaultEditBreadcrumbText', {
defaultMessage: 'Edit',
defaultMessage: 'Edit visualization',
});
export function getLandingBreadcrumbs() {
@ -44,9 +36,18 @@ export function getLandingBreadcrumbs() {
];
}
export function getCreateBreadcrumbs() {
export function getCreateBreadcrumbs({
byValue,
originatingAppName,
redirectToOrigin,
}: {
byValue?: boolean;
originatingAppName?: string;
redirectToOrigin?: () => void;
}) {
return [
...getLandingBreadcrumbs(),
...(originatingAppName ? [{ text: originatingAppName, onClick: redirectToOrigin }] : []),
...(!byValue ? getLandingBreadcrumbs() : []),
{
text: i18n.translate('visualize.editor.createBreadcrumb', {
defaultMessage: 'Create',
@ -55,16 +56,23 @@ export function getCreateBreadcrumbs() {
];
}
export function getBreadcrumbsPrefixedWithApp(originatingApp: string) {
const originatingAppBreadcrumb = appPrefixes[originatingApp];
return [originatingAppBreadcrumb, ...getLandingBreadcrumbs(), { text: defaultEditText }];
}
export function getEditBreadcrumbs(text: string = defaultEditText) {
export function getEditBreadcrumbs(
{
byValue,
originatingAppName,
redirectToOrigin,
}: {
byValue?: boolean;
originatingAppName?: string;
redirectToOrigin?: () => void;
},
title: string = defaultEditText
) {
return [
...getLandingBreadcrumbs(),
...(originatingAppName ? [{ text: originatingAppName, onClick: redirectToOrigin }] : []),
...(!byValue ? getLandingBreadcrumbs() : []),
{
text,
text: title,
},
];
}

View file

@ -142,7 +142,7 @@ export const getTopNavConfig = (
stateTransfer.clearEditorState();
}
chrome.docTitle.change(savedVis.lastSavedTitle);
chrome.setBreadcrumbs(getEditBreadcrumbs(savedVis.lastSavedTitle));
chrome.setBreadcrumbs(getEditBreadcrumbs({}, savedVis.lastSavedTitle));
if (id !== visualizationIdFromUrl) {
history.replace({

View file

@ -27,6 +27,7 @@ import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs';
import { VisualizeServices } from '../../types';
import { VisualizeConstants } from '../../visualize_constants';
import { setDefaultEditor } from '../../../services';
import { createEmbeddableStateTransferMock } from '../../../../../embeddable/public/mocks';
const mockDefaultEditorControllerDestroy = jest.fn();
const mockEmbeddableHandlerDestroy = jest.fn();
@ -52,7 +53,7 @@ jest.mock('../get_visualization_instance', () => ({
getVisualizationInstance: jest.fn(() => mockSavedVisInstance),
}));
jest.mock('../breadcrumbs', () => ({
getEditBreadcrumbs: jest.fn((text) => text),
getEditBreadcrumbs: jest.fn((args, title) => title),
getCreateBreadcrumbs: jest.fn((text) => text),
}));
@ -81,12 +82,15 @@ describe('useSavedVisInstance', () => {
mockServices = ({
...coreStartMock,
toastNotifications,
stateTransferService: createEmbeddableStateTransferMock(),
chrome: { setBreadcrumbs: jest.fn(), docTitle: { change: jest.fn() } },
history: {
location: {
pathname: VisualizeConstants.EDIT_PATH,
},
replace: () => {},
},
dashboard: { dashboardFeatureFlagConfig: { allowByValueEmbeddables: false } },
visualizations: {
all: jest.fn(() => [
{
@ -111,7 +115,7 @@ describe('useSavedVisInstance', () => {
test('should not load instance until chrome is defined', () => {
const { result } = renderHook(() =>
useSavedVisInstance(mockServices, eventEmitter, undefined, undefined)
useSavedVisInstance(mockServices, eventEmitter, undefined, undefined, undefined)
);
expect(mockGetVisualizationInstance).not.toHaveBeenCalled();
expect(result.current.visEditorController).toBeUndefined();
@ -122,7 +126,7 @@ describe('useSavedVisInstance', () => {
describe('edit saved visualization route', () => {
test('should load instance and initiate an editor if chrome is set up', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useSavedVisInstance(mockServices, eventEmitter, true, savedVisId)
useSavedVisInstance(mockServices, eventEmitter, true, undefined, savedVisId)
);
result.current.visEditorRef.current = document.createElement('div');
@ -131,7 +135,11 @@ describe('useSavedVisInstance', () => {
await waitForNextUpdate();
expect(mockServices.chrome.setBreadcrumbs).toHaveBeenCalledWith('Test Vis');
expect(getEditBreadcrumbs).toHaveBeenCalledWith('Test Vis');
expect(mockServices.chrome.docTitle.change).toHaveBeenCalledWith('Test Vis');
expect(getEditBreadcrumbs).toHaveBeenCalledWith(
{ originatingAppName: undefined, redirectToOrigin: undefined },
'Test Vis'
);
expect(getCreateBreadcrumbs).not.toHaveBeenCalled();
expect(mockEmbeddableHandlerRender).not.toHaveBeenCalled();
expect(result.current.visEditorController).toBeDefined();
@ -140,7 +148,7 @@ describe('useSavedVisInstance', () => {
test('should destroy the editor and the savedVis on unmount if chrome exists', async () => {
const { result, unmount, waitForNextUpdate } = renderHook(() =>
useSavedVisInstance(mockServices, eventEmitter, true, savedVisId)
useSavedVisInstance(mockServices, eventEmitter, true, undefined, savedVisId)
);
result.current.visEditorRef.current = document.createElement('div');
@ -167,7 +175,7 @@ describe('useSavedVisInstance', () => {
test('should create new visualization based on search params', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useSavedVisInstance(mockServices, eventEmitter, true, undefined)
useSavedVisInstance(mockServices, eventEmitter, true, undefined, undefined)
);
result.current.visEditorRef.current = document.createElement('div');
@ -191,7 +199,7 @@ describe('useSavedVisInstance', () => {
search: '?type=myVisType&indexPattern=1a2b3c4d',
};
renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined));
renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined, undefined));
expect(mockGetVisualizationInstance).not.toHaveBeenCalled();
expect(redirectWhenMissing).toHaveBeenCalled();
@ -204,7 +212,7 @@ describe('useSavedVisInstance', () => {
search: '?type=area',
};
renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined));
renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined, undefined));
expect(mockGetVisualizationInstance).not.toHaveBeenCalled();
expect(redirectWhenMissing).toHaveBeenCalled();
@ -215,7 +223,7 @@ describe('useSavedVisInstance', () => {
describe('embeded mode', () => {
test('should create new visualization based on search params', async () => {
const { result, unmount, waitForNextUpdate } = renderHook(() =>
useSavedVisInstance(mockServices, eventEmitter, false, savedVisId)
useSavedVisInstance(mockServices, eventEmitter, false, undefined, savedVisId)
);
// mock editor ref

View file

@ -38,6 +38,7 @@ export const useSavedVisInstance = (
services: VisualizeServices,
eventEmitter: EventEmitter,
isChromeVisible: boolean | undefined,
originatingApp: string | undefined,
visualizationIdFromUrl: string | undefined
) => {
const [state, setState] = useState<{
@ -49,12 +50,14 @@ export const useSavedVisInstance = (
useEffect(() => {
const {
application: { navigateToApp },
chrome,
history,
http: { basePath },
dashboard,
setActiveUrl,
toastNotifications,
http: { basePath },
stateTransferService,
application: { navigateToApp },
} = services;
const getSavedVisInstance = async () => {
try {
@ -93,11 +96,25 @@ export const useSavedVisInstance = (
const { embeddableHandler, savedVis, vis } = savedVisInstance;
const originatingAppName = originatingApp
? stateTransferService.getAppNameFromId(originatingApp)
: undefined;
const redirectToOrigin = originatingApp ? () => navigateToApp(originatingApp) : undefined;
const byValueCreateMode = dashboard.dashboardFeatureFlagConfig.allowByValueEmbeddables;
if (savedVis.id) {
chrome.setBreadcrumbs(getEditBreadcrumbs(savedVis.title));
chrome.setBreadcrumbs(
getEditBreadcrumbs({ originatingAppName, redirectToOrigin }, savedVis.title)
);
chrome.docTitle.change(savedVis.title);
} else {
chrome.setBreadcrumbs(getCreateBreadcrumbs());
chrome.setBreadcrumbs(
getCreateBreadcrumbs({
byValue: byValueCreateMode,
originatingAppName,
redirectToOrigin,
})
);
}
let visEditorController;
@ -174,6 +191,7 @@ export const useSavedVisInstance = (
}, [
eventEmitter,
isChromeVisible,
originatingApp,
services,
state.savedVisInstance,
state.visEditorController,

View file

@ -22,7 +22,7 @@ import { useEffect, useRef, useState } from 'react';
import { VisualizeInput } from 'src/plugins/visualizations/public';
import { ByValueVisInstance, IEditorController, VisualizeServices } from '../../types';
import { getVisualizationInstanceFromInput } from '../get_visualization_instance';
import { getBreadcrumbsPrefixedWithApp, getEditBreadcrumbs } from '../breadcrumbs';
import { getEditBreadcrumbs } from '../breadcrumbs';
import { getDefaultEditor } from '../../../services';
export const useVisByValue = (
@ -39,7 +39,11 @@ export const useVisByValue = (
const visEditorRef = useRef<HTMLDivElement>(null);
const loaded = useRef(false);
useEffect(() => {
const { chrome } = services;
const {
chrome,
application: { navigateToApp },
stateTransferService,
} = services;
const getVisInstance = async () => {
if (!valueInput || loaded.current || !visEditorRef.current) {
return;
@ -55,11 +59,13 @@ export const useVisByValue = (
embeddableHandler
);
if (chrome && originatingApp) {
chrome.setBreadcrumbs(getBreadcrumbsPrefixedWithApp(originatingApp));
} else if (chrome) {
chrome.setBreadcrumbs(getEditBreadcrumbs());
}
const originatingAppName = originatingApp
? stateTransferService.getAppNameFromId(originatingApp)
: undefined;
const redirectToOrigin = originatingApp ? () => navigateToApp(originatingApp) : undefined;
chrome?.setBreadcrumbs(
getEditBreadcrumbs({ byValue: true, originatingAppName, redirectToOrigin })
);
loaded.current = true;
setState({

View file

@ -202,6 +202,7 @@ export class VisualizePlugin
visualizeCapabilities: coreStart.application.capabilities.visualize,
visualizations: pluginsStart.visualizations,
embeddable: pluginsStart.embeddable,
stateTransferService: pluginsStart.embeddable.getStateTransfer(),
setActiveUrl,
createVisEmbeddableFromObject:
pluginsStart.visualizations.__LEGACY.createVisEmbeddableFromObject,

View file

@ -25,7 +25,6 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');
const globalNav = getService('globalNav');
const PageObjects = getPageObjects(['common']);
const defaultFindTimeout = config.get('timeouts.find');
@ -42,14 +41,9 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo
await appsMenu.clickLink('Visualize', { category: 'kibana' });
await this.onAppLeaveWarning(ignoreAppLeaveWarning);
await this.awaitGlobalLoadingIndicatorHidden();
await retry.waitFor('first breadcrumb to be "Visualize"', async () => {
const firstBreadcrumb = await globalNav.getFirstBreadcrumb();
if (firstBreadcrumb !== 'Visualize') {
log.debug('-- first breadcrumb =', firstBreadcrumb);
return false;
}
return true;
await retry.waitFor('Visualize app to be loaded', async () => {
const isNavVisible = await testSubjects.exists('top-nav');
return isNavVisible;
});
}

View file

@ -4518,7 +4518,6 @@
"visualize.createVisualization.failedToLoadErrorMessage": "ビジュアライゼーションを読み込めませんでした",
"visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "indexPatternまたはsavedSearchIdが必要です",
"visualize.createVisualization.noVisTypeErrorMessage": "有効なビジュアライゼーションタイプを指定してください",
"visualize.dashboard.prefix.breadcrumb": "ダッシュボード",
"visualize.discover.visualizeFieldLabel": "Visualizeフィールド",
"visualize.editor.createBreadcrumb": "作成",
"visualize.editor.defaultEditBreadcrumbText": "編集",

View file

@ -4520,7 +4520,6 @@
"visualize.createVisualization.failedToLoadErrorMessage": "无法加载可视化",
"visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "必须提供 indexPattern 或 savedSearchId",
"visualize.createVisualization.noVisTypeErrorMessage": "必须提供有效的可视化类型",
"visualize.dashboard.prefix.breadcrumb": "仪表板",
"visualize.discover.visualizeFieldLabel": "可视化字段",
"visualize.editor.createBreadcrumb": "创建",
"visualize.editor.defaultEditBreadcrumbText": "编辑",