CodeEditor: Improved styles when the code editor is loading (#88102)

* fix: monaco loading state

* chore: betterer

* test: leverage e2e selectors

* refactor: prefer HOC to wrap with testid

* refactor: add clarity

* docs: add clarity

* refactor: loading messaging

* test: rename vars to improve clarity

* test: rename vars to improve clarity
This commit is contained in:
Nick Richmond 2024-05-29 15:55:18 -04:00 committed by GitHub
parent 5de7d4d06d
commit 7334f71e09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 56 additions and 10 deletions

View File

@ -5054,8 +5054,6 @@ exports[`no gf-form usage`] = {
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
], ],
"packages/grafana-prometheus/src/components/PromQueryField.tsx:5381": [ "packages/grafana-prometheus/src/components/PromQueryField.tsx:5381": [
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],

View File

@ -526,6 +526,9 @@ export const Components = {
CodeEditor: { CodeEditor: {
container: 'data-testid Code editor container', container: 'data-testid Code editor container',
}, },
ReactMonacoEditor: {
container: 'data-testid ReactMonacoEditor container',
},
DashboardImportPage: { DashboardImportPage: {
textarea: 'data-testid-import-dashboard-textarea', textarea: 'data-testid-import-dashboard-textarea',
submit: 'data-testid-load-dashboard', submit: 'data-testid-load-dashboard',

View File

@ -238,7 +238,7 @@ class PromQueryFieldClass extends React.PureComponent<PromQueryFieldProps, PromQ
<Icon name={labelBrowserVisible ? 'angle-down' : 'angle-right'} /> <Icon name={labelBrowserVisible ? 'angle-down' : 'angle-right'} />
</button> </button>
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15"> <div className="flex-grow-1 min-width-15">
<MonacoQueryFieldWrapper <MonacoQueryFieldWrapper
languageProvider={languageProvider} languageProvider={languageProvider}
history={history} history={history}

View File

@ -83,6 +83,11 @@ const getStyles = (theme: GrafanaTheme2, placeholder: string) => {
container: css({ container: css({
borderRadius: theme.shape.radius.default, borderRadius: theme.shape.radius.default,
border: `1px solid ${theme.components.input.borderColor}`, border: `1px solid ${theme.components.input.borderColor}`,
display: 'flex',
flexDirection: 'row',
justifyContent: 'start',
alignItems: 'center',
height: '100%',
}), }),
placeholder: css({ placeholder: css({
'::after': { '::after': {

View File

@ -1,5 +1,10 @@
import { css } from '@emotion/css';
import React from 'react'; import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles2 } from '../../themes';
import { useAsyncDependency } from '../../utils/useAsyncDependency'; import { useAsyncDependency } from '../../utils/useAsyncDependency';
import { ErrorWithStack } from '../ErrorBoundary/ErrorWithStack'; import { ErrorWithStack } from '../ErrorBoundary/ErrorWithStack';
import { LoadingPlaceholder } from '../LoadingPlaceholder/LoadingPlaceholder'; import { LoadingPlaceholder } from '../LoadingPlaceholder/LoadingPlaceholder';
@ -11,13 +16,14 @@ import type { ReactMonacoEditorProps } from './types';
* @internal * @internal
* Experimental export * Experimental export
**/ **/
export const ReactMonacoEditorLazy = (props: ReactMonacoEditorProps) => { const MonacoEditorLazy = (props: ReactMonacoEditorProps) => {
const styles = useStyles2(getStyles);
const { loading, error, dependency } = useAsyncDependency( const { loading, error, dependency } = useAsyncDependency(
import(/* webpackChunkName: "react-monaco-editor" */ './ReactMonacoEditor') import(/* webpackChunkName: "react-monaco-editor" */ './ReactMonacoEditor')
); );
if (loading) { if (loading) {
return <LoadingPlaceholder text={''} />; return <LoadingPlaceholder text={'Loading editor'} className={styles.container} />;
} }
if (error) { if (error) {
@ -31,5 +37,29 @@ export const ReactMonacoEditorLazy = (props: ReactMonacoEditorProps) => {
} }
const ReactMonacoEditor = dependency.ReactMonacoEditor; const ReactMonacoEditor = dependency.ReactMonacoEditor;
return <ReactMonacoEditor {...props} />; return <ReactMonacoEditor {...props} loading={props.loading ?? null} />;
}; };
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
marginBottom: 'unset',
marginLeft: theme.spacing(1),
}),
};
};
const withContainer = <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {
const WithContainer = (props: P) => (
// allow tests to easily determine if the code editor has rendered in any of its three states (loading, error, or ready)
<div data-testid={selectors.components.ReactMonacoEditor.container}>
<Component {...props} />
</div>
);
WithContainer.displayName = Component.displayName;
return WithContainer;
};
export const ReactMonacoEditorLazy = withContainer(MonacoEditorLazy);

View File

@ -1,6 +1,8 @@
import { render, screen, waitFor } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { createLokiDatasource } from '../../__mocks__/datasource'; import { createLokiDatasource } from '../../__mocks__/datasource';
import { MonacoQueryFieldWrapper, Props } from './MonacoQueryFieldWrapper'; import { MonacoQueryFieldWrapper, Props } from './MonacoQueryFieldWrapper';
@ -25,7 +27,8 @@ describe('MonacoFieldWrapper', () => {
renderComponent(); renderComponent();
await waitFor(async () => { await waitFor(async () => {
expect(await screen.findByText('Loading...')).toBeInTheDocument(); const monacoEditor = await screen.findByTestId(selectors.components.ReactMonacoEditor.container);
expect(monacoEditor).toBeInTheDocument();
}); });
}); });
}); });

View File

@ -1,6 +1,8 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { createLokiDatasource } from '../../__mocks__/datasource'; import { createLokiDatasource } from '../../__mocks__/datasource';
import MonacoQueryField from './MonacoQueryField'; import MonacoQueryField from './MonacoQueryField';
@ -31,6 +33,7 @@ describe('MonacoQueryField', () => {
test('Renders with no errors', async () => { test('Renders with no errors', async () => {
renderComponent(); renderComponent();
expect(await screen.findByText('Loading...')).toBeInTheDocument(); const monacoEditor = await screen.findByTestId(selectors.components.ReactMonacoEditor.container);
expect(monacoEditor).toBeInTheDocument();
}); });
}); });

View File

@ -1,6 +1,8 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { createLokiDatasource } from '../../__mocks__/datasource'; import { createLokiDatasource } from '../../__mocks__/datasource';
import { LokiQuery } from '../../types'; import { LokiQuery } from '../../types';
@ -32,7 +34,8 @@ describe('LokiQueryCodeEditor', () => {
props.showExplain = true; props.showExplain = true;
props.datasource.metadataRequest = jest.fn().mockResolvedValue([]); props.datasource.metadataRequest = jest.fn().mockResolvedValue([]);
render(<LokiQueryCodeEditor {...props} query={defaultQuery} />); render(<LokiQueryCodeEditor {...props} query={defaultQuery} />);
expect(await screen.findByText('Loading...')).toBeInTheDocument(); const monacoEditor = await screen.findByTestId(selectors.components.ReactMonacoEditor.container);
expect(monacoEditor).toBeInTheDocument();
expect(screen.getByText(EXPLAIN_LABEL_FILTER_CONTENT)).toBeInTheDocument(); expect(screen.getByText(EXPLAIN_LABEL_FILTER_CONTENT)).toBeInTheDocument();
}); });
@ -40,7 +43,8 @@ describe('LokiQueryCodeEditor', () => {
const props = createDefaultProps(); const props = createDefaultProps();
props.datasource.metadataRequest = jest.fn().mockResolvedValue([]); props.datasource.metadataRequest = jest.fn().mockResolvedValue([]);
render(<LokiQueryCodeEditor {...props} query={defaultQuery} />); render(<LokiQueryCodeEditor {...props} query={defaultQuery} />);
expect(await screen.findByText('Loading...')).toBeInTheDocument(); const monacoEditor = await screen.findByTestId(selectors.components.ReactMonacoEditor.container);
expect(monacoEditor).toBeInTheDocument();
expect(screen.queryByText(EXPLAIN_LABEL_FILTER_CONTENT)).not.toBeInTheDocument(); expect(screen.queryByText(EXPLAIN_LABEL_FILTER_CONTENT)).not.toBeInTheDocument();
}); });
}); });