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 (
+