diff --git a/docs/sources/datasources/mssql/query-editor/index.md b/docs/sources/datasources/mssql/query-editor/index.md index e43ca6d7e0f..51622c2a025 100644 --- a/docs/sources/datasources/mssql/query-editor/index.md +++ b/docs/sources/datasources/mssql/query-editor/index.md @@ -120,6 +120,8 @@ To filter on more columns, click the plus (`+`) button to the right of the condi To remove a filter, click the `x` button next to that filter's dropdown. +After selecting a date type column, you can choose Macros from the operators list and select timeFilter which will add the $\_\_timeFilter macro to the query with the selected date column. + ### Group results To group results by column, toggle the **Group** switch at the top of the editor. diff --git a/docs/sources/datasources/mysql/_index.md b/docs/sources/datasources/mysql/_index.md index 985653e5f9b..ae6e6eefdba 100644 --- a/docs/sources/datasources/mysql/_index.md +++ b/docs/sources/datasources/mysql/_index.md @@ -201,10 +201,17 @@ Add further value columns by clicking the plus button and another column dropdow ### Filter data (WHERE) -To add a filter, flip the switch at the top of the editor. -Using the first dropdown, select if all the filters need to match (AND) or if only one of the filters needs to match (OR). +To add a filter, toggle the **Filter** switch at the top of the editor. +This reveals a **Filter by column value** section with two dropdown selectors. -To add more columns to filter on use the plus button. +Use the first dropdown to choose whether all of the filters need to match (`AND`), or if only one of the filters needs to match (`OR`). +Use the second dropdown to choose a filter. + +To filter on more columns, click the plus (`+`) button to the right of the condition dropdown. + +To remove a filter, click the `x` button next to that filter's dropdown. + +After selecting a date type column, you can choose Macros from the operators list and select timeFilter which will add the $\_\_timeFilter macro to the query with the selected date column. ### Group By diff --git a/docs/sources/datasources/postgres/_index.md b/docs/sources/datasources/postgres/_index.md index 53bc8b58173..a5691589025 100644 --- a/docs/sources/datasources/postgres/_index.md +++ b/docs/sources/datasources/postgres/_index.md @@ -114,10 +114,17 @@ Add further value columns by clicking the plus button and another column dropdow ### Filter data (WHERE) -To add a filter, flip the switch at the top of the editor. -Using the first dropdown, select if all the filters need to match (AND) or if only one of the filters needs to match (OR). +To add a filter, toggle the **Filter** switch at the top of the editor. +This reveals a **Filter by column value** section with two dropdown selectors. -To add more columns to filter on use the plus button. +Use the first dropdown to choose whether all of the filters need to match (`AND`), or if only one of the filters needs to match (`OR`). +Use the second dropdown to choose a filter. + +To filter on more columns, click the plus (`+`) button to the right of the condition dropdown. + +To remove a filter, click the `x` button next to that filter's dropdown. + +After selecting a date type column, you can choose Macros from the operators list and select timeFilter which will add the $\_\_timeFilter macro to the query with the selected date column. ### Group By diff --git a/e2e/various-suite/mysql.spec.ts b/e2e/various-suite/mysql.spec.ts index 6ef2502d87b..336573d556a 100644 --- a/e2e/various-suite/mysql.spec.ts +++ b/e2e/various-suite/mysql.spec.ts @@ -9,10 +9,6 @@ const normalTableName = tablesResponse.results.tables.frames[0].data.values[0][0 describe('MySQL datasource', () => { beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('code editor autocomplete should handle table name escaping/quoting', () => { cy.intercept('POST', '/api/ds/query', (req) => { if (req.body.queries[0].refId === 'datasets') { req.alias = 'datasets'; @@ -31,11 +27,14 @@ describe('MySQL datasource', () => { }); } }); - + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); e2e.pages.Explore.visit(); e2e.components.DataSourcePicker.container().should('be.visible').type('gdev-mysql{enter}'); + cy.wait('@datasets'); + }); + it('code editor autocomplete should handle table name escaping/quoting', () => { cy.get("label[for^='option-code']").should('be.visible').click(); cy.get('textarea').type('S{downArrow}{enter}'); cy.wait('@tables'); @@ -60,4 +59,61 @@ describe('MySQL datasource', () => { cy.get('textarea').type('.'); cy.get('.suggest-widget').contains('No suggestions.').should('be.visible'); }); + + describe('visual query builder', () => { + it('should be able to add timeFilter macro', () => { + cy.get("[aria-label='Table selector']").should('be.visible').click(); + selectOption(normalTableName); + // Open column selector + cy.get("[id^='select-column-0']").should('be.visible').click(); + selectOption('createdAt'); + + // Toggle where row + cy.get("label[for^='sql-filter']").last().should('be.visible').click(); + + // Click add filter button + cy.get('button[title="Add filter"]').should('be.visible').click(); + cy.get('button[title="Add filter"]').should('be.visible').click(); // For some reason we need to click twice + + // Open field selector + cy.get("[aria-label='Field']").should('be.visible').click(); + selectOption('createdAt'); + + // Open operator selector + cy.get("[aria-label='Operator']").should('be.visible').click(); + selectOption('Macros'); + + // Open macros value selector + cy.get("[aria-label='Macros value selector']").should('be.visible').click(); + selectOption('timeFilter'); + + // Validate that the timeFilter macro was added + + e2e.components.CodeEditor.container() + .get('textarea') + .should( + 'have.value', + `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 + + // For some reason the input is not visible the second time so we need to force the click + cy.get("[aria-label='Operator']").click({ force: true }); + selectOption('=='); + + e2e.components.DateTimePicker.input().should('be.visible').click().blur(); + + e2e.components.CodeEditor.container() + .get('textarea') + .should( + 'not.have.value', + `SELECT\n createdAt\nFROM\n DataMaker.normalTable\nWHERE\n $__timeFilter(createdAt)\nLIMIT\n 50` + ); + }); + }); }); + +function selectOption(option: string) { + cy.get("[aria-label='Select option']").contains(option).should('be.visible').click(); +} diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 069c69d4a30..a4d03a8d1f2 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -32,6 +32,9 @@ export const Components = { rolePicker: 'Built-in role picker', permissionLevel: 'Permission Level', }, + DateTimePicker: { + input: 'data-testid date-time-input', + }, DataSource: { TestData: { QueryTab: { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx index 821ee4088a1..1f4bed87550 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx @@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { dateTime } from '@grafana/data'; +import { Components } from '@grafana/e2e-selectors'; import { DateTimePicker, Props } from './DateTimePicker'; @@ -33,7 +34,7 @@ describe('Date time picker', () => { it('should update date onblur', async () => { const onChangeInput = jest.fn(); render(); - const dateTimeInput = screen.getByTestId('date-time-input'); + const dateTimeInput = screen.getByTestId(Components.DateTimePicker.input); await userEvent.clear(dateTimeInput); await userEvent.type(dateTimeInput, '2021-07-31 12:30:30'); expect(dateTimeInput).toHaveDisplayValue('2021-07-31 12:30:30'); @@ -44,7 +45,7 @@ describe('Date time picker', () => { it('should not update onblur if invalid date', async () => { const onChangeInput = jest.fn(); render(); - const dateTimeInput = screen.getByTestId('date-time-input'); + const dateTimeInput = screen.getByTestId(Components.DateTimePicker.input); await userEvent.clear(dateTimeInput); await userEvent.type(dateTimeInput, '2021:05:05 12-00-00'); expect(dateTimeInput).toHaveDisplayValue('2021:05:05 12-00-00'); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index c6d18567228..0b7dc9bc50e 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -8,6 +8,7 @@ import { usePopper } from 'react-popper'; import { useMedia } from 'react-use'; import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@grafana/data'; +import { Components } from '@grafana/e2e-selectors'; import { useStyles2, useTheme2 } from '../../../themes'; import { Button } from '../../Button/Button'; @@ -232,7 +233,7 @@ const DateTimeInput = React.forwardRef( addonAfter={icon} value={internalDate.value} onBlur={onBlur} - data-testid="date-time-input" + data-testid={Components.DateTimePicker.input} placeholder="Select date/time" ref={ref} /> diff --git a/public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx b/public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx index 423d327d9e7..4e911fcf3d9 100644 --- a/public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx +++ b/public/app/features/plugins/sql/components/visual-query-builder/AwesomeQueryBuilder.tsx @@ -2,7 +2,6 @@ import { AnyObject, BasicConfig, Config, - JsonItem, JsonTree, Operator, Settings, @@ -24,38 +23,14 @@ const buttonLabels = { remove: 'Remove', }; -export const emptyInitValue: JsonItem = { - id: Utils.uuid(), - type: 'group' as const, - children1: { - [Utils.uuid()]: { - type: 'rule', - properties: { - field: null, - operator: null, - value: [], - valueSrc: [], - }, - }, - }, -}; - export const emptyInitTree: JsonTree = { id: Utils.uuid(), - type: 'group' as const, - children1: { - [Utils.uuid()]: { - type: 'rule', - properties: { - field: null, - operator: null, - value: [], - valueSrc: [], - }, - }, - }, + type: 'group', }; +const TIME_FILTER = 'timeFilter'; +const macros = [TIME_FILTER]; + export const widgets: Widgets = { ...BasicConfig.widgets, text: { @@ -86,15 +61,47 @@ export const widgets: Widgets = { datetime: { ...BasicConfig.widgets.datetime, factory: function DateTimeInput(props) { + if (props?.operator === Op.MACROS) { + return ( +