Skip to content

Commit

Permalink
[Code] Embedded Code Snippet Component (#47183)
Browse files Browse the repository at this point in the history
* Add Integrations page and POC CodeBlock usage

* Adds new route hidden behind a config var
(`xpack.code.integrations.enabld`)
* Updates CodeBlock component to have a smaller API surface
* Adds several example components for various pieces of the new APM
integration designs.

* Pare down stub data

Hits and ranges were both part of our search results queries; O11y won't
have that information.
  • Loading branch information
rylnd authored Oct 5, 2019
1 parent 7d96a13 commit c505fcb
Show file tree
Hide file tree
Showing 14 changed files with 434 additions and 69 deletions.
4 changes: 4 additions & 0 deletions x-pack/legacy/plugins/code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const code = (kibana: any) =>
const config = server.config();
return {
codeUiEnabled: config.get('xpack.code.ui.enabled'),
codeIntegrationsEnabled: config.get('xpack.code.integrations.enabled'),
};
},
hacks: ['plugins/code/hacks/toggle_app_link_in_nav'],
Expand All @@ -49,6 +50,9 @@ export const code = (kibana: any) =>
ui: Joi.object({
enabled: Joi.boolean().default(true),
}).default(),
integrations: Joi.object({
enabled: Joi.boolean().default(false),
}).default(),
enabled: Joi.boolean().default(true),
}).default();
},
Expand Down
7 changes: 7 additions & 0 deletions x-pack/legacy/plugins/code/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import React from 'react';
import { HashRouter as Router, Redirect, Switch } from 'react-router-dom';

import chrome from 'ui/chrome';
import { connect } from 'react-redux';
import { RootState } from '../reducers';
import { Admin } from './admin_page/admin';
Expand All @@ -17,6 +18,7 @@ import { NotFound } from './main/not_found';
import { Route } from './route';
import * as ROUTES from './routes';
import { Search } from './search_page/search';
import { Integrations } from './integrations';

const RooComponent = (props: { setupOk?: boolean }) => {
if (props.setupOk) {
Expand All @@ -33,6 +35,8 @@ const Root = connect(mapStateToProps)(RooComponent);

const Empty = () => null;

const integrationsEnabled = chrome.getInjected('codeIntegrationsEnabled');

export const App = () => {
return (
<Router>
Expand All @@ -45,6 +49,9 @@ export const App = () => {
<Route path={ROUTES.SEARCH} component={Search} />
<Route path={ROUTES.REPO} render={Empty} exact={true} />
<Route path={ROUTES.SETUP} component={SetupGuide} exact={true} />
{integrationsEnabled && (
<Route path={ROUTES.INTEGRATIONS} component={Integrations} exact={true} />
)}
<Route path="*" component={NotFound} />
</Switch>
</Router>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,72 @@
*/

import { EuiPanel } from '@elastic/eui';
import { editor, IPosition, IRange } from 'monaco-editor';
import { editor, IRange } from 'monaco-editor';
import React from 'react';

import { ResizeChecker } from '../shared/resize_checker';
import { monaco } from '../../monaco/monaco';
import { registerEditor } from '../../monaco/single_selection_helper';

interface Props {
code: string;
fileComponent?: React.ReactNode;
startLine?: number;
language?: string;
highlightRanges?: IRange[];
onClick?: (event: IPosition) => void;
export interface Position {
lineNumber: string;
column: number;
}

export interface Props {
content: string;
header: React.ReactNode;
language: string;
highlightRanges: IRange[];
onClick: (event: Position) => void;
folding: boolean;
lineNumbersFunc: (line: number) => string;
/**
* Returns the line number to display for a given line.
* @param lineIndex The index of the given line (0-indexed)
*/
lineNumber: (lineIndex: number) => string;
className?: string;
}

export class CodeBlock extends React.PureComponent<Props> {
static defaultProps = {
header: undefined,
folding: false,
highlightRanges: [],
language: 'text',
lineNumber: String,
onClick: () => {},
};

private el: HTMLDivElement | null = null;
private ed?: editor.IStandaloneCodeEditor;
private resizeChecker?: ResizeChecker;
private currentHighlightDecorations: string[] = [];

public async componentDidMount() {
const { content, highlightRanges, language, onClick } = this.props;

if (this.el) {
await this.tryLoadFile(this.props.code, this.props.language || 'text');
await this.tryLoadFile(content, language);
this.ed!.onMouseDown((e: editor.IEditorMouseEvent) => {
if (
this.props.onClick &&
onClick &&
(e.target.type === monaco.editor.MouseTargetType.GUTTER_LINE_NUMBERS ||
e.target.type === monaco.editor.MouseTargetType.CONTENT_TEXT)
) {
const position = e.target.position || { lineNumber: 0, column: 0 };
const lineNumber = (this.props.startLine || 0) + position.lineNumber;
this.props.onClick({
const lineNumber = this.lineNumber(position.lineNumber);

onClick({
lineNumber,
column: position.column,
});
}
});
registerEditor(this.ed!);
if (this.props.highlightRanges) {
const decorations = this.props.highlightRanges.map((range: IRange) => {

if (highlightRanges.length) {
const decorations = highlightRanges.map((range: IRange) => {
return {
range,
options: {
Expand All @@ -66,6 +88,7 @@ export class CodeBlock extends React.PureComponent<Props> {
});
}
}

private async tryLoadFile(code: string, language: string) {
try {
await monaco.editor.colorize(code, language, {});
Expand All @@ -79,7 +102,7 @@ export class CodeBlock extends React.PureComponent<Props> {
this.ed = monaco.editor.create(this.el!, {
value: code,
language,
lineNumbers: this.lineNumbersFunc.bind(this),
lineNumbers: this.lineNumber,
readOnly: true,
folding: this.props.folding,
minimap: {
Expand All @@ -103,17 +126,16 @@ export class CodeBlock extends React.PureComponent<Props> {
}

public componentDidUpdate(prevProps: Readonly<Props>) {
if (
prevProps.code !== this.props.code ||
prevProps.highlightRanges !== this.props.highlightRanges
) {
const { content, highlightRanges } = this.props;

if (prevProps.content !== content || prevProps.highlightRanges !== highlightRanges) {
if (this.ed) {
const model = this.ed.getModel();
if (model) {
model.setValue(this.props.code);
model.setValue(content);

if (this.props.highlightRanges) {
const decorations = this.props.highlightRanges!.map((range: IRange) => {
if (highlightRanges.length) {
const decorations = highlightRanges!.map((range: IRange) => {
return {
range,
options: {
Expand All @@ -138,19 +160,20 @@ export class CodeBlock extends React.PureComponent<Props> {
}

public render() {
const linesCount = this.props.code.split('\n').length;
const { className, header } = this.props;
const height = this.lines.length * 18;

return (
<EuiPanel style={{ marginBottom: '2rem' }} paddingSize="s">
{this.props.fileComponent}
<div ref={r => (this.el = r)} style={{ height: linesCount * 18 }} />
<EuiPanel paddingSize="s" className={className}>
{header}
<div ref={r => (this.el = r)} style={{ height }} />
</EuiPanel>
);
}

private lineNumbersFunc = (line: number) => {
if (this.props.lineNumbersFunc) {
return this.props.lineNumbersFunc(line);
}
return `${(this.props.startLine || 0) + line}`;
};
private lineNumber = (lineIndex: number) => this.props.lineNumber(lineIndex - 1);

private get lines(): string[] {
return this.props.content.split('\n');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import {
EuiTitle,
} from '@elastic/eui';
import classname from 'classnames';
import { IPosition } from 'monaco-editor';
import queryString from 'querystring';
import React from 'react';
import { parseSchema } from '../../../common/uri_util';
import { GroupedFileResults, GroupedRepoResults } from '../../actions';
import { history } from '../../utils/url';
import { CodeBlock } from '../codeblock/codeblock';
import { CodeBlock, Position } from '../codeblock/codeblock';

interface Props {
isLoading: boolean;
Expand Down Expand Up @@ -114,12 +113,9 @@ export class ReferencesPanel extends React.Component<Props, State> {

private renderReference(file: GroupedFileResults) {
const key = `${file.uri}`;
const lineNumberFn = (l: number) => {
return file.lineNumbers[l - 1];
};
const fileComponent = (
const header = (
<React.Fragment>
<EuiText>
<EuiText size="s">
<a href={`#${this.computeUrl(file.uri)}`}>{file.file}</a>
</EuiText>
<EuiSpacer size="s" />
Expand All @@ -128,23 +124,22 @@ export class ReferencesPanel extends React.Component<Props, State> {

return (
<CodeBlock
className="referencesPanel__code-block"
key={key}
header={header}
content={file.code}
language={file.language}
startLine={0}
code={file.code}
folding={false}
lineNumbersFunc={lineNumberFn}
lineNumber={i => file.lineNumbers[i]}
highlightRanges={file.highlights}
fileComponent={fileComponent}
onClick={this.onCodeClick.bind(this, file.lineNumbers, file.uri)}
onClick={this.onCodeClick(file.uri)}
/>
);
}

private onCodeClick(lineNumbers: string[], url: string, pos: IPosition) {
const line = parseInt(lineNumbers[pos.lineNumber - 1], 10);
history.push(this.computeUrl(url, line));
}
private onCodeClick = (url: string) => (position: Position) => {
const lineNum = parseInt(position.lineNumber, 10);
history.push(this.computeUrl(url, lineNum));
};

private computeUrl(url: string, line?: number) {
const { uri } = parseSchema(url)!;
Expand All @@ -158,6 +153,7 @@ export class ReferencesPanel extends React.Component<Props, State> {
tab: 'references',
refUrl: this.props.refUrl,
});

return line !== undefined ? `${uri}!L${line}:0?${query}` : `${uri}?${query}`;
}
}
Loading

0 comments on commit c505fcb

Please sign in to comment.