Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ES|QL] Comment parsing and pretty-printing #192173

Merged
merged 56 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d996c76
comment parsing and pretty-printing
vadimkibana Sep 5, 2024
3cd5b15
assign comments correctly to binary expression operands
vadimkibana Sep 6, 2024
a8c461f
improve tests
vadimkibana Sep 6, 2024
2027672
ability to add top comment to binary expression left operand
vadimkibana Sep 6, 2024
8538a7c
support top decorations on second bin exp operand
vadimkibana Sep 6, 2024
2b2935b
correctly indent rename expression top comments
vadimkibana Sep 9, 2024
2dfa74e
add argument surround text
vadimkibana Sep 9, 2024
eaf8152
add rename expression operand comment test
vadimkibana Sep 9, 2024
5cd78c2
setup storybook
vadimkibana Sep 10, 2024
4ef2f14
add slate dependencies
vadimkibana Sep 10, 2024
2d4d0a9
add slate-history package
vadimkibana Sep 10, 2024
bc30d0c
remove slate packages
vadimkibana Sep 10, 2024
37b7c3c
add flexible input component
vadimkibana Sep 10, 2024
0084e3f
add annotations component
vadimkibana Sep 10, 2024
a4a3689
setup inspector state and context
vadimkibana Sep 10, 2024
02da474
connect state and create two panel layout
vadimkibana Sep 10, 2024
4ece700
show ast and tokens
vadimkibana Sep 10, 2024
5b7f100
render token table
vadimkibana Sep 10, 2024
ff7a5e6
improve demo
vadimkibana Sep 10, 2024
028cbec
render sources
vadimkibana Sep 10, 2024
631d759
add limit panel
vadimkibana Sep 10, 2024
32078ce
improve demo
vadimkibana Sep 10, 2024
b5e0812
edit sources from ui
vadimkibana Sep 11, 2024
a865501
add ability to add sources from ui
vadimkibana Sep 11, 2024
59e3812
make limit editable
vadimkibana Sep 11, 2024
b63ae38
support more comment formatting cases
vadimkibana Sep 11, 2024
d3d7123
setup highlighting css
vadimkibana Sep 11, 2024
e1535ae
change annotation format
vadimkibana Sep 11, 2024
0fcb6d1
add ability to walk all comments
vadimkibana Sep 11, 2024
7b58c03
add syntax highlighting
vadimkibana Sep 11, 2024
dc70aaf
color literals
vadimkibana Sep 11, 2024
cfa2e5c
improve highlighting
vadimkibana Sep 11, 2024
1d6479a
highlight command options
vadimkibana Sep 11, 2024
5193a9c
add concept of focused node
vadimkibana Sep 11, 2024
2c0c55f
improve highlighting
vadimkibana Sep 11, 2024
d08cd82
make limit hoverable
vadimkibana Sep 11, 2024
feda18e
add ability to remove nodes
vadimkibana Sep 11, 2024
b04caef
improve comment from top assignment
vadimkibana Sep 19, 2024
5883609
make inspector layout vertical
vadimkibana Sep 19, 2024
1b3897c
improve ast view
vadimkibana Sep 19, 2024
20ed0d2
add formatter tab
vadimkibana Sep 19, 2024
47d0e7a
update example plugin
vadimkibana Sep 19, 2024
2bb105f
add wrapping formatter controls
vadimkibana Sep 19, 2024
397d4bc
improve wrapping printer
vadimkibana Sep 19, 2024
c8a550d
improve pretty printer components
vadimkibana Sep 19, 2024
408861a
Merge branch 'comment-printer-7' into comment-printer-5
vadimkibana Sep 19, 2024
6501ad4
merge upstream
vadimkibana Sep 19, 2024
5cdc9d6
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Sep 19, 2024
9db451a
use latest parser api in worker
vadimkibana Sep 20, 2024
8279bdb
update ignored error message
vadimkibana Sep 20, 2024
75d43c7
Merge remote-tracking branch 'upstream/main' into comment-printer-5
vadimkibana Sep 20, 2024
935a796
fix typecheck error
vadimkibana Sep 20, 2024
4ab3f76
Merge branch 'main' into comment-printer-5
elasticmachine Sep 20, 2024
388bb48
Merge branch 'main' into comment-printer-5
stratoula Sep 25, 2024
66ea24b
remove unused props
vadimkibana Sep 25, 2024
2e73f1f
Merge remote-tracking branch 'upstream/main' into comment-printer-5
vadimkibana Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/esql_ast_inspector/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

module.exports = require('@kbn/storybook').defaultConfig;
75 changes: 6 additions & 69 deletions examples/esql_ast_inspector/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,84 +7,21 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React, { useRef, useState } from 'react';
import {
EuiPage,
EuiPageBody,
EuiPageSection,
EuiPageHeader,
EuiSpacer,
EuiForm,
EuiTextArea,
EuiFormRow,
EuiButton,
} from '@elastic/eui';
import * as React from 'react';
import { EuiPage, EuiPageBody, EuiPageSection, EuiPageHeader, EuiSpacer } from '@elastic/eui';
import { EuiProvider } from '@elastic/eui';
import { EsqlInspector } from './components/esql_inspector';

import type { CoreStart } from '@kbn/core/public';

import { EditorError, ESQLAst, getAstAndSyntaxErrors } from '@kbn/esql-ast';
import { CodeEditor } from '@kbn/code-editor';
import type { StartDependencies } from './plugin';

export const App = (props: { core: CoreStart; plugins: StartDependencies }) => {
const [currentErrors, setErrors] = useState<EditorError[]>([]);
const [currentQuery, setQuery] = useState(
'from index1 | eval var0 = round(numberField, 2) | stats by stringField'
);

const inputRef = useRef<HTMLTextAreaElement | null>(null);

const [ast, setAST] = useState<ESQLAst>(getAstAndSyntaxErrors(currentQuery).ast);

const parseQuery = (query: string) => {
const { ast: _ast, errors } = getAstAndSyntaxErrors(query);
setErrors(errors);
setAST(_ast);
};

export const App = () => {
return (
<EuiProvider>
<EuiPage>
<EuiPageBody style={{ maxWidth: 800, margin: '0 auto' }}>
<EuiPageBody style={{ maxWidth: 1200, margin: '0 auto' }}>
<EuiPageHeader paddingSize="s" bottomBorder={true} pageTitle="ES|QL AST Inspector" />
<EuiPageSection paddingSize="s">
<p>This app gives you the AST for a particular ES|QL query.</p>

<EuiSpacer />

<EuiForm>
<EuiFormRow
fullWidth
label="Query"
isInvalid={Boolean(currentErrors.length)}
error={currentErrors.map((error) => error.message)}
>
<EuiTextArea
inputRef={(node) => {
inputRef.current = node;
}}
isInvalid={Boolean(currentErrors.length)}
fullWidth
value={currentQuery}
onChange={(e) => setQuery(e.target.value)}
css={{
height: '5em',
}}
/>
</EuiFormRow>
<EuiFormRow fullWidth>
<EuiButton fullWidth onClick={() => parseQuery(inputRef.current?.value ?? '')}>
Parse
</EuiButton>
</EuiFormRow>
</EuiForm>
<EuiSpacer />
<CodeEditor
allowFullScreen={true}
languageId={'json'}
value={JSON.stringify(ast, null, 2)}
/>
<EsqlInspector />
</EuiPageSection>
</EuiPageBody>
</EuiPage>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import * as React from 'react';
import { Annotations } from './annotations';

export default {
title: '<Annotations>',
parameters: {},
};

export const Default = () => (
<Annotations
value={'FROM index | LIMIT 10 | SORT some_field'}
annotations={[
[0, 4, (text) => <span style={{ color: 'red' }}>{text}</span>],
[5, 10, (text) => <span style={{ color: 'blue' }}>{text}</span>],
[13, 18, (text) => <span style={{ color: 'red' }}>{text}</span>],
[19, 21, (text) => <span style={{ color: 'green' }}>{text}</span>],
]}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import * as React from 'react';
import type { Annotation } from './types';

export interface AnnotationsProps {
value: string;
annotations?: Annotation[];
}

export const Annotations: React.FC<AnnotationsProps> = (props) => {
const { value, annotations = [] } = props;
const annotationNodes: React.ReactNode[] = [];

let pos = 0;

for (const [start, end, render] of annotations) {
if (start > pos) {
const text = value.slice(pos, start);

annotationNodes.push(<span>{text}</span>);
}

const text = value.slice(start, end);

pos = end;
annotationNodes.push(render(text));
}

if (pos < value.length) {
const text = value.slice(pos);
annotationNodes.push(<span>{text}</span>);
}

return React.createElement('span', {}, ...annotationNodes);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { Annotations, type AnnotationsProps } from './annotations';
export type { Annotation } from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,10 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { Token } from 'antlr4';
import * as React from 'react';

export function getPosition(
token: Pick<Token, 'start' | 'stop'> | null,
lastToken?: Pick<Token, 'stop'> | undefined
) {
if (!token || token.start < 0) {
return { min: 0, max: 0 };
}
const endFirstToken = token.stop > -1 ? Math.max(token.stop + 1, token.start) : undefined;
const endLastToken = lastToken?.stop;
return {
min: token.start,
max: endLastToken ?? endFirstToken ?? Infinity,
};
}
export type Annotation = [
start: number,
end: number,
annotation: (text: string) => React.ReactNode
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import * as React from 'react';
import { css } from '@emotion/react';
import { Annotations, type Annotation } from '../annotations';
import { FlexibleInput } from '../flexible_input/flexible_input';

const blockCss = css({
display: 'inline-block',
position: 'relative',
width: '100%',
fontSize: '18px',
lineHeight: '1.3',
fontFamily:
"'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace",
});

const backdropCss = css({
display: 'inline-block',
position: 'absolute',
left: 0,
width: '100%',
pointerEvents: 'all',
userSelect: 'none',
whiteSpace: 'pre',
color: 'rgba(255, 255, 255, 0.01)',
});

const inputCss = css({
display: 'inline-block',
color: 'rgba(255, 255, 255, 0.01)',
caretColor: '#07f',
});

const overlayCss = css({
display: 'inline-block',
position: 'absolute',
left: 0,
width: '100%',
pointerEvents: 'none',
userSelect: 'none',
whiteSpace: 'pre',
});

export interface EsqlEditorProps {
src: string;
backdrops?: Annotation[][];
highlight?: Annotation[];
onChange: (src: string) => void;
}

export const EsqlEditor: React.FC<EsqlEditorProps> = (props) => {
const { src, highlight, onChange } = props;

const backdrops: React.ReactNode[] = [];

if (props.backdrops) {
for (let i = 0; i < props.backdrops.length; i++) {
const backdrop = props.backdrops[i];

backdrops.push(
<div key={i} css={backdropCss}>
<Annotations value={src} annotations={backdrop} />
</div>
);
}
}

const overlay = !!highlight && (
<div css={overlayCss}>
<Annotations value={src} annotations={highlight} />
</div>
);

return (
<div css={blockCss}>
{backdrops}
<div css={inputCss}>
<FlexibleInput multiline value={src} onChange={(e) => onChange(e.target.value)} />
</div>
{overlay}
</div>
);
};
Loading