Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, used to identify resource groups.

## 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="A comma-separated list of strings, used to identify 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('div').filter({hasText: /^Impersonate logged in user$/}).getByLabel('Toggle switch').click();
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('div').filter({hasText: /^Impersonate logged in user$/}).getByLabel('Toggle switch').click();
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