mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Console to NP ready (#43346)
* General structure of Public w/ legacy brace + autocomplete 🤔 Refactor Resizer functionality (panel component) Refactor Play Button Refactor Auto-completion Refactor Docs opener Refactor Storage * First refactor of kbn ace keyboard mode to TS+React * clean up unused props * console_menu.js -> console_menu.tsx * Remove unused file from quarantine and added fixed ui ace keyboard mode react hook * - Refactored history and storage to app-wide services - Pre-emptive changes to tests - sense-history -> HistoryList - removed unused kbn top nav v2 component * A lot of cleanup, re-introduced editor resize checker, re-introduced history viewer as TS+React. `history` still needs refactoring. * First iteration of tap nav menu, with history toggle working * Lots of fixes Also moved over and integrated remaining three react components * Moved a lot of files around again, tidied up NP set up * Replace angular directive * Remove used code * Re-order imports and move all ace dependencies to same location * Remove more unused code * Revise quarantined setup mocks * Don't suggest 'undefined' or other null-like values in autocomplete * Clean up api_server folder * Re-add missing style * Updated karma spec mock * Fix editors cutting of at bottom of screen * Refactor console editors into single components Refactor a lot of business logic to main.tsx container Minor renaming of variables for better readability * Updated use of contexts with better error message Fixed broken render sync cycles (using useCallback) Fixed Main container render cycle (added missing deps to useEffect) Fixed default input and removed auto indent from being called on init for already formatted text * Updated test mocks * Update to be more in line with NP conventions https://github.com/elastic/kibana/blob/master/src/core/CONVENTIONS.md * Update console history when making new requests Fixed spacing between editor and console history Moved registration of keyboard commands to TS Fixed setup_mocks.js after renaming app to application * Clean up git merge conflict artifact * Use updated NP interfaces * More typings fixed after updating local project dependencies * Removing some dependencies on KUI and font awesome from legacy editor * Fix clear history not re-rendering Refactor prop name to be more descriptive * Simplify split_panel and add tests * Fix accessibility tabbing behaviour for ace editor * Refactor ConsoleEditor into two separate components Remove unused changeCursor code Remove unused textArea ref Use default lodash debounce (remove unnecessary arg) * Major a11y fix when tabbing Major fix for ace in IE11 and Edge browsers * Update comment
This commit is contained in:
parent
19837fea23
commit
5c2d0cae9c
292 changed files with 2484 additions and 1263 deletions
|
@ -39,7 +39,7 @@ class MyPlugin {
|
|||
id: 'my-app',
|
||||
title: 'My Application',
|
||||
async mount(context, params) {
|
||||
const { renderApp } = await import('./applcation');
|
||||
const { renderApp } = await import('./application');
|
||||
return renderApp(context, params);
|
||||
}
|
||||
});
|
||||
|
@ -267,7 +267,7 @@ export class MyPlugin {
|
|||
application.register({
|
||||
id: 'my-app',
|
||||
async mount(context, params) {
|
||||
const { renderApp } = await import('./applcation');
|
||||
const { renderApp } = await import('./application');
|
||||
return renderApp(context, params);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,18 +23,17 @@ import { resolve, join, sep } from 'path';
|
|||
import url from 'url';
|
||||
import { has, isEmpty, head, pick } from 'lodash';
|
||||
|
||||
import { resolveApi } from './api_server/server';
|
||||
import { addExtensionSpecFilePath } from './api_server/spec';
|
||||
// @ts-ignore
|
||||
import { resolveApi } from './server/api_server/server';
|
||||
// @ts-ignore
|
||||
import { addExtensionSpecFilePath } from './server/api_server/spec';
|
||||
// @ts-ignore
|
||||
import { setHeaders } from './server/set_headers';
|
||||
// @ts-ignore
|
||||
import { ProxyConfigCollection, getElasticsearchProxyConfig, createProxyRoute } from './server';
|
||||
|
||||
import {
|
||||
ProxyConfigCollection,
|
||||
getElasticsearchProxyConfig,
|
||||
createProxyRoute
|
||||
} from './server';
|
||||
|
||||
function filterHeaders(originalHeaders, headersToKeep) {
|
||||
const normalizeHeader = function (header) {
|
||||
function filterHeaders(originalHeaders: any, headersToKeep: any) {
|
||||
const normalizeHeader = function(header: any) {
|
||||
if (!header) {
|
||||
return '';
|
||||
}
|
||||
|
@ -48,51 +47,64 @@ function filterHeaders(originalHeaders, headersToKeep) {
|
|||
return pick(originalHeaders, headersToKeepNormalized);
|
||||
}
|
||||
|
||||
export default function (kibana) {
|
||||
// eslint-disable-next-line
|
||||
export default function(kibana: any) {
|
||||
const modules = resolve(__dirname, 'public/webpackShims/');
|
||||
const src = resolve(__dirname, 'public/src/');
|
||||
const quarantinedSrc = resolve(__dirname, 'public/quarantined/src/');
|
||||
const npSrc = resolve(__dirname, 'np_ready/public');
|
||||
|
||||
let defaultVars;
|
||||
const apps = [];
|
||||
let defaultVars: any;
|
||||
const apps: any[] = [];
|
||||
return new kibana.Plugin({
|
||||
id: 'console',
|
||||
require: ['elasticsearch'],
|
||||
|
||||
config: function (Joi) {
|
||||
config(Joi: any) {
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
proxyFilter: Joi.array().items(Joi.string()).single().default(['.*']),
|
||||
proxyFilter: Joi.array()
|
||||
.items(Joi.string())
|
||||
.single()
|
||||
.default(['.*']),
|
||||
ssl: Joi.object({
|
||||
verify: Joi.boolean(),
|
||||
}).default(),
|
||||
proxyConfig: Joi.array().items(
|
||||
Joi.object().keys({
|
||||
match: Joi.object().keys({
|
||||
protocol: Joi.string().default('*'),
|
||||
host: Joi.string().default('*'),
|
||||
port: Joi.string().default('*'),
|
||||
path: Joi.string().default('*')
|
||||
}),
|
||||
proxyConfig: Joi.array()
|
||||
.items(
|
||||
Joi.object().keys({
|
||||
match: Joi.object().keys({
|
||||
protocol: Joi.string().default('*'),
|
||||
host: Joi.string().default('*'),
|
||||
port: Joi.string().default('*'),
|
||||
path: Joi.string().default('*'),
|
||||
}),
|
||||
|
||||
timeout: Joi.number(),
|
||||
ssl: Joi.object().keys({
|
||||
verify: Joi.boolean(),
|
||||
ca: Joi.array().single().items(Joi.string()),
|
||||
cert: Joi.string(),
|
||||
key: Joi.string()
|
||||
}).default()
|
||||
})
|
||||
).default()
|
||||
timeout: Joi.number(),
|
||||
ssl: Joi.object()
|
||||
.keys({
|
||||
verify: Joi.boolean(),
|
||||
ca: Joi.array()
|
||||
.single()
|
||||
.items(Joi.string()),
|
||||
cert: Joi.string(),
|
||||
key: Joi.string(),
|
||||
})
|
||||
.default(),
|
||||
})
|
||||
)
|
||||
.default(),
|
||||
}).default();
|
||||
},
|
||||
|
||||
deprecations: function () {
|
||||
deprecations() {
|
||||
return [
|
||||
(settings, log) => {
|
||||
(settings: any, log: any) => {
|
||||
if (has(settings, 'proxyConfig')) {
|
||||
log('Config key "proxyConfig" is deprecated. Configuration can be inferred from the "elasticsearch" settings');
|
||||
log(
|
||||
'Config key "proxyConfig" is deprecated. Configuration can be inferred from the "elasticsearch" settings'
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
|
@ -105,16 +117,18 @@ export default function (kibana) {
|
|||
};
|
||||
},
|
||||
|
||||
async init(server, options) {
|
||||
async init(server: any, options: any) {
|
||||
server.expose('addExtensionSpecFilePath', addExtensionSpecFilePath);
|
||||
if (options.ssl && options.ssl.verify) {
|
||||
throw new Error('sense.ssl.verify is no longer supported.');
|
||||
}
|
||||
|
||||
const config = server.config();
|
||||
const legacyEsConfig = await server.newPlatform.setup.core.elasticsearch.legacy.config$.pipe(first()).toPromise();
|
||||
const legacyEsConfig = await server.newPlatform.setup.core.elasticsearch.legacy.config$
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const proxyConfigCollection = new ProxyConfigCollection(options.proxyConfig);
|
||||
const proxyPathFilters = options.proxyFilter.map(str => new RegExp(str));
|
||||
const proxyPathFilters = options.proxyFilter.map((str: string) => new RegExp(str));
|
||||
|
||||
defaultVars = {
|
||||
elasticsearchUrl: url.format(
|
||||
|
@ -122,54 +136,59 @@ export default function (kibana) {
|
|||
),
|
||||
};
|
||||
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: head(legacyEsConfig.hosts),
|
||||
pathFilters: proxyPathFilters,
|
||||
getConfigForReq(req, uri) {
|
||||
const filteredHeaders = filterHeaders(req.headers, legacyEsConfig.requestHeadersWhitelist);
|
||||
const headers = setHeaders(filteredHeaders, legacyEsConfig.customHeaders);
|
||||
server.route(
|
||||
createProxyRoute({
|
||||
baseUrl: head(legacyEsConfig.hosts),
|
||||
pathFilters: proxyPathFilters,
|
||||
getConfigForReq(req: any, uri: any) {
|
||||
const filteredHeaders = filterHeaders(
|
||||
req.headers,
|
||||
legacyEsConfig.requestHeadersWhitelist
|
||||
);
|
||||
const headers = setHeaders(filteredHeaders, legacyEsConfig.customHeaders);
|
||||
|
||||
if (!isEmpty(config.get('console.proxyConfig'))) {
|
||||
return {
|
||||
...proxyConfigCollection.configForUri(uri),
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
if (!isEmpty(config.get('console.proxyConfig'))) {
|
||||
return {
|
||||
...proxyConfigCollection.configForUri(uri),
|
||||
...getElasticsearchProxyConfig(legacyEsConfig),
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...getElasticsearchProxyConfig(legacyEsConfig),
|
||||
headers,
|
||||
};
|
||||
}
|
||||
}));
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
server.route({
|
||||
path: '/api/console/api_server',
|
||||
method: ['GET', 'POST'],
|
||||
handler: function (req, h) {
|
||||
handler(req: any, h: any) {
|
||||
const { sense_version: version, apis } = req.query;
|
||||
if (!apis) {
|
||||
throw Boom.badRequest('"apis" is a required param.');
|
||||
}
|
||||
|
||||
return resolveApi(version, apis.split(','), h);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
uiExports: {
|
||||
apps: apps,
|
||||
hacks: ['plugins/console/hacks/register'],
|
||||
devTools: ['plugins/console/console'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
apps,
|
||||
hacks: ['plugins/console/quarantined/hacks/register'],
|
||||
devTools: [`${npSrc}/legacy`],
|
||||
styleSheetPaths: resolve(__dirname, 'public/quarantined/index.scss'),
|
||||
|
||||
injectDefaultVars: () => defaultVars,
|
||||
|
||||
noParse: [
|
||||
join(modules, 'ace' + sep),
|
||||
join(modules, 'moment_src/moment' + sep),
|
||||
join(src, 'sense_editor/mode/worker.js')
|
||||
]
|
||||
}
|
||||
});
|
||||
join(quarantinedSrc, 'sense_editor/mode/worker.js'),
|
||||
],
|
||||
},
|
||||
} as any);
|
||||
}
|
6
src/legacy/core_plugins/console/np_ready/kibana.json
Normal file
6
src/legacy/core_plugins/console/np_ready/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "console",
|
||||
"version": "kibana",
|
||||
"server": true,
|
||||
"ui": true
|
||||
}
|
|
@ -17,23 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiContextMenuPanel,
|
||||
EuiContextMenuItem,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export class ConsoleMenu extends Component {
|
||||
constructor(props) {
|
||||
interface Props {
|
||||
getCurl: (cb: (text: string) => void) => void;
|
||||
getDocumentation: () => Promise<string | null>;
|
||||
autoIndent: (ev: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isPopoverOpen: boolean;
|
||||
curlCode: string;
|
||||
}
|
||||
|
||||
export class ConsoleMenu extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -47,13 +49,13 @@ export class ConsoleMenu extends Component {
|
|||
this.props.getCurl(text => {
|
||||
this.setState({ curlCode: text });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
copyAsCurl() {
|
||||
this.copyText(this.state.curlCode);
|
||||
}
|
||||
|
||||
copyText(text) {
|
||||
copyText(text: string) {
|
||||
const textField = document.createElement('textarea');
|
||||
textField.innerText = text;
|
||||
document.body.appendChild(textField);
|
||||
|
@ -74,17 +76,27 @@ export class ConsoleMenu extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
openDocs = () => {
|
||||
openDocs = async () => {
|
||||
this.closePopover();
|
||||
this.props.getDocumentation();
|
||||
this.props.openDocumentation();
|
||||
}
|
||||
const documentation = await this.props.getDocumentation();
|
||||
if (!documentation) {
|
||||
return;
|
||||
}
|
||||
window.open(documentation, '_blank');
|
||||
};
|
||||
|
||||
// Using `any` here per this issue: https://github.com/elastic/eui/issues/2265
|
||||
autoIndent: any = (event: React.MouseEvent) => {
|
||||
this.closePopover();
|
||||
this.props.autoIndent(event);
|
||||
};
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<EuiButtonIcon
|
||||
iconType="wrench"
|
||||
onClick={this.onButtonClick}
|
||||
// @ts-ignore
|
||||
aria-label={
|
||||
<FormattedMessage
|
||||
id="console.requestOptionsButtonAriaLabel"
|
||||
|
@ -95,39 +107,37 @@ export class ConsoleMenu extends Component {
|
|||
);
|
||||
|
||||
const items = [
|
||||
(
|
||||
<EuiContextMenuItem
|
||||
key="Copy as cURL"
|
||||
id="ConCopyAsCurl"
|
||||
disabled={!document.queryCommandSupported('copy')}
|
||||
onClick={() => { this.closePopover(); this.copyAsCurl(); }}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="console.requestOptions.copyAsUrlButtonLabel"
|
||||
defaultMessage="Copy as cURL"
|
||||
/>
|
||||
</EuiContextMenuItem>
|
||||
), (
|
||||
<EuiContextMenuItem
|
||||
key="Open documentation"
|
||||
onClick={() => { this.openDocs(); }}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="console.requestOptions.openDocumentationButtonLabel"
|
||||
defaultMessage="Open documentation"
|
||||
/>
|
||||
</EuiContextMenuItem>
|
||||
), (
|
||||
<EuiContextMenuItem
|
||||
key="Auto indent"
|
||||
onClick={(event) => { this.closePopover(); this.props.autoIndent(event); }}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="console.requestOptions.autoIndentButtonLabel"
|
||||
defaultMessage="Auto indent"
|
||||
/>
|
||||
</EuiContextMenuItem>
|
||||
)
|
||||
<EuiContextMenuItem
|
||||
key="Copy as cURL"
|
||||
id="ConCopyAsCurl"
|
||||
disabled={!document.queryCommandSupported('copy')}
|
||||
onClick={() => {
|
||||
this.closePopover();
|
||||
this.copyAsCurl();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="console.requestOptions.copyAsUrlButtonLabel"
|
||||
defaultMessage="Copy as cURL"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="Open documentation"
|
||||
onClick={() => {
|
||||
this.openDocs();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="console.requestOptions.openDocumentationButtonLabel"
|
||||
defaultMessage="Open documentation"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem key="Auto indent" onClick={this.autoIndent}>
|
||||
<FormattedMessage
|
||||
id="console.requestOptions.autoIndentButtonLabel"
|
||||
defaultMessage="Auto indent"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
];
|
||||
|
||||
return (
|
||||
|
@ -140,18 +150,9 @@ export class ConsoleMenu extends Component {
|
|||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
items={items}
|
||||
/>
|
||||
<EuiContextMenuPanel items={items} />
|
||||
</EuiPopover>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleMenu.propTypes = {
|
||||
getCurl: PropTypes.func.isRequired,
|
||||
openDocumentation: PropTypes.func.isRequired,
|
||||
getDocumentation: PropTypes.func.isRequired,
|
||||
autoIndent: PropTypes.func.isRequired,
|
||||
};
|
|
@ -19,10 +19,10 @@
|
|||
|
||||
import React, { useEffect } from 'react';
|
||||
// @ts-ignore
|
||||
import exampleText from 'raw-loader!./help_example.txt';
|
||||
import exampleText from 'raw-loader!../constants/help_example.txt';
|
||||
import $ from 'jquery';
|
||||
// @ts-ignore
|
||||
import SenseEditor from '../sense_editor/editor';
|
||||
import SenseEditor from '../../../../public/quarantined/src/sense_editor/editor';
|
||||
|
||||
interface EditorExampleProps {
|
||||
panel: string;
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 * from './split_panel';
|
||||
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';
|
|
@ -35,7 +35,8 @@ import {
|
|||
EuiOverlayMask,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
import { DevToolsSettings } from './dev_tools_settings';
|
||||
|
||||
import { DevToolsSettings } from '../../services';
|
||||
|
||||
export type AutocompleteOptions = 'fields' | 'indices' | 'templates';
|
||||
|
||||
|
@ -145,11 +146,9 @@ export function DevToolsSettingsModal(props: Props) {
|
|||
onClick={() => {
|
||||
// Only refresh the currently selected settings.
|
||||
props.refreshAutocompleteSettings({
|
||||
autocomplete: {
|
||||
fields,
|
||||
indices,
|
||||
templates,
|
||||
},
|
||||
fields,
|
||||
indices,
|
||||
templates,
|
||||
});
|
||||
}}
|
||||
>
|
|
@ -0,0 +1,93 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Split panel should render correctly 1`] = `
|
||||
<PanelsContainer>
|
||||
<PanelContextProvider
|
||||
registry={
|
||||
PanelRegistry {
|
||||
"panels": Array [
|
||||
Object {
|
||||
"getWidth": [Function],
|
||||
"initialWidth": "100%",
|
||||
"setWidth": [Function],
|
||||
},
|
||||
Object {
|
||||
"getWidth": [Function],
|
||||
"initialWidth": "100%",
|
||||
"setWidth": [Function],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
onMouseMove={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"display": "flex",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Panel
|
||||
key=".0"
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"display": "flex",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<p
|
||||
style={
|
||||
Object {
|
||||
"width": "50px",
|
||||
}
|
||||
}
|
||||
>
|
||||
A
|
||||
</p>
|
||||
</div>
|
||||
</Panel>
|
||||
<Resizer
|
||||
key="resizer"
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
<div
|
||||
className="conApp__resizer"
|
||||
data-test-subj="splitPanelResizer"
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
︙
|
||||
</div>
|
||||
</Resizer>
|
||||
<Panel
|
||||
key=".1"
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"display": "flex",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<p
|
||||
style={
|
||||
Object {
|
||||
"width": "50px",
|
||||
}
|
||||
}
|
||||
>
|
||||
B
|
||||
</p>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
</PanelContextProvider>
|
||||
</PanelsContainer>
|
||||
`;
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
type ResizerMouseEvent = React.MouseEvent<HTMLDivElement, MouseEvent>;
|
||||
|
||||
export interface Props {
|
||||
onMouseDown: (eve: ResizerMouseEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This component uses styling constants from public UI - should be removed, next iteration should incl. horizontal and vertical resizers.
|
||||
*/
|
||||
export function Resizer(props: Props) {
|
||||
return (
|
||||
<div {...props} className="conApp__resizer" data-test-subj="splitPanelResizer">
|
||||
︙
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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, { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { usePanelContext } from '../context';
|
||||
|
||||
export interface Props {
|
||||
children: ReactNode[] | ReactNode;
|
||||
initialWidth?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export function Panel({ children, initialWidth = '100%', style = {} }: Props) {
|
||||
const [width, setWidth] = useState(initialWidth);
|
||||
const { registry } = usePanelContext();
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
registry.registerPanel({
|
||||
initialWidth,
|
||||
setWidth(value) {
|
||||
setWidth(value + '%');
|
||||
},
|
||||
getWidth() {
|
||||
return divRef.current!.getBoundingClientRect().width;
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={divRef} style={{ ...style, width, display: 'flex' }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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, { Children, ReactNode, useRef, useState } from 'react';
|
||||
|
||||
import { PanelContextProvider } from '../context';
|
||||
import { Resizer } from '../components/resizer';
|
||||
import { PanelRegistry } from '../registry';
|
||||
|
||||
export interface Props {
|
||||
children: ReactNode;
|
||||
onPanelWidthChange?: (arrayOfPanelWidths: number[]) => any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isDragging: boolean;
|
||||
currentResizerPos: number;
|
||||
}
|
||||
|
||||
const initialState: State = { isDragging: false, currentResizerPos: -1 };
|
||||
|
||||
const pxToPercent = (proportion: number, whole: number) => (proportion / whole) * 100;
|
||||
|
||||
export function PanelsContainer({ children, onPanelWidthChange }: Props) {
|
||||
const [firstChild, secondChild] = Children.toArray(children);
|
||||
|
||||
const registryRef = useRef(new PanelRegistry());
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [state, setState] = useState<State>(initialState);
|
||||
|
||||
const getContainerWidth = () => {
|
||||
return containerRef.current!.getBoundingClientRect().width;
|
||||
};
|
||||
|
||||
const childrenWithResizer = [
|
||||
firstChild,
|
||||
<Resizer
|
||||
key={'resizer'}
|
||||
onMouseDown={event => {
|
||||
event.preventDefault();
|
||||
setState({
|
||||
...state,
|
||||
isDragging: true,
|
||||
currentResizerPos: event.clientX,
|
||||
});
|
||||
}}
|
||||
/>,
|
||||
secondChild,
|
||||
];
|
||||
|
||||
return (
|
||||
<PanelContextProvider registry={registryRef.current}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{ display: 'flex', height: '100%', width: '100%' }}
|
||||
onMouseMove={event => {
|
||||
if (state.isDragging) {
|
||||
const { clientX: x } = event;
|
||||
const { current: registry } = registryRef;
|
||||
const [left, right] = registry.getPanels();
|
||||
const delta = x - state.currentResizerPos;
|
||||
const containerWidth = getContainerWidth();
|
||||
const leftPercent = pxToPercent(left.getWidth() + delta, containerWidth);
|
||||
const rightPercent = pxToPercent(right.getWidth() - delta, containerWidth);
|
||||
left.setWidth(leftPercent);
|
||||
right.setWidth(rightPercent);
|
||||
|
||||
if (onPanelWidthChange) {
|
||||
onPanelWidthChange([leftPercent, rightPercent]);
|
||||
}
|
||||
|
||||
setState({ ...state, currentResizerPos: x });
|
||||
}
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setState(initialState);
|
||||
}}
|
||||
>
|
||||
{childrenWithResizer}
|
||||
</div>
|
||||
</PanelContextProvider>
|
||||
);
|
||||
}
|
|
@ -17,31 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { HelpPanel } from '../components/help_panel';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { PanelRegistry } from './registry';
|
||||
|
||||
let isOpen = false;
|
||||
const PanelContext = createContext({ registry: new PanelRegistry() });
|
||||
|
||||
export function showHelpPanel(): () => void {
|
||||
const onClose = () => {
|
||||
if (!container) return;
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
isOpen = false;
|
||||
};
|
||||
|
||||
const container = document.getElementById('consoleHelpPanel');
|
||||
|
||||
if (container && !isOpen) {
|
||||
isOpen = true;
|
||||
const element = (
|
||||
<I18nContext>
|
||||
<HelpPanel onClose={onClose} />
|
||||
</I18nContext>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
}
|
||||
|
||||
return onClose;
|
||||
interface ContextProps {
|
||||
children: any;
|
||||
registry: PanelRegistry;
|
||||
}
|
||||
|
||||
export function PanelContextProvider({ children, registry }: ContextProps) {
|
||||
return <PanelContext.Provider value={{ registry }}>{children}</PanelContext.Provider>;
|
||||
}
|
||||
|
||||
export const usePanelContext = () => {
|
||||
const context = useContext(PanelContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('usePanelContext must be used within a <PanelContextProvider />');
|
||||
}
|
||||
return context;
|
||||
};
|
|
@ -17,14 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export interface DevToolsSettings {
|
||||
fontSize: number;
|
||||
wrapMode: boolean;
|
||||
autocomplete: {
|
||||
fields: boolean;
|
||||
indices: boolean;
|
||||
templates: boolean;
|
||||
};
|
||||
polling: boolean;
|
||||
tripleQuotes: boolean;
|
||||
}
|
||||
export { Panel } from './containers/panel';
|
||||
export { PanelsContainer } from './containers/panel_container';
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 interface PanelController {
|
||||
setWidth: (percent: number) => void;
|
||||
getWidth: () => number;
|
||||
initialWidth: string;
|
||||
}
|
||||
|
||||
export class PanelRegistry {
|
||||
private panels: PanelController[] = [];
|
||||
|
||||
registerPanel(panel: PanelController) {
|
||||
this.panels.push(panel);
|
||||
}
|
||||
|
||||
getResizerNeighbours(idx: number) {
|
||||
return [this.panels[idx], this.panels[idx + 1]];
|
||||
}
|
||||
|
||||
getPanels() {
|
||||
return this.panels.map(panel => ({ ...panel }));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import { spy } from 'sinon';
|
||||
|
||||
import { PanelsContainer, Panel } from '.';
|
||||
|
||||
const testComponentA = <p style={{ width: '50px' }}>A</p>;
|
||||
const testComponentB = <p style={{ width: '50px' }}>B</p>;
|
||||
|
||||
describe('Split panel', () => {
|
||||
it('should render correctly', () => {
|
||||
const panelContainer = mount(
|
||||
<PanelsContainer>
|
||||
<Panel>{testComponentA}</Panel>
|
||||
<Panel>{testComponentB}</Panel>
|
||||
</PanelsContainer>
|
||||
);
|
||||
expect(toJson(panelContainer)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should calculate sizes correctly on mouse drags', () => {
|
||||
// Since this test is not running in the browser we can't expect all of the
|
||||
// APIs for sizing to be available. The below is a very hacky way of setting
|
||||
// the DOMElement width so that we have a lightweight test for width calculation
|
||||
// logic.
|
||||
const div = mount(<div />);
|
||||
const proto = (div
|
||||
.find('div')
|
||||
.first()
|
||||
.getDOMNode() as any).__proto__;
|
||||
const originalGetBoundingClientRect = proto.getBoundingClientRect;
|
||||
|
||||
proto.getBoundingClientRect = spy(() => {
|
||||
return {
|
||||
width: 1000,
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
// Everything here runs sync.
|
||||
let widthsCache: number[] = [];
|
||||
const onWidthChange = (widths: number[]) => {
|
||||
widthsCache = widths;
|
||||
};
|
||||
|
||||
const panelContainer = mount(
|
||||
<PanelsContainer onPanelWidthChange={onWidthChange}>
|
||||
<Panel initialWidth={'50%'}>{testComponentA}</Panel>
|
||||
<Panel initialWidth={'50%'}>{testComponentB}</Panel>
|
||||
</PanelsContainer>
|
||||
);
|
||||
|
||||
const resizer = panelContainer.find(`[data-test-subj~="splitPanelResizer"]`).first();
|
||||
|
||||
resizer.simulate('mousedown', { clientX: 0 });
|
||||
resizer.simulate('mousemove', { clientX: 250 });
|
||||
resizer.simulate('mouseup');
|
||||
|
||||
panelContainer.update();
|
||||
|
||||
expect(widthsCache).toEqual([125, 75]);
|
||||
} finally {
|
||||
proto.getBoundingClientRect = originalGetBoundingClientRect;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { EuiTabs, EuiTab } from '@elastic/eui';
|
||||
|
||||
export interface TopNavMenuItem {
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
onClick: () => void;
|
||||
testId: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items: TopNavMenuItem[];
|
||||
}
|
||||
|
||||
export function TopNavMenu({ items }: Props) {
|
||||
return (
|
||||
<EuiTabs size="s">
|
||||
{items.map((item, idx) => {
|
||||
return (
|
||||
<EuiTab key={idx} onClick={item.onClick} title={item.label} data-test-subj={item.testId}>
|
||||
{item.label}
|
||||
</EuiTab>
|
||||
);
|
||||
})}
|
||||
</EuiTabs>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { Editor, EditorOutput, ConsoleHistory, autoIndent, getDocumentation } from './legacy';
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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, { CSSProperties, useEffect, useRef, useState } from 'react';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { useAppContext } from '../../../../context';
|
||||
import { useUIAceKeyboardMode } from '../use_ui_ace_keyboard_mode';
|
||||
import { ConsoleMenu } from '../../../../components';
|
||||
import { autoIndent, getDocumentation } from '../console_menu_actions';
|
||||
import { registerCommands } from './keyboard_shortcuts';
|
||||
|
||||
// @ts-ignore
|
||||
import { initializeInput } from '../../../../../../../public/quarantined/src/input';
|
||||
|
||||
export interface EditorProps {
|
||||
onEditorReady?: (editor: any) => void;
|
||||
sendCurrentRequest?: () => void;
|
||||
docLinkVersion: string;
|
||||
}
|
||||
|
||||
const abs: CSSProperties = {
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
bottom: '0',
|
||||
right: '0',
|
||||
};
|
||||
|
||||
function Component({ onEditorReady, docLinkVersion, sendCurrentRequest = () => {} }: EditorProps) {
|
||||
const {
|
||||
services: { history, settings },
|
||||
} = useAppContext();
|
||||
|
||||
const editorRef = useRef<HTMLDivElement | null>(null);
|
||||
const actionsRef = useRef<HTMLDivElement | null>(null);
|
||||
const editorInstanceRef = useRef<any | null>(null);
|
||||
|
||||
const [textArea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
|
||||
useUIAceKeyboardMode(textArea);
|
||||
|
||||
const openDocumentation = async () => {
|
||||
const documentation = await getDocumentation(editorInstanceRef.current!, docLinkVersion);
|
||||
if (!documentation) {
|
||||
return;
|
||||
}
|
||||
window.open(documentation, '_blank');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const $editor = $(editorRef.current!);
|
||||
const $actions = $(actionsRef.current!);
|
||||
editorInstanceRef.current = initializeInput($editor, $actions, history, settings);
|
||||
if (onEditorReady) {
|
||||
onEditorReady({ editor: editorInstanceRef.current, element: editorRef.current! });
|
||||
}
|
||||
|
||||
setTextArea(editorRef.current!.querySelector('textarea'));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
registerCommands({
|
||||
input: editorInstanceRef.current,
|
||||
sendCurrentRequestToES: sendCurrentRequest,
|
||||
openDocumentation,
|
||||
});
|
||||
}, [sendCurrentRequest]);
|
||||
|
||||
return (
|
||||
<div style={abs} className="conApp">
|
||||
<div className="conApp__editor">
|
||||
<ul className="conApp__autoComplete" id="autocomplete" />
|
||||
<div ref={actionsRef} className="conApp__editorActions" id="ConAppEditorActions">
|
||||
<EuiToolTip
|
||||
content={i18n.translate('console.sendRequestButtonTooltip', {
|
||||
defaultMessage: 'click to send request',
|
||||
})}
|
||||
>
|
||||
<button
|
||||
onClick={sendCurrentRequest}
|
||||
data-test-subj="send-request-button"
|
||||
className="conApp__editorActionButton conApp__editorActionButton--success"
|
||||
>
|
||||
<EuiIcon type="play" />
|
||||
</button>
|
||||
</EuiToolTip>
|
||||
<ConsoleMenu
|
||||
getCurl={(cb: any) => {
|
||||
editorInstanceRef.current!.getRequestsAsCURL(cb);
|
||||
}}
|
||||
getDocumentation={() => {
|
||||
return getDocumentation(editorInstanceRef.current!, docLinkVersion);
|
||||
}}
|
||||
autoIndent={(event: any) => {
|
||||
autoIndent(editorInstanceRef.current!, event);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref={editorRef}
|
||||
id="ConAppEditor"
|
||||
className="conApp__editorContent"
|
||||
data-test-subj="request-editor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Editor = React.memo(Component);
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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, { useEffect, useRef } from 'react';
|
||||
import $ from 'jquery';
|
||||
|
||||
// @ts-ignore
|
||||
import { initializeOutput } from '../../../../../../../public/quarantined/src/output';
|
||||
import { useAppContext } from '../../../../context';
|
||||
|
||||
export interface EditorOutputProps {
|
||||
onReady?: (ref: any) => void;
|
||||
}
|
||||
|
||||
function Component({ onReady }: EditorOutputProps) {
|
||||
const editorRef = useRef<null | HTMLDivElement>(null);
|
||||
const {
|
||||
services: { settings },
|
||||
} = useAppContext();
|
||||
|
||||
useEffect(() => {
|
||||
const editor$ = $(editorRef.current!);
|
||||
const outputEditor = initializeOutput(editor$, settings);
|
||||
if (onReady) {
|
||||
onReady({ editor: outputEditor, element: editorRef.current! });
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={editorRef} className="conApp__output" data-test-subj="response-editor">
|
||||
<div className="conApp__outputContent" id="ConAppOutput" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const EditorOutput = React.memo(Component);
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { Editor } from './editor';
|
||||
export { EditorOutput } from './editor_output';
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
interface Actions {
|
||||
input: any;
|
||||
sendCurrentRequestToES: () => void;
|
||||
openDocumentation: () => void;
|
||||
}
|
||||
|
||||
export function registerCommands({ input, sendCurrentRequestToES, openDocumentation }: Actions) {
|
||||
input.commands.addCommand({
|
||||
name: 'send to elasticsearch',
|
||||
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
|
||||
exec: () => sendCurrentRequestToES(),
|
||||
});
|
||||
input.commands.addCommand({
|
||||
name: 'open documentation',
|
||||
bindKey: { win: 'Ctrl-/', mac: 'Command-/' },
|
||||
exec: () => {
|
||||
openDocumentation();
|
||||
},
|
||||
});
|
||||
input.commands.addCommand({
|
||||
name: 'auto indent request',
|
||||
bindKey: { win: 'Ctrl-I', mac: 'Command-I' },
|
||||
exec: () => {
|
||||
input.autoIndent();
|
||||
},
|
||||
});
|
||||
input.commands.addCommand({
|
||||
name: 'move to previous request start or end',
|
||||
bindKey: { win: 'Ctrl-Up', mac: 'Command-Up' },
|
||||
exec: () => {
|
||||
input.moveToPreviousRequestEdge();
|
||||
},
|
||||
});
|
||||
input.commands.addCommand({
|
||||
name: 'move to next request start or end',
|
||||
bindKey: { win: 'Ctrl-Down', mac: 'Command-Down' },
|
||||
exec: () => {
|
||||
input.moveToNextRequestEdge();
|
||||
},
|
||||
});
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* 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, { useEffect, useRef, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { memoize } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
keyCodes,
|
||||
EuiSpacer,
|
||||
EuiIcon,
|
||||
EuiTitle,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { useAppContext } from '../../../../context';
|
||||
import { HistoryViewer } from './history_viewer';
|
||||
|
||||
interface Props {
|
||||
close: () => void;
|
||||
clearHistory: () => void;
|
||||
restoreFromHistory: (req: any) => void;
|
||||
requests: any[];
|
||||
}
|
||||
|
||||
const CHILD_ELEMENT_PREFIX = 'historyReq';
|
||||
|
||||
export function ConsoleHistory({ close, requests, clearHistory, restoreFromHistory }: Props) {
|
||||
const {
|
||||
services: { settings },
|
||||
ResizeChecker,
|
||||
} = useAppContext();
|
||||
|
||||
const listRef = useRef<HTMLUListElement | null>(null);
|
||||
|
||||
const [viewingReq, setViewingReq] = useState<any>(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
const selectedReq = useRef<any>(null);
|
||||
|
||||
const scrollIntoView = (idx: number) => {
|
||||
const activeDescendant = listRef.current!.querySelector(`#${CHILD_ELEMENT_PREFIX}${idx}`);
|
||||
if (activeDescendant) {
|
||||
activeDescendant.scrollIntoView();
|
||||
}
|
||||
};
|
||||
|
||||
const [describeReq] = useState(() => {
|
||||
const _describeReq = (req: any) => {
|
||||
const endpoint = req.endpoint;
|
||||
const date = moment(req.time);
|
||||
|
||||
let formattedDate = date.format('MMM D');
|
||||
if (date.diff(moment(), 'days') > -7) {
|
||||
formattedDate = date.fromNow();
|
||||
}
|
||||
|
||||
return `${endpoint} (${formattedDate})`;
|
||||
};
|
||||
|
||||
(_describeReq as any).cache = new WeakMap();
|
||||
|
||||
return memoize(_describeReq);
|
||||
});
|
||||
|
||||
const initialize = () => {
|
||||
const nextSelectedIndex = 0;
|
||||
(describeReq as any).cache = new WeakMap();
|
||||
setViewingReq(requests[nextSelectedIndex]);
|
||||
selectedReq.current = requests[nextSelectedIndex];
|
||||
setSelectedIndex(nextSelectedIndex);
|
||||
scrollIntoView(nextSelectedIndex);
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
clearHistory();
|
||||
initialize();
|
||||
};
|
||||
|
||||
const restore = (req: any = selectedReq.current) => {
|
||||
restoreFromHistory(req);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
}, [requests]);
|
||||
|
||||
/* eslint-disable */
|
||||
return (
|
||||
<>
|
||||
<div className="conHistory">
|
||||
<EuiTitle size="s">
|
||||
<h2>{i18n.translate('console.historyPage.pageTitle', { defaultMessage: 'History' })}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<div className="conHistory__body">
|
||||
<ul
|
||||
ref={listRef}
|
||||
onKeyDown={(ev: React.KeyboardEvent) => {
|
||||
if (ev.keyCode === keyCodes.ENTER) {
|
||||
restore();
|
||||
return;
|
||||
}
|
||||
|
||||
let currentIdx = selectedIndex;
|
||||
|
||||
if (ev.keyCode === keyCodes.UP) {
|
||||
ev.preventDefault();
|
||||
--currentIdx;
|
||||
} else if (ev.keyCode === keyCodes.DOWN) {
|
||||
ev.preventDefault();
|
||||
++currentIdx;
|
||||
}
|
||||
|
||||
const nextSelectedIndex = Math.min(Math.max(0, currentIdx), requests.length - 1);
|
||||
|
||||
setViewingReq(requests[nextSelectedIndex]);
|
||||
selectedReq.current = requests[nextSelectedIndex];
|
||||
setSelectedIndex(nextSelectedIndex);
|
||||
scrollIntoView(nextSelectedIndex);
|
||||
}}
|
||||
role="listbox"
|
||||
className="list-group conHistory__reqs"
|
||||
tabIndex={0}
|
||||
aria-activedescendant={`${CHILD_ELEMENT_PREFIX}${selectedIndex}`}
|
||||
aria-label={i18n.translate('console.historyPage.requestListAriaLabel', {
|
||||
defaultMessage: 'History of sent requests',
|
||||
})}
|
||||
>
|
||||
{requests.map((req, idx) => {
|
||||
const reqDescription = describeReq(req);
|
||||
const isSelected = viewingReq === req;
|
||||
return (
|
||||
// Ignore a11y issues on li's
|
||||
// eslint-disable-next-line
|
||||
<li
|
||||
key={idx}
|
||||
id={`${CHILD_ELEMENT_PREFIX}${idx}`}
|
||||
className={`list-group-item conHistory__req ${
|
||||
isSelected ? 'conHistory__req-selected' : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
setViewingReq(req);
|
||||
selectedReq.current = req;
|
||||
setSelectedIndex(idx);
|
||||
scrollIntoView(idx);
|
||||
}}
|
||||
role="option"
|
||||
onMouseEnter={() => setViewingReq(req)}
|
||||
onMouseLeave={() => setViewingReq(selectedReq.current)}
|
||||
onDoubleClick={() => restore(req)}
|
||||
aria-label={i18n.translate('console.historyPage.itemOfRequestListAriaLabel', {
|
||||
defaultMessage: 'Request: {historyItem}',
|
||||
values: { historyItem: reqDescription },
|
||||
})}
|
||||
aria-selected={isSelected}
|
||||
>
|
||||
{reqDescription}
|
||||
<span className="conHistory__reqIcon">
|
||||
<EuiIcon type="arrowRight" />
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<div className="conHistory__body__spacer" />
|
||||
|
||||
<HistoryViewer settings={settings} req={viewingReq} ResizeChecker={ResizeChecker} />
|
||||
</div>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="danger" onClick={() => clear()}>
|
||||
{i18n.translate('console.historyPage.clearHistoryButtonLabel', {
|
||||
defaultMessage: 'Clear',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="primary" onClick={() => close()}>
|
||||
{i18n.translate('console.historyPage.closehistoryButtonLabel', {
|
||||
defaultMessage: 'Close',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton color="primary" disabled={!selectedReq} onClick={() => restore()}>
|
||||
{i18n.translate('console.historyPage.applyHistoryButtonLabel', {
|
||||
defaultMessage: 'Apply',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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, { useEffect, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import $ from 'jquery';
|
||||
|
||||
import { Settings } from '../../../../../services';
|
||||
import { subscribeResizeChecker } from '../subscribe_console_resize_checker';
|
||||
|
||||
// @ts-ignore
|
||||
import SenseEditor from '../../../../../../../public/quarantined/src/sense_editor/editor';
|
||||
|
||||
interface Props {
|
||||
settings: Settings;
|
||||
req: any | null;
|
||||
ResizeChecker: any;
|
||||
}
|
||||
|
||||
export function HistoryViewer({ settings, ResizeChecker, req }: Props) {
|
||||
const divRef = useRef<HTMLDivElement | null>(null);
|
||||
const viewerRef = useRef<any | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const viewer = new SenseEditor($(divRef.current!));
|
||||
viewerRef.current = viewer;
|
||||
viewer.renderer.setShowPrintMargin(false);
|
||||
viewer.$blockScrolling = Infinity;
|
||||
const unsubscribe = subscribeResizeChecker(ResizeChecker, divRef.current!, viewer);
|
||||
settings.applyCurrentSettings(viewer);
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
if (viewerRef.current) {
|
||||
const { current: viewer } = viewerRef;
|
||||
if (req) {
|
||||
const s = req.method + ' ' + req.endpoint + '\n' + (req.data || '');
|
||||
viewer.setValue(s);
|
||||
viewer.clearSelection();
|
||||
} else {
|
||||
viewer.getSession().setValue(
|
||||
i18n.translate('console.historyPage.noHistoryTextMessage', {
|
||||
defaultMessage: 'No history available',
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="conHistory__viewer" ref={divRef} />;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { ConsoleHistory } from './console_history';
|
|
@ -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.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { getEndpointFromPosition } from '../../../../../../public/quarantined/src/autocomplete';
|
||||
|
||||
export function autoIndent(editor: any, event: any) {
|
||||
editor.autoIndent();
|
||||
event.preventDefault();
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
export function getDocumentation(editor: any, docLinkVersion: string): Promise<string | null> {
|
||||
return new Promise(resolve => {
|
||||
editor.getRequestsInRange((requests: any) => {
|
||||
if (!requests || requests.length === 0) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const position = requests[0].range.end;
|
||||
position.column = position.column - 1;
|
||||
const endpoint = getEndpointFromPosition(editor, position);
|
||||
if (endpoint && endpoint.documentation && endpoint.documentation.indexOf('http') !== -1) {
|
||||
const nextDocumentation = endpoint.documentation
|
||||
.replace('/master/', `/${docLinkVersion}/`)
|
||||
.replace('/current/', `/${docLinkVersion}/`);
|
||||
resolve(nextDocumentation);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { EditorOutput, Editor } from './console_editor';
|
||||
export { ConsoleHistory } from './console_history';
|
||||
export { getDocumentation, autoIndent } from './console_menu_actions';
|
|
@ -17,10 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ResizeChecker } from 'ui/resize_checker';
|
||||
|
||||
export function applyResizeCheckerToEditors($scope, $el, ...editors) {
|
||||
export function subscribeResizeChecker(ResizeChecker: any, $el: any, ...editors: any[]) {
|
||||
const checker = new ResizeChecker($el);
|
||||
checker.on('resize', () => editors.forEach(e => e.resize()));
|
||||
$scope.$on('$destroy', () => checker.destroy());
|
||||
return () => checker.destroy();
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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, { useEffect, useRef } from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { keyCodes, EuiText } from '@elastic/eui';
|
||||
|
||||
const OverlayText = () => (
|
||||
// The point of this element is for accessibility purposes, so ignore eslint error
|
||||
// in this case
|
||||
//
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||
<>
|
||||
<EuiText size="s">Press Enter to start editing.</EuiText>
|
||||
<EuiText size="s">When you’re done, press Escape to stop editing.</EuiText>
|
||||
</>
|
||||
);
|
||||
|
||||
export function useUIAceKeyboardMode(aceTextAreaElement: HTMLTextAreaElement | null) {
|
||||
const overlayMountNode = useRef<HTMLDivElement | null>(null);
|
||||
const autoCompleteVisibleRef = useRef<boolean>(false);
|
||||
|
||||
function onDismissOverlay(event: KeyboardEvent) {
|
||||
if (event.keyCode === keyCodes.ENTER) {
|
||||
event.preventDefault();
|
||||
aceTextAreaElement!.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function enableOverlay() {
|
||||
if (overlayMountNode.current) {
|
||||
overlayMountNode.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
const isAutoCompleteVisible = () => {
|
||||
const autoCompleter = document.querySelector<HTMLDivElement>('.ace_autocomplete');
|
||||
if (!autoCompleter) {
|
||||
return false;
|
||||
}
|
||||
// The autoComplete is just hidden when it's closed, not removed from the DOM.
|
||||
return autoCompleter.style.display !== 'none';
|
||||
};
|
||||
|
||||
const documentKeyDownListener = () => {
|
||||
autoCompleteVisibleRef.current = isAutoCompleteVisible();
|
||||
};
|
||||
|
||||
const aceKeydownListener = (event: KeyboardEvent) => {
|
||||
if (event.keyCode === keyCodes.ESCAPE && !autoCompleteVisibleRef.current) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
enableOverlay();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (aceTextAreaElement) {
|
||||
// We don't control HTML elements inside of ace so we imperatively create an element
|
||||
// that acts as a container and insert it just before ace's textarea element
|
||||
// so that the overlay lives at the correct spot in the DOM hierarchy.
|
||||
overlayMountNode.current = document.createElement('div');
|
||||
overlayMountNode.current.className = 'kbnUiAceKeyboardHint';
|
||||
overlayMountNode.current.setAttribute('role', 'application');
|
||||
overlayMountNode.current.tabIndex = 0;
|
||||
overlayMountNode.current.addEventListener('focus', enableOverlay);
|
||||
overlayMountNode.current.addEventListener('keydown', onDismissOverlay);
|
||||
|
||||
ReactDOM.render(<OverlayText />, overlayMountNode.current);
|
||||
|
||||
aceTextAreaElement.parentElement!.insertBefore(overlayMountNode.current, aceTextAreaElement);
|
||||
aceTextAreaElement.setAttribute('tabindex', '-1');
|
||||
|
||||
// Order of events:
|
||||
// 1. Document capture event fires first and we check whether an autocomplete menu is open on keydown
|
||||
// (not ideal because this is scoped to the entire document).
|
||||
// 2. Ace changes it's state (like hiding or showing autocomplete menu)
|
||||
// 3. We check what button was pressed and whether autocomplete was visible then determine
|
||||
// whether it should act like a dismiss or if we should display an overlay.
|
||||
document.addEventListener('keydown', documentKeyDownListener, { capture: true });
|
||||
aceTextAreaElement.addEventListener('keydown', aceKeydownListener);
|
||||
}
|
||||
return () => {
|
||||
if (aceTextAreaElement) {
|
||||
document.removeEventListener('keydown', documentKeyDownListener);
|
||||
aceTextAreaElement.removeEventListener('keydown', aceKeydownListener);
|
||||
document.removeChild(overlayMountNode.current!);
|
||||
}
|
||||
};
|
||||
}, [aceTextAreaElement]);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { Main } from './main';
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { IScope } from 'angular';
|
||||
import { showSettingsModal } from './settings_show_modal';
|
||||
interface Props {
|
||||
onClickHistory: () => void;
|
||||
onClickSettings: () => void;
|
||||
onClickHelp: () => void;
|
||||
}
|
||||
|
||||
// help
|
||||
import { showHelpPanel } from './help_show_panel';
|
||||
|
||||
export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) {
|
||||
export function getTopNavConfig({ onClickHistory, onClickSettings, onClickHelp }: Props) {
|
||||
return [
|
||||
{
|
||||
id: 'history',
|
||||
|
@ -35,8 +35,8 @@ export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) {
|
|||
description: i18n.translate('console.topNav.historyTabDescription', {
|
||||
defaultMessage: 'History',
|
||||
}),
|
||||
run: () => {
|
||||
$scope.$evalAsync(toggleHistory);
|
||||
onClick: () => {
|
||||
onClickHistory();
|
||||
},
|
||||
testId: 'consoleHistoryButton',
|
||||
},
|
||||
|
@ -48,8 +48,8 @@ export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) {
|
|||
description: i18n.translate('console.topNav.settingsTabDescription', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
run: () => {
|
||||
showSettingsModal();
|
||||
onClick: () => {
|
||||
onClickSettings();
|
||||
},
|
||||
testId: 'consoleSettingsButton',
|
||||
},
|
||||
|
@ -61,11 +61,8 @@ export function getTopNavConfig($scope: IScope, toggleHistory: () => {}) {
|
|||
description: i18n.translate('console.topNav.helpTabDescription', {
|
||||
defaultMessage: 'Help',
|
||||
}),
|
||||
run: () => {
|
||||
const hideHelpPanel = showHelpPanel();
|
||||
$scope.$on('$destroy', () => {
|
||||
hideHelpPanel();
|
||||
});
|
||||
onClick: () => {
|
||||
onClickHelp();
|
||||
},
|
||||
testId: 'consoleHelpButton',
|
||||
},
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { Main } from './main';
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* 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, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
|
||||
// @ts-ignore
|
||||
import mappings from '../../../../../public/quarantined/src/mappings';
|
||||
// @ts-ignore
|
||||
import init from '../../../../../public/quarantined/src/app';
|
||||
|
||||
import { EditorOutput, Editor, ConsoleHistory } from '../editor';
|
||||
import { subscribeResizeChecker } from '../editor/legacy/subscribe_console_resize_checker';
|
||||
|
||||
import {
|
||||
AutocompleteOptions,
|
||||
TopNavMenu,
|
||||
WelcomePanel,
|
||||
DevToolsSettingsModal,
|
||||
HelpPanel,
|
||||
PanelsContainer,
|
||||
Panel,
|
||||
} from '../../components';
|
||||
|
||||
import { useAppContext } from '../../context';
|
||||
import { StorageKeys, DevToolsSettings } from '../../../services';
|
||||
|
||||
import { getTopNavConfig } from './get_top_nav';
|
||||
|
||||
const INITIAL_PANEL_WIDTH = 50;
|
||||
const PANEL_MIN_WIDTH = '100px';
|
||||
|
||||
// We only run certain initialization after we know all our editors have
|
||||
// been instantiated -- which is what we use the below streams for.
|
||||
const inputReadySubject$ = new BehaviorSubject<any>(null);
|
||||
const outputReadySubject$ = new BehaviorSubject<any>(null);
|
||||
const editorsReady$ = combineLatest(inputReadySubject$, outputReadySubject$);
|
||||
|
||||
export function Main() {
|
||||
const {
|
||||
services: { storage, settings, history },
|
||||
docLinkVersion,
|
||||
ResizeChecker,
|
||||
} = useAppContext();
|
||||
|
||||
const [editorReady, setEditorReady] = useState<boolean>(false);
|
||||
const [inputEditor, setInputEditor] = useState<any>(null);
|
||||
const [outputEditor, setOutputEditor] = useState<any>(null);
|
||||
const [showWelcome, setShowWelcomePanel] = useState(
|
||||
() => storage.get('version_welcome_shown') !== '@@SENSE_REVISION'
|
||||
);
|
||||
|
||||
const [showingHistory, setShowHistory] = useState(false);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
|
||||
const containerRef = useRef<null | HTMLDivElement>(null);
|
||||
|
||||
const onInputEditorReady = useCallback((value: any) => {
|
||||
inputReadySubject$.next(value);
|
||||
}, []);
|
||||
|
||||
const onOutputEditorReady = useCallback((value: any) => {
|
||||
outputReadySubject$.next(value);
|
||||
}, []);
|
||||
|
||||
const [firstPanelWidth, secondPanelWidth] = storage.get(StorageKeys.WIDTH, [
|
||||
INITIAL_PANEL_WIDTH,
|
||||
INITIAL_PANEL_WIDTH,
|
||||
]);
|
||||
|
||||
const onPanelWidthChange = useCallback(
|
||||
debounce((widths: number[]) => {
|
||||
storage.set(StorageKeys.WIDTH, widths);
|
||||
}, 300),
|
||||
[]
|
||||
);
|
||||
|
||||
const [pastRequests, setPastRequests] = useState<any[]>(() => history.getHistory());
|
||||
|
||||
const sendCurrentRequest = useCallback(() => {
|
||||
inputEditor.focus();
|
||||
inputEditor.sendCurrentRequestToES(() => {
|
||||
setPastRequests(history.getHistory());
|
||||
}, outputEditor);
|
||||
}, [inputEditor, outputEditor]);
|
||||
|
||||
const clearHistory = useCallback(() => {
|
||||
history.clearHistory();
|
||||
setPastRequests(history.getHistory());
|
||||
}, []);
|
||||
|
||||
const restoreFromHistory = useCallback((req: any) => {
|
||||
history.restoreFromHistory(req);
|
||||
}, []);
|
||||
|
||||
const renderConsoleHistory = () => {
|
||||
return editorReady ? (
|
||||
<ConsoleHistory
|
||||
restoreFromHistory={restoreFromHistory}
|
||||
clearHistory={clearHistory}
|
||||
requests={pastRequests}
|
||||
close={() => setShowHistory(false)}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const refreshAutocompleteSettings = (selectedSettings: any) => {
|
||||
mappings.retrieveAutoCompleteInfo(selectedSettings);
|
||||
};
|
||||
|
||||
const getAutocompleteDiff = (newSettings: DevToolsSettings, prevSettings: DevToolsSettings) => {
|
||||
return Object.keys(newSettings.autocomplete).filter(key => {
|
||||
// @ts-ignore
|
||||
return prevSettings.autocomplete[key] !== newSettings.autocomplete[key];
|
||||
});
|
||||
};
|
||||
|
||||
const fetchAutocompleteSettingsIfNeeded = (
|
||||
newSettings: DevToolsSettings,
|
||||
prevSettings: DevToolsSettings
|
||||
) => {
|
||||
// We'll only retrieve settings if polling is on. The expectation here is that if the user
|
||||
// disables polling it's because they want manual control over the fetch request (possibly
|
||||
// because it's a very expensive request given their cluster and bandwidth). In that case,
|
||||
// they would be unhappy with any request that's sent automatically.
|
||||
if (newSettings.polling) {
|
||||
const autocompleteDiff = getAutocompleteDiff(newSettings, prevSettings);
|
||||
|
||||
const isSettingsChanged = autocompleteDiff.length > 0;
|
||||
const isPollingChanged = prevSettings.polling !== newSettings.polling;
|
||||
|
||||
if (isSettingsChanged) {
|
||||
// If the user has changed one of the autocomplete settings, then we'll fetch just the
|
||||
// ones which have changed.
|
||||
const changedSettings: any = autocompleteDiff.reduce(
|
||||
(changedSettingsAccum: any, setting: string): any => {
|
||||
changedSettingsAccum[setting] =
|
||||
newSettings.autocomplete[setting as AutocompleteOptions];
|
||||
return changedSettingsAccum;
|
||||
},
|
||||
{}
|
||||
);
|
||||
mappings.retrieveAutoCompleteInfo(changedSettings.autocomplete);
|
||||
} else if (isPollingChanged) {
|
||||
// If the user has turned polling on, then we'll fetch all selected autocomplete settings.
|
||||
mappings.retrieveAutoCompleteInfo();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSaveSettings = async (newSettings: DevToolsSettings) => {
|
||||
const prevSettings = settings.getCurrentSettings();
|
||||
settings.updateSettings(newSettings);
|
||||
fetchAutocompleteSettingsIfNeeded(newSettings, prevSettings);
|
||||
setShowSettings(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let resizerSubscriptions: Array<() => void> = [];
|
||||
const subscription = editorsReady$.subscribe(([input, output]) => {
|
||||
settings.registerOutput(output.editor);
|
||||
settings.registerInput(input.editor);
|
||||
history.setEditor(input.editor);
|
||||
|
||||
init(input.editor, output.editor, history);
|
||||
|
||||
resizerSubscriptions = resizerSubscriptions.concat([
|
||||
subscribeResizeChecker(ResizeChecker, containerRef.current!, input.editor, output.editor),
|
||||
subscribeResizeChecker(ResizeChecker, input.element, input.editor),
|
||||
subscribeResizeChecker(ResizeChecker, output.element, output.editor),
|
||||
]);
|
||||
|
||||
setInputEditor(input.editor);
|
||||
setOutputEditor(output.editor);
|
||||
setEditorReady(true);
|
||||
});
|
||||
|
||||
return () => {
|
||||
resizerSubscriptions.map(done => done());
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="consoleContainer" style={{ height: '100%', width: '100%' }} ref={containerRef}>
|
||||
<EuiFlexGroup
|
||||
style={{ height: '100%' }}
|
||||
gutterSize="none"
|
||||
direction="column"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TopNavMenu
|
||||
items={getTopNavConfig({
|
||||
onClickHistory: () => setShowHistory(!showingHistory),
|
||||
onClickSettings: () => setShowSettings(true),
|
||||
onClickHelp: () => setShowHelp(!showHelp),
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{showingHistory ? <EuiFlexItem grow={false}>{renderConsoleHistory()}</EuiFlexItem> : null}
|
||||
<EuiFlexItem>
|
||||
<PanelsContainer onPanelWidthChange={onPanelWidthChange}>
|
||||
<Panel
|
||||
style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }}
|
||||
initialWidth={firstPanelWidth + '%'}
|
||||
>
|
||||
<Editor
|
||||
sendCurrentRequest={sendCurrentRequest}
|
||||
onEditorReady={onInputEditorReady}
|
||||
docLinkVersion={docLinkVersion}
|
||||
/>
|
||||
</Panel>
|
||||
<Panel
|
||||
style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }}
|
||||
initialWidth={secondPanelWidth + '%'}
|
||||
>
|
||||
<EditorOutput onReady={onOutputEditorReady} />
|
||||
</Panel>
|
||||
</PanelsContainer>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{showWelcome ? (
|
||||
<WelcomePanel
|
||||
onDismiss={() => {
|
||||
storage.set('version_welcome_shown', '@@SENSE_REVISION');
|
||||
setShowWelcomePanel(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showSettings ? (
|
||||
<DevToolsSettingsModal
|
||||
onSaveSettings={onSaveSettings}
|
||||
onClose={() => setShowSettings(false)}
|
||||
refreshAutocompleteSettings={refreshAutocompleteSettings}
|
||||
settings={settings.getCurrentSettings()}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showHelp ? <HelpPanel onClose={() => setShowHelp(false)} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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, { createContext, useContext } from 'react';
|
||||
import { History, Storage, Settings } from '../../services';
|
||||
|
||||
interface ContextValue {
|
||||
services: {
|
||||
history: History;
|
||||
storage: Storage;
|
||||
settings: Settings;
|
||||
};
|
||||
docLinkVersion: string;
|
||||
ResizeChecker: any;
|
||||
}
|
||||
|
||||
interface ContextProps {
|
||||
value: ContextValue;
|
||||
children: any;
|
||||
}
|
||||
|
||||
const AppContext = createContext<ContextValue>(null as any);
|
||||
|
||||
export function AppContextProvider({ children, value }: ContextProps) {
|
||||
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
||||
}
|
||||
|
||||
export const useAppContext = () => {
|
||||
const context = useContext(AppContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAppContext must be used inside the AppContextProvider.');
|
||||
}
|
||||
return context;
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { useAppContext, AppContextProvider } from './app_context';
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { AppContextProvider } from './context';
|
||||
import { Main } from './containers';
|
||||
import { createStorage, createHistory, createSettings, Settings } from '../services';
|
||||
|
||||
let settingsRef: Settings;
|
||||
export function legacyBackDoorToSettings() {
|
||||
return settingsRef;
|
||||
}
|
||||
|
||||
export function boot(deps: { docLinkVersion: string; I18nContext: any; ResizeChecker: any }) {
|
||||
const { I18nContext, ResizeChecker } = deps;
|
||||
|
||||
const storage = createStorage({
|
||||
engine: window.localStorage,
|
||||
prefix: 'sense:',
|
||||
});
|
||||
const history = createHistory({ storage });
|
||||
const settings = createSettings({ storage });
|
||||
settingsRef = settings;
|
||||
|
||||
return (
|
||||
<I18nContext>
|
||||
<AppContextProvider
|
||||
value={{ ...deps, services: { storage, history, settings }, ResizeChecker }}
|
||||
>
|
||||
<Main />
|
||||
</AppContextProvider>
|
||||
</I18nContext>
|
||||
);
|
||||
}
|
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
|
@ -17,14 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import 'ngreact';
|
||||
import { PluginInitializerContext } from '../../../../../core/public';
|
||||
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('apps/sense', ['react']);
|
||||
import { ConsoleUIPlugin } from './plugin';
|
||||
|
||||
import { ConsoleMenu } from '../components/console_menu';
|
||||
export { ConsoleUIPlugin as Plugin };
|
||||
|
||||
module.directive('consoleMenu', function (reactDirective) {
|
||||
return reactDirective(wrapInI18nContext(ConsoleMenu));
|
||||
});
|
||||
export function plugin(ctx: PluginInitializerContext) {
|
||||
return new ConsoleUIPlugin(ctx);
|
||||
}
|
93
src/legacy/core_plugins/console/np_ready/public/legacy.ts
Normal file
93
src/legacy/core_plugins/console/np_ready/public/legacy.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 'brace';
|
||||
import 'brace/ext/language_tools';
|
||||
import 'brace/ext/searchbox';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/mode/text';
|
||||
|
||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { DOC_LINK_VERSION } from 'ui/documentation_links';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { ResizeChecker } from 'ui/resize_checker';
|
||||
import 'ui/autoload/styles';
|
||||
import 'ui/capabilities/route_setup';
|
||||
/* eslint-enable @kbn/eslint/no-restricted-paths */
|
||||
|
||||
import template from '../../public/quarantined/index.html';
|
||||
import { App } from '../../../../../core/public';
|
||||
|
||||
export interface XPluginSet {
|
||||
__LEGACY: {
|
||||
I18nContext: any;
|
||||
ResizeChecker: any;
|
||||
docLinkVersion: string;
|
||||
};
|
||||
}
|
||||
|
||||
import { plugin } from '.';
|
||||
|
||||
const pluginInstance = plugin({} as any);
|
||||
|
||||
const anyObject = {} as any;
|
||||
|
||||
uiRoutes.when('/dev_tools/console', {
|
||||
requireUICapability: 'dev_tools.show',
|
||||
controller: function RootController($scope) {
|
||||
// Stub out this config for now...
|
||||
$scope.topNavMenu = [];
|
||||
|
||||
$scope.initReactApp = () => {
|
||||
const targetElement = document.querySelector<HTMLDivElement>('#consoleRoot');
|
||||
if (!targetElement) {
|
||||
const message = `Could not mount Console App!`;
|
||||
npSetup.core.fatalErrors.add(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const mockedSetupCore = {
|
||||
...npSetup.core,
|
||||
application: {
|
||||
register(app: App): void {
|
||||
try {
|
||||
app.mount(anyObject, { element: targetElement, appBasePath: '' });
|
||||
} catch (e) {
|
||||
npSetup.core.fatalErrors.add(e);
|
||||
}
|
||||
},
|
||||
registerMountContext() {},
|
||||
},
|
||||
};
|
||||
|
||||
pluginInstance.setup(mockedSetupCore, {
|
||||
...npSetup.plugins,
|
||||
__LEGACY: {
|
||||
I18nContext,
|
||||
ResizeChecker,
|
||||
docLinkVersion: DOC_LINK_VERSION,
|
||||
},
|
||||
});
|
||||
pluginInstance.start(npStart.core);
|
||||
};
|
||||
},
|
||||
template,
|
||||
});
|
49
src/legacy/core_plugins/console/np_ready/public/plugin.ts
Normal file
49
src/legacy/core_plugins/console/np_ready/public/plugin.ts
Normal file
|
@ -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 { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
import { PluginInitializerContext, Plugin, CoreStart, CoreSetup } from '../../../../../core/public';
|
||||
import { XPluginSet } from './legacy';
|
||||
import { boot } from './application';
|
||||
|
||||
export class ConsoleUIPlugin implements Plugin<any, any> {
|
||||
// @ts-ignore
|
||||
constructor(private readonly ctx: PluginInitializerContext) {}
|
||||
|
||||
async setup({ application }: CoreSetup, pluginSet: XPluginSet) {
|
||||
const {
|
||||
__LEGACY: { docLinkVersion, I18nContext, ResizeChecker },
|
||||
} = pluginSet;
|
||||
|
||||
application.register({
|
||||
id: 'console',
|
||||
order: 1,
|
||||
title: 'Console',
|
||||
mount(ctx, { element }) {
|
||||
render(boot({ docLinkVersion, I18nContext, ResizeChecker }), element);
|
||||
return () => {
|
||||
unmountComponentAtNode(element);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async start(core: CoreStart) {}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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 { Storage } from './index';
|
||||
|
||||
export class History {
|
||||
private editor: any;
|
||||
|
||||
constructor(private readonly storage: Storage) {}
|
||||
|
||||
setEditor(editor: any) {
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
// stupid simple restore function, called when the user
|
||||
// chooses to restore a request from the history
|
||||
// PREVENTS history from needing to know about the input
|
||||
restoreFromHistory(req: any) {
|
||||
const session = this.editor.getSession();
|
||||
let pos = this.editor.getCursorPosition();
|
||||
let prefix = '';
|
||||
let suffix = '\n';
|
||||
if (this.editor.parser.isStartRequestRow(pos.row)) {
|
||||
pos.column = 0;
|
||||
suffix += '\n';
|
||||
} else if (this.editor.parser.isEndRequestRow(pos.row)) {
|
||||
const line = session.getLine(pos.row);
|
||||
pos.column = line.length;
|
||||
prefix = '\n\n';
|
||||
} else if (this.editor.parser.isInBetweenRequestsRow(pos.row)) {
|
||||
pos.column = 0;
|
||||
} else {
|
||||
pos = this.editor.nextRequestEnd(pos);
|
||||
prefix = '\n\n';
|
||||
}
|
||||
|
||||
let s = prefix + req.method + ' ' + req.endpoint;
|
||||
if (req.data) {
|
||||
s += '\n' + req.data;
|
||||
}
|
||||
|
||||
s += suffix;
|
||||
|
||||
session.insert(pos, s);
|
||||
this.editor.clearSelection();
|
||||
this.editor.moveCursorTo(pos.row + prefix.length, 0);
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
getHistoryKeys() {
|
||||
return this.storage
|
||||
.keys()
|
||||
.filter((key: string) => key.indexOf('hist_elem') === 0)
|
||||
.sort()
|
||||
.reverse();
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
return this.getHistoryKeys().map(key => this.storage.get(key));
|
||||
}
|
||||
|
||||
addToHistory(endpoint: string, method: string, data: any) {
|
||||
const keys = this.getHistoryKeys();
|
||||
keys.splice(0, 500); // only maintain most recent X;
|
||||
$.each(keys, (i, k) => {
|
||||
this.storage.delete(k);
|
||||
});
|
||||
|
||||
const timestamp = new Date().getTime();
|
||||
const k = 'hist_elem_' + timestamp;
|
||||
this.storage.set(k, {
|
||||
time: timestamp,
|
||||
endpoint,
|
||||
method,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
updateCurrentState(content: any) {
|
||||
const timestamp = new Date().getTime();
|
||||
this.storage.set('editor_state', {
|
||||
time: timestamp,
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
getSavedEditorState() {
|
||||
const saved = this.storage.get('editor_state');
|
||||
if (!saved) return;
|
||||
const { time, content } = saved;
|
||||
return { time, content };
|
||||
}
|
||||
|
||||
clearHistory() {
|
||||
this.getHistoryKeys().forEach(key => this.storage.delete(key));
|
||||
}
|
||||
}
|
||||
|
||||
export function createHistory(deps: { storage: Storage }) {
|
||||
return new History(deps.storage);
|
||||
}
|
|
@ -17,21 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import uiRoutes from 'ui/routes';
|
||||
import template from './index.html';
|
||||
|
||||
require('brace');
|
||||
|
||||
require('ui/autoload/styles');
|
||||
require('ui/capabilities/route_setup');
|
||||
|
||||
require('./src/controllers/sense_controller');
|
||||
require('./src/directives/sense_history');
|
||||
require('./src/directives/console_menu_directive');
|
||||
|
||||
|
||||
uiRoutes.when('/dev_tools/console', {
|
||||
requireUICapability: 'dev_tools.show',
|
||||
controller: 'SenseController',
|
||||
template,
|
||||
});
|
||||
export { createHistory, History } from './history';
|
||||
export { createStorage, Storage, StorageKeys } from './storage';
|
||||
export { createSettings, Settings, DevToolsSettings } from './settings';
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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 { Storage } from './index';
|
||||
|
||||
export interface DevToolsSettings {
|
||||
fontSize: number;
|
||||
wrapMode: boolean;
|
||||
autocomplete: {
|
||||
fields: boolean;
|
||||
indices: boolean;
|
||||
templates: boolean;
|
||||
};
|
||||
polling: boolean;
|
||||
tripleQuotes: boolean;
|
||||
}
|
||||
|
||||
export class Settings {
|
||||
private input: any | null = null;
|
||||
private output: any | null = null;
|
||||
|
||||
constructor(private readonly storage: Storage) {}
|
||||
|
||||
/**
|
||||
* TODO: Slight hackiness going on here - late registration of dependencies should be refactored
|
||||
*/
|
||||
registerInput(input: any) {
|
||||
this.input = input;
|
||||
}
|
||||
/**
|
||||
* TODO: Slight hackiness going on here - late registration of dependencies should be refactored
|
||||
*/
|
||||
registerOutput(output: any) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
getFontSize() {
|
||||
return this.storage.get('font_size', 14);
|
||||
}
|
||||
|
||||
setFontSize(size: any) {
|
||||
this.storage.set('font_size', size);
|
||||
this.applyCurrentSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
getWrapMode() {
|
||||
return this.storage.get('wrap_mode', true);
|
||||
}
|
||||
|
||||
setWrapMode(mode: any) {
|
||||
this.storage.set('wrap_mode', mode);
|
||||
this.applyCurrentSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
setTripleQuotes(tripleQuotes: any) {
|
||||
this.storage.set('triple_quotes', tripleQuotes);
|
||||
return true;
|
||||
}
|
||||
|
||||
getTripleQuotes() {
|
||||
return this.storage.get('triple_quotes', true);
|
||||
}
|
||||
|
||||
getAutocomplete() {
|
||||
return this.storage.get('autocomplete_settings', {
|
||||
fields: true,
|
||||
indices: true,
|
||||
templates: true,
|
||||
});
|
||||
}
|
||||
|
||||
setAutocomplete(settings: any) {
|
||||
this.storage.set('autocomplete_settings', settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
getPolling() {
|
||||
return this.storage.get('console_polling', true);
|
||||
}
|
||||
|
||||
setPolling(polling: any) {
|
||||
this.storage.set('console_polling', polling);
|
||||
this.applyCurrentSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
applyCurrentSettings(editor?: any) {
|
||||
if (typeof editor === 'undefined') {
|
||||
if (this.input) this.applyCurrentSettings(this.input);
|
||||
if (this.output) this.applyCurrentSettings(this.output);
|
||||
} else if (editor) {
|
||||
editor.getSession().setUseWrapMode(this.getWrapMode());
|
||||
editor.$el.css('font-size', this.getFontSize() + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentSettings(): DevToolsSettings {
|
||||
return {
|
||||
autocomplete: this.getAutocomplete(),
|
||||
wrapMode: this.getWrapMode(),
|
||||
tripleQuotes: this.getTripleQuotes(),
|
||||
fontSize: parseFloat(this.getFontSize()),
|
||||
polling: Boolean(this.getPolling()),
|
||||
};
|
||||
}
|
||||
|
||||
updateSettings({ fontSize, wrapMode, tripleQuotes, autocomplete, polling }: any) {
|
||||
this.setFontSize(fontSize);
|
||||
this.setWrapMode(wrapMode);
|
||||
this.setTripleQuotes(tripleQuotes);
|
||||
this.setAutocomplete(autocomplete);
|
||||
this.setPolling(polling);
|
||||
this.input.focus();
|
||||
return this.getCurrentSettings();
|
||||
}
|
||||
}
|
||||
|
||||
interface Deps {
|
||||
storage: Storage;
|
||||
}
|
||||
|
||||
export function createSettings({ storage }: Deps) {
|
||||
return new Settings(storage);
|
||||
}
|
|
@ -17,44 +17,47 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
const { transform, keys, startsWith } = require('lodash');
|
||||
import { transform, keys, startsWith } from 'lodash';
|
||||
|
||||
class Storage {
|
||||
constructor(engine, prefix) {
|
||||
this.engine = engine;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
type IStorageEngine = typeof window.localStorage;
|
||||
|
||||
encode(val) {
|
||||
export enum StorageKeys {
|
||||
WIDTH = 'widths',
|
||||
}
|
||||
|
||||
export class Storage {
|
||||
constructor(private readonly engine: IStorageEngine, private readonly prefix: string) {}
|
||||
|
||||
encode(val: any) {
|
||||
return JSON.stringify(val);
|
||||
}
|
||||
|
||||
decode(val) {
|
||||
decode(val: any) {
|
||||
if (typeof val === 'string') {
|
||||
return JSON.parse(val);
|
||||
}
|
||||
}
|
||||
|
||||
encodeKey(key) {
|
||||
encodeKey(key: string) {
|
||||
return `${this.prefix}${key}`;
|
||||
}
|
||||
|
||||
decodeKey(key) {
|
||||
decodeKey(key: string) {
|
||||
if (startsWith(key, this.prefix)) {
|
||||
return `${key.slice(this.prefix.length)}`;
|
||||
}
|
||||
}
|
||||
|
||||
set(key, val) {
|
||||
set(key: string, val: any) {
|
||||
this.engine.setItem(this.encodeKey(key), this.encode(val));
|
||||
return val;
|
||||
}
|
||||
|
||||
has(key) {
|
||||
has(key: string) {
|
||||
return this.engine.getItem(this.encodeKey(key)) != null;
|
||||
}
|
||||
|
||||
get(key, _default) {
|
||||
get<T>(key: string, _default?: T) {
|
||||
if (this.has(key)) {
|
||||
return this.decode(this.engine.getItem(this.encodeKey(key)));
|
||||
} else {
|
||||
|
@ -62,11 +65,11 @@ class Storage {
|
|||
}
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
delete(key: string) {
|
||||
return this.engine.removeItem(this.encodeKey(key));
|
||||
}
|
||||
|
||||
keys() {
|
||||
keys(): string[] {
|
||||
return transform(keys(this.engine), (ours, key) => {
|
||||
const ourKey = this.decodeKey(key);
|
||||
if (ourKey != null) ours.push(ourKey);
|
||||
|
@ -74,6 +77,6 @@ class Storage {
|
|||
}
|
||||
}
|
||||
|
||||
const instance = new Storage(window.localStorage, 'sense:');
|
||||
|
||||
export default instance;
|
||||
export function createStorage(deps: { engine: IStorageEngine; prefix: string }) {
|
||||
return new Storage(deps.engine, deps.prefix);
|
||||
}
|
0
src/legacy/core_plugins/console/np_ready/server/.gitkeep
Normal file
0
src/legacy/core_plugins/console/np_ready/server/.gitkeep
Normal file
21
src/legacy/core_plugins/console/public/README.md
Normal file
21
src/legacy/core_plugins/console/public/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
## New Platform (NP) Ready vs Quarantined
|
||||
|
||||
We want to move toward more modularised code in the Console app.
|
||||
Part of the effort means separating out different console components
|
||||
like:
|
||||
|
||||
- The language parser
|
||||
- The editor rendering component
|
||||
- Autocomplete
|
||||
- The UI container components
|
||||
|
||||
In addition to this effort we want to bring Console in line with NP
|
||||
requirements and ensure that we are not using angular and public ui
|
||||
in this app anymore.
|
||||
|
||||
The quarantined folder contains all of the code that has not been cleared
|
||||
for living in the new platform as it has not been properly refactored
|
||||
or has dependencies on, for example, UI public.
|
||||
|
||||
Over time, the quarantined part of the code should shrink to nothing
|
||||
and we should only have NP ready code.
|
|
@ -1,42 +0,0 @@
|
|||
<kbn-top-nav
|
||||
app-name="'console'"
|
||||
config="topNavMenu"
|
||||
></kbn-top-nav>
|
||||
<kbn-dev-tools-app data-test-subj="console">
|
||||
<sense-history ng-show="showHistory" is-shown="showHistory" close="closeHistory()" history-dirty="lastRequestTimestamp"></sense-history>
|
||||
<div class="conApp">
|
||||
<div class="conApp__editor">
|
||||
<ul class="conApp__autoComplete" id="autocomplete"></ul>
|
||||
<div class="conApp__editorActions" id="ConAppEditorActions">
|
||||
<kbn-tooltip text="{{:: 'console.sendRequestButtonTooltip' | i18n: { defaultMessage: 'click to send request' } }}">
|
||||
<button
|
||||
class="conApp__editorActionButton conApp__editorActionButton--success"
|
||||
ng-click="sendSelected()"
|
||||
data-test-subj="send-request-button">
|
||||
<i class="fa fa-play"></i>
|
||||
</button>
|
||||
</kbn-tooltip>
|
||||
<console-menu
|
||||
auto-indent="autoIndent"
|
||||
get-documentation="getDocumentation"
|
||||
open-documentation="openDocumentation"
|
||||
get-curl="getRequestsAsCURL"
|
||||
>
|
||||
</console-menu>
|
||||
</div>
|
||||
|
||||
<div class="conApp__editorContent" id="ConAppEditor" data-test-subj="request-editor">GET _search
|
||||
{
|
||||
"query": { "match_all": {} }
|
||||
}</div>
|
||||
|
||||
</div>
|
||||
<div class="conApp__resizer" id="ConAppResizer">︙</div>
|
||||
<div class="conApp__output" data-test-subj="response-editor">
|
||||
<div class="conApp__outputContent" id="ConAppOutput">{}</div>
|
||||
</div>
|
||||
</div>
|
||||
</kbn-dev-tools-app>
|
||||
<div id="consoleWelcomePanel"></div>
|
||||
<div id="consoleHelpPanel"></div>
|
||||
<div id="consoleSettingsModal"></div>
|
|
@ -1,19 +1,26 @@
|
|||
// TODO: Move all of the styles here (should be modularised by, e.g., CSS-in-JS or CSS modules).
|
||||
#consoleRoot {
|
||||
height: 100%;
|
||||
// Make sure the editor actions don't create scrollbars on this container
|
||||
// SASSTODO: Uncomment when tooltips are EUI-ified (inside portals)
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.consoleContainer {
|
||||
padding: $euiSizeS;
|
||||
}
|
||||
|
||||
.conApp {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
padding: $euiSizeS;
|
||||
// Make sure the editor actions don't create scrollbars on this container
|
||||
// SASSTODO: Uncomment when tooltips are EUI-ified (inside portals)
|
||||
// overflow: hidden;
|
||||
}
|
||||
|
||||
.conApp__editor {
|
||||
// Default size of left side is half the large breakpoint
|
||||
// but this is inline overridden by the resizer
|
||||
width: map-get($euiBreakpoints, "l") / 2;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
|
||||
// Required on IE11 to render ace editor correctly after first input.
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
@ -24,6 +31,7 @@
|
|||
|
||||
.conApp__editorContent,
|
||||
.conApp__outputContent {
|
||||
height: 100%;
|
||||
flex: 1 1 1px;
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ DevToolsRegistryProvider.register(() => ({
|
|||
order: 1,
|
||||
name: 'console',
|
||||
display: i18n.translate('console.consoleDisplayName', {
|
||||
defaultMessage: 'Console'
|
||||
defaultMessage: 'Console',
|
||||
}),
|
||||
url: '#/dev_tools/console'
|
||||
url: '#/dev_tools/console',
|
||||
}));
|
|
@ -0,0 +1,3 @@
|
|||
<kbn-dev-tools-app data-test-subj="console">
|
||||
<div id="consoleRoot" data-ng-init="initReactApp()"></div>
|
||||
</kbn-dev-tools-app>
|
|
@ -20,10 +20,11 @@
|
|||
import sinon from 'sinon';
|
||||
import $ from 'jquery';
|
||||
|
||||
import history from '../history';
|
||||
import mappings from '../mappings';
|
||||
import init from '../app';
|
||||
|
||||
const history = { getSavedEditorState() {}, };
|
||||
|
||||
describe('console app initialization', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
|
@ -33,7 +34,6 @@ describe('console app initialization', () => {
|
|||
beforeEach(() => {
|
||||
ajaxDoneStub = sinon.stub();
|
||||
sandbox.stub($, 'ajax').returns({ done: ajaxDoneStub });
|
||||
sandbox.stub(history, 'getSavedEditorState');
|
||||
sandbox.stub(mappings, 'retrieveAutoCompleteInfo');
|
||||
|
||||
inputMock = {
|
||||
|
@ -41,11 +41,11 @@ describe('console app initialization', () => {
|
|||
moveToNextRequestEdge: sinon.stub(),
|
||||
highlightCurrentRequestsAndUpdateActionBar: sinon.stub(),
|
||||
updateActionsBar: sinon.stub(),
|
||||
getSession: sinon.stub().returns({ on() {} })
|
||||
getSession: sinon.stub().returns({ on() {} }),
|
||||
};
|
||||
|
||||
outputMock = {
|
||||
update: sinon.stub()
|
||||
update: sinon.stub(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -57,13 +57,13 @@ describe('console app initialization', () => {
|
|||
const mockContent = {};
|
||||
ajaxDoneStub.yields(mockContent);
|
||||
|
||||
init(inputMock, outputMock, 'https://state.link.com/content');
|
||||
init(inputMock, outputMock, history, 'https://state.link.com/content');
|
||||
|
||||
sinon.assert.calledOnce($.ajax);
|
||||
sinon.assert.calledWithExactly($.ajax, {
|
||||
url: 'https://state.link.com/content',
|
||||
dataType: 'text',
|
||||
kbnXsrfToken: false
|
||||
kbnXsrfToken: false,
|
||||
});
|
||||
|
||||
sinon.assert.calledTwice(inputMock.moveToNextRequestEdge);
|
||||
|
@ -81,14 +81,14 @@ describe('console app initialization', () => {
|
|||
const mockContent = {};
|
||||
ajaxDoneStub.yields(mockContent);
|
||||
|
||||
init(inputMock, outputMock, 'https://api.github.com/content');
|
||||
init(inputMock, outputMock, history, 'https://api.github.com/content');
|
||||
|
||||
sinon.assert.calledOnce($.ajax);
|
||||
sinon.assert.calledWithExactly($.ajax, {
|
||||
url: 'https://api.github.com/content',
|
||||
dataType: 'text',
|
||||
kbnXsrfToken: false,
|
||||
headers: { Accept: 'application/vnd.github.v3.raw' }
|
||||
headers: { Accept: 'application/vnd.github.v3.raw' },
|
||||
});
|
||||
|
||||
sinon.assert.calledTwice(inputMock.moveToNextRequestEdge);
|
|
@ -18,18 +18,21 @@
|
|||
*/
|
||||
|
||||
const $ = require('jquery');
|
||||
|
||||
const history = require('./history');
|
||||
const mappings = require('./mappings');
|
||||
|
||||
export default function init(input, output, sourceLocation = 'stored') {
|
||||
const DEFAULT_INPUT_VALUE = `GET _search
|
||||
{
|
||||
"query": {
|
||||
"match_all": {}
|
||||
}
|
||||
}`;
|
||||
|
||||
export default function init(input, output, history, sourceLocation = 'stored') {
|
||||
$(document.body).removeClass('fouc');
|
||||
|
||||
// set the value of the input and clear the output
|
||||
function resetToValues(content) {
|
||||
if (content != null) {
|
||||
input.update(content);
|
||||
}
|
||||
input.update(content != null ? content : DEFAULT_INPUT_VALUE);
|
||||
output.update('');
|
||||
}
|
||||
|
||||
|
@ -49,8 +52,7 @@ export default function init(input, output, sourceLocation = 'stored') {
|
|||
try {
|
||||
const content = input.getValue();
|
||||
history.updateCurrentState(content);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log('Ignoring saving error: ' + e);
|
||||
}
|
||||
}
|
||||
|
@ -60,76 +62,34 @@ export default function init(input, output, sourceLocation = 'stored') {
|
|||
if (sourceLocation === 'stored') {
|
||||
if (previousSaveState) {
|
||||
resetToValues(previousSaveState.content);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
resetToValues();
|
||||
input.autoIndent();
|
||||
}
|
||||
}
|
||||
else if (/^https?:\/\//.test(sourceLocation)) {
|
||||
} else if (/^https?:\/\//.test(sourceLocation)) {
|
||||
const loadFrom = {
|
||||
url: sourceLocation,
|
||||
// Having dataType here is required as it doesn't allow jQuery to `eval` content
|
||||
// coming from the external source thereby preventing XSS attack.
|
||||
dataType: 'text',
|
||||
kbnXsrfToken: false
|
||||
kbnXsrfToken: false,
|
||||
};
|
||||
|
||||
if (/https?:\/\/api.github.com/.test(sourceLocation)) {
|
||||
loadFrom.headers = { Accept: 'application/vnd.github.v3.raw' };
|
||||
}
|
||||
|
||||
$.ajax(loadFrom).done((data) => {
|
||||
$.ajax(loadFrom).done(data => {
|
||||
resetToValues(data);
|
||||
input.moveToNextRequestEdge(true);
|
||||
input.highlightCurrentRequestsAndUpdateActionBar();
|
||||
input.updateActionsBar();
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
resetToValues();
|
||||
}
|
||||
input.moveToNextRequestEdge(true);
|
||||
}
|
||||
|
||||
// stupid simple restore function, called when the user
|
||||
// chooses to restore a request from the history
|
||||
// PREVENTS history from needing to know about the input
|
||||
history.restoreFromHistory = function applyHistoryElem(req) {
|
||||
const session = input.getSession();
|
||||
let pos = input.getCursorPosition();
|
||||
let prefix = '';
|
||||
let suffix = '\n';
|
||||
if (input.parser.isStartRequestRow(pos.row)) {
|
||||
pos.column = 0;
|
||||
suffix += '\n';
|
||||
}
|
||||
else if (input.parser.isEndRequestRow(pos.row)) {
|
||||
const line = session.getLine(pos.row);
|
||||
pos.column = line.length;
|
||||
prefix = '\n\n';
|
||||
}
|
||||
else if (input.parser.isInBetweenRequestsRow(pos.row)) {
|
||||
pos.column = 0;
|
||||
}
|
||||
else {
|
||||
pos = input.nextRequestEnd(pos);
|
||||
prefix = '\n\n';
|
||||
}
|
||||
|
||||
let s = prefix + req.method + ' ' + req.endpoint;
|
||||
if (req.data) {
|
||||
s += '\n' + req.data;
|
||||
}
|
||||
|
||||
s += suffix;
|
||||
|
||||
session.insert(pos, s);
|
||||
input.clearSelection();
|
||||
input.moveCursorTo(pos.row + prefix.length, 0);
|
||||
input.focus();
|
||||
};
|
||||
|
||||
setupAutosave();
|
||||
loadSavedState();
|
||||
mappings.retrieveAutoCompleteInfo();
|
|
@ -28,7 +28,6 @@ import { populateContext } from './autocomplete/engine';
|
|||
import { URL_PATH_END_MARKER } from './autocomplete/components';
|
||||
import _ from 'lodash';
|
||||
import ace from 'brace';
|
||||
import 'brace/ext/language_tools';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const AceRange = ace.acequire('ace/range').Range;
|
||||
|
@ -914,30 +913,34 @@ export default function (editor) {
|
|||
callback(null, []);
|
||||
}
|
||||
else {
|
||||
const terms = _.map(context.autoCompleteSet, function (term) {
|
||||
if (typeof term !== 'object') {
|
||||
term = {
|
||||
name: term
|
||||
const terms = _.map(
|
||||
context
|
||||
.autoCompleteSet
|
||||
.filter(term => Boolean(term) && term.name != null),
|
||||
function (term) {
|
||||
if (typeof term !== 'object') {
|
||||
term = {
|
||||
name: term
|
||||
};
|
||||
} else {
|
||||
term = _.clone(term);
|
||||
}
|
||||
const defaults = {
|
||||
value: term.name,
|
||||
meta: 'API',
|
||||
score: 0,
|
||||
context: context,
|
||||
};
|
||||
} else {
|
||||
term = _.clone(term);
|
||||
}
|
||||
const defaults = {
|
||||
value: term.name,
|
||||
meta: 'API',
|
||||
score: 0,
|
||||
context: context,
|
||||
};
|
||||
// we only need out custom insertMatch behavior for the body
|
||||
if (context.autoCompleteType === 'body') {
|
||||
defaults.completer = {
|
||||
insertMatch: function () {
|
||||
return applyTerm(term);
|
||||
}
|
||||
};
|
||||
}
|
||||
return _.defaults(term, defaults);
|
||||
});
|
||||
// we only need out custom insertMatch behavior for the body
|
||||
if (context.autoCompleteType === 'body') {
|
||||
defaults.completer = {
|
||||
insertMatch: function () {
|
||||
return applyTerm(term);
|
||||
}
|
||||
};
|
||||
}
|
||||
return _.defaults(term, defaults);
|
||||
});
|
||||
|
||||
terms.sort(function (t1, t2) {
|
||||
/* score sorts from high to low */
|
|
@ -8,27 +8,22 @@
|
|||
.conHistory__body {
|
||||
display: flex;
|
||||
height: $euiSizeXL * 10;
|
||||
> ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.conHistory__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: $euiSize;
|
||||
}
|
||||
|
||||
.conHistory__footerButtonsRight {
|
||||
text-align: right;
|
||||
.conHistory__body__spacer {
|
||||
flex: 0 0 1%;
|
||||
}
|
||||
|
||||
.conHistory__reqs,
|
||||
.conHistory__viewer {
|
||||
flex: 0 0 50%;
|
||||
flex: 0 0 49.5%;
|
||||
}
|
||||
|
||||
.conHistory__reqs {
|
||||
overflow: auto;
|
||||
margin-right: $euiSizeL;
|
||||
}
|
||||
|
||||
.conHistory__req {
|
|
@ -17,81 +17,49 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
require('brace');
|
||||
require('brace/ext/searchbox');
|
||||
import Autocomplete from './autocomplete';
|
||||
import Autocomplete from './autocomplete';
|
||||
import mappings from './mappings';
|
||||
const SenseEditor = require('./sense_editor/editor');
|
||||
const settings = require('./settings');
|
||||
const utils = require('./utils');
|
||||
const es = require('./es');
|
||||
const history = require('./history');
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
||||
let input;
|
||||
export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocumentation = () => {}) {
|
||||
export function initializeInput($el, $actionsEl, history, settings) {
|
||||
input = new SenseEditor($el);
|
||||
|
||||
// this may not exist if running from tests
|
||||
if (uiModules) {
|
||||
const appSense = uiModules.get('app/sense');
|
||||
if (appSense && appSense.setupResizeCheckerForRootEditors) {
|
||||
appSense.setupResizeCheckerForRootEditors($el, input, output);
|
||||
}
|
||||
}
|
||||
|
||||
input.autocomplete = new Autocomplete(input);
|
||||
|
||||
input.$actions = $actionsEl;
|
||||
|
||||
input.commands.addCommand({
|
||||
name: 'auto indent request',
|
||||
bindKey: { win: 'Ctrl-I', mac: 'Command-I' },
|
||||
exec: function () {
|
||||
input.autoIndent();
|
||||
}
|
||||
});
|
||||
input.commands.addCommand({
|
||||
name: 'move to previous request start or end',
|
||||
bindKey: { win: 'Ctrl-Up', mac: 'Command-Up' },
|
||||
exec: function () {
|
||||
input.moveToPreviousRequestEdge();
|
||||
}
|
||||
});
|
||||
input.commands.addCommand({
|
||||
name: 'move to next request start or end',
|
||||
bindKey: { win: 'Ctrl-Down', mac: 'Command-Down' },
|
||||
exec: function () {
|
||||
input.moveToNextRequestEdge();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Setup the "send" shortcut
|
||||
*/
|
||||
|
||||
let CURRENT_REQ_ID = 0;
|
||||
|
||||
function sendCurrentRequestToES(addedToHistoryCb) {
|
||||
|
||||
function sendCurrentRequestToES(addedToHistoryCb, output) {
|
||||
const reqId = ++CURRENT_REQ_ID;
|
||||
|
||||
input.getRequestsInRange(function (requests) {
|
||||
input.getRequestsInRange(requests => {
|
||||
if (reqId !== CURRENT_REQ_ID) {
|
||||
return;
|
||||
}
|
||||
output.update('');
|
||||
if (output) {
|
||||
output.update('');
|
||||
}
|
||||
|
||||
if (requests.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isMultiRequest = requests.length > 1;
|
||||
const finishChain = function () { /* noop */ };
|
||||
const finishChain = () => {
|
||||
/* noop */
|
||||
};
|
||||
|
||||
let isFirstRequest = true;
|
||||
|
||||
const sendNextRequest = function () {
|
||||
const sendNextRequest = () => {
|
||||
if (reqId !== CURRENT_REQ_ID) {
|
||||
return;
|
||||
}
|
||||
|
@ -107,7 +75,7 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
esData += '\n';
|
||||
} //append a new line for bulk requests.
|
||||
|
||||
es.send(esMethod, esPath, esData).always(function (dataOrjqXHR, textStatus, jqXhrORerrorThrown) {
|
||||
es.send(esMethod, esPath, esData).always((dataOrjqXHR, textStatus, jqXhrORerrorThrown) => {
|
||||
if (reqId !== CURRENT_REQ_ID) {
|
||||
return;
|
||||
}
|
||||
|
@ -117,14 +85,14 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
function modeForContentType(contentType) {
|
||||
if (contentType.indexOf('text/plain') >= 0) {
|
||||
return 'ace/mode/text';
|
||||
}
|
||||
else if (contentType.indexOf('application/yaml') >= 0) {
|
||||
} else if (contentType.indexOf('application/yaml') >= 0) {
|
||||
return 'ace/mode/yaml';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const isSuccess = typeof xhr.status === 'number' &&
|
||||
const isSuccess =
|
||||
typeof xhr.status === 'number' &&
|
||||
// Things like DELETE index where the index is not there are OK.
|
||||
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404);
|
||||
|
||||
|
@ -151,8 +119,7 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
// assume json - auto pretty
|
||||
try {
|
||||
value = utils.expandLiteralStrings(value);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
// nothing to do here
|
||||
}
|
||||
}
|
||||
|
@ -166,12 +133,15 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
if (isMultiRequest) {
|
||||
value = '# ' + req.method + ' ' + req.url + '\n' + value;
|
||||
}
|
||||
if (isFirstRequest) {
|
||||
output.update(value, mode);
|
||||
}
|
||||
else {
|
||||
output.append('\n' + value);
|
||||
|
||||
if (output) {
|
||||
if (isFirstRequest) {
|
||||
output.update(value, mode);
|
||||
} else {
|
||||
output.append('\n' + value);
|
||||
}
|
||||
}
|
||||
|
||||
isFirstRequest = false;
|
||||
// single request terminate via sendNextRequest as well
|
||||
sendNextRequest();
|
||||
|
@ -184,8 +154,7 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
if (value[0] === '{') {
|
||||
try {
|
||||
value = JSON.stringify(JSON.parse(value), null, 2);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
// nothing to do here
|
||||
}
|
||||
}
|
||||
|
@ -196,11 +165,12 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
if (isMultiRequest) {
|
||||
value = '# ' + req.method + ' ' + req.url + '\n' + value;
|
||||
}
|
||||
if (isFirstRequest) {
|
||||
output.update(value, mode);
|
||||
}
|
||||
else {
|
||||
output.append('\n' + value);
|
||||
if (output) {
|
||||
if (isFirstRequest) {
|
||||
output.update(value, mode);
|
||||
} else {
|
||||
output.append('\n' + value);
|
||||
}
|
||||
}
|
||||
finishChain();
|
||||
}
|
||||
|
@ -211,20 +181,6 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
input.commands.addCommand({
|
||||
name: 'send to elasticsearch',
|
||||
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
|
||||
exec: () => sendCurrentRequestToES()
|
||||
});
|
||||
input.commands.addCommand({
|
||||
name: 'open documentation',
|
||||
bindKey: { win: 'Ctrl-/', mac: 'Command-/' },
|
||||
exec: () => {
|
||||
openDocumentation();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Init the editor
|
||||
*/
|
||||
|
@ -235,7 +191,6 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
|
|||
input.highlightCurrentRequestsAndUpdateActionBar();
|
||||
|
||||
input.sendCurrentRequestToES = sendCurrentRequestToES;
|
||||
require('./input_resize')(input, output);
|
||||
|
||||
return input;
|
||||
}
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { legacyBackDoorToSettings } from '../../../np_ready/public/application';
|
||||
|
||||
const $ = require('jquery');
|
||||
const _ = require('lodash');
|
||||
const es = require('./es');
|
||||
const settings = require('./settings');
|
||||
|
||||
// NOTE: If this value ever changes to be a few seconds or less, it might introduce flakiness
|
||||
// due to timing issues in our app.js tests.
|
||||
|
@ -257,7 +258,7 @@ function clear() {
|
|||
}
|
||||
|
||||
function retrieveSettings(settingsKey, settingsToRetrieve) {
|
||||
const currentSettings = settings.getAutocomplete();
|
||||
const currentSettings = legacyBackDoorToSettings().getAutocomplete();
|
||||
const settingKeyToPathMap = {
|
||||
fields: '_mapping',
|
||||
indices: '_aliases',
|
||||
|
@ -289,7 +290,7 @@ function retrieveSettings(settingsKey, settingsToRetrieve) {
|
|||
// unchanged alone (both selected and unselected).
|
||||
// 3. Poll: Use saved. Fetch selected. Ignore unselected.
|
||||
|
||||
function retrieveAutoCompleteInfo(settingsToRetrieve = settings.getAutocomplete()) {
|
||||
function retrieveAutoCompleteInfo(settingsToRetrieve = legacyBackDoorToSettings().getAutocomplete()) {
|
||||
if (pollTimeoutId) {
|
||||
clearTimeout(pollTimeoutId);
|
||||
}
|
||||
|
@ -329,7 +330,7 @@ function retrieveAutoCompleteInfo(settingsToRetrieve = settings.getAutocomplete(
|
|||
pollTimeoutId = setTimeout(() => {
|
||||
// This looks strange/inefficient, but it ensures correct behavior because we don't want to send
|
||||
// a scheduled request if the user turns off polling.
|
||||
if (settings.getPolling()) {
|
||||
if (legacyBackDoorToSettings().getPolling()) {
|
||||
retrieveAutoCompleteInfo();
|
||||
}
|
||||
}, POLL_INTERVAL);
|
|
@ -19,19 +19,18 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
const ace = require('brace');
|
||||
const settings = require('./settings');
|
||||
const OutputMode = require('./sense_editor/mode/output');
|
||||
const smartResize = require('./smart_resize');
|
||||
|
||||
let output;
|
||||
export function initializeOutput($el) {
|
||||
export function initializeOutput($el, settings) {
|
||||
output = ace.acequire('ace/ace').edit($el[0]);
|
||||
|
||||
const outputMode = new OutputMode.Mode();
|
||||
|
||||
output.$blockScrolling = Infinity;
|
||||
output.resize = smartResize(output);
|
||||
output.update = function (val, mode, cb) {
|
||||
output.update = (val, mode, cb) => {
|
||||
if (typeof mode === 'function') {
|
||||
cb = mode;
|
||||
mode = void 0;
|
||||
|
@ -39,14 +38,14 @@ export function initializeOutput($el) {
|
|||
|
||||
const session = output.getSession();
|
||||
|
||||
session.setMode(val ? (mode || outputMode) : 'ace/mode/text');
|
||||
session.setMode(val ? mode || outputMode : 'ace/mode/text');
|
||||
session.setValue(val);
|
||||
if (typeof cb === 'function') {
|
||||
setTimeout(cb);
|
||||
}
|
||||
};
|
||||
|
||||
output.append = function (val, foldPrevious, cb) {
|
||||
output.append = (val, foldPrevious, cb) => {
|
||||
if (typeof foldPrevious === 'function') {
|
||||
cb = foldPrevious;
|
||||
foldPrevious = true;
|
||||
|
@ -68,12 +67,13 @@ export function initializeOutput($el) {
|
|||
|
||||
output.$el = $el;
|
||||
|
||||
(function (session) {
|
||||
// eslint-disable-next-line
|
||||
(function setupSession(session) {
|
||||
session.setMode('ace/mode/text');
|
||||
session.setFoldStyle('markbeginend');
|
||||
session.setTabSize(2);
|
||||
session.setUseWrapMode(true);
|
||||
}(output.getSession()));
|
||||
})(output.getSession());
|
||||
|
||||
output.setShowPrintMargin(false);
|
||||
output.setReadOnly(true);
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
const ace = require('brace');
|
||||
import ace from 'brace';
|
||||
require('./output_highlight_rules');
|
||||
|
||||
|
|
@ -21,8 +21,8 @@ import { get, throttle } from 'lodash';
|
|||
|
||||
export default function (editor) {
|
||||
const resize = editor.resize;
|
||||
const throttledResize = throttle(() => {
|
||||
|
||||
const throttledResize = throttle(() => {
|
||||
resize.call(editor);
|
||||
|
||||
// Keep current top line in view when resizing to avoid losing user context
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue