Compare commits

...

11 Commits

Author SHA1 Message Date
grafakus 7f584d143f shrug... 2026-01-14 15:15:51 +01:00
grafakus e38240369b Update cue files++ 2026-01-14 14:58:36 +01:00
grafakus 26ac64cf1a Merge branch 'main' into grafakus/query-variable-support-multiprops 2026-01-14 14:20:50 +01:00
Fabrizio ba6a783997 Add Db2 plugin (#116190)
* Add new Db2 plugin

* Fix ID

* Fix ID

* Run `i18n-extract`

* Linting

* Fix ID

* Linting

* Rename plugin

* Fix i18n entry

* Run `yarn i18n-extract`
2026-01-14 14:20:24 +01:00
grafakus d8777012cb Fix lint issues 2026-01-14 14:19:18 +01:00
grafakus dfa07d8e10 Fix openapi tests 2026-01-14 14:17:43 +01:00
Andreas Christou f704b8aa79 Cloud Monitoring: Add support for Google Cloud universe_domain (#115931)
* feat(cloud-monitoring): add support for Google Cloud universe_domain (#110083)

This change introduces support for Google Cloud's `universe_domain`, enabling connections to sovereign cloud environments with custom API endpoints.

- Adds an optional "Universe Domain" field in the Google Cloud Monitoring data source configuration (frontend and backend).
- Allows specifying a custom API domain (e.g., `s3nsapis.fr`) for use in sovereign environments.
- Defaults to `googleapis.com` to ensure backward compatibility for existing configurations.

Signed-off-by: Andreas Christou <andreas.christou@grafana.com>

* Minor docs update

* Doc updates

* Update editor

* Update docs/sources/datasources/google-cloud-monitoring/_index.md

Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>

* Lint

* Review

---------

Signed-off-by: Andreas Christou <andreas.christou@grafana.com>
Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>
2026-01-14 13:16:09 +00:00
grafakus dc63eb3314 Regen 2026-01-14 14:03:13 +01:00
grafakus 1e3c6fed55 Merge branch 'main' into grafakus/query-variable-support-multiprops
# Conflicts:
#	apps/dashboard/pkg/apis/dashboard_manifest.go
2026-01-14 13:50:54 +01:00
grafakus d02201f564 Update schemas 2026-01-14 13:41:21 +01:00
grafakus 2a9377ac02 QueryVariable: Support preview and autocomplete of multi-props 2026-01-14 13:14:32 +01:00
35 changed files with 1099 additions and 40 deletions
@@ -800,6 +800,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -804,6 +804,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -241,6 +241,8 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable
@@ -241,6 +241,8 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable
@@ -804,6 +804,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -1426,6 +1426,8 @@ type DashboardVariableOption struct {
Text DashboardStringOrArrayOfString `json:"text"`
// Value of the option
Value DashboardStringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewDashboardVariableOption creates a new DashboardVariableOption object.
@@ -5133,6 +5133,22 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardVariableOption(ref common.Refer
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardStringOrArrayOfString"),
},
},
"properties": {
SchemaProps: spec.SchemaProps{
Description: "Additional properties for multi-props variables",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"text", "value"},
},
@@ -808,6 +808,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
@@ -1429,6 +1429,8 @@ type DashboardVariableOption struct {
Text DashboardStringOrArrayOfString `json:"text"`
// Value of the option
Value DashboardStringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewDashboardVariableOption creates a new DashboardVariableOption object.
@@ -5196,6 +5196,22 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardVariableOption(ref common.Refere
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardStringOrArrayOfString"),
},
},
"properties": {
SchemaProps: spec.SchemaProps{
Description: "Additional properties for multi-props variables",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"text", "value"},
},
File diff suppressed because one or more lines are too long
@@ -103,10 +103,11 @@ To configure basic settings for the data source, complete the following steps:
1. Set the data source's basic configuration options:
| Name | Description |
| ----------- | ------------------------------------------------------------------------ |
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
| **Default** | Sets whether the data source is pre-selected for new panels. |
| Name | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
| **Default** | Sets whether the data source is pre-selected for new panels. |
| **Universe Domain** | The universe domain to connect to. For more information, refer to [Documentation on universe domains](https://docs.cloud.google.com/python/docs/reference/monitoring/latest/google.cloud.monitoring_v3.services.service_monitoring_service.ServiceMonitoringServiceAsyncClient#google_cloud_monitoring_v3_services_service_monitoring_service_ServiceMonitoringServiceAsyncClient_universe_domain). Defaults to `googleapis.com`. |
### Provision the data source
@@ -129,6 +130,7 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
universeDomain: googleapis.com
secureJsonData:
privateKey: |
-----BEGIN PRIVATE KEY-----
@@ -152,6 +154,7 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
universeDomain: googleapis.com
privateKeyPath: /etc/secrets/gce.pem
```
@@ -166,6 +169,7 @@ datasources:
access: proxy
jsonData:
authenticationType: gce
universeDomain: googleapis.com
```
## Import pre-configured dashboards
@@ -87,6 +87,7 @@ With a Grafana Enterprise license, you also get access to premium data sources,
- [CockroachDB](/grafana/plugins/grafana-cockroachdb-datasource)
- [Databricks](/grafana/plugins/grafana-databricks-datasource)
- [DataDog](/grafana/plugins/grafana-datadog-datasource)
- [IBM Db2](/grafana/plugins/grafana-ibmdb2-datasource)
- [Drone](/grafana/plugins/grafana-drone-datasource)
- [DynamoDB](/grafana/plugins/grafana-dynamodb-datasource/)
- [Dynatrace](/grafana/plugins/grafana-dynatrace-datasource)
+2
View File
@@ -237,6 +237,8 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable
@@ -91,6 +91,8 @@ export interface VariableOption {
text: string | string[];
value: string | string[];
isNone?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
properties?: Record<string, any>;
}
export interface IntervalVariableModel extends VariableWithOptions {
@@ -118,6 +120,7 @@ export interface QueryVariableModel extends VariableWithMultiSupport {
definition: string;
sort: VariableSort;
queryValue?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
query: any;
regex: string;
regexApplyTo?: VariableRegexApplyTo;
@@ -193,6 +196,7 @@ export interface BaseVariableModel {
skipUrlSync: boolean;
index: number;
state: LoadingState;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: any | null;
description: string | null;
usedInRepeat?: boolean;
@@ -231,6 +231,10 @@ export const defaultVariableModel: Partial<VariableModel> = {
* Option to be selected in a variable.
*/
export interface VariableOption {
/**
* Additional properties for multi-props variables
*/
properties?: Record<string, string>;
/**
* Whether the option is selected or not
*/
@@ -715,7 +715,9 @@ VariableOption: {
// Text to be displayed for the option
text: string | [...string]
// Value of the option
value: string | [...string]
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification
+2
View File
@@ -903,6 +903,8 @@ type VariableOption struct {
Text StringOrArrayOfString `json:"text"`
// Value of the option
Value StringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewVariableOption creates a new VariableOption object.
@@ -3912,6 +3912,13 @@
"value"
],
"properties": {
"properties": {
"description": "Additional properties for multi-props variables",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"selected": {
"description": "Whether the option is selected or not",
"type": "boolean"
@@ -3939,6 +3939,13 @@
"value"
],
"properties": {
"properties": {
"description": "Additional properties for multi-props variables",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"selected": {
"description": "Whether the option is selected or not",
"type": "boolean"
+6 -3
View File
@@ -92,7 +92,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
}, nil
}
url := fmt.Sprintf("%v/v3/projects/%v/metricDescriptors", dsInfo.services[cloudMonitor].url, defaultProject)
url := fmt.Sprintf("%s/v3/projects/%s/metricDescriptors", dsInfo.services[cloudMonitor].url, defaultProject)
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
@@ -139,6 +139,7 @@ type datasourceInfo struct {
defaultProject string
clientEmail string
tokenUri string
universeDomain string
services map[string]datasourceService
privateKey string
usingImpersonation bool
@@ -150,6 +151,7 @@ type datasourceJSONData struct {
DefaultProject string `json:"defaultProject"`
ClientEmail string `json:"clientEmail"`
TokenURI string `json:"tokenUri"`
UniverseDomain string `json:"universeDomain"`
UsingImpersonation bool `json:"usingImpersonation"`
ServiceAccountToImpersonate string `json:"serviceAccountToImpersonate"`
}
@@ -179,6 +181,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
defaultProject: jsonData.DefaultProject,
clientEmail: jsonData.ClientEmail,
tokenUri: jsonData.TokenURI,
universeDomain: jsonData.UniverseDomain,
usingImpersonation: jsonData.UsingImpersonation,
serviceAccountToImpersonate: jsonData.ServiceAccountToImpersonate,
services: map[string]datasourceService{},
@@ -194,13 +197,13 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
return nil, err
}
for name, info := range routes {
for name := range routes {
client, err := newHTTPClient(dsInfo, opts, &httpClientProvider, name)
if err != nil {
return nil, err
}
dsInfo.services[name] = datasourceService{
url: info.url,
url: buildURL(name, dsInfo.universeDomain),
client: client,
}
}
+9 -2
View File
@@ -23,12 +23,12 @@ type routeInfo struct {
var routes = map[string]routeInfo{
cloudMonitor: {
method: "GET",
url: "https://monitoring.googleapis.com",
url: "https://monitoring.",
scopes: []string{cloudMonitorScope},
},
resourceManager: {
method: "GET",
url: "https://cloudresourcemanager.googleapis.com",
url: "https://cloudresourcemanager.",
scopes: []string{resourceManagerScope},
},
}
@@ -68,6 +68,13 @@ func getMiddleware(model *datasourceInfo, routePath string) (httpclient.Middlewa
return tokenprovider.AuthMiddleware(provider), nil
}
func buildURL(route string, universeDomain string) string {
if universeDomain == "" {
universeDomain = "googleapis.com"
}
return routes[route].url + universeDomain
}
func newHTTPClient(model *datasourceInfo, opts httpclient.Options, clientProvider *httpclient.Provider, route string) (*http.Client, error) {
m, err := getMiddleware(model, route)
if err != nil {
@@ -111,7 +111,7 @@ func Test_setRequestVariables(t *testing.T) {
im: &fakeInstance{
services: map[string]datasourceService{
cloudMonitor: {
url: routes[cloudMonitor].url,
url: buildURL(cloudMonitor, "googleapis.com"),
client: &http.Client{},
},
},
@@ -284,6 +284,7 @@ function variableValueOptionsToVariableOptions(varState: MultiValueVariable['sta
value: String(o.value),
text: o.label,
selected: Array.isArray(varState.value) ? varState.value.includes(o.value) : varState.value === o.value,
...(o.properties && { properties: o.properties }),
}));
}
@@ -69,7 +69,6 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDelete
const isHasVariableOptions = hasVariableOptions(variable);
const optionsForSelect = isHasVariableOptions ? variable.getOptionsForSelect(false) : [];
const hasMultiProps = 'valuesFormat' in variable.state && variable.state.valuesFormat === 'json';
const onDeleteVariable = (hideModal: () => void) => () => {
reportInteraction('Delete variable');
@@ -125,7 +124,7 @@ export function VariableEditorForm({ variable, onTypeChange, onGoBack, onDelete
{EditorToRender && <EditorToRender variable={variable} onRunQuery={onRunQuery} />}
{isHasVariableOptions && <VariableValuesPreview options={optionsForSelect} hasMultiProps={hasMultiProps} />}
{isHasVariableOptions && <VariableValuesPreview options={optionsForSelect} />}
<div className={styles.buttonContainer}>
<Stack gap={2}>
@@ -10,13 +10,16 @@ import { Button, InlineFieldRow, InlineLabel, InteractiveTable, Text, useStyles2
export interface Props {
options: VariableValueOption[];
hasMultiProps?: boolean;
}
export const VariableValuesPreview = ({ options, hasMultiProps }: Props) => {
const hasMultiProps = (options: Props['options']) => {
return Object.keys(options[1]?.properties ?? options[0]?.properties ?? {}).length > 0;
};
export const VariableValuesPreview = ({ options }: Props) => {
const styles = useStyles2(getStyles);
const hasOptions = options.length > 0;
const displayMultiPropsPreview = config.featureToggles.multiPropsVariables && hasMultiProps;
const displayMultiPropsPreview = config.featureToggles.multiPropsVariables && hasOptions && hasMultiProps(options);
return (
<div className={styles.previewContainer} style={{ gap: '8px' }}>
@@ -43,7 +46,8 @@ function VariableValuesWithPropsPreview({ options }: { options: VariableValueOpt
return {
data,
columns: Object.keys(data[0] ?? {}).map((id) => ({
// the option at index 0 can be "All" so we try to grab the column names from the 2nd option
columns: Object.keys(data[1] ?? data[0] ?? {}).map((id) => ({
id,
// see https://github.com/TanStack/table/issues/1671
header: unsanitizeKey(id),
@@ -62,7 +66,6 @@ function VariableValuesWithPropsPreview({ options }: { options: VariableValueOpt
/>
);
}
const sanitizeKey = (key: string) => key.replace(/\./g, '__dot__');
const unsanitizeKey = (key: string) => key.replace(/__dot__/g, '.');
@@ -69,7 +69,7 @@ function ModalEditorMultiProps(props: ModalEditorProps) {
{queryValidationError && <FieldValidationMessage>{queryValidationError.message}</FieldValidationMessage>}
</div>
<div>
<VariableValuesPreview options={options} hasMultiProps={valuesFormat === 'json'} />
<VariableValuesPreview options={options} />
</div>
</Stack>
<Modal.ButtonRow>
@@ -53,7 +53,7 @@ describe('buildCategories', () => {
it('should add enterprise phantom plugins', () => {
const enterprisePluginsCategory = categories[3];
expect(enterprisePluginsCategory.title).toBe('Enterprise plugins');
expect(enterprisePluginsCategory.plugins.length).toBe(31);
expect(enterprisePluginsCategory.plugins.length).toBe(32);
expect(enterprisePluginsCategory.plugins[0].name).toBe('Adobe Analytics');
expect(enterprisePluginsCategory.plugins[enterprisePluginsCategory.plugins.length - 1].name).toBe('Zendesk');
});
@@ -13,6 +13,7 @@ import catchpointSvg from 'img/plugins/catchpoint.svg';
import cloudflareJpg from 'img/plugins/cloudflare.jpg';
import cockroachdbJpg from 'img/plugins/cockroachdb.jpg';
import datadogPng from 'img/plugins/datadog.png';
import db2Svg from 'img/plugins/db2.svg';
import droneSvg from 'img/plugins/drone.svg';
import dynatracePng from 'img/plugins/dynatrace.png';
import gitlabSvg from 'img/plugins/gitlab.svg';
@@ -418,6 +419,12 @@ function getEnterprisePhantomPlugins(): DataSourcePluginMeta[] {
name: 'SolarWinds',
imgUrl: solarWindsSvg,
}),
getPhantomPlugin({
id: 'grafana-ibmdb2-datasource',
description: t('datasources.get-enterprise-phantom-plugins.description.ibmdb2-datasource', 'IBM Db2 data source'),
name: 'IBM Db2',
imgUrl: db2Svg,
}),
];
}
@@ -81,16 +81,17 @@ const buildLabelPath = (label: string) => {
};
const getVariableValueProperties = (variable: TypedVariableModel): string[] => {
if (!('valuesFormat' in variable) || variable.valuesFormat !== 'json') {
if (!('options' in variable) || !variable.options[0].properties) {
return [];
}
function collectFieldPaths(option: Record<string, string>, currentPath: string) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function collectFieldPaths(properties: Record<string, any>, currentPath: string) {
let paths: string[] = [];
for (const field in option) {
if (option.hasOwnProperty(field)) {
for (const field in properties) {
if (properties.hasOwnProperty(field)) {
const newPath = `${currentPath}.${field}`;
const value = option[field];
const value = properties[field];
if (typeof value === 'object' && value !== null) {
paths = [...paths, ...collectFieldPaths(value, newPath)];
}
@@ -100,11 +101,7 @@ const getVariableValueProperties = (variable: TypedVariableModel): string[] => {
return paths;
}
try {
return collectFieldPaths(JSON.parse(variable.query)[0], variable.name);
} catch {
return [];
}
return collectFieldPaths(variable.options[0].properties, variable.name);
};
export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [
@@ -503,13 +503,16 @@ describe('linkSrv', () => {
});
describe('getPanelLinksVariableSuggestions', () => {
it('then it should return template variables, json properties and built-ins', () => {
it('then it should return template variables, options properties and built-ins', () => {
const templateSrvWithJsonValues = initTemplateSrv('key', [
{
type: 'custom',
name: 'customServers',
valuesFormat: 'json',
query: '[{"name":"web","ip":"192.168.0.100"},{"name":"ads","ip":"192.168.0.142"}]',
options: [
{ text: 'web', value: 'web', properties: { name: 'web', ip: '192.168.0.100' } },
{ text: 'ads', value: 'ads', properties: { name: 'ads', ip: '192.168.0.142' } },
],
},
]);
setTemplateSrv(templateSrvWithJsonValues);
@@ -1,10 +1,10 @@
import { memo } from 'react';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { DataSourcePluginOptionsEditorProps, updateDatasourcePluginJsonDataOption } from '@grafana/data';
import { ConnectionConfig } from '@grafana/google-sdk';
import { ConfigSection, DataSourceDescription } from '@grafana/plugin-ui';
import { reportInteraction, config } from '@grafana/runtime';
import { Divider, SecureSocksProxySettings } from '@grafana/ui';
import { config, reportInteraction } from '@grafana/runtime';
import { Divider, Field, Input, SecureSocksProxySettings, Stack } from '@grafana/ui';
import { CloudMonitoringOptions, CloudMonitoringSecureJsonData } from '../../types/types';
@@ -36,14 +36,33 @@ export const ConfigEditor = memo(({ options, onOptionsChange }: Props) => {
<Divider />
<ConfigSection
title="Additional settings"
description="Additional settings are optional settings that can be configured for more control over your data source. This includes Secure Socks Proxy."
description="Additional settings are optional settings that can be configured for more control over your data source. This includes Secure Socks Proxy and Universe Domain."
isCollapsible
isInitiallyOpen={options.jsonData.enableSecureSocksProxy !== undefined}
isInitiallyOpen={
options.jsonData.enableSecureSocksProxy !== undefined || options.jsonData.universeDomain !== undefined
}
>
<SecureSocksProxySettings options={options} onOptionsChange={onOptionsChange} />
<Stack direction={'column'}>
<Field noMargin label="Universe Domain">
<Input
width={50}
value={options.jsonData.universeDomain}
onChange={(event) =>
updateDatasourcePluginJsonDataOption(
{ options, onOptionsChange },
'universeDomain',
event.currentTarget.value
)
}
placeholder="googleapis.com"
></Input>
</Field>
<SecureSocksProxySettings options={options} onOptionsChange={onOptionsChange} />
</Stack>
</ConfigSection>
</>
)}
<Divider />
</>
);
});
@@ -38,6 +38,7 @@ export interface Aggregation {
export interface CloudMonitoringOptions extends DataSourceOptions {
gceDefaultProject?: string;
enableSecureSocksProxy?: boolean;
universeDomain?: string;
}
export interface CloudMonitoringSecureJsonData extends DataSourceSecureJsonData {}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

+1
View File
@@ -6983,6 +6983,7 @@
"drone-datasource": "Drone datasource",
"git-lab-integration-and-datasource": "GitLab integration and datasource",
"honeycomb-integration-and-datasource": "Honeycomb integration and datasource",
"ibmdb2-datasource": "IBM Db2 data source",
"jira-integration-and-datasource": "Jira integration and datasource",
"logic-monitor-devices-datasource": "LogicMonitor Devices datasource",
"mongo-db-integration-and-data-source": "MongoDB integration and data source",