SQL Datasources: Reinstate SQL data source behavior around database selection when default configured databases already exist (#65659)
* badlm0mma/sql_datasource_update/ initial notes * baldm0mma/sql_datasource_update/ solution * baldm0mma/sql_datasource_update/ clean datasetSelector * baldm0mma/sql_datasource_update/ clean up queryEditor.tsx * baldm0mma/sql_datasource_update/ clewan up queryHeader.tsx * baldm0mma/sql_datasource_update/ clean up tableSelector.tsx * baldm0mma/sql_datasource_update/ clean up mysqlDatasource.ts * baldm0mma/sql_datasource_update/ clean up configurationEditor.tsx * baldm0mma/sql_datasource_update/ rem conlog from queryEditor.tsx * baldm0mma/sql_datasource_update/ rem conlog from queryEditor.tsx * baldm0mma/sql_datasource_update/ remove conlog in tableSelector.tsx * baldm0mma/sql_datasource_update/ rem conlog in sqlDatasource.ts * baldm0mma/sql_datasource_update/ update deafult database value in sqlDatasource.ts * baldm0mma/sql_datasource_update/ update logic to accomidate no preconfig * baldm0mma/sql_datasource_update/ update props * baldm0mma/sql_datasource_update/ update prop names * baldm0mma/sql_datasource_update/ update prop names in tableSelector * baldm0mma/sql_datasource_update/ update annos in datasetSelector * baldm0mma/sql_datasource_update/ update naming * baldm0mma/sql_datasource_update/ update to standard langauae * baldm0mma/sql_datasource_update/ update prop names * baldm0mma/sql_datasource_update/ update annos in datasetSelector * baldm0mma/sql_datasource_update/ remove unused import in tableSelector.tsx * baldm0mma/sql_datasource_update/ remove addDefaultdataset * baldm0mma/sql_datasource_update/ reset query when needed * baldm0mma/sql_datasource_update/ update asymc return val * baldm0mma/sql_datasource_update/ remove psql query editor * baldm0mma/sql_datasource_update/ remove con logs in defaults.ts * baldm0mma/sql_datasource_update/ revert postgres changes * baldm0mma/sql_datasource_update/ update postgres naming * baldm0mma/sql_datasource_update/ add altert * baldm0mma/sql_datasource_update/ update annos and add alerts and alert logic * baldm0mma/sql_datasource_update/ update postgres nomenclature * baldm0mma/sql_datasource_update/ update annos and remove con logs * baldm0mma/sql_datasource_update/ update nomenclature * baldm0mma/sql_datasource_update/ drone fix * baldm0mma/sql_datasource_update/ export and format * baldm0mma/sql_datasource_update/ update docs for mysql datasource with dataset dropdown disable info * baldm0mma/sql_datasource_update/ update mssql docs * baldm0mma/sql_datasource_update/ update postgres docs and alert logic * baldm0mma/sql_datasource_update/ add tests * baldm0mma/sql_datasource_update/ update docs * baldm0mma/sql_datasource_update/ update test names * baldm0mma/sql_datasource_update/ update alert language * baldm0mma/sql_datasource_update/ correct spelling * baldm0mma/sql_datasource_update/ update postgres query builder image * baldm0mma/sql_datasource_update/ update annos * baldm0mma/sql_datasource_update/ update tests * baldm0mma/sql_datasource_update/ docs updated * baldm0mma/sql_datsource_update/ refactor determinePlaceholder * Update public/app/features/plugins/sql/components/QueryEditor.tsx spelling Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * Update public/app/features/plugins/sql/components/QueryEditor.tsx spelling Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * baldm0mma/sql_datasource_update/ remove superfluous cleanup data from tests * baldm0mma/sql_datasource_update/ update spelling * Update public/app/features/plugins/sql/components/SqlComponents.test.tsx spelling Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * baldm0mma/sql_datasource_update/ add logic to prevent db call for tables if dataset it nullish * baldm0mma/sql_datasource_update/ update alert tests * baldm0mma/sql_datasource_update/ update button text * baldm0mma/sql_datasource_update/ update tests to preferred standard * baldm0mma/sql_datasource_update/add feature flag * baldm0mma/sql_datasource_update/ update mssql docs * baldm0mma/sql_datasource_update/ add feature flag to registry * baldm0mma/sql_datasource_update/ adjust table and dataset dropdown logic * baldm0mma/sql_datasource_update/ update testing to deal with feature flag * baldm0mma/sql_datasource_update/ update wioth cascadeDisable * baldm0mma/sql_datasource_update/ update naming * baldm0mma/sql_datasource_update/ update tests to reflect enabled feature flag * baldm0mma/sql_datasource_update/ update annotations * baldm0mma/sql_datasource_update/ update annos in queryEd * baldm0mma/sql_datasource_update/ update test names * baldm0mma/sql_datasource_update/ update anno issues * baldm0mma/slq_datasource_update/ add query to sqlDatasource with error * baldm0mma/sql_datasource_update/ update docs language * baldm0mma/sql_datasource_update/ remove notes to self * baldm0mma/sql_datasource_update/ add QueryEditorFeatureFlag.utils.ts * baldm0mma/sql_datasource_update/ update database into json * baldm0mma/sql_datasource_update/ found file * Update docs/sources/datasources/mssql/query-editor/index.md Co-authored-by: lwandz13 <126723338+lwandz13@users.noreply.github.com> * baldm0mma/sql_datasource_update/ update feature flag and toggles * baldm0mma/sql_datasource_update/ add hasConfigIssue, update annos, rethink disable logic * baldm0mma/sql_datasource_update/ update warning language * baldm0mma/sql_datasource_update/ update button content * baldm0mma/sql_datasource_update/ update jsonData logic in frontendsettings.go * baldm0mma/sql_datasource_update/ update jsonData logic * baldm0mma/sql_datasource_update/ update annos in frontendsettings.go * baldm0mma/sql_datasource_update/ update sql editor docs * baldm0mma/sql_datasource_update/ update mysql docs * baldm0mma/sql_datasource_update/ update postgres docs * baldm0mma/sql_datasource_update/ remove unused code in datasetSelector.tsx * baldm0mma/sql_datasource_update/ update syntax conventions * baldm0mma/sql_datasource_update/ add logs * baldm0mma/sql_datasource_update/ remove unused code * baldm0mma/sql_datasource_update/ remove conlogs * baldm0mma/sql_datasource_update/ update tests * baldm0mma/sql_datasource_update/ add second error to query * baldm0mma/sql_datasource_update/ run make gen-cue * baldm0mma/sql_database_update/ lint registry * baldm0mma/sql_datasource_update/ update registry * baldm0mma/sql_datasource_update/ upate datasource logic * baldm0mma/sql_datasource_update/ add logs * baldm0mma/sql_datasource_update/ add comms to self * baldm0mma/sql_datasource_update/ comment out false pos tests, and add investigatory comments * baldm0mma/sql_database_update/ update query error to only test for config change if query was made in "builder" mode * baldm0mma/sql_datasource_update/ update annos in frontendsettings.go * baldm0mma/sql_datasource_update/ update error logic to datasource * baldm0mma/sql_datasource_update/ remove alerts from query editor * baldm0mma/sql_datasource_update/ remove unused imports * baldm0mma/sql_datasource_update/ update tests * baldm0mma/sql_datasource_update/ remove comments * baldm0mma/sql_datasource_update/ remove logs in queryGroup.tsx * baldm0mma/sql_datasource_update/ remove outdated annotation in datasetSelector.tsx * baldm0mma/sql_datasource_update/ remove superfluous test and update test description * baldm0mma/sql_datasource_update/ remove feature flag * baldm0mma/sql_datasource_update/ add back feature flag * baldm0mma/sql_datasource_update/ update to enums * baldm0mma/sql_datasource_update/ update panel caps * baldm0mma/sql_datasource_update/ update dataset selector to default update the database correctly * baldm0mma/sql_datasource_update/ move onChange into conditional * baldm0mma/sql_datasource_update/ add logic for previous datasets choice * baldm0mma/sql_datasource_update/ add back previous logic for assigning default datasets * baldm0mma/sql_datasource_update/ update useEffect dep array * baldm0mma/sql_datasource_update/ remove feature toggle * baldm0mma/sql_datasource_update/ add feature toggle --------- Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> Co-authored-by: lwandz13 <126723338+lwandz13@users.noreply.github.com>
This commit is contained in:
co-authored by
Zoltán Bedi
lwandz13
parent
52c4761218
commit
c0a1fc2cbd
@@ -87,12 +87,12 @@ Code mode supports autocompletion of tables, columns, SQL keywords, standard SQL
|
||||
|
||||
In **Builder mode**, you can build queries using a visual interface.
|
||||
|
||||
### Select a dataset and table
|
||||
### Dataset and table selection
|
||||
|
||||
In the **Dataset** dropdown, select the MS SQL database to query.
|
||||
In the **Dataset** dropdown, select the MSSQL database to query. Grafana populates the dropdown with all databases that the user can access.
|
||||
Once you select a database, Grafana populates the dropdown with all available tables.
|
||||
|
||||
Grafana populates the dropdown with the databases that the configured user can access.
|
||||
When you select a dataset, Grafana populates the **Table** dropdown with available tables.
|
||||
**Note:** If a default database has been configured through the Data Source Configuration page (or through a provisioning configuration file), the user will only be able to use that single preconfigured database for querying.
|
||||
|
||||
### Select columns and aggregation functions (SELECT)
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ If your table or database name contains a reserved word or a [not permitted char
|
||||
In the dataset dropdown, choose the MySQL database to query. The dropdown is be populated with the databases that the user has access to.
|
||||
When the dataset is selected, the table dropdown is populated with the tables that are available.
|
||||
|
||||
**Note:** If a default database has been configured through the Data Source Configuration page (or through a provisioning configuration file), the user will only be able to use that single preconfigured database for querying.
|
||||
|
||||
### Columns and Aggregation functions (SELECT)
|
||||
|
||||
Using the dropdown, select a column to include in the data. You can also specify an optional aggregation function.
|
||||
|
||||
@@ -88,7 +88,7 @@ Make sure the user does not get any unwanted privileges from the public role.
|
||||
|
||||
## Query builder
|
||||
|
||||
{{< figure src="/static/img/docs/v92/postgresql_query_builder.png" class="docs-image--no-shadow" caption="PostgreSQL query builder" >}}
|
||||
{{< figure src="/static/img/docs/screenshot-postgres-query-editor.png" class="docs-image--no-shadow" caption="PostgreSQL query builder" >}}
|
||||
|
||||
The PostgreSQL query builder is available when editing a panel using a PostgreSQL data source. The built query can be run by pressing the `Run query` button in the top right corner of the editor.
|
||||
|
||||
@@ -96,10 +96,10 @@ The PostgreSQL query builder is available when editing a panel using a PostgreSQ
|
||||
|
||||
The response from PostgreSQL can be formatted as either a table or as a time series. To use the time series format one of the columns must be named `time`.
|
||||
|
||||
### Dataset and Table selection
|
||||
### Dataset and table selection
|
||||
|
||||
In the dataset dropdown, choose the PostgreSQL database to query. The dropdown is be populated with the databases that the user has access to.
|
||||
When the dataset is selected, the table dropdown is populated with the tables that are available.
|
||||
The dataset dropdown will be populated with the configured database to which the user has access.
|
||||
The table dropdown is populated with the tables that are available within that database.
|
||||
|
||||
### Columns and Aggregation functions (SELECT)
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ Some stable features are enabled by default. You can disable a stable feature by
|
||||
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
|
||||
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
|
||||
| `dataSourcePageHeader` | Apply new pageHeader UI in data source edit page |
|
||||
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
|
||||
|
||||
## Alpha feature toggles
|
||||
|
||||
|
||||
@@ -100,4 +100,5 @@ export interface FeatureToggles {
|
||||
extraThemes?: boolean;
|
||||
lokiPredefinedOperations?: boolean;
|
||||
pluginsFrontendSandbox?: boolean;
|
||||
sqlDatasourceDatabaseSelection?: boolean;
|
||||
}
|
||||
|
||||
@@ -375,6 +375,16 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
}
|
||||
}
|
||||
|
||||
// Update `jsonData.database` for outdated provisioned SQL datasources created WITHOUT the `jsonData` object in their configuration.
|
||||
// In these cases, the `Database` value is defined (if at all) on the root level of the provisioning config object.
|
||||
// This is done for easier warning/error checking on the front end.
|
||||
if (ds.Type == datasources.DS_MSSQL) || (ds.Type == datasources.DS_MYSQL) || (ds.Type == datasources.DS_POSTGRES) {
|
||||
// Only update if the value isn't already assigned.
|
||||
if dsDTO.JSONData["database"] == nil || dsDTO.JSONData["database"] == "" {
|
||||
dsDTO.JSONData["database"] = ds.Database
|
||||
}
|
||||
}
|
||||
|
||||
if (ds.Type == datasources.DS_INFLUXDB) || (ds.Type == datasources.DS_ES) {
|
||||
dsDTO.Database = ds.Database
|
||||
}
|
||||
|
||||
@@ -555,5 +555,12 @@ var (
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "sqlDatasourceDatabaseSelection",
|
||||
Description: "Enables previous SQL data source dataset dropdown behavior",
|
||||
FrontendOnly: true,
|
||||
State: FeatureStateBeta,
|
||||
Owner: grafanaBiSquad,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -81,3 +81,4 @@ dataSourcePageHeader,beta,@grafana/enterprise-datasources,false,false,false,true
|
||||
extraThemes,alpha,@grafana/grafana-frontend-platform,false,false,false,true
|
||||
lokiPredefinedOperations,alpha,@grafana/observability-logs,false,false,false,true
|
||||
pluginsFrontendSandbox,alpha,@grafana/plugins-platform-backend,false,false,false,true
|
||||
sqlDatasourceDatabaseSelection,beta,@grafana/grafana-bi-squad,false,false,false,true
|
||||
|
||||
|
@@ -334,4 +334,8 @@ const (
|
||||
// FlagPluginsFrontendSandbox
|
||||
// Enables the plugins frontend sandbox
|
||||
FlagPluginsFrontendSandbox = "pluginsFrontendSandbox"
|
||||
|
||||
// FlagSqlDatasourceDatabaseSelection
|
||||
// Enables previous SQL data source dataset dropdown behavior
|
||||
FlagSqlDatasourceDatabaseSelection = "sqlDatasourceDatabaseSelection"
|
||||
)
|
||||
|
||||
@@ -6,47 +6,78 @@ import { Select } from '@grafana/ui';
|
||||
|
||||
import { DB, ResourceSelectorProps, toOption } from '../types';
|
||||
|
||||
interface DatasetSelectorProps extends ResourceSelectorProps {
|
||||
import { isSqlDatasourceDatabaseSelectionFeatureFlagEnabled } from './QueryEditorFeatureFlag.utils';
|
||||
|
||||
export interface DatasetSelectorProps extends ResourceSelectorProps {
|
||||
db: DB;
|
||||
value: string | null;
|
||||
applyDefault?: boolean;
|
||||
disabled?: boolean;
|
||||
dataset: string | undefined;
|
||||
preconfiguredDataset: string;
|
||||
isPostgresInstance: boolean | undefined;
|
||||
onChange: (v: SelectableValue) => void;
|
||||
}
|
||||
|
||||
export const DatasetSelector = ({ db, value, onChange, disabled, className, applyDefault }: DatasetSelectorProps) => {
|
||||
export const DatasetSelector = ({
|
||||
dataset,
|
||||
db,
|
||||
isPostgresInstance,
|
||||
onChange,
|
||||
preconfiguredDataset,
|
||||
}: DatasetSelectorProps) => {
|
||||
/*
|
||||
The behavior of this component - for MSSQL and MySQL datasources - is based on whether the user chose to create a datasource
|
||||
with or without a default database (preconfiguredDataset). If the user configured a default database, this selector
|
||||
should only allow that single preconfigured database option to be selected. If the user chose to NOT assign/configure a default database,
|
||||
then the user should be able to use this component to choose between multiple databases available to the datasource.
|
||||
*/
|
||||
// `hasPreconfigCondition` is true if either 1) the sql datasource has a preconfigured default database,
|
||||
// OR if 2) the datasource is Postgres. In either case the only option available to the user is the preconfigured database.
|
||||
const hasPreconfigCondition = !!preconfiguredDataset || isPostgresInstance;
|
||||
|
||||
const state = useAsync(async () => {
|
||||
if (isSqlDatasourceDatabaseSelectionFeatureFlagEnabled()) {
|
||||
// If a default database is already configured for a MSSQL or MySQL data source, OR the data source is Postgres, no need to fetch other databases.
|
||||
if (hasPreconfigCondition) {
|
||||
// Set the current database to the preconfigured database.
|
||||
onChange(toOption(preconfiguredDataset));
|
||||
return [toOption(preconfiguredDataset)];
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no preconfigured database, but there is a selected dataset, set the current database to the selected dataset.
|
||||
if (dataset) {
|
||||
onChange(toOption(dataset));
|
||||
}
|
||||
|
||||
// Otherwise, fetch all databases available to the datasource.
|
||||
const datasets = await db.datasets();
|
||||
return datasets.map(toOption);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!applyDefault) {
|
||||
return;
|
||||
}
|
||||
// Set default dataset when values are fetched
|
||||
if (!value) {
|
||||
if (state.value && state.value[0]) {
|
||||
onChange(state.value[0]);
|
||||
}
|
||||
} else {
|
||||
if (state.value && state.value.find((v) => v.value === value) === undefined) {
|
||||
// if value is set and newly fetched values does not contain selected value
|
||||
if (state.value.length > 0) {
|
||||
if (!isSqlDatasourceDatabaseSelectionFeatureFlagEnabled()) {
|
||||
// Set default dataset when values are fetched
|
||||
if (!dataset) {
|
||||
if (state.value && state.value[0]) {
|
||||
onChange(state.value[0]);
|
||||
}
|
||||
} else {
|
||||
if (state.value && state.value.find((v) => v.value === dataset) === undefined) {
|
||||
// if value is set and newly fetched values does not contain selected value
|
||||
if (state.value.length > 0) {
|
||||
onChange(state.value[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [state.value, value, applyDefault, onChange]);
|
||||
}, [state.value, onChange, dataset]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
className={className}
|
||||
aria-label="Dataset selector"
|
||||
value={value}
|
||||
value={dataset}
|
||||
options={state.value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
disabled={state.loading}
|
||||
isLoading={state.loading}
|
||||
menuShouldPortal={true}
|
||||
/>
|
||||
|
||||
@@ -13,13 +13,23 @@ import { QueryHeader, QueryHeaderProps } from './QueryHeader';
|
||||
import { RawEditor } from './query-editor-raw/RawEditor';
|
||||
import { VisualEditor } from './visual-query-builder/VisualEditor';
|
||||
|
||||
interface Props extends QueryEditorProps<SqlDatasource, SQLQuery, SQLOptions> {
|
||||
queryHeaderProps?: Pick<QueryHeaderProps, 'isDatasetSelectorHidden'>;
|
||||
interface SqlQueryEditorProps extends QueryEditorProps<SqlDatasource, SQLQuery, SQLOptions> {
|
||||
queryHeaderProps?: Pick<QueryHeaderProps, 'isPostgresInstance'>;
|
||||
}
|
||||
|
||||
export function SqlQueryEditor({ datasource, query, onChange, onRunQuery, range, queryHeaderProps }: Props) {
|
||||
export function SqlQueryEditor({
|
||||
datasource,
|
||||
query,
|
||||
onChange,
|
||||
onRunQuery,
|
||||
range,
|
||||
queryHeaderProps,
|
||||
}: SqlQueryEditorProps) {
|
||||
const [isQueryRunnable, setIsQueryRunnable] = useState(true);
|
||||
const db = datasource.getDB();
|
||||
|
||||
const { preconfiguredDatabase } = datasource;
|
||||
const isPostgresInstance = !!queryHeaderProps?.isPostgresInstance;
|
||||
const { loading, error } = useAsync(async () => {
|
||||
return () => {
|
||||
if (datasource.getDB(datasource.id).init !== undefined) {
|
||||
@@ -80,13 +90,14 @@ export function SqlQueryEditor({ datasource, query, onChange, onRunQuery, range,
|
||||
<>
|
||||
<QueryHeader
|
||||
db={db}
|
||||
preconfiguredDataset={preconfiguredDatabase}
|
||||
onChange={onQueryHeaderChange}
|
||||
onRunQuery={onRunQuery}
|
||||
onQueryRowChange={setQueryRowFilter}
|
||||
queryRowFilter={queryRowFilter}
|
||||
query={queryWithDefaults}
|
||||
isQueryRunnable={isQueryRunnable}
|
||||
{...queryHeaderProps}
|
||||
isPostgresInstance={isPostgresInstance}
|
||||
/>
|
||||
|
||||
<Space v={0.5} />
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
export const isSqlDatasourceDatabaseSelectionFeatureFlagEnabled = () => {
|
||||
return !!config.featureToggles.sqlDatasourceDatabaseSelection;
|
||||
};
|
||||
@@ -10,17 +10,19 @@ import { SQLQuery, QueryFormat, QueryRowFilter, QUERY_FORMAT_OPTIONS, DB } from
|
||||
|
||||
import { ConfirmModal } from './ConfirmModal';
|
||||
import { DatasetSelector } from './DatasetSelector';
|
||||
import { isSqlDatasourceDatabaseSelectionFeatureFlagEnabled } from './QueryEditorFeatureFlag.utils';
|
||||
import { TableSelector } from './TableSelector';
|
||||
|
||||
export interface QueryHeaderProps {
|
||||
db: DB;
|
||||
query: QueryWithDefaults;
|
||||
onChange: (query: SQLQuery) => void;
|
||||
onRunQuery: () => void;
|
||||
onQueryRowChange: (queryRowFilter: QueryRowFilter) => void;
|
||||
queryRowFilter: QueryRowFilter;
|
||||
isPostgresInstance?: boolean;
|
||||
isQueryRunnable: boolean;
|
||||
isDatasetSelectorHidden?: boolean;
|
||||
onChange: (query: SQLQuery) => void;
|
||||
onQueryRowChange: (queryRowFilter: QueryRowFilter) => void;
|
||||
onRunQuery: () => void;
|
||||
preconfiguredDataset: string;
|
||||
query: QueryWithDefaults;
|
||||
queryRowFilter: QueryRowFilter;
|
||||
}
|
||||
|
||||
const editorModes = [
|
||||
@@ -30,13 +32,14 @@ const editorModes = [
|
||||
|
||||
export function QueryHeader({
|
||||
db,
|
||||
isPostgresInstance,
|
||||
isQueryRunnable,
|
||||
onChange,
|
||||
onQueryRowChange,
|
||||
onRunQuery,
|
||||
preconfiguredDataset,
|
||||
query,
|
||||
queryRowFilter,
|
||||
onChange,
|
||||
onRunQuery,
|
||||
onQueryRowChange,
|
||||
isQueryRunnable,
|
||||
isDatasetSelectorHidden,
|
||||
}: QueryHeaderProps) {
|
||||
const { editorMode } = query;
|
||||
const [_, copyToClipboard] = useCopyToClipboard();
|
||||
@@ -86,9 +89,20 @@ export function QueryHeader({
|
||||
sql: undefined,
|
||||
rawSql: '',
|
||||
};
|
||||
|
||||
onChange(next);
|
||||
};
|
||||
|
||||
const datasetDropdownIsAvailable = () => {
|
||||
// If the feature flag is DISABLED, && the datasource is Postgres (`isPostgresInstance`),
|
||||
// we want to hide the dropdown - as per previous behavior.
|
||||
if (!isSqlDatasourceDatabaseSelectionFeatureFlagEnabled() && isPostgresInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditorHeader>
|
||||
@@ -205,24 +219,23 @@ export function QueryHeader({
|
||||
<>
|
||||
<Space v={0.5} />
|
||||
<EditorRow>
|
||||
{isDatasetSelectorHidden ? null : (
|
||||
{datasetDropdownIsAvailable() && (
|
||||
<EditorField label="Dataset" width={25}>
|
||||
<DatasetSelector
|
||||
db={db}
|
||||
value={query.dataset === undefined ? null : query.dataset}
|
||||
dataset={query.dataset}
|
||||
isPostgresInstance={isPostgresInstance}
|
||||
preconfiguredDataset={preconfiguredDataset}
|
||||
onChange={onDatasetChange}
|
||||
/>
|
||||
</EditorField>
|
||||
)}
|
||||
|
||||
<EditorField label="Table" width={25}>
|
||||
<TableSelector
|
||||
db={db}
|
||||
query={query}
|
||||
value={query.table === undefined ? null : query.table}
|
||||
dataset={query.dataset || preconfiguredDataset}
|
||||
table={query.table}
|
||||
onChange={onTableChange}
|
||||
forceFetch={isDatasetSelectorHidden}
|
||||
applyDefault
|
||||
/>
|
||||
</EditorField>
|
||||
</EditorRow>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { DatasetSelector } from './DatasetSelector';
|
||||
import { buildMockDatasetSelectorProps, buildMockTableSelectorProps } from './SqlComponents.testHelpers';
|
||||
import { TableSelector } from './TableSelector';
|
||||
|
||||
beforeEach(() => {
|
||||
config.featureToggles.sqlDatasourceDatabaseSelection = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.featureToggles.sqlDatasourceDatabaseSelection = false;
|
||||
});
|
||||
|
||||
describe('DatasetSelector', () => {
|
||||
it('should only query the database when needed', async () => {
|
||||
const mockProps = buildMockDatasetSelectorProps();
|
||||
render(<DatasetSelector {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockProps.db.datasets).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not query the database if Postgres instance, and no preconfigured database', async () => {
|
||||
const mockProps = buildMockDatasetSelectorProps({ isPostgresInstance: true });
|
||||
render(<DatasetSelector {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockProps.db.datasets).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not query the database if preconfigured', async () => {
|
||||
const mockProps = buildMockDatasetSelectorProps({ preconfiguredDataset: 'database 1' });
|
||||
render(<DatasetSelector {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockProps.db.datasets).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('TableSelector', () => {
|
||||
it('should only query the database when needed', async () => {
|
||||
const mockProps = buildMockTableSelectorProps({ dataset: 'database 1' });
|
||||
render(<TableSelector {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockProps.db.tables).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not query the database if no dataset is passed as a prop', async () => {
|
||||
const mockProps = buildMockTableSelectorProps();
|
||||
render(<TableSelector {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockProps.db.tables).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { TimeRange, PluginType } from '@grafana/data';
|
||||
|
||||
import { DB, SQLQuery, SQLSelectableValue, ValidationResults } from '../types';
|
||||
|
||||
import { DatasetSelectorProps } from './DatasetSelector';
|
||||
import { TableSelectorProps } from './TableSelector';
|
||||
|
||||
const buildMockDB = (): DB => ({
|
||||
datasets: jest.fn(() => Promise.resolve(['dataset1', 'dataset2'])),
|
||||
tables: jest.fn((_ds: string | undefined) => Promise.resolve(['table1', 'table2'])),
|
||||
fields: jest.fn((_query: SQLQuery, _order?: boolean) => Promise.resolve<SQLSelectableValue[]>([])),
|
||||
validateQuery: jest.fn((_query: SQLQuery, _range?: TimeRange) =>
|
||||
Promise.resolve<ValidationResults>({ query: { refId: '123' }, error: '', isError: false, isValid: true })
|
||||
),
|
||||
dsID: jest.fn(() => 1234),
|
||||
getEditorLanguageDefinition: jest.fn(() => ({ id: '4567' })),
|
||||
toRawSql: (_query: SQLQuery) => '',
|
||||
});
|
||||
|
||||
// This data is of type `SqlDatasource`
|
||||
export const buildMockDatasource = (hasDefaultDatabaseConfigured?: boolean) => {
|
||||
return {
|
||||
id: Infinity,
|
||||
type: '',
|
||||
name: '',
|
||||
uid: '',
|
||||
responseParser: { transformMetricFindResponse: jest.fn() },
|
||||
interval: '',
|
||||
db: buildMockDB(),
|
||||
preconfiguredDatabase: hasDefaultDatabaseConfigured ? 'default database' : '',
|
||||
getDB: () => buildMockDB(),
|
||||
getQueryModel: jest.fn(),
|
||||
getResponseParser: jest.fn(),
|
||||
interpolateVariable: jest.fn(),
|
||||
interpolateVariablesInQueries: jest.fn(),
|
||||
filterQuery: jest.fn(),
|
||||
applyTemplateVariables: jest.fn(),
|
||||
metricFindQuery: jest.fn(),
|
||||
templateSrv: {
|
||||
getVariables: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
containsTemplate: jest.fn(),
|
||||
updateTimeRange: jest.fn(),
|
||||
},
|
||||
runSql: jest.fn(),
|
||||
runMetaQuery: jest.fn(),
|
||||
targetContainsTemplate: jest.fn(),
|
||||
query: jest.fn(),
|
||||
getRequestHeaders: jest.fn(),
|
||||
streamOptionsProvider: jest.fn(),
|
||||
getResource: jest.fn(),
|
||||
postResource: jest.fn(),
|
||||
callHealthCheck: jest.fn(),
|
||||
testDatasource: jest.fn(),
|
||||
getRef: jest.fn(),
|
||||
meta: {
|
||||
id: '',
|
||||
name: '',
|
||||
type: PluginType.panel,
|
||||
info: {
|
||||
author: { name: '' },
|
||||
description: '',
|
||||
links: [],
|
||||
logos: { large: '', small: '' },
|
||||
screenshots: [],
|
||||
updated: '',
|
||||
version: '',
|
||||
},
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export function buildMockDatasetSelectorProps(overrides?: Partial<DatasetSelectorProps>): DatasetSelectorProps {
|
||||
return {
|
||||
db: buildMockDB(),
|
||||
dataset: '',
|
||||
isPostgresInstance: false,
|
||||
onChange: jest.fn(),
|
||||
preconfiguredDataset: '',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMockTableSelectorProps(overrides?: Partial<TableSelectorProps>): TableSelectorProps {
|
||||
return {
|
||||
db: buildMockDB(),
|
||||
dataset: '',
|
||||
table: '',
|
||||
onChange: jest.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -4,32 +4,32 @@ import { useAsync } from 'react-use';
|
||||
import { SelectableValue, toOption } from '@grafana/data';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { QueryWithDefaults } from '../defaults';
|
||||
import { DB, ResourceSelectorProps } from '../types';
|
||||
|
||||
interface TableSelectorProps extends ResourceSelectorProps {
|
||||
export interface TableSelectorProps extends ResourceSelectorProps {
|
||||
db: DB;
|
||||
value: string | null;
|
||||
query: QueryWithDefaults;
|
||||
table: string | undefined;
|
||||
dataset: string | undefined;
|
||||
onChange: (v: SelectableValue) => void;
|
||||
forceFetch?: boolean;
|
||||
}
|
||||
|
||||
export const TableSelector = ({ db, query, value, className, onChange, forceFetch }: TableSelectorProps) => {
|
||||
export const TableSelector = ({ db, dataset, table, className, onChange }: TableSelectorProps) => {
|
||||
const state = useAsync(async () => {
|
||||
if (!query.dataset && !forceFetch) {
|
||||
// No need to attempt to fetch tables for an unknown dataset.
|
||||
if (!dataset) {
|
||||
return [];
|
||||
}
|
||||
const tables = await db.tables(query.dataset);
|
||||
|
||||
const tables = await db.tables(dataset);
|
||||
return tables.map(toOption);
|
||||
}, [query.dataset]);
|
||||
}, [dataset]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
className={className}
|
||||
disabled={state.loading}
|
||||
aria-label="Table selector"
|
||||
value={value}
|
||||
value={table}
|
||||
options={state.value}
|
||||
onChange={onChange}
|
||||
isLoading={state.loading}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { lastValueFrom, Observable, throwError } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
DataFrameView,
|
||||
DataQuery,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceInstanceSettings,
|
||||
DataSourceRef,
|
||||
MetricFindValue,
|
||||
ScopedVars,
|
||||
TimeRange,
|
||||
CoreApp,
|
||||
} from '@grafana/data';
|
||||
import { EditorMode } from '@grafana/experimental';
|
||||
import {
|
||||
BackendDataSourceResponse,
|
||||
DataSourceWithBackend,
|
||||
@@ -30,12 +34,15 @@ import { MACRO_NAMES } from '../constants';
|
||||
import { DB, SQLQuery, SQLOptions, SqlQueryModel, QueryFormat } from '../types';
|
||||
import migrateAnnotation from '../utils/migration';
|
||||
|
||||
import { isSqlDatasourceDatabaseSelectionFeatureFlagEnabled } from './../components/QueryEditorFeatureFlag.utils';
|
||||
|
||||
export abstract class SqlDatasource extends DataSourceWithBackend<SQLQuery, SQLOptions> {
|
||||
id: number;
|
||||
responseParser: ResponseParser;
|
||||
name: string;
|
||||
interval: string;
|
||||
db: DB;
|
||||
preconfiguredDatabase: string;
|
||||
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<SQLOptions>,
|
||||
@@ -48,6 +55,11 @@ export abstract class SqlDatasource extends DataSourceWithBackend<SQLQuery, SQLO
|
||||
const settingsData = instanceSettings.jsonData || {};
|
||||
this.interval = settingsData.timeInterval || '1m';
|
||||
this.db = this.getDB();
|
||||
/*
|
||||
The `settingsData.database` will be defined if a default database has been defined in either
|
||||
1) the ConfigurationEditor.tsx, OR 2) the provisioning config file, either under `jsondata.database`, or simply `database`.
|
||||
*/
|
||||
this.preconfiguredDatabase = settingsData.database ?? '';
|
||||
this.annotations = {
|
||||
prepareAnnotation: migrateAnnotation,
|
||||
QueryEditor: SqlQueryEditor,
|
||||
@@ -115,6 +127,50 @@ export abstract class SqlDatasource extends DataSourceWithBackend<SQLQuery, SQLO
|
||||
};
|
||||
}
|
||||
|
||||
query(request: DataQueryRequest<SQLQuery>): Observable<DataQueryResponse> {
|
||||
// This logic reenables the previous SQL behavior regarding what databases are available for the user to query.
|
||||
if (isSqlDatasourceDatabaseSelectionFeatureFlagEnabled()) {
|
||||
const databaseIssue = this.checkForDatabaseIssue(request);
|
||||
|
||||
if (!!databaseIssue) {
|
||||
const error = new Error(databaseIssue);
|
||||
return throwError(() => error);
|
||||
}
|
||||
}
|
||||
|
||||
return super.query(request);
|
||||
}
|
||||
|
||||
private checkForDatabaseIssue(request: DataQueryRequest<SQLQuery>) {
|
||||
// If the datasource is Postgres and there is no default database configured - either never configured or removed - return a database issue.
|
||||
if (this.type === 'postgres' && !this.preconfiguredDatabase) {
|
||||
return `You do not currently have a default database configured for this data source. Postgres requires a default
|
||||
database with which to connect. Please configure one through the Data Sources Configuration page, or if you
|
||||
are using a provisioning file, update that configuration file with a default database.`;
|
||||
}
|
||||
|
||||
// No need to check for database change/update issues if the datasource is being used in Explore.
|
||||
if (request.app !== CoreApp.Explore) {
|
||||
/*
|
||||
If a preconfigured datasource database has been added/updated - and the user has built ANY number of queries using a
|
||||
database OTHER than the preconfigured one, return a database issue - since those databases are no longer available.
|
||||
The user will need to update their queries to use the preconfigured database.
|
||||
*/
|
||||
if (!!this.preconfiguredDatabase) {
|
||||
for (const target of request.targets) {
|
||||
// Test for database configuration change only if query was made in `builder` mode.
|
||||
if (target.editorMode === EditorMode.Builder && target.dataset !== this.preconfiguredDatabase) {
|
||||
return `The configuration for this panel's data source has been modified. The previous database used in this panel's
|
||||
saved query is no longer available. Please update the query to use the new database option.
|
||||
Previous query parameters will be preserved until the query is updated.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async metricFindQuery(query: string, optionalOptions?: MetricFindQueryOptions): Promise<MetricFindValue[]> {
|
||||
let refId = 'tempvar';
|
||||
if (optionalOptions && optionalOptions.variable && optionalOptions.variable.name) {
|
||||
|
||||
@@ -147,6 +147,7 @@ export class QueryGroup extends PureComponent<Props, State> {
|
||||
const queries = await updateQueries(nextDS, newSettings.uid, this.state.queries, currentDS);
|
||||
|
||||
const dataSource = await this.dataSourceSrv.get(newSettings.name);
|
||||
|
||||
this.onChange({
|
||||
queries,
|
||||
dataSource: {
|
||||
|
||||
@@ -83,7 +83,7 @@ export class MssqlDatasource extends SqlDatasource {
|
||||
validateQuery: (query) =>
|
||||
Promise.resolve({ isError: false, isValid: true, query, error: '', rawSql: query.rawSql }),
|
||||
dsID: () => this.id,
|
||||
dispose: (dsID?: string) => {},
|
||||
dispose: (_dsID?: string) => {},
|
||||
toRawSql,
|
||||
lookup: async (path?: string) => {
|
||||
if (!path) {
|
||||
|
||||
@@ -29,11 +29,13 @@ export class MySqlDatasource extends SqlDatasource {
|
||||
const args = {
|
||||
getMeta: (identifier?: TableIdentifier) => this.fetchMeta(identifier),
|
||||
};
|
||||
|
||||
this.sqlLanguageDefinition = {
|
||||
id: 'mysql',
|
||||
completionProvider: getSqlCompletionProvider(args),
|
||||
formatter: formatSQL,
|
||||
};
|
||||
|
||||
return this.sqlLanguageDefinition;
|
||||
}
|
||||
|
||||
@@ -88,11 +90,12 @@ export class MySqlDatasource extends SqlDatasource {
|
||||
if (this.db !== undefined) {
|
||||
return this.db;
|
||||
}
|
||||
|
||||
return {
|
||||
datasets: () => this.fetchDatasets(),
|
||||
tables: (dataset?: string) => this.fetchTables(dataset),
|
||||
fields: (query: SQLQuery) => this.fetchFields(query),
|
||||
validateQuery: (query: SQLQuery, range?: TimeRange) =>
|
||||
validateQuery: (query: SQLQuery, _range?: TimeRange) =>
|
||||
Promise.resolve({ query, error: '', isError: false, isValid: true }),
|
||||
dsID: () => this.id,
|
||||
toRawSql,
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import { SQLOptions, SQLQuery } from 'app/features/plugins/sql/types';
|
||||
|
||||
import { PostgresDatasource } from './datasource';
|
||||
|
||||
const queryHeaderProps = { isDatasetSelectorHidden: true };
|
||||
const queryHeaderProps = { isPostgresInstance: true };
|
||||
|
||||
export function QueryEditor(props: QueryEditorProps<PostgresDatasource, SQLQuery, SQLOptions>) {
|
||||
export function PostgresQueryEditor(props: QueryEditorProps<PostgresDatasource, SQLQuery, SQLOptions>) {
|
||||
return <SqlQueryEditor {...props} queryHeaderProps={queryHeaderProps} />;
|
||||
}
|
||||
@@ -81,6 +81,7 @@ export class PostgresDatasource extends SqlDatasource {
|
||||
if (this.db !== undefined) {
|
||||
return this.db;
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => Promise.resolve(true),
|
||||
datasets: () => Promise.resolve([]),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DataSourcePlugin } from '@grafana/data';
|
||||
import { SQLQuery } from 'app/features/plugins/sql/types';
|
||||
|
||||
import { CheatSheet } from './CheatSheet';
|
||||
import { QueryEditor } from './QueryEditor';
|
||||
import { PostgresQueryEditor } from './PostgresQueryEditor';
|
||||
import { PostgresConfigEditor } from './configuration/ConfigurationEditor';
|
||||
import { PostgresDatasource } from './datasource';
|
||||
import { PostgresOptions, SecureJsonData } from './types';
|
||||
@@ -10,6 +10,6 @@ import { PostgresOptions, SecureJsonData } from './types';
|
||||
export const plugin = new DataSourcePlugin<PostgresDatasource, SQLQuery, PostgresOptions, SecureJsonData>(
|
||||
PostgresDatasource
|
||||
)
|
||||
.setQueryEditor(QueryEditor)
|
||||
.setQueryEditor(PostgresQueryEditor)
|
||||
.setQueryEditorHelp(CheatSheet)
|
||||
.setConfigEditor(PostgresConfigEditor);
|
||||
|
||||
Reference in New Issue
Block a user