Skip to content
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
71 changes: 71 additions & 0 deletions src/QueryCodeEditor.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { QueryCodeEditor } from './QueryCodeEditor';
import { mockDatasource, mockQuery } from './__mocks__/datasource';
import '@testing-library/jest-dom';
import { select } from 'react-select-event';

const ds = mockDatasource;
const q = mockQuery;

const props = {
datasource: ds,
query: q,
onChange: jest.fn(),
onRunQuery: jest.fn(),
};

beforeEach(() => {
ds.getResource = jest.fn().mockResolvedValue([]);
ds.postResource = jest.fn().mockResolvedValue([]);
});

describe('QueryCodeEditor', () => {
it('should list and select an schema', async () => {
ds.getResource = jest.fn().mockResolvedValue(['public', 'foo']);
const onChange = jest.fn();
render(<QueryCodeEditor {...props} queries={[]} onChange={onChange} />);
const selectEl = screen.getByText('$__schema = public');
expect(selectEl).toBeInTheDocument();
selectEl.click();

await select(selectEl, 'foo', { container: document.body });

expect(onChange).toHaveBeenCalledWith({
...q,
schema: 'foo',
});
});

it('should list and select a table', async () => {
ds.postResource = jest.fn().mockResolvedValue(['foo', 'bar']);
const onChange = jest.fn();
render(<QueryCodeEditor {...props} queries={[]} onChange={onChange} />);
const selectEl = screen.getByText('$__table = ?');
expect(selectEl).toBeInTheDocument();
selectEl.click();

await select(selectEl, 'foo', { container: document.body });

expect(onChange).toHaveBeenCalledWith({
...q,
table: 'foo',
});
});

it('should list and select a column', async () => {
ds.postResource = jest.fn().mockResolvedValue(['foo', 'bar']);
const onChange = jest.fn();
render(<QueryCodeEditor {...props} queries={[]} onChange={onChange} />);
const selectEl = screen.getByText('$__column = ?');
expect(selectEl).toBeInTheDocument();
selectEl.click();

await select(selectEl, 'foo', { container: document.body });

expect(onChange).toHaveBeenCalledWith({
...q,
column: 'foo',
});
});
});
89 changes: 89 additions & 0 deletions src/QueryCodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { defaults } from 'lodash';

import React from 'react';
import { QueryEditorProps } from '@grafana/data';
import { DataSource } from './datasource';
import { defaultQuery, RedshiftDataSourceOptions, RedshiftQuery } from './types';
import { CodeEditor, InlineFormLabel } from '@grafana/ui';
import { getTemplateSrv } from '@grafana/runtime';
import ResourceMacro from 'ResourceMacro';
import { getSuggestions } from 'Suggestions';

type Props = QueryEditorProps<DataSource, RedshiftQuery, RedshiftDataSourceOptions>;

export function QueryCodeEditor(props: Props) {
const onChange = (value: RedshiftQuery) => {
props.onChange(value);
props.onRunQuery();
};

const onRawSqlChange = (rawSQL: string) => {
props.onChange({
...props.query,
rawSQL,
});
props.onRunQuery();
};

const { rawSQL } = defaults(props.query, defaultQuery);

const loadSchemas = async () => {
const schemas: string[] = await props.datasource.getResource('schemas');
return schemas.map((schema) => ({ label: schema, value: schema })).concat({ label: '-- remove --', value: '' });
};

const loadTables = async () => {
const tables: string[] = await props.datasource.postResource('tables', {
schema: props.query.schema || '',
});
return tables.map((table) => ({ label: table, value: table })).concat({ label: '-- remove --', value: '' });
};

const loadColumns = async () => {
const columns: string[] = await props.datasource.postResource('columns', {
table: props.query.table,
});
return columns.map((column) => ({ label: column, value: column })).concat({ label: '-- remove --', value: '' });
};

return (
<>
<div className={'gf-form-inline'}>
<InlineFormLabel width={8} className="query-keyword">
Macros
</InlineFormLabel>
{ResourceMacro({
resource: 'schema',
query: props.query,
loadOptions: loadSchemas,
updateQuery: onChange,
})}
{ResourceMacro({
resource: 'table',
query: props.query,
loadOptions: loadTables,
updateQuery: onChange,
})}
{ResourceMacro({
resource: 'column',
query: props.query,
loadOptions: loadColumns,
updateQuery: onChange,
})}
<div className="gf-form gf-form--grow">
<div className="gf-form-label gf-form-label--grow" />
</div>
</div>
<CodeEditor
height={'250px'}
language={'redshift'}
value={rawSQL}
onBlur={onRawSqlChange}
// removed onSave due to bug: https://github.com/grafana/grafana/issues/39264
showMiniMap={false}
showLineNumbers={true}
getSuggestions={() => getSuggestions({ query: props.query, templateSrv: getTemplateSrv() })}
/>
</>
);
}
13 changes: 4 additions & 9 deletions src/QueryEditor.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import { QueryEditor } from './QueryEditor';
import { mockDatasource, mockQuery } from './__mocks__/datasource';
import '@testing-library/jest-dom';
Expand All @@ -21,19 +21,14 @@ const props = {
describe('QueryEditor', () => {
it('should render Macros input', async () => {
render(<QueryEditor {...props} />);
expect(screen.getByText('$__schema = public')).toBeInTheDocument();
await waitFor(() => screen.getByText('$__schema = public'));
expect(screen.getByText('$__table = ?')).toBeInTheDocument();
expect(screen.getByText('$__column = ?')).toBeInTheDocument();
});

it('should not include the Format As input if the query editor does not support multiple queries', async () => {
render(<QueryEditor {...props} queries={undefined} />);
expect(screen.queryByText('Format as')).not.toBeInTheDocument();
});

it('should include the Format As input', async () => {
render(<QueryEditor {...props} queries={[]} />);
expect(screen.queryByText('Format as')).toBeInTheDocument();
render(<QueryEditor {...props} />);
await waitFor(() => screen.getByText('Format as'));
});

it('should allow to change the fill mode', async () => {
Expand Down
Loading