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

[8.x] [ES|QL] Enhances the inline documentation experience (#192156) #193444

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail

VALIDATION_PACKAGE_DIR="packages/kbn-esql-validation-autocomplete"
EDITOR_PACKAGE_DIR="packages/kbn-text-based-editor"
EDITOR_PACKAGE_DIR="packages/kbn-language-documentation-popover"
GIT_SCOPE="$VALIDATION_PACKAGE_DIR/**/* $EDITOR_PACKAGE_DIR/**/*"

report_main_step () {
Expand Down
9 changes: 5 additions & 4 deletions packages/kbn-language-documentation-popover/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* 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 { LanguageDocumentationPopover } from './src/components/documentation_popover';
export { LanguageDocumentationPopoverContent } from './src/components/documentation_content';
export type { LanguageDocumentationSections } from './src/components/documentation_content';
export { LanguageDocumentationPopover } from './src/components/as_popover';
export { LanguageDocumentationPopoverContent } from './src/components/as_popover/popover_content';
export { LanguageDocumentationFlyout } from './src/components/as_flyout';
export { LanguageDocumentationInline } from './src/components/as_inline';
export type { LanguageDocumentationSections } from './src/types';
9 changes: 7 additions & 2 deletions packages/kbn-language-documentation-popover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@
"private": true,
"sideEffects": [
"*.scss"
]
}
],
"scripts": {
"make:docs": "ts-node --transpileOnly scripts/generate_esql_docs.ts",
"postmake:docs": "yarn run lint:fix",
"lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-language-documentation-popover/src/sections/generated"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ import * as recast from 'recast';
const n = recast.types.namedTypes;
import fs from 'fs';
import path from 'path';
import { functions } from '../src/inline_documentation/generated/scalar_functions';
import { functions } from '../src/sections/generated/scalar_functions';

(function () {
const pathToElasticsearch = process.argv[2];
const { scalarFunctions, aggregationFunctions } = loadFunctionDocs(pathToElasticsearch);
writeFunctionDocs(
scalarFunctions,
path.join(__dirname, '../src/inline_documentation/generated/scalar_functions.tsx')
path.join(__dirname, '../src/sections/generated/scalar_functions.tsx')
);
writeFunctionDocs(
aggregationFunctions,
path.join(__dirname, '../src/inline_documentation/generated/aggregation_functions.tsx')
path.join(__dirname, '../src/sections/generated/aggregation_functions.tsx')
);
})();

Expand Down Expand Up @@ -86,7 +86,7 @@ function writeFunctionDocs(functionDocs: Map<string, string>, pathToDocsFile: st
// Do not edit manually... automatically generated by scripts/generate_esql_docs.ts
{
label: i18n.translate(
'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.${name}',
'languageDocumentationPopover.documentationESQL.${name}',
{
defaultMessage: '${name.toUpperCase()}',
}
Expand All @@ -97,7 +97,7 @@ function writeFunctionDocs(functionDocs: Map<string, string>, pathToDocsFile: st
readOnly
enableSoftLineBreaks
markdownContent={i18n.translate(
'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.${name}.markdown',
'languageDocumentationPopover.documentationESQL.${name}.markdown',
{
defaultMessage: \`${docWithoutLinks.replaceAll('`', '\\`')}\`,
description:
Expand Down
11 changes: 11 additions & 0 deletions packages/kbn-language-documentation-popover/setup_tests.ts
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".
*/

// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom';
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import React from 'react';
import { storiesOf } from '@storybook/react';
import { LanguageDocumentationPopover } from '../components/documentation_popover';
import { LanguageDocumentationPopover } from '../components/as_popover';

const sections = {
groups: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 React from 'react';
import { screen, render, fireEvent, waitFor } from '@testing-library/react';
import { LanguageDocumentationFlyout } from '.';

jest.mock('../../sections', () => {
const module = jest.requireActual('../../sections');
return {
...module,
getESQLDocsSections: () => ({
groups: [
{
label: 'Section one',
description: 'Section 1 description',
items: [],
},
{
label: 'Section two',
items: [
{
label: 'Section two item 1',
description: 'Section two item 1 description',
},
{
label: 'Section two item 2',
description: 'Section two item 2 description',
},
],
},
{
label: 'Section three',
items: [
{
label: 'Section three item 1',
description: 'Section three item 1 description',
},
{
label: 'Section three item 2',
description: 'Section three item 2 description',
},
],
},
],
initialSection: <span>Here is the initial section</span>,
}),
};
});

describe('###Documentation flyout component', () => {
const renderFlyout = (linkToDocumentation?: string) => {
return render(
<LanguageDocumentationFlyout
isHelpMenuOpen={true}
onHelpMenuVisibilityChange={jest.fn()}
linkToDocumentation={linkToDocumentation}
/>
);
};
it('has a header element for navigation through the sections', () => {
renderFlyout();
expect(screen.getByTestId('language-documentation-navigation-search')).toBeInTheDocument();
expect(screen.getByTestId('language-documentation-navigation-dropdown')).toBeInTheDocument();
expect(screen.queryByTestId('language-documentation-navigation-link')).not.toBeInTheDocument();
});

it('has a link if linkToDocumentation prop is given', () => {
renderFlyout('meow');
expect(screen.getByTestId('language-documentation-navigation-link')).toBeInTheDocument();
});

it('contains the two last sections', async () => {
renderFlyout();
await waitFor(() => {
expect(screen.getByText('Section two')).toBeInTheDocument();
expect(screen.getByText('Section three')).toBeInTheDocument();
});
});

it('contains the correct section if user updates the search input', async () => {
renderFlyout();
const input = screen.getByTestId('language-documentation-navigation-search');
fireEvent.change(input, { target: { value: 'two' } });
await waitFor(() => {
expect(screen.getByText('Section two')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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 React, { useCallback, useEffect, useState, useRef, useMemo } from 'react';
import {
EuiFlyout,
useEuiTheme,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiTitle,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { getFilteredGroups } from '../../utils/get_filtered_groups';
import { DocumentationMainContent, DocumentationNavigation } from '../shared';
import { getESQLDocsSections } from '../../sections';
import type { LanguageDocumentationSections } from '../../types';

interface DocumentationFlyoutProps {
isHelpMenuOpen: boolean;
onHelpMenuVisibilityChange: (status: boolean) => void;
searchInDescription?: boolean;
linkToDocumentation?: string;
}

function DocumentationFlyout({
searchInDescription,
linkToDocumentation,
isHelpMenuOpen,
onHelpMenuVisibilityChange,
}: DocumentationFlyoutProps) {
const [documentationSections, setDocumentationSections] =
useState<LanguageDocumentationSections>();

const { euiTheme } = useEuiTheme();
const DEFAULT_WIDTH = euiTheme.base * 34;

const [selectedSection, setSelectedSection] = useState<string | undefined>();
const [searchText, setSearchText] = useState('');

const scrollTargets = useRef<Record<string, HTMLElement>>({});

const onNavigationChange = useCallback((selectedOptions) => {
setSelectedSection(selectedOptions.length ? selectedOptions[0].label : undefined);
if (selectedOptions.length) {
const scrollToElement = scrollTargets.current[selectedOptions[0].label];
scrollToElement.scrollIntoView();
}
}, []);

useEffect(() => {
onHelpMenuVisibilityChange(isHelpMenuOpen ?? false);
}, [isHelpMenuOpen, onHelpMenuVisibilityChange]);

useEffect(() => {
async function getDocumentation() {
const sections = await getESQLDocsSections();
setDocumentationSections(sections);
}
if (!documentationSections) {
getDocumentation();
}
}, [documentationSections]);

const filteredGroups = useMemo(() => {
return getFilteredGroups(searchText, searchInDescription, documentationSections, 1);
}, [documentationSections, searchText, searchInDescription]);

return (
<>
{isHelpMenuOpen && (
<EuiFlyout
ownFocus
onClose={() => onHelpMenuVisibilityChange(false)}
aria-labelledby="esqlInlineDocumentationFlyout"
type="push"
size={DEFAULT_WIDTH}
paddingSize="m"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h3>
{i18n.translate('languageDocumentationPopover.documentationFlyoutTitle', {
defaultMessage: 'ES|QL quick reference',
})}
</h3>
</EuiTitle>
<EuiSpacer size="m" />
<DocumentationNavigation
searchText={searchText}
setSearchText={setSearchText}
onNavigationChange={onNavigationChange}
filteredGroups={filteredGroups}
linkToDocumentation={linkToDocumentation}
selectedSection={selectedSection}
/>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<DocumentationMainContent
searchText={searchText}
scrollTargets={scrollTargets}
filteredGroups={filteredGroups}
sections={documentationSections}
/>
</EuiFlyoutBody>
</EuiFlyout>
)}
</>
);
}

export const LanguageDocumentationFlyout = React.memo(DocumentationFlyout);
Loading