Skip to content
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ docker run -d -p 3000:3000 \
* OAuth
* Raw SQL editor only, no query builder yet
* Macros
* Client tags support for resource group identification

## Macros support

Expand Down
11 changes: 8 additions & 3 deletions pkg/trino/datasource-context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import (
)

const (
accessTokenKey = "accessToken"
trinoUserHeader = "X-Trino-User"
bearerPrefix = "Bearer "
accessTokenKey = "accessToken"
trinoUserHeader = "X-Trino-User"
trinoClientTagsKey = "X-Trino-Client-Tags"
bearerPrefix = "Bearer "
)

type SQLDatasourceWithTrinoUserContext struct {
Expand All @@ -40,6 +41,10 @@ func (ds *SQLDatasourceWithTrinoUserContext) QueryData(ctx context.Context, req
ctx = context.WithValue(ctx, trinoUserHeader, user)
}

if settings.ClientTags != "" {
ctx = context.WithValue(ctx, trinoClientTagsKey, settings.ClientTags)
}

return ds.SQLDatasource.QueryData(ctx, req)
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/trino/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func (s *TrinoDatasource) SetQueryArgs(ctx context.Context, headers http.Header)

user := ctx.Value(trinoUserHeader)
accessToken := ctx.Value(accessTokenKey)
clientTags := ctx.Value(trinoClientTagsKey)

if user != nil {
args = append(args, sql.Named(trinoUserHeader, string(user.(*backend.User).Login)))
Expand All @@ -92,6 +93,10 @@ func (s *TrinoDatasource) SetQueryArgs(ctx context.Context, headers http.Header)
args = append(args, sql.Named(accessTokenKey, accessToken.(string)))
}

if clientTags != nil {
args = append(args, sql.Named(trinoClientTagsKey, clientTags.(string)))
}

return args
}

Expand Down
1 change: 1 addition & 0 deletions pkg/trino/models/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type TrinoDatasourceSettings struct {
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
ImpersonationUser string `json:"impersonationUser"`
ClientTags string `json:"clientTags"`
}

func (s *TrinoDatasourceSettings) Load(config backend.DataSourceInstanceSettings) error {
Expand Down
17 changes: 17 additions & 0 deletions src/ConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export class ConfigEditor extends PureComponent<Props, State> {
const onImpersonationUserChange = (event: ChangeEvent<HTMLInputElement>) => {
onOptionsChange({...options, jsonData: {...options.jsonData, impersonationUser: event.target.value}})
};
const onClientTagsChange = (event: ChangeEvent<HTMLInputElement>) => {
onOptionsChange({...options, jsonData: {...options.jsonData, clientTags: event.target.value}})
};
return (
<div className="gf-form-group">
<DataSourceHttpSettings
Expand Down Expand Up @@ -72,6 +75,20 @@ export class ConfigEditor extends PureComponent<Props, State> {
/>
</InlineField>
</div>
<div className="gf-form-inline">
<InlineField
label="Client Tags"
tooltip="Comma-separated list of tag strings for identifying Trino resource groups"
labelWidth={26}
>
<Input
value={options.jsonData?.clientTags ?? ''}
onChange={onClientTagsChange}
width={60}
placeholder="tag1,tag2,tag3"
/>
</InlineField>
</div>
</div>

<h3 className="page-heading">OAuth Trino Authentication</h3>
Expand Down
17 changes: 16 additions & 1 deletion src/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async function goToTrinoSettings(page: Page) {

async function setupDataSourceWithAccessToken(page: Page) {
await page.getByTestId('data-testid Datasource HTTP settings url').fill('http://trino:8080');
await page.locator('div').filter({hasText: /^Impersonate logged in userAccess token$/}).getByLabel('Toggle switch').click();
await page.locator('#trino-settings-enable-impersonation').click({ force: true });
await page.locator('div').filter({hasText: /^Access token$/}).locator('input[type="password"]').fill('aaa');
await page.getByTestId('data-testid Data source settings page Save and Test button').click();
}
Expand All @@ -35,6 +35,14 @@ async function setupDataSourceWithClientCredentials(page: Page, clientId: string
await page.getByTestId('data-testid Data source settings page Save and Test button').click();
}

async function setupDataSourceWithClientTags(page: Page, clientTags: string) {
await page.getByTestId('data-testid Datasource HTTP settings url').fill('http://trino:8080');
await page.locator('#trino-settings-enable-impersonation').click({ force: true });
await page.locator('div').filter({hasText: /^Access token$/}).locator('input[type="password"]').fill('aaa');
await page.locator('div').filter({hasText: /^Client Tags$/}).locator('input').fill(clientTags);
await page.getByTestId('data-testid Data source settings page Save and Test button').click();
}

async function runQueryAndCheckResults(page: Page) {
await page.getByLabel(EXPORT_DATA).click();
await page.getByTestId('data-testid TimePicker Open Button').click();
Expand Down Expand Up @@ -76,3 +84,10 @@ test('test client credentials flow with configured access token', async ({ page
await setupDataSourceWithClientCredentials(page, GRAFANA_CLIENT);
await expect(page.getByLabel(EXPORT_DATA)).toHaveCount(0);
});

test('test with client tags', async ({ page }) => {
await login(page);
await goToTrinoSettings(page);
await setupDataSourceWithClientTags(page, 'tag1,tag2,tag3');
await runQueryAndCheckResults(page);
});
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export interface TrinoDataSourceOptions extends DataSourceJsonData {
enableImpersonation?: boolean;
tokenUrl?: string;
clientId?: string;
impersonationUser?: string
impersonationUser?: string;
clientTags?: string;
}
/**
* Value that is used in the backend, but never sent over HTTP to the frontend
Expand Down
Loading