From eb1026efe23a6629a15c746caefb44b980a98cf6 Mon Sep 17 00:00:00 2001 From: Maciej Bodek Date: Wed, 29 May 2024 15:20:49 +0200 Subject: [PATCH 1/2] Remove `TLS Method` configuration option --- README.md | 45 ++++--- .../questdb_questdb_datasource.yaml | 1 - src/components/ui/CertificationKey.tsx | 5 +- src/selectors.ts | 2 + src/views/QuestDBConfigEditor.test.tsx | 11 +- src/views/QuestDBConfigEditor.tsx | 120 ++++-------------- 6 files changed, 60 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index ea9253a..f822267 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,17 @@ example, statements like `UPDATE users SET name='blahblah'` and `DROP TABLE importantTable;` would be executed. To configure a readonly user, follow these steps: -* Open Source version + +- Open Source version 1. Set the following properties in server.conf file: - pg.readonly.user.enabled=true - pg.readonly.user=myuser - pg.readonly.password=secret 2. Restart QuestDB instance. -* Enterprise version +- Enterprise version 1. Create user: - CREATE USER grafana_readonly; - 2. Grant read permission on selected tables/table columns ; + 2. Grant read permission on selected tables/table columns ; - GRANT SELECT ON table1, ... TO grafana_readonly; ### Manual configuration @@ -58,7 +59,6 @@ datasources: port: 8812 username: admin tlsMode: disable - # tlsConfigurationMethod: file-path | file-content # tlsCACertFile: # timeout: # queryTimeout: @@ -88,11 +88,13 @@ interprets timestamp rows without explicit time zone as UTC. Any column except To create multi-line time series, the query must return at least 3 fields in the following order: -- field 1: `timestamp` field with an alias of `time` -- field 2: value to group by + +- field 1: `timestamp` field with an alias of `time` +- field 2: value to group by - field 3+: the metric values For example: + ```sql SELECT pickup_datetime AS time, cab_type, avg(fare_amount) AS avg_fare_amount FROM trips @@ -109,24 +111,25 @@ Table visualizations will always be available for any valid QuestDB query. To simplify syntax and to allow for dynamic parts, like date range filters, the query can contain macros. Here is an example of a query with a macro that will use Grafana's time filter: + ```sql SELECT desginated_timestamp, data_stuff FROM test_data WHERE $__timeFilter(desginated_timestamp) ``` -| Macro | Description | Output example | -|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| -| *$__timeFilter(columnName)* | Replaced by a conditional that filters the data (using the provided column) based on the time range of the panel in seconds | `timestamp >= cast(1706263425598000 as timestamp) AND timestamp <= cast(1706285057560000 as timestamp)` | -| *$__fromTime* | Replaced by the starting time of the range of the panel cast to timestamp | `cast(1706263425598000 as timestamp)` | -| *$__toTime* | Replaced by the ending time of the range of the panel cast to timestamp | `cast(1706285057560000 as timestamp)` | -| *$__sampleByInterval* | Replaced by the interval followed by unit: d, h, s or T (millisecond). Example: 1d, 5h, 20s, 1T. | `20s` (20 seconds) , `1T` (1 millisecond) | -| *$__conditionalAll(condition, $templateVar)* | Replaced by the first parameter when the template variable in the second parameter does not select every value. Replaced by the 1=1 when the template variable selects every value. | `condition` or `1=1` | +| Macro | Description | Output example | +| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| _$\_\_timeFilter(columnName)_ | Replaced by a conditional that filters the data (using the provided column) based on the time range of the panel in seconds | `timestamp >= cast(1706263425598000 as timestamp) AND timestamp <= cast(1706285057560000 as timestamp)` | +| _$\_\_fromTime_ | Replaced by the starting time of the range of the panel cast to timestamp | `cast(1706263425598000 as timestamp)` | +| _$\_\_toTime_ | Replaced by the ending time of the range of the panel cast to timestamp | `cast(1706285057560000 as timestamp)` | +| _$\_\_sampleByInterval_ | Replaced by the interval followed by unit: d, h, s or T (millisecond). Example: 1d, 5h, 20s, 1T. | `20s` (20 seconds) , `1T` (1 millisecond) | +| _$\_\_conditionalAll(condition, $templateVar)_ | Replaced by the first parameter when the template variable in the second parameter does not select every value. Replaced by the 1=1 when the template variable selects every value. | `condition` or `1=1` | The plugin also supports notation using braces {}. Use this notation when queries are needed inside parameters. -Additionally, Grafana has the built-in [`$__interval` macro][query-transform-data-query-options], which calculates an interval in seconds or milliseconds. -It shouldn't be used with SAMPLE BY because of time unit incompatibility, 1ms vs 1T (expected by QuestDB). Use `$__sampleByInterval` instead. +Additionally, Grafana has the built-in [`$__interval` macro][query-transform-data-query-options], which calculates an interval in seconds or milliseconds. +It shouldn't be used with SAMPLE BY because of time unit incompatibility, 1ms vs 1T (expected by QuestDB). Use `$__sampleByInterval` instead. ### Templates and variables @@ -144,12 +147,12 @@ Ad hoc filters allow you to add key/value filters that are automatically added to all metric queries that use the specified data source, without being explicitly used in queries. -By default, Ad Hoc filters will be populated with all Tables and Columns. If +By default, Ad Hoc filters will be populated with all Tables and Columns. If you have a default database defined in the Datasource settings, all Tables from that database will be used to populate the filters. As this could be slow/expensive, you can introduce a second variable to allow limiting the Ad Hoc filters. It should be a `constant` type named `questdb_adhoc_query` -and can contain: a comma delimited list of tables to show only columns for one or more tables. +and can contain: a comma delimited list of tables to show only columns for one or more tables. For more information on Ad Hoc filters, check the [Grafana docs](https://grafana.com/docs/grafana/latest/variables/variable-types/add-ad-hoc-filters/) @@ -162,7 +165,7 @@ You may choose to hide this variable from view as it serves no further purpose. ## Learn more -* Add [Annotations](https://grafana.com/docs/grafana/latest/dashboards/annotations/). -* Configure and use [Templates and variables](https://grafana.com/docs/grafana/latest/variables/). -* Add [Transformations](https://grafana.com/docs/grafana/latest/panels/transformations/). -* Set up alerting; refer to [Alerts overview](https://grafana.com/docs/grafana/latest/alerting/). +- Add [Annotations](https://grafana.com/docs/grafana/latest/dashboards/annotations/). +- Configure and use [Templates and variables](https://grafana.com/docs/grafana/latest/variables/). +- Add [Transformations](https://grafana.com/docs/grafana/latest/panels/transformations/). +- Set up alerting; refer to [Alerts overview](https://grafana.com/docs/grafana/latest/alerting/). diff --git a/provisioning/datasources/questdb_questdb_datasource.yaml b/provisioning/datasources/questdb_questdb_datasource.yaml index 4594e5a..1cae193 100644 --- a/provisioning/datasources/questdb_questdb_datasource.yaml +++ b/provisioning/datasources/questdb_questdb_datasource.yaml @@ -7,7 +7,6 @@ datasources: port: 8812 username: admin tlsMode: disable - # tlsConfigurationMethod: file-path | file-content # tlsCACertFile: # timeout: # queryTimeout: diff --git a/src/components/ui/CertificationKey.tsx b/src/components/ui/CertificationKey.tsx index 5db3d19..344f7e5 100644 --- a/src/components/ui/CertificationKey.tsx +++ b/src/components/ui/CertificationKey.tsx @@ -3,15 +3,16 @@ import { Input, Button, TextArea, Field } from '@grafana/ui'; interface Props { label: string; + tooltip?: string; hasCert: boolean; placeholder: string; onChange: (event: ChangeEvent) => void; onClick: (event: MouseEvent) => void; } -export const CertificationKey: FC = ({ hasCert, label, onChange, onClick, placeholder }) => { +export const CertificationKey: FC = ({ hasCert, label, tooltip, onChange, onClick, placeholder }) => { return ( - + {hasCert ? ( <> diff --git a/src/selectors.ts b/src/selectors.ts index 06c8a9b..7f3ab82 100644 --- a/src/selectors.ts +++ b/src/selectors.ts @@ -24,6 +24,8 @@ export const Components = { TLSCACert: { label: 'TLS/SSL Root Certificate', placeholder: 'CA Cert. Begins with -----BEGIN CERTIFICATE-----', + tooltip: + "Allows you to configure certificates by specifying its content. The content will be stored encrypted in Grafana's database.", }, TLSClientCert: { label: 'TLS/SSL Client Certificate', diff --git a/src/views/QuestDBConfigEditor.test.tsx b/src/views/QuestDBConfigEditor.test.tsx index 43c1880..bb06120 100644 --- a/src/views/QuestDBConfigEditor.test.tsx +++ b/src/views/QuestDBConfigEditor.test.tsx @@ -22,7 +22,6 @@ describe('ConfigEditor', () => { expect(screen.getByPlaceholderText(Components.ConfigEditor.Username.placeholder)).toBeInTheDocument(); expect(screen.getByPlaceholderText(Components.ConfigEditor.Password.placeholder)).toBeInTheDocument(); expect(screen.getByText(Components.ConfigEditor.TlsMode.placeholder)).toBeInTheDocument(); - expect(screen.getByText(Components.ConfigEditor.TlsMethod.label)).toBeInTheDocument(); }); it('with password', async () => { render( @@ -63,7 +62,7 @@ describe('ConfigEditor', () => { expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSClientCert.placeholder)).not.toBeInTheDocument(); expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSClientKey.placeholder)).not.toBeInTheDocument(); }); - it('with tlsMode and filePath tlsMethod', async () => { + it('with verifyCA tlsMode and fileContent tlsMethod', async () => { render( { jsonData: { ...mockConfigEditorProps().options.jsonData, tlsMode: PostgresTLSModes.verifyCA, - tlsConfigurationMethod: PostgresTLSMethods.filePath, + tlsConfigurationMethod: PostgresTLSMethods.fileContent, }, }} /> ); expect(screen.queryByText(PostgresTLSModes.verifyCA)).toBeInTheDocument(); - expect(screen.queryByText(Components.ConfigEditor.TlsMethod.placeholder)).toBeInTheDocument(); - expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSCACertFile.placeholder)).toBeInTheDocument(); - expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSCACert.placeholder)).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSCACertFile.placeholder)).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSCACert.placeholder)).toBeInTheDocument(); }); it('with verifyFull tlsMode and fileContent tlsMethod', async () => { @@ -98,7 +96,6 @@ describe('ConfigEditor', () => { /> ); expect(screen.queryByText(PostgresTLSModes.verifyFull)).toBeInTheDocument(); - expect(screen.queryByText(Components.ConfigEditor.TlsMethod.placeholder)).toBeInTheDocument(); expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSCACertFile.placeholder)).not.toBeInTheDocument(); expect(screen.queryByPlaceholderText(Components.ConfigEditor.TLSCACert.placeholder)).toBeInTheDocument(); }); diff --git a/src/views/QuestDBConfigEditor.tsx b/src/views/QuestDBConfigEditor.tsx index 0d849da..f654a43 100644 --- a/src/views/QuestDBConfigEditor.tsx +++ b/src/views/QuestDBConfigEditor.tsx @@ -8,7 +8,7 @@ import { import { Field, Input, SecretInput, Select, Switch } from '@grafana/ui'; import { CertificationKey } from '../components/ui/CertificationKey'; import { Components } from './../selectors'; -import { PostgresTLSModes, PostgresTLSMethods, QuestDBConfig, QuestDBSecureConfig } from './../types'; +import { PostgresTLSModes, QuestDBConfig, QuestDBSecureConfig } from './../types'; import { gte } from 'semver'; import { ConfigSection, DataSourceDescription } from '@grafana/experimental'; import { config } from '@grafana/runtime'; @@ -41,15 +41,6 @@ export const ConfigEditor: React.FC = (props) => { }, }); }; - const onTlsConfigurationMethodChange = (method?: PostgresTLSMethods) => { - onOptionsChange({ - ...options, - jsonData: { - ...options.jsonData, - tlsConfigurationMethod: method, - }, - }); - }; const onSwitchToggle = (key: keyof Pick, value: boolean) => { onOptionsChange({ ...options, @@ -119,11 +110,6 @@ export const ConfigEditor: React.FC = (props) => { { value: PostgresTLSModes.verifyFull, label: 'verify-full' }, ]; - const tlsMethods: Array> = [ - { value: PostgresTLSMethods.filePath, label: 'File system path' }, - { value: PostgresTLSMethods.fileContent, label: 'Certificate content' }, - ]; - return ( <> = (props) => { {jsonData.tlsMode && jsonData.tlsMode !== PostgresTLSModes.disable ? ( <> - - + onCertificateChangeFactory('tlsClientCert', e.currentTarget.value)} + placeholder={Components.ConfigEditor.TLSClientCert.placeholder} + label={Components.ConfigEditor.TLSClientCert.label} + onClick={() => onResetClickFactory('tlsClientCert')} + /> + onCertificateChangeFactory('tlsClientKey', e.currentTarget.value)} + onClick={() => onResetClickFactory('tlsClientKey')} /> - - {false && ( - <> - - - - - - - - )} - - )} + + )} + ) : null} From 6d8212c8a3b614c399f25fbb1865a0e7384c75a0 Mon Sep 17 00:00:00 2001 From: Maciej Bodek Date: Thu, 6 Jun 2024 13:37:47 +0200 Subject: [PATCH 2/2] Fix the backend tests --- pkg/plugin/driver_test.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/plugin/driver_test.go b/pkg/plugin/driver_test.go index 7cd711b..404cc8d 100644 --- a/pkg/plugin/driver_test.go +++ b/pkg/plugin/driver_test.go @@ -5,8 +5,6 @@ import ( "database/sql" "encoding/json" "fmt" - "github.com/docker/docker/api/types/mount" - "github.com/lib/pq" "math" "os" "path" @@ -15,6 +13,9 @@ import ( "testing" "time" + "github.com/docker/docker/api/types/mount" + "github.com/lib/pq" + "github.com/docker/docker/api/types/container" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" @@ -212,7 +213,6 @@ func TestInsertAndQueryData(t *testing.T) { " ge8 geohash(8c)," + " ip ipv4, " + " uuid_ uuid ," + - " l256 long256," + " ts timestamp " + ") TIMESTAMP(ts) PARTITION BY YEAR BYPASS WAL") require.NoError(t, err) @@ -233,20 +233,18 @@ func TestInsertAndQueryData(t *testing.T) { require.NoError(t, err) stmt, err := conn.Prepare("INSERT INTO all_types values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, " + - "cast($13 as geohash(1c)), cast($14 as geohash(2c)) , cast($15 as geohash(4c)), cast($16 as geohash(8c)), $17, $18, cast('' || $19 as long256), $20)") + "cast($13 as geohash(1c)), cast($14 as geohash(2c)) , cast($15 as geohash(4c)), cast($16 as geohash(8c)), $17, $18, $19)") require.NoError(t, err) defer stmt.Close() var data = [][]interface{}{ - {bool(false), int16(0), int16(0), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, timestamp}, - + {bool(false), int16(0), int16(0), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, timestamp}, {bool(true), int16(1), int16(1), mkstring("a"), mkint32(4), mkint64(5), &date, ×tamp, mkfloat32(12.345), mkfloat64(1.0234567890123), mkstring("string"), mkstring("symbol"), mkstring("r"), mkstring("rj"), mkstring("rjtw"), mkstring("rjtwedd0"), mkstring("1.2.3.4"), - mkstring("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"), mkstring("0x5dd94b8492b4be20632d0236ddb8f47c91efc2568b4d452847b4a645dbe4871a"), ×tamp}, - + mkstring("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"), ×tamp}, {bool(true), int16(math.MaxInt8), int16(math.MaxInt16), mkstring("z"), mkint32(math.MaxInt32), mkint64(math.MaxInt64), &date, mktimestamp("1970-01-01T00:00:00.000000", t), mkfloat32(math.MaxFloat32), mkfloat64(math.MaxFloat64), mkstring("XXX"), mkstring(" "), mkstring("e"), mkstring("ee"), mkstring("eeee"), mkstring("eeeeeeee"), - mkstring("255.255.255.255"), mkstring("a0eebc99-ffff-ffff-ffff-ffffffffffff"), mkstring("0x5dd94b8492b4be20632d0236ddb8f47c91efc2568b4d452847b4a645dbefffff"), mktimestamp("2020-03-31T00:00:00.987654", t)}} + mkstring("255.255.255.255"), mkstring("a0eebc99-ffff-ffff-ffff-ffffffffffff"), mktimestamp("2020-03-31T00:00:00.987654", t)}} for i := 1; i < len(data); i++ { _, err = stmt.Exec(data[i]...)