Skip to content

Commit

Permalink
Provide React render services for Console OverlayText
Browse files Browse the repository at this point in the history
  • Loading branch information
tsullivan committed May 29, 2024
1 parent a177bd8 commit 40228e2
Show file tree
Hide file tree
Showing 15 changed files with 88 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
storage,
},
docLinkVersion,
...startServices
} = useServicesContext();

const { settings } = useEditorReadContext();
Expand All @@ -80,7 +81,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
const editorInstanceRef = useRef<senseEditor.SenseEditor | null>(null);

const [textArea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
useUIAceKeyboardMode(textArea, settings.isAccessibilityOverlayEnabled);
useUIAceKeyboardMode(textArea, startServices, settings.isAccessibilityOverlayEnabled);

const openDocumentation = useCallback(async () => {
const documentation = await getDocumentation(editorInstanceRef.current!, docLinkVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export class MonacoEditorActionsProvider {
public async sendRequests(dispatch: Dispatch<Actions>, context: ContextValue): Promise<void> {
const {
services: { notifications, trackUiMetric, http, settings, history, autocompleteInfo },
startServices,
...startServices
} = context;
const { toasts } = notifications;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ import {
getStorage,
} from '../../../services';
import { createUsageTracker } from '../../../services/tracker';
import { MetricsTracker, EmbeddableConsoleDependencies } from '../../../types';
import {
MetricsTracker,
EmbeddableConsoleDependencies,
ConsoleStartServices,
} from '../../../types';

import { createApi, createEsHostService } from '../../lib';
import { EsHostService } from '../../lib/es_host_service';
Expand All @@ -47,7 +51,7 @@ import {
import { Main } from '../main';
import { EditorContentSpinner } from '../../components';

interface ConsoleDependencies {
interface ConsoleDependencies extends ConsoleStartServices {
autocompleteInfo: AutocompleteInfo;
docLinks: DocLinksStart['links'];
docLinkVersion: string;
Expand All @@ -70,7 +74,7 @@ const loadDependencies = async (
docLinks: { DOC_LINK_VERSION, links },
http,
notifications,
theme: { theme$ },
...startServices
} = core;
const trackUiMetric = createUsageTracker(usageCollection);
trackUiMetric.load('opened_embedded_app');
Expand All @@ -86,6 +90,7 @@ const loadDependencies = async (

autocompleteInfo.mapping.setup(http, settings);
return {
...startServices,
autocompleteInfo,
docLinks: links,
docLinkVersion: DOC_LINK_VERSION,
Expand All @@ -96,7 +101,7 @@ const loadDependencies = async (
objectStorageClient,
settings,
storage,
theme$,
theme$: startServices.theme.theme$,
trackUiMetric,
};
};
Expand All @@ -113,8 +118,6 @@ interface ConsoleWrapperProps
export const ConsoleWrapper = (props: ConsoleWrapperProps) => {
const [dependencies, setDependencies] = useState<ConsoleDependencies | null>(null);
const { core, usageCollection, onKeyDown, isMonacoEnabled, isOpen } = props;
const { analytics, i18n, theme } = core;
const startServices = { analytics, i18n, theme };

useEffect(() => {
if (dependencies === null && isOpen) {
Expand Down Expand Up @@ -144,11 +147,13 @@ export const ConsoleWrapper = (props: ConsoleWrapperProps) => {
settings,
storage,
trackUiMetric,
...startServices
} = dependencies;
return (
<KibanaRenderContextProvider {...core}>
<ServicesContextProvider
value={{
...startServices,
docLinkVersion,
docLinks,
services: {
Expand All @@ -165,7 +170,6 @@ export const ConsoleWrapper = (props: ConsoleWrapperProps) => {
config: {
isMonacoEnabled,
},
startServices,
}}
>
<RequestContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const serviceContextMock = {
const esHostService = createEsHostService({ api });
(storage.keys as jest.Mock).mockImplementation(() => []);
return {
...coreStart,
services: {
trackUiMetric: { count: () => {}, load: () => {} },
storage,
Expand All @@ -48,7 +49,6 @@ export const serviceContextMock = {
config: {
isMonacoEnabled: false,
},
startServices: coreStart,
};
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ interface ContextServices {
autocompleteInfo: AutocompleteInfo;
}

export interface ContextValue {
export interface ContextValue extends ConsoleStartServices {
services: ContextServices;
docLinkVersion: string;
docLinks: DocLinksStart['links'];
config: {
isMonacoEnabled: boolean;
};
startServices: ConsoleStartServices;
}

interface ContextProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { SenseEditor } from '../../models';
export const useSendCurrentRequest = () => {
const {
services: { history, settings, notifications, trackUiMetric, http, autocompleteInfo, storage },
startServices,
...startServices
} = useServicesContext();

const dispatch = useRequestActionContext();
Expand Down
7 changes: 3 additions & 4 deletions src/plugins/console/public/application/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { ServicesContextProvider, EditorContextProvider, RequestContextProvider
import { createApi, createEsHostService } from './lib';
import { ConsoleStartServices } from '../types';

export interface BootDependencies {
export interface BootDependencies extends ConsoleStartServices {
http: HttpSetup;
docLinkVersion: string;
notifications: NotificationsSetup;
Expand All @@ -36,7 +36,6 @@ export interface BootDependencies {
docLinks: DocLinksStart['links'];
autocompleteInfo: AutocompleteInfo;
isMonacoEnabled: boolean;
startServices: ConsoleStartServices;
}

export async function renderApp({
Expand All @@ -48,7 +47,7 @@ export async function renderApp({
docLinks,
autocompleteInfo,
isMonacoEnabled,
startServices,
...startServices
}: BootDependencies) {
const trackUiMetric = createUsageTracker(usageCollection);
trackUiMetric.load('opened_app');
Expand All @@ -71,6 +70,7 @@ export async function renderApp({
<KibanaRenderContextProvider {...startServices}>
<ServicesContextProvider
value={{
...startServices,
docLinkVersion,
docLinks,
services: {
Expand All @@ -87,7 +87,6 @@ export async function renderApp({
config: {
isMonacoEnabled,
},
startServices,
}}
>
<RequestContextProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/console/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class ConsoleUIPlugin
const { renderApp } = await import('./application');

return renderApp({
...startServices,
http,
docLinkVersion: DOC_LINK_VERSION,
docLinks: links,
Expand All @@ -98,7 +99,6 @@ export class ConsoleUIPlugin
element,
autocompleteInfo: this.autocompleteInfo,
isMonacoEnabled,
startServices,
});
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,32 @@
import React, { useEffect, useRef } from 'react';
import * as ReactDOM from 'react-dom';
import { keys, EuiText } from '@elastic/eui';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';

import './_ui_ace_keyboard_mode.scss';
import type { AnalyticsServiceStart, I18nStart, ThemeServiceStart } from '@kbn/core/public';

const OverlayText = () => (
const OverlayText = (startServices: StartServices) => (
// The point of this element is for accessibility purposes, so ignore eslint error
// in this case
//
<>
<KibanaRenderContextProvider {...startServices}>
<EuiText size="s" data-test-subj="a11y-overlay">
Press Enter to start editing.
</EuiText>
<EuiText size="s">When you&rsquo;re done, press Escape to stop editing.</EuiText>
</>
</KibanaRenderContextProvider>
);

interface StartServices {
analytics: Pick<AnalyticsServiceStart, 'reportEvent'>;
i18n: I18nStart;
theme: Pick<ThemeServiceStart, 'theme$'>;
}

export function useUIAceKeyboardMode(
aceTextAreaElement: HTMLTextAreaElement | null,
startServices: StartServices,
isAccessibilityOverlayEnabled: boolean = true
) {
const overlayMountNode = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -75,7 +84,7 @@ export function useUIAceKeyboardMode(
overlayMountNode.current.addEventListener('focus', enableOverlay);
overlayMountNode.current.addEventListener('keydown', onDismissOverlay);

ReactDOM.render(<OverlayText />, overlayMountNode.current);
ReactDOM.render(<OverlayText {...startServices} />, overlayMountNode.current);

aceTextAreaElement.parentElement!.insertBefore(overlayMountNode.current, aceTextAreaElement);
aceTextAreaElement.setAttribute('tabindex', '-1');
Expand All @@ -99,5 +108,5 @@ export function useUIAceKeyboardMode(
}
}
};
}, [aceTextAreaElement, isAccessibilityOverlayEnabled]);
}, [aceTextAreaElement, startServices, isAccessibilityOverlayEnabled]);
}
1 change: 1 addition & 0 deletions src/plugins/es_ui_shared/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@kbn/storybook",
"@kbn/shared-ux-link-redirect-app",
"@kbn/code-editor",
"@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
import 'brace';
import 'brace/mode/json';

import { coreMock } from '@kbn/core/public/mocks';
import { registerTestBed } from '@kbn/test-jest-helpers';
import { Editor, Props } from './editor';

const coreStart = coreMock.createStart();

describe('Editor Component', () => {
it('renders', async () => {
const props: Props = {
...coreStart,
initialValue: '',
licenseEnabled: true,
onEditorReady: (e: any) => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import { EuiScreenReaderOnly } from '@elastic/eui';
import { Editor as AceEditor } from 'brace';

import { SearchProfilerStartServices } from '../../../../types';
import { ace } from '../../../../shared_imports';
import { initializeEditor } from './init_editor';

Expand All @@ -19,7 +20,7 @@ type EditorShim = ReturnType<typeof createEditorShim>;

export type EditorInstance = EditorShim;

export interface Props {
export interface Props extends SearchProfilerStartServices {
licenseEnabled: boolean;
initialValue: string;
onEditorReady: (editor: EditorShim) => void;
Expand All @@ -38,43 +39,45 @@ const createEditorShim = (aceEditor: AceEditor) => {

const EDITOR_INPUT_ID = 'SearchProfilerTextArea';

export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Props) => {
const containerRef = useRef<HTMLDivElement>(null as any);
const editorInstanceRef = useRef<AceEditor>(null as any);
export const Editor = memo(
({ licenseEnabled, initialValue, onEditorReady, ...startServices }: Props) => {
const containerRef = useRef<HTMLDivElement>(null as any);
const editorInstanceRef = useRef<AceEditor>(null as any);

const [textArea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
const [textArea, setTextArea] = useState<HTMLTextAreaElement | null>(null);

useUIAceKeyboardMode(textArea);
useUIAceKeyboardMode(textArea, startServices);

useEffect(() => {
const divEl = containerRef.current;
editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled });
editorInstanceRef.current.setValue(initialValue, 1);
const textarea = divEl.querySelector<HTMLTextAreaElement>('textarea');
if (textarea) {
textarea.setAttribute('id', EDITOR_INPUT_ID);
}
setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null);
useEffect(() => {
const divEl = containerRef.current;
editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled });
editorInstanceRef.current.setValue(initialValue, 1);
const textarea = divEl.querySelector<HTMLTextAreaElement>('textarea');
if (textarea) {
textarea.setAttribute('id', EDITOR_INPUT_ID);
}
setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null);

onEditorReady(createEditorShim(editorInstanceRef.current));
onEditorReady(createEditorShim(editorInstanceRef.current));

return () => {
if (editorInstanceRef.current) {
editorInstanceRef.current.destroy();
}
};
}, [initialValue, onEditorReady, licenseEnabled]);
return () => {
if (editorInstanceRef.current) {
editorInstanceRef.current.destroy();
}
};
}, [initialValue, onEditorReady, licenseEnabled]);

return (
<>
<EuiScreenReaderOnly>
<label htmlFor={EDITOR_INPUT_ID}>
{i18n.translate('xpack.searchProfiler.editorElementLabel', {
defaultMessage: 'Dev Tools Search Profiler editor',
})}
</label>
</EuiScreenReaderOnly>
<div data-test-subj="searchProfilerEditor" ref={containerRef} />
</>
);
});
return (
<>
<EuiScreenReaderOnly>
<label htmlFor={EDITOR_INPUT_ID}>
{i18n.translate('xpack.searchProfiler.editorElementLabel', {
defaultMessage: 'Dev Tools Search Profiler editor',
})}
</label>
</EuiScreenReaderOnly>
<div data-test-subj="searchProfilerEditor" ref={containerRef} />
</>
);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const ProfileQueryEditor = memo(() => {

const dispatch = useProfilerActionContext();

const { getLicenseStatus, notifications, location } = useAppContext();
const { getLicenseStatus, notifications, location, ...startServices } = useAppContext();

const queryParams = new URLSearchParams(location.search);
const indexName = queryParams.get('index');
Expand Down Expand Up @@ -119,6 +119,7 @@ export const ProfileQueryEditor = memo(() => {
onEditorReady={onEditorReady}
licenseEnabled={licenseEnabled}
initialValue={searchProfilerQuery ? searchProfilerQuery : INITIAL_EDITOR_VALUE}
{...startServices}
/>
</EuiFlexItem>

Expand Down
Loading

0 comments on commit 40228e2

Please sign in to comment.