SQL: Fix builder crashes when any in selected (#102871)
This commit is contained in:
@@ -40,8 +40,8 @@ export const fieldsResponse = (refId: string) => ({
|
||||
},
|
||||
data: {
|
||||
values: [
|
||||
['createdAt', 'id', 'time', 'updatedAt', 'bigint'],
|
||||
['datetime', 'int', 'datetime', 'datetime', 'int'],
|
||||
['createdAt', 'id', 'time', 'updatedAt', 'bigint', 'name'],
|
||||
['datetime', 'int', 'datetime', 'datetime', 'int', 'string'],
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { normalTableName, tableNameWithSpecialCharacter } from './mocks/mysql.mocks';
|
||||
import { tableNameWithSpecialCharacter } from './mocks/mysql.mocks';
|
||||
import { mockDataSourceRequest } from './utils';
|
||||
|
||||
test.beforeEach(mockDataSourceRequest);
|
||||
@@ -24,48 +23,3 @@ test('code editor autocomplete should handle table name escaping/quoting', async
|
||||
await page.keyboard.press('Control+I');
|
||||
await expect(page.getByLabel(tableNameWithSpecialCharacter)).toBeVisible();
|
||||
});
|
||||
|
||||
test('visual query builder should handle time filter macro', async ({ explorePage, page }) => {
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.headerTableSelector).click();
|
||||
await page.getByText(normalTableName, { exact: true }).click();
|
||||
|
||||
// Open column selector
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.selectColumn).click();
|
||||
const select = page.getByLabel('Select options menu');
|
||||
await select.locator(page.getByText('createdAt')).click();
|
||||
|
||||
// Toggle where row
|
||||
await page.getByLabel('Filter').last().click();
|
||||
|
||||
// Click add filter button
|
||||
await page.getByRole('button', { name: 'Add filter' }).click();
|
||||
await page.getByRole('button', { name: 'Add filter' }).click(); // For some reason we need to click twice
|
||||
|
||||
// Open field selector
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.filterField).click();
|
||||
await select.locator(page.getByText('createdAt')).click();
|
||||
|
||||
// Open operator selector
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.filterOperator).click();
|
||||
await select.locator(page.getByText('Macros')).click();
|
||||
|
||||
// Open macros value selector
|
||||
await explorePage.getByGrafanaSelector('Macros value selector').click();
|
||||
await select.locator(page.getByText('timeFilter', { exact: true })).click();
|
||||
|
||||
// Validate that the timeFilter macro was added
|
||||
await expect(
|
||||
explorePage.getByGrafanaSelector(selectors.components.CodeEditor.container).getByRole('textbox')
|
||||
).toHaveValue(`SELECT\n createdAt\nFROM\n DataMaker.normalTable\nWHERE\n $__timeFilter(createdAt)\nLIMIT\n 50`);
|
||||
|
||||
// Validate that the timeFilter macro was removed when changed to equals operator
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.filterOperator).click();
|
||||
await select.locator(page.getByText('==')).click();
|
||||
|
||||
await explorePage.getByGrafanaSelector(selectors.components.DateTimePicker.input).click();
|
||||
await explorePage.getByGrafanaSelector(selectors.components.DateTimePicker.input).blur();
|
||||
|
||||
await expect(
|
||||
explorePage.getByGrafanaSelector(selectors.components.CodeEditor.container).getByRole('textbox')
|
||||
).not.toHaveValue(`SELECT\n createdAt\nFROM\n DataMaker.normalTable\nWHERE\n createdAt = NULL\nLIMIT\n 50`);
|
||||
});
|
||||
|
||||
@@ -43,3 +43,122 @@ test('visual query builder should handle macros', async ({ explorePage, page })
|
||||
`SELECT\n $__timeGroupAlias(createdAt, $__interval),\n AVG(\`bigint\`)\nFROM\n DataMaker.normalTable\nLIMIT\n 50`
|
||||
);
|
||||
});
|
||||
|
||||
test('visual query builder should handle time filter macro', async ({ explorePage, page }) => {
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.headerTableSelector).click();
|
||||
await page.getByText(normalTableName, { exact: true }).click();
|
||||
|
||||
// Open column selector
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.selectColumn).click();
|
||||
const select = page.getByLabel('Select options menu');
|
||||
await select.locator(page.getByText('createdAt')).click();
|
||||
|
||||
// Toggle where row
|
||||
await page.getByLabel('Filter').last().click();
|
||||
|
||||
// Click add filter button
|
||||
await page.getByRole('button', { name: 'Add filter' }).click();
|
||||
await page.getByRole('button', { name: 'Add filter' }).click(); // For some reason we need to click twice
|
||||
|
||||
// Open field selector
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.filterField).click();
|
||||
await select.locator(page.getByText('createdAt')).click();
|
||||
|
||||
// Open operator selector
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.filterOperator).click();
|
||||
await select.locator(page.getByText('Macros')).click();
|
||||
|
||||
// Open macros value selector
|
||||
await explorePage.getByGrafanaSelector('Macros value selector').click();
|
||||
await select.locator(page.getByText('timeFilter', { exact: true })).click();
|
||||
|
||||
// Validate that the timeFilter macro was added
|
||||
await expect(
|
||||
explorePage.getByGrafanaSelector(selectors.components.CodeEditor.container).getByRole('textbox')
|
||||
).toHaveValue(`SELECT\n createdAt\nFROM\n DataMaker.normalTable\nWHERE\n $__timeFilter(createdAt)\nLIMIT\n 50`);
|
||||
|
||||
// Validate that the timeFilter macro was removed when changed to equals operator
|
||||
await explorePage.getByGrafanaSelector(selectors.components.SQLQueryEditor.filterOperator).click();
|
||||
await select.locator(page.getByText('==')).click();
|
||||
|
||||
await explorePage.getByGrafanaSelector(selectors.components.DateTimePicker.input).click();
|
||||
await explorePage.getByGrafanaSelector(selectors.components.DateTimePicker.input).blur();
|
||||
|
||||
await expect(
|
||||
explorePage.getByGrafanaSelector(selectors.components.CodeEditor.container).getByRole('textbox')
|
||||
).not.toHaveValue(`SELECT\n createdAt\nFROM\n DataMaker.normalTable\nWHERE\n createdAt = NULL\nLIMIT\n 50`);
|
||||
});
|
||||
|
||||
test('visual query builder should not crash when filter is set to select_any_in', async ({ explorePage, page }) => {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('schemaVersion', '1');
|
||||
queryParams.set('orgId', '1');
|
||||
const panes = {
|
||||
mmm: {
|
||||
datasource: 'P4FDCC188E688367F',
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
datasource: {
|
||||
type: 'mysql',
|
||||
uid: 'P4FDCC188E688367F',
|
||||
},
|
||||
format: 'table',
|
||||
rawSql: "SELECT * FROM DataMaker.normalTable WHERE name IN ('a') LIMIT 50 ",
|
||||
editorMode: 'builder',
|
||||
sql: {
|
||||
columns: [
|
||||
{
|
||||
type: 'function',
|
||||
parameters: [
|
||||
{
|
||||
type: 'functionParameter',
|
||||
name: '*',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
groupBy: [
|
||||
{
|
||||
type: 'groupBy',
|
||||
property: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
limit: 50,
|
||||
whereJsonTree: {
|
||||
id: 'baa99aa9-0123-4456-b89a-b195d1dcfc6a',
|
||||
type: 'group',
|
||||
children1: [
|
||||
{
|
||||
type: 'rule',
|
||||
id: 'bb9a8bba-89ab-4cde-b012-3195d1dd2c91',
|
||||
properties: {
|
||||
fieldSrc: 'field',
|
||||
field: 'name',
|
||||
operator: 'select_any_in',
|
||||
value: ['a'],
|
||||
valueSrc: ['value'],
|
||||
valueType: ['text'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
whereString: "name IN ('a')",
|
||||
},
|
||||
dataset: 'DataMaker',
|
||||
table: 'normalTable',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
queryParams.set('panes', JSON.stringify(panes));
|
||||
|
||||
await explorePage.goto({ queryParams });
|
||||
|
||||
// Validate the query
|
||||
await expect(
|
||||
explorePage.getByGrafanaSelector(selectors.components.CodeEditor.container).getByRole('textbox')
|
||||
).toHaveValue(`SELECT\n *\nFROM\n DataMaker.normalTable\nWHERE\n name IN ('a')\nLIMIT\n 50`);
|
||||
});
|
||||
|
||||
@@ -244,6 +244,7 @@ function getCustomOperators(config: BasicConfig) {
|
||||
|
||||
// IN operator expects array, override IN formatter for multi-value variables
|
||||
const sqlFormatInOp = supportedOperators[Op.IN].sqlFormatOp?.bind(config.ctx) || noop;
|
||||
const formatInOp = supportedOperators[Op.IN].formatOp?.bind(config.ctx) || noop;
|
||||
const customSqlInFormatter = (
|
||||
field: string,
|
||||
op: string,
|
||||
@@ -259,7 +260,7 @@ function getCustomOperators(config: BasicConfig) {
|
||||
|
||||
// NOT IN operator expects array, override NOT IN formatter for multi-value variables
|
||||
const sqlFormatNotInOp = supportedOperators[Op.NOT_IN].sqlFormatOp?.bind(config.ctx) || noop;
|
||||
|
||||
const formatNotInOp = supportedOperators[Op.NOT_IN].formatOp?.bind(config.ctx) || noop;
|
||||
const customSqlNotInFormatter = (
|
||||
field: string,
|
||||
op: string,
|
||||
@@ -277,10 +278,26 @@ function getCustomOperators(config: BasicConfig) {
|
||||
...supportedOperators,
|
||||
[Op.IN]: {
|
||||
...supportedOperators[Op.IN],
|
||||
formatOp: (
|
||||
field: string,
|
||||
op: string,
|
||||
value: string | string[] | ImmutableList<string>,
|
||||
valueSrc?: ValueSource
|
||||
) => {
|
||||
return formatInOp(field, op, splitIfString(value), valueSrc);
|
||||
},
|
||||
sqlFormatOp: customSqlInFormatter,
|
||||
},
|
||||
[Op.NOT_IN]: {
|
||||
...supportedOperators[Op.NOT_IN],
|
||||
formatOp: (
|
||||
field: string,
|
||||
op: string,
|
||||
value: string | string[] | ImmutableList<string>,
|
||||
valueSrc?: ValueSource
|
||||
) => {
|
||||
return formatNotInOp(field, op, splitIfString(value), valueSrc);
|
||||
},
|
||||
sqlFormatOp: customSqlNotInFormatter,
|
||||
},
|
||||
[Op.MACROS]: {
|
||||
|
||||
Reference in New Issue
Block a user