fix 'Save modal dark theme issues in Discover, Dashboard, and Maps' (#149147)

Fixes https://github.com/elastic/kibana/issues/149130 and
https://github.com/elastic/kibana/issues/142636

PR updates `showSaveModal` to wrap modal in `KibanaThemeProvider` to
resolve dark theme issues. Rather than passing in theme$ as a new
parameter, theme$ is imported from within the plugin itself.

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2023-01-19 07:55:22 -07:00 committed by GitHub
parent ff5101d3c1
commit d5f6fe84c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 48 additions and 71 deletions

View file

@ -27,7 +27,6 @@ export function runSaveAs(this: DashboardContainer) {
timefilter: { timefilter }, timefilter: { timefilter },
}, },
}, },
coreContext: { i18nContext },
savedObjectsTagging: { hasApi: hasSavedObjectsTagging }, savedObjectsTagging: { hasApi: hasSavedObjectsTagging },
dashboardSavedObject: { checkForDuplicateDashboardTitle, saveDashboardStateToSavedObject }, dashboardSavedObject: { checkForDuplicateDashboardTitle, saveDashboardStateToSavedObject },
} = pluginServices.getServices(); } = pluginServices.getServices();
@ -125,7 +124,7 @@ export function runSaveAs(this: DashboardContainer) {
/> />
); );
this.clearOverlays(); this.clearOverlays();
showSaveModal(dashboardSaveModal, i18nContext); showSaveModal(dashboardSaveModal);
}); });
} }

View file

@ -187,7 +187,7 @@ export async function onSaveSearch({
onClose={onClose ?? (() => {})} onClose={onClose ?? (() => {})}
/> />
); );
showSaveModal(saveModal, services.core.i18n.Context); showSaveModal(saveModal);
} }
const SaveSearchObjectModal: React.FC<{ const SaveSearchObjectModal: React.FC<{

View file

@ -27,8 +27,6 @@ export const mockAttributeService = <
const core = customCore ? customCore : coreMock.createStart(); const core = customCore ? customCore : coreMock.createStart();
return new AttributeService<A, V, R, M>( return new AttributeService<A, V, R, M>(
type, type,
jest.fn(),
core.i18n.Context,
core.notifications.toasts, core.notifications.toasts,
options, options,
jest.fn().mockReturnValue(() => ({ getDisplayName: () => type })) jest.fn().mockReturnValue(() => ({ getDisplayName: () => type }))

View file

@ -9,8 +9,13 @@
import React from 'react'; import React from 'react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { get, omit } from 'lodash'; import { get, omit } from 'lodash';
import { I18nStart, NotificationsStart } from '@kbn/core/public'; import { NotificationsStart } from '@kbn/core/public';
import { SavedObjectSaveModal, OnSaveProps, SaveResult } from '@kbn/saved-objects-plugin/public'; import {
SavedObjectSaveModal,
OnSaveProps,
SaveResult,
showSaveModal,
} from '@kbn/saved-objects-plugin/public';
import { import {
EmbeddableInput, EmbeddableInput,
SavedObjectEmbeddableInput, SavedObjectEmbeddableInput,
@ -61,11 +66,6 @@ export class AttributeService<
> { > {
constructor( constructor(
private type: string, private type: string,
private showSaveModal: (
saveModal: React.ReactElement,
I18nContext: I18nStart['Context']
) => void,
private i18nContext: I18nStart['Context'],
private toasts: NotificationsStart['toasts'], private toasts: NotificationsStart['toasts'],
private options: AttributeServiceOptions<SavedObjectAttributes, MetaInfo>, private options: AttributeServiceOptions<SavedObjectAttributes, MetaInfo>,
getEmbeddableFactory?: (embeddableFactoryId: string) => EmbeddableFactory getEmbeddableFactory?: (embeddableFactoryId: string) => EmbeddableFactory
@ -178,7 +178,7 @@ export class AttributeService<
} }
}; };
if (saveOptions && (saveOptions as { showSaveModal: boolean }).showSaveModal) { if (saveOptions && (saveOptions as { showSaveModal: boolean }).showSaveModal) {
this.showSaveModal( showSaveModal(
<SavedObjectSaveModal <SavedObjectSaveModal
onSave={onSave} onSave={onSave}
onClose={() => {}} onClose={() => {}}
@ -190,8 +190,7 @@ export class AttributeService<
showCopyOnSave={false} showCopyOnSave={false}
objectType={this.type} objectType={this.type}
showDescription={false} showDescription={false}
/>, />
this.i18nContext
); );
} }
}); });

View file

@ -10,7 +10,7 @@ import React from 'react';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { identity } from 'lodash'; import { identity } from 'lodash';
import type { SerializableRecord } from '@kbn/utility-types'; import type { SerializableRecord } from '@kbn/utility-types';
import { getSavedObjectFinder, showSaveModal } from '@kbn/saved-objects-plugin/public'; import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public';
import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { Start as InspectorStart } from '@kbn/inspector-plugin/public'; import { Start as InspectorStart } from '@kbn/inspector-plugin/public';
import { import {
@ -211,14 +211,7 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
getEmbeddableFactory: this.getEmbeddableFactory, getEmbeddableFactory: this.getEmbeddableFactory,
getEmbeddableFactories: this.getEmbeddableFactories, getEmbeddableFactories: this.getEmbeddableFactories,
getAttributeService: (type: string, options) => getAttributeService: (type: string, options) =>
new AttributeService( new AttributeService(type, core.notifications.toasts, options, this.getEmbeddableFactory),
type,
showSaveModal,
core.i18n.Context,
core.notifications.toasts,
options,
this.getEmbeddableFactory
),
getStateTransfer: (storage?: Storage) => getStateTransfer: (storage?: Storage) =>
storage storage
? new EmbeddableStateTransfer( ? new EmbeddableStateTransfer(

View file

@ -0,0 +1,17 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { CoreStart } from '@kbn/core/public';
let coreStart: CoreStart;
export function setStartServices(core: CoreStart) {
coreStart = core;
}
export const getI18n = () => coreStart.i18n;
export const getTheme = () => coreStart.theme;

View file

@ -18,6 +18,7 @@ import {
} from './saved_object'; } from './saved_object';
import { PER_PAGE_SETTING, LISTING_LIMIT_SETTING } from '../common'; import { PER_PAGE_SETTING, LISTING_LIMIT_SETTING } from '../common';
import { SavedObject } from './types'; import { SavedObject } from './types';
import { setStartServices } from './kibana_services';
export interface SavedObjectSetup { export interface SavedObjectSetup {
registerDecorator: (config: SavedObjectDecoratorConfig<any>) => void; registerDecorator: (config: SavedObjectDecoratorConfig<any>) => void;
@ -63,6 +64,7 @@ export class SavedObjectsPublicPlugin
}; };
} }
public start(core: CoreStart, { data, dataViews }: SavedObjectsStartDeps) { public start(core: CoreStart, { data, dataViews }: SavedObjectsStartDeps) {
setStartServices(core);
return { return {
SavedObjectClass: createSavedObjectClass( SavedObjectClass: createSavedObjectClass(
{ {

View file

@ -9,7 +9,8 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { I18nStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { getI18n, getTheme } from '../kibana_services';
/** /**
* Represents the result of trying to persist the saved object. * Represents the result of trying to persist the saved object.
@ -31,7 +32,6 @@ interface MinimalSaveModalProps {
export function showSaveModal( export function showSaveModal(
saveModal: React.ReactElement<MinimalSaveModalProps>, saveModal: React.ReactElement<MinimalSaveModalProps>,
I18nContext: I18nStart['Context'],
Wrapper?: React.FC Wrapper?: React.FC
) { ) {
const container = document.createElement('div'); const container = document.createElement('div');
@ -57,13 +57,11 @@ export function showSaveModal(
onClose: closeModal, onClose: closeModal,
}); });
const wrappedElement = Wrapper ? ( const I18nContext = getI18n().Context;
<I18nContext> ReactDOM.render(
<Wrapper>{element}</Wrapper> <KibanaThemeProvider theme$={getTheme().theme$}>
</I18nContext> <I18nContext>{Wrapper ? <Wrapper>{element}</Wrapper> : element}</I18nContext>
) : ( </KibanaThemeProvider>,
<I18nContext>{element}</I18nContext> container
); );
ReactDOM.render(wrappedElement, container);
} }

View file

@ -15,7 +15,6 @@ import { parse } from 'query-string';
import { Capabilities } from '@kbn/core/public'; import { Capabilities } from '@kbn/core/public';
import { TopNavMenuData } from '@kbn/navigation-plugin/public'; import { TopNavMenuData } from '@kbn/navigation-plugin/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { import {
showSaveModal, showSaveModal,
SavedObjectSaveModalOrigin, SavedObjectSaveModalOrigin,
@ -598,18 +597,7 @@ export const getTopNavConfig = (
); );
} }
const WrapperComponent = ({ children }: { children?: React.ReactNode }) => { showSaveModal(saveModal, presentationUtil.ContextProvider);
const ContextProvider = !originatingApp
? presentationUtil.ContextProvider
: React.Fragment;
return (
<KibanaThemeProvider theme$={theme.theme$}>
<ContextProvider>{children}</ContextProvider>
</KibanaThemeProvider>
);
};
showSaveModal(saveModal, I18nContext, WrapperComponent);
}, },
}, },
] ]

View file

@ -10,7 +10,6 @@ import { I18nProvider } from '@kbn/i18n-react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { showSaveModal } from '@kbn/saved-objects-plugin/public';
import { Workspace } from '../types'; import { Workspace } from '../types';
import { createGraphStore } from '../state_management'; import { createGraphStore } from '../state_management';
import { createWorkspace } from '../services/workspace/graph_client_workspace'; import { createWorkspace } from '../services/workspace/graph_client_workspace';
@ -108,12 +107,10 @@ export const WorkspaceRoute = ({
http: coreStart.http, http: coreStart.http,
overlays: coreStart.overlays, overlays: coreStart.overlays,
savedObjectsClient, savedObjectsClient,
showSaveModal,
savePolicy: graphSavePolicy, savePolicy: graphSavePolicy,
changeUrl: (newUrl) => history.push(newUrl), changeUrl: (newUrl) => history.push(newUrl),
notifyReact: () => setRenderCounter((cur) => cur + 1), notifyReact: () => setRenderCounter((cur) => cur + 1),
chrome, chrome,
I18nContext: coreStart.i18n.Context,
handleSearchQueryError, handleSearchQueryError,
}) })
); );

View file

@ -5,9 +5,10 @@
* 2.0. * 2.0.
*/ */
import React, { ReactElement } from 'react'; import React from 'react';
import { I18nStart, OverlayStart, SavedObjectsClientContract } from '@kbn/core/public'; import { OverlayStart, SavedObjectsClientContract } from '@kbn/core/public';
import { SaveResult } from '@kbn/saved-objects-plugin/public'; import { SaveResult } from '@kbn/saved-objects-plugin/public';
import { showSaveModal } from '@kbn/saved-objects-plugin/public';
import { GraphWorkspaceSavedObject, GraphSavePolicy } from '../types'; import { GraphWorkspaceSavedObject, GraphSavePolicy } from '../types';
import { SaveModal, OnSaveGraphProps } from '../components/save_modal'; import { SaveModal, OnSaveGraphProps } from '../components/save_modal';
@ -31,16 +32,12 @@ export function openSaveModal({
hasData, hasData,
workspace, workspace,
saveWorkspace, saveWorkspace,
showSaveModal,
I18nContext,
services, services,
}: { }: {
savePolicy: GraphSavePolicy; savePolicy: GraphSavePolicy;
hasData: boolean; hasData: boolean;
workspace: GraphWorkspaceSavedObject; workspace: GraphWorkspaceSavedObject;
saveWorkspace: SaveWorkspaceHandler; saveWorkspace: SaveWorkspaceHandler;
showSaveModal: (el: ReactElement, I18nContext: I18nStart['Context']) => void;
I18nContext: I18nStart['Context'];
services: SaveWorkspaceServices; services: SaveWorkspaceServices;
}) { }) {
const currentTitle = workspace.title; const currentTitle = workspace.title;
@ -79,7 +76,6 @@ export function openSaveModal({
title={workspace.title} title={workspace.title}
description={workspace.description} description={workspace.description}
showCopyOnSave={Boolean(workspace.id)} showCopyOnSave={Boolean(workspace.id)}
/>, />
I18nContext
); );
} }

View file

@ -66,9 +66,6 @@ export function createMockGraphStore({
return { id: '123', title: 'test-pattern' } as unknown as DataView; return { id: '123', title: 'test-pattern' } as unknown as DataView;
}), }),
}, },
I18nContext: jest
.fn()
.mockImplementation(({ children }: { children: React.ReactNode }) => children),
notifications: { notifications: {
toasts: { toasts: {
addDanger: jest.fn(), addDanger: jest.fn(),
@ -78,7 +75,6 @@ export function createMockGraphStore({
http: {} as HttpStart, http: {} as HttpStart,
notifyReact: jest.fn(), notifyReact: jest.fn(),
savePolicy: 'configAndData', savePolicy: 'configAndData',
showSaveModal: jest.fn(),
overlays: { overlays: {
openModal: jest.fn(), openModal: jest.fn(),
} as unknown as OverlayStart, } as unknown as OverlayStart,

View file

@ -229,9 +229,7 @@ function showModal(
savePolicy: deps.savePolicy, savePolicy: deps.savePolicy,
hasData: workspace.nodes.length > 0 || workspace.blocklistedNodes.length > 0, hasData: workspace.nodes.length > 0 || workspace.blocklistedNodes.length > 0,
workspace: savedWorkspace, workspace: savedWorkspace,
showSaveModal: deps.showSaveModal,
saveWorkspace: saveWorkspaceHandler, saveWorkspace: saveWorkspaceHandler,
I18nContext: deps.I18nContext,
services: { services: {
savedObjectsClient: deps.savedObjectsClient, savedObjectsClient: deps.savedObjectsClient,
overlays: deps.overlays, overlays: deps.overlays,

View file

@ -7,9 +7,8 @@
import createSagaMiddleware, { SagaMiddleware } from 'redux-saga'; import createSagaMiddleware, { SagaMiddleware } from 'redux-saga';
import { combineReducers, createStore, Store, AnyAction, Dispatch, applyMiddleware } from 'redux'; import { combineReducers, createStore, Store, AnyAction, Dispatch, applyMiddleware } from 'redux';
import { ChromeStart, I18nStart, OverlayStart, SavedObjectsClientContract } from '@kbn/core/public'; import { ChromeStart, OverlayStart, SavedObjectsClientContract } from '@kbn/core/public';
import { CoreStart } from '@kbn/core/public'; import { CoreStart } from '@kbn/core/public';
import { ReactElement } from 'react';
import { import {
fieldsReducer, fieldsReducer,
FieldsState, FieldsState,
@ -48,12 +47,10 @@ export interface GraphStoreDependencies {
http: CoreStart['http']; http: CoreStart['http'];
overlays: OverlayStart; overlays: OverlayStart;
savedObjectsClient: SavedObjectsClientContract; savedObjectsClient: SavedObjectsClientContract;
showSaveModal: (el: ReactElement, I18nContext: I18nStart['Context']) => void;
savePolicy: GraphSavePolicy; savePolicy: GraphSavePolicy;
changeUrl: (newUrl: string) => void; changeUrl: (newUrl: string) => void;
notifyReact: () => void; notifyReact: () => void;
chrome: ChromeStart; chrome: ChromeStart;
I18nContext: I18nStart['Context'];
basePath: string; basePath: string;
handleSearchQueryError: (err: Error | string) => void; handleSearchQueryError: (err: Error | string) => void;
} }

View file

@ -90,7 +90,7 @@ const attributeServiceMockFromSavedVis = (document: Document): LensAttributeServ
LensByValueInput, LensByValueInput,
LensByReferenceInput, LensByReferenceInput,
LensUnwrapMetaInfo LensUnwrapMetaInfo
>('lens', jest.fn(), core.i18n.Context, core.notifications.toasts, options); >('lens', core.notifications.toasts, options);
service.unwrapAttributes = jest.fn((input: LensByValueInput | LensByReferenceInput) => { service.unwrapAttributes = jest.fn((input: LensByValueInput | LensByReferenceInput) => {
return Promise.resolve({ return Promise.resolve({
attributes: { attributes: {

View file

@ -24,7 +24,6 @@ import {
getMapsCapabilities, getMapsCapabilities,
getIsAllowByValueEmbeddables, getIsAllowByValueEmbeddables,
getInspector, getInspector,
getCoreI18n,
getSavedObjectsClient, getSavedObjectsClient,
getCoreOverlays, getCoreOverlays,
getSavedObjectsTagging, getSavedObjectsTagging,
@ -249,7 +248,7 @@ export function getTopNavConfig({
); );
} }
showSaveModal(saveModal, getCoreI18n().Context, PresentationUtilContext); showSaveModal(saveModal, PresentationUtilContext);
}, },
}); });