Skip to content

Commit 99557a1

Browse files
authored
Rel 1.6.3 (#179)
* add project filter * add bucket filter * prep for 1.6.3
1 parent 4d97e61 commit 99557a1

11 files changed

Lines changed: 515 additions & 88 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Changelog
2+
## 1.6.3 (2026-04-05)
3+
* Add Project List Filter to restrict which projects appear in dropdowns using regex patterns
4+
* Add Log Bucket Filter with include/exclude support — prefix patterns with `!` to exclude matching buckets (e.g., `!.*/_Default` to hide Default buckets)
5+
* Fix race condition in variable query where default project could be unresolved before bucket/view queries run
6+
27
## 1.6.2 (2026-03-18)
38
* Fix project dropdown search failing with "contains global restriction" error
49
* Fix `useEffect` dependency causing excessive API calls for log buckets and views

README.md

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,65 @@ Once Grafana is configured with Google authentication for signing in, ensure tha
4949

5050
You can then configure the data source with the `OAuth Passthrough` authentication method. Ensure that you provide a default project ID otherwise the health-check will fail.
5151

52+
### Universe Domain
53+
54+
If you are using a Google Cloud environment that uses a custom universe domain (e.g., a sovereign or isolated cloud), you can configure the **Universe Domain** in the data source settings. This tells the plugin to use a different API endpoint instead of the default `googleapis.com`.
55+
56+
Leave this field empty to use the default `googleapis.com` domain.
57+
5258
### Grafana Configuration
5359

5460
1. With Grafana restarted, navigate to `Configuration -> Data sources` (or the route `/datasources`)
5561
2. Click "Add data source"
5662
3. Select "Google Cloud Logging"
5763
4. Provide credentials from your JWT file, either by uploading it using the file selector or by pasting its contents directly into the designated field
58-
5. Click "Save & test" to test that logs can be queried from Cloud Logging
64+
5. Optionally, configure the **Universe Domain** if you are using a non-default GCP environment
65+
6. Optionally, configure the **Project List Filter** to restrict which projects appear in the project dropdown (see [Project List Filter](#project-list-filter) below)
66+
7. Optionally, configure the **Log Bucket Filter** to include or exclude specific log buckets from the bucket dropdown (see [Log Bucket Filter](#log-bucket-filter) below)
67+
8. Click "Save & test" to test that logs can be queried from Cloud Logging
5968

6069
![image info](https://github.com/GoogleCloudPlatform/cloud-logging-data-source-plugin/blob/main/src/img/cloud_logging_config.png?raw=true)
6170

71+
### Project List Filter
72+
73+
If you have access to many GCP projects, you can restrict which projects appear in the project dropdown by configuring a **Project List Filter** in the data source settings.
74+
75+
Enter project IDs or regex patterns in the text area, one per line. Only projects matching at least one pattern will appear in the dropdown. Leave the field empty to show all projects (the default behavior).
76+
77+
Each line is treated as a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions) anchored to the full project ID. For example:
78+
79+
| Pattern | Matches |
80+
| --- | --- |
81+
| `my-project-123` | Only the exact project `my-project-123` |
82+
| `team-alpha-.*` | All projects starting with `team-alpha-` |
83+
| `prod-.*-logging` | Projects like `prod-us-logging`, `prod-eu-logging`, etc. |
84+
85+
You can combine multiple patterns (one per line) to match the union of all patterns. If a pattern contains invalid regex syntax, it is treated as a literal string match.
86+
87+
### Log Bucket Filter
88+
89+
You can control which log buckets appear in the bucket dropdown by configuring a **Log Bucket Filter** in the data source settings. This supports both **include** and **exclude** patterns, which is especially useful for excluding default system buckets from initiative projects.
90+
91+
Enter patterns in the text area, one per line:
92+
- **Include patterns** (no prefix): Only buckets matching at least one include pattern are shown.
93+
- **Exclude patterns** (prefixed with `!`): Buckets matching any exclude pattern are removed.
94+
95+
When both include and exclude patterns are present, include patterns are applied first, then exclude patterns remove from the result. Leave the field empty to show all buckets (the default behavior).
96+
97+
Patterns are [regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions) anchored to the **full bucket path** as returned by the API (e.g., `global/buckets/_Default` or `locations/us-central1/buckets/my-bucket`).
98+
99+
| Pattern | Effect |
100+
| --- | --- |
101+
| `!.*/_Default` | Exclude all `_Default` buckets across all locations |
102+
| `!.*/_Default`<br>`!.*/_Required` | Exclude both `_Default` and `_Required` buckets |
103+
| `.*my-app-logs` | Include only buckets ending with `my-app-logs` |
104+
| `global/buckets/.*`<br>`!.*/_Default` | Include only `global/buckets/*` buckets, but exclude `_Default` |
105+
106+
If a pattern contains invalid regex syntax, it is treated as a literal string match.
107+
62108
### An alternative way to provision the data source
63109

64-
After the plugin is installed, you can define and configure the data source in YAML files as part of Grafanas provisioning system, similar to [the Google Cloud Monitoring plugin](https://grafana.com/docs/grafana/latest/datasources/google-cloud-monitoring/#provision-the-data-source). For more information about provisioning, and for available configuration options, refer to [Provisioning Grafana](https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources).
110+
After the plugin is installed, you can define and configure the data source in YAML files as part of Grafana's provisioning system, similar to [the Google Cloud Monitoring plugin](https://grafana.com/docs/grafana/latest/datasources/google-cloud-monitoring/#provision-the-data-source). For more information about provisioning, and for available configuration options, refer to [Provisioning Grafana](https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources).
65111

66112
The following YAML is an example.
67113

@@ -74,6 +120,15 @@ datasources:
74120
access: proxy
75121
jsonData:
76122
authenticationType: gce
123+
# Optional: restrict the project dropdown to matching projects (regex supported)
124+
# projectListFilter: |
125+
# my-project-123
126+
# team-alpha-.*
127+
# Optional: filter log buckets (prefix with ! to exclude)
128+
# logBucketFilter: |
129+
# !.*/_Default
130+
# Optional: custom universe domain for sovereign cloud environments
131+
# universeDomain: googleapis.com
77132
```
78133

79134
### Supported variables

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "googlecloud-logging-datasource",
3-
"version": "1.6.2",
3+
"version": "1.6.3",
44
"description": "Backend Grafana plugin that enables visualization of GCP Cloud Logging logs in Grafana.",
55
"scripts": {
66
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
@@ -23,9 +23,9 @@
2323
"@grafana/tsconfig": "^2.0.1",
2424
"@playwright/test": "^1.57.0",
2525
"@stylistic/eslint-plugin-ts": "^4.4.0",
26-
"@swc/core": "^1.15.0",
26+
"@swc/core": "^1.15.24",
2727
"@swc/helpers": "^0.5.0",
28-
"@swc/jest": "^0.2.0",
28+
"@swc/jest": "^0.2.39",
2929
"@testing-library/jest-dom": "^6.6.0",
3030
"@testing-library/react": "^16.3.0",
3131
"@types/jest": "^29.5.0",

src/CloudLoggingVariableFindQuery.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default class CloudLoggingVariableFindQuery {
2525
async execute(query: CloudLoggingVariableQuery) {
2626
try {
2727
if (!query.projectId) {
28-
this.datasource.getDefaultProject().then(r => query.projectId = r);
28+
query.projectId = await this.datasource.getDefaultProject();
2929
}
3030
switch (query.selectedQueryType) {
3131
case LogFindQueryScopes.Projects:
@@ -44,7 +44,7 @@ export default class CloudLoggingVariableFindQuery {
4444
}
4545

4646
async handleProjectsQuery() {
47-
const projects = await this.datasource.getProjects();
47+
const projects = await this.datasource.getFilteredProjects();
4848
return (projects).map((s) => ({
4949
text: s,
5050
value: s,
@@ -58,7 +58,7 @@ export default class CloudLoggingVariableFindQuery {
5858
if (projectId.startsWith('$')) {
5959
p = getTemplateSrv().replace(projectId)
6060
}
61-
buckets = await this.datasource.getLogBuckets(p);
61+
buckets = await this.datasource.getFilteredBuckets(p);
6262
return (buckets).map((s) => ({
6363
text: s,
6464
value: s,

src/ConfigEditor.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import { DataSourcePluginOptionsEditorProps, SelectableValue } from '@grafana/data';
1818
import { ConnectionConfig, GoogleAuthType } from '@grafana/google-sdk';
19-
import { Checkbox, Field, Input, SecretInput, Select } from '@grafana/ui';
19+
import { Checkbox, Field, Input, SecretInput, Select, TextArea } from '@grafana/ui';
2020
import React, { PureComponent } from 'react';
2121
import { authTypes, CloudLoggingOptions, DataSourceSecureJsonData } from './types';
2222

@@ -206,6 +206,48 @@ const defaultProject = (props: Props) => {
206206
onPointerLeaveCapture={undefined}
207207
/>
208208
</Field>
209+
<Field
210+
label="Project List Filter"
211+
description="Enter project IDs or regex patterns, one per line. Only matching projects will appear in the project dropdown. Leave empty to show all projects."
212+
>
213+
<TextArea
214+
value={options.jsonData.projectListFilter || ''}
215+
placeholder={'my-project-id\nteam-.*\nprod-.*-logging'}
216+
rows={4}
217+
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
218+
onOptionsChange({
219+
...options,
220+
jsonData: {
221+
...options.jsonData,
222+
projectListFilter: e.target.value,
223+
},
224+
});
225+
}}
226+
onPointerEnterCapture={undefined}
227+
onPointerLeaveCapture={undefined}
228+
/>
229+
</Field>
230+
<Field
231+
label="Log Bucket Filter"
232+
description="Filter log buckets in dropdowns. Enter patterns one per line. Prefix a line with ! to exclude matching buckets. Without !, only matching buckets are included. Patterns are regex matched against the full bucket path. Leave empty to show all buckets."
233+
>
234+
<TextArea
235+
value={options.jsonData.logBucketFilter || ''}
236+
placeholder={'!.*/_Default\nmy-custom-bucket-.*'}
237+
rows={4}
238+
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
239+
onOptionsChange({
240+
...options,
241+
jsonData: {
242+
...options.jsonData,
243+
logBucketFilter: e.target.value,
244+
},
245+
});
246+
}}
247+
onPointerEnterCapture={undefined}
248+
onPointerLeaveCapture={undefined}
249+
/>
250+
</Field>
209251
</>
210252
);
211253
};

src/QueryEditor.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,21 @@ export function LoggingQueryEditor({ datasource, query, range, onChange, onRunQu
3434
}
3535
};
3636

37-
// Apply defaults if needed
37+
// Apply defaults if needed, and validate against project list filter
3838
if (!query.projectId) {
39-
datasource.getDefaultProject().then(r => query.projectId = r);
39+
datasource.getDefaultProject().then(r => {
40+
if (datasource.filterProjects([r]).length > 0) {
41+
query.projectId = r;
42+
}
43+
});
44+
} else if (!query.projectId.startsWith('$') && datasource.filterProjects([query.projectId]).length === 0) {
45+
// Previously selected project no longer passes the filter — clear it
46+
query.projectId = '';
47+
}
48+
49+
if (query.bucketId && !query.bucketId.startsWith('$') && datasource.filterBuckets([query.bucketId]).length === 0) {
50+
// Previously selected bucket no longer passes the filter — clear it
51+
query.bucketId = '';
4052
}
4153

4254
// Check query field from query params to support default way of propagating query from other parts of grafana.
@@ -71,7 +83,7 @@ export function LoggingQueryEditor({ datasource, query, range, onChange, onRunQu
7183
};
7284

7385
const loadProjects = useCallback((inputValue: string): Promise<Array<SelectableValue<string>>> => {
74-
return datasource.getProjects(inputValue || undefined).then(res => {
86+
return datasource.getFilteredProjects(inputValue || undefined).then(res => {
7587
setFetchError(undefined);
7688
return res.map(project => ({
7789
label: project,
@@ -88,15 +100,15 @@ export function LoggingQueryEditor({ datasource, query, range, onChange, onRunQu
88100
if (!query.projectId) {
89101
datasource.getDefaultProject().then(r => {
90102
query.projectId = r;
91-
datasource.getLogBuckets(query.projectId).then(res => {
103+
datasource.getFilteredBuckets(query.projectId).then(res => {
92104
setBuckets(res.map(bucket => ({
93105
label: bucket,
94106
value: bucket,
95107
})));
96108
}).catch(err => setFetchError(sanitizeFetchError(err)));
97109
});
98110
} else if (!query.projectId.startsWith('$')) {
99-
datasource.getLogBuckets(query.projectId).then(res => {
111+
datasource.getFilteredBuckets(query.projectId).then(res => {
100112
setBuckets(res.map(bucket => ({
101113
label: bucket,
102114
value: bucket,

src/VariableQueryEditor.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ export class CloudLoggingVariableQueryEditor extends PureComponent<Props, Variab
4949
async componentDidMount() {
5050
await this.props.datasource.ensureGCEDefaultProject();
5151
const projectId = this.props.query.projectId || (await this.props.datasource.getDefaultProject());
52-
const projects = (await this.props.datasource.getProjects());
52+
const projects = (await this.props.datasource.getFilteredProjects());
5353
let buckets: string[] = [];
5454
if (!projectId.startsWith('$')) {
55-
buckets = await this.props.datasource.getLogBuckets(projectId);
55+
buckets = await this.props.datasource.getFilteredBuckets(projectId);
5656
}
5757

5858
const state: any = {
@@ -80,7 +80,7 @@ export class CloudLoggingVariableQueryEditor extends PureComponent<Props, Variab
8080
async onProjectChange(projectId: string) {
8181
let buckets: string[] = [];
8282
if (!projectId.startsWith('$')) {
83-
buckets = await this.props.datasource.getLogBuckets(projectId);
83+
buckets = await this.props.datasource.getFilteredBuckets(projectId);
8484
}
8585
const state: any = {
8686
buckets,

0 commit comments

Comments
 (0)