diff --git a/e2e/plugin-e2e/mysql/mocks/mysql.mocks.ts b/e2e/plugin-e2e/mysql/mocks/mysql.mocks.ts index a72c83903f3..63718c6d1a4 100644 --- a/e2e/plugin-e2e/mysql/mocks/mysql.mocks.ts +++ b/e2e/plugin-e2e/mysql/mocks/mysql.mocks.ts @@ -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'], ], }, }, diff --git a/e2e/plugin-e2e/mysql/mysql.spec.ts b/e2e/plugin-e2e/mysql/mysql.spec.ts index 8d56d882f27..525e0074918 100644 --- a/e2e/plugin-e2e/mysql/mysql.spec.ts +++ b/e2e/plugin-e2e/mysql/mysql.spec.ts @@ -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`); -}); diff --git a/e2e/plugin-e2e/mysql/visual-query-builder.spec.ts b/e2e/plugin-e2e/mysql/visual-query-builder.spec.ts index e87c06bea31..68450533ee1 100644 --- a/e2e/plugin-e2e/mysql/visual-query-builder.spec.ts +++ b/e2e/plugin-e2e/mysql/visual-query-builder.spec.ts @@ -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`); +}); diff --git a/packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx b/packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx index 9fab4cdb9f3..f33cca5f75a 100644 --- a/packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx +++ b/packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx @@ -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, + 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, + valueSrc?: ValueSource + ) => { + return formatNotInOp(field, op, splitIfString(value), valueSrc); + }, sqlFormatOp: customSqlNotInFormatter, }, [Op.MACROS]: {