Skip to content

Commit

Permalink
[Console] Text Objects (elastic#52402) (elastic#55668)
Browse files Browse the repository at this point in the history
* WiP

* Initial commit for localStorage -> SavedObjects, untested [skip ci]

* Restore text_object model and mappings

* Fix use of mappings

* WIP on anonymous saved objects and user ids

* refactor: remove saved objects entirely and rescope changes to only introducing text objects

* Update use of .findAll after changes

* Where did that come from?

* Slight refactor to generic names
Removed unused files (mappings.json, README.md)
Documented object storage client interface
Failure to restore previous state does not block editor
Updated copy

* Rename exported variable

* Document TextObject interface

* Rename EuiLoadingContent10 -> EditorContentSpinner

* Update src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx

Co-Authored-By: Rory Hunter <[email protected]>

* Update src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx

Co-Authored-By: Rory Hunter <[email protected]>

* Update src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx

Co-Authored-By: Rory Hunter <[email protected]>

* Update src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx

Co-Authored-By: Rory Hunter <[email protected]>

* Update src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx

Co-Authored-By: Rory Hunter <[email protected]>

* Added FunctionComponent imports

* Implement preventing editting console text if init failed

* Simply console boot states for now

* This reverts commit 07b7bfb.

* Fix eslint issue
Update copy

Co-authored-by: Rory Hunter <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>

Co-authored-by: Rory Hunter <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
3 people authored Jan 23, 2020
1 parent 1d14b68 commit 00c346b
Show file tree
Hide file tree
Showing 24 changed files with 565 additions and 25 deletions.
48 changes: 48 additions & 0 deletions src/legacy/core_plugins/console/common/text_object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export const textObjectTypeName = 'text-object';

/**
* Describes the shape of persisted objects that contain information about the current text in the
* text editor.
*/
export interface TextObject {
/**
* An ID that uniquely identifies this object.
*/
id: string;

/**
* UNIX timestamp of when the object was created.
*/
createdAt: number;

/**
* UNIX timestamp of when the object was last updated.
*/
updatedAt: number;

/**
* Text value input by the user.
*
* Used to re-populate a text editor buffer.
*/
text: string;
}
49 changes: 49 additions & 0 deletions src/legacy/core_plugins/console/common/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { TextObject } from './text_object';

export interface IdObject {
id: string;
}

export interface ObjectStorage<O extends IdObject> {
/**
* Creates a new object in the underlying persistance layer.
*
* @remarks Does not accept an ID, a new ID is generated and returned with the newly created object.
*/
create(obj: Omit<O, 'id'>): Promise<O>;

/**
* This method should update specific object in the persistance layer.
*/
update(obj: O): Promise<void>;

/**
* A function that will return all of the objects in the persistance layer.
*
* @remarks Unless an error is thrown this function should always return an array (empty if there are not objects present).
*/
findAll(): Promise<O[]>;
}

export interface ObjectStorageClient {
text: ObjectStorage<TextObject>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, { FunctionComponent } from 'react';
import { EuiLoadingContent, EuiPageContent } from '@elastic/eui';

export const EditorContentSpinner: FunctionComponent = () => {
return (
<EuiPageContent className="conApp__editor__spinner">
<EuiLoadingContent lines={10} />
</EuiPageContent>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
*/

export * from './split_panel';
export { SomethingWentWrongCallout } from './something_went_wrong_callout';
export { TopNavMenuItem, TopNavMenu } from './top_nav_menu';
export { ConsoleMenu } from './console_menu';
export { WelcomePanel } from './welcome_panel';
export { AutocompleteOptions, DevToolsSettingsModal } from './settings_modal';
export { HelpPanel } from './help_panel';
export { EditorContentSpinner } from './editor_content_spinner';
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, { FunctionComponent, useEffect } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiCallOut, EuiText, EuiButton, EuiSpacer } from '@elastic/eui';

interface Props {
error: Error;
onButtonClick: () => void;
}

export const SomethingWentWrongCallout: FunctionComponent<Props> = ({ error, onButtonClick }) => {
useEffect(() => {
// eslint-disable-next-line no-console
console.error(error);
}, [error]);

return (
<EuiCallOut
iconType="alert"
color="danger"
title={i18n.translate('console.loadingError.title', {
defaultMessage: 'Cannot load Console',
})}
>
<EuiText>
<p>
<FormattedMessage
id="console.loadingError.message"
defaultMessage="Try reloading to get the latest data."
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<EuiButton color="danger" onClick={() => onButtonClick()}>
<FormattedMessage id="console.loadingError.buttonLabel" defaultMessage="Reload Console" />
</EuiButton>
</EuiCallOut>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import React from 'react';
import React, { FunctionComponent } from 'react';
import { EuiTabs, EuiTab } from '@elastic/eui';

export interface TopNavMenuItem {
Expand All @@ -29,19 +29,26 @@ export interface TopNavMenuItem {
}

interface Props {
disabled?: boolean;
items: TopNavMenuItem[];
}

export function TopNavMenu({ items }: Props) {
export const TopNavMenu: FunctionComponent<Props> = ({ items, disabled }) => {
return (
<EuiTabs size="s">
{items.map((item, idx) => {
return (
<EuiTab key={idx} onClick={item.onClick} title={item.label} data-test-subj={item.testId}>
<EuiTab
key={idx}
disabled={disabled}
onClick={item.onClick}
title={item.label}
data-test-subj={item.testId}
>
{item.label}
</EuiTab>
);
})}
</EuiTabs>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,26 @@
import React, { useCallback } from 'react';
import { debounce } from 'lodash';

import { EditorContentSpinner } from '../../components';
import { Panel, PanelsContainer } from '../../components/split_panel';
import { Editor as EditorUI, EditorOutput } from './legacy/console_editor';
import { StorageKeys } from '../../../services';
import { useServicesContext } from '../../contexts';
import { useEditorReadContext, useServicesContext } from '../../contexts';

const INITIAL_PANEL_WIDTH = 50;
const PANEL_MIN_WIDTH = '100px';

export const Editor = () => {
interface Props {
loading: boolean;
}

export const Editor = ({ loading }: Props) => {
const {
services: { storage },
} = useServicesContext();

const { currentTextObject } = useEditorReadContext();

const [firstPanelWidth, secondPanelWidth] = storage.get(StorageKeys.WIDTH, [
INITIAL_PANEL_WIDTH,
INITIAL_PANEL_WIDTH,
Expand All @@ -45,19 +52,25 @@ export const Editor = () => {
[]
);

if (!currentTextObject) return null;

return (
<PanelsContainer onPanelWidthChange={onPanelWidthChange} resizerClassName="conApp__resizer">
<Panel
style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }}
initialWidth={firstPanelWidth}
>
<EditorUI />
{loading ? (
<EditorContentSpinner />
) : (
<EditorUI initialTextValue={currentTextObject.text} />
)}
</Panel>
<Panel
style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }}
initialWidth={secondPanelWidth}
>
<EditorOutput />
{loading ? <EditorContentSpinner /> : <EditorOutput />}
</Panel>
</PanelsContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => {
<ServicesContextProvider value={mockedAppContextValue}>
<RequestContextProvider>
<EditorContextProvider settings={{} as any}>
<Editor />
<Editor initialTextValue="" />
</EditorContextProvider>
</RequestContextProvider>
</ServicesContextProvider>
Expand All @@ -72,6 +72,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => {
updateCurrentState: jest.fn(),
} as any,
notifications: notificationServiceMock.createSetupContract(),
objectStorageClient: {} as any,
},
docLinkVersion: 'NA',
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,22 @@ import { autoIndent, getDocumentation } from '../console_menu_actions';
import { registerCommands } from './keyboard_shortcuts';
import { applyCurrentSettings } from './apply_editor_settings';

import { useSendCurrentRequestToES, useSetInputEditor } from '../../../../hooks';
import {
useSendCurrentRequestToES,
useSetInputEditor,
useSaveCurrentTextObject,
} from '../../../../hooks';

import * as senseEditor from '../../../../models/sense_editor';
// @ts-ignore
import mappings from '../../../../../lib/mappings/mappings';

import { subscribeResizeChecker } from '../subscribe_console_resize_checker';

export interface EditorProps {
initialTextValue: string;
}

const abs: CSSProperties = {
position: 'absolute',
top: '0',
Expand All @@ -58,7 +66,7 @@ const DEFAULT_INPUT_VALUE = `GET _search
}
}`;

function EditorUI() {
function EditorUI({ initialTextValue }: EditorProps) {
const {
services: { history, notifications },
docLinkVersion,
Expand All @@ -68,6 +76,7 @@ function EditorUI() {
const { settings } = useEditorReadContext();
const setInputEditor = useSetInputEditor();
const sendCurrentRequestToES = useSendCurrentRequestToES();
const saveCurrentTextObject = useSaveCurrentTextObject();

const editorRef = useRef<HTMLDivElement | null>(null);
const editorInstanceRef = useRef<senseEditor.SenseEditor | null>(null);
Expand Down Expand Up @@ -132,10 +141,7 @@ function EditorUI() {
if (initialQueryParams.load_from) {
loadBufferFromRemote(initialQueryParams.load_from);
} else {
const { content: text } = history.getSavedEditorState() || {
content: DEFAULT_INPUT_VALUE,
};
editor.update(text);
editor.update(initialTextValue || DEFAULT_INPUT_VALUE);
}

function setupAutosave() {
Expand All @@ -153,7 +159,7 @@ function EditorUI() {
function saveCurrentState() {
try {
const content = editor.getCoreEditor().getValue();
history.updateCurrentState(content);
saveCurrentTextObject(content);
} catch (e) {
// Ignoring saving error
}
Expand All @@ -172,7 +178,7 @@ function EditorUI() {
mappings.clearSubscriptions();
window.removeEventListener('hashchange', onHashChange);
};
}, [history, setInputEditor]);
}, [saveCurrentTextObject, initialTextValue, history, setInputEditor]);

useEffect(() => {
const { current: editor } = editorInstanceRef;
Expand Down
Loading

0 comments on commit 00c346b

Please sign in to comment.