Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config update #387

Merged
merged 11 commits into from
Nov 26, 2024
4 changes: 2 additions & 2 deletions docs/sources/setup/datasource.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ weight: 103

1. Click on the GitHub data source plugin which you have installed.

1. Go to its settings tab and at the bottom, you will find the **Connection** section.
1. Go to its settings tab and at the bottom, you will find the **Authentication** section.

1. Paste the access token.
{{< figure alt="Configuring API Token" src="/media/docs/grafana/data-sources/github/github-plugin-confg-token.png" >}}

(_Optional_): If you using the GitHub Enterprise, then select the **Enterprise** option inside the **Additional Settings** section and paste the URL of your GitHub Enterprise.
1. (_Optional_): If you using the GitHub Enterprise Server, then select the **Enterprise Server** option inside the **Connection** section and paste the URL of your GitHub Enterprise Server.

1. Click **Save & Test** button and you should see a confirmation dialog box that says "Data source is working".

Expand Down
11 changes: 11 additions & 0 deletions docs/sources/setup/token.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ Read more about [personal access tokens](https://docs.github.com/en/authenticati
1. Define the permissions which you want to allow.
1. Click **Generate Token**.

## Creating a fine-grained personal access token

This is an example when you want to use the fine-grained personal access token. \
Read more about [fine-grained personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token).

1. Login to your GitHub account.
1. Navigate to [Personal access tokens](https://github.com/settings/tokens?type=beta) and click **Generate new token**.
1. Provide a name for the token.
1. Provide necessary permissions which you want to allow. Ensure you are providing `read-only` permissions.
1. Click **Generate token**.

## Using GitHub App Authentication

You can also authenticate using a GitHub App instead of a personal access token. This method allows for better security and fine-grained access to resources.
Expand Down
11 changes: 9 additions & 2 deletions pkg/github/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package github

import (
"context"
"fmt"
"strings"

"github.com/grafana/github-datasource/pkg/dfutil"
Expand Down Expand Up @@ -187,9 +188,15 @@ func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRe
})
if err != nil {
if strings.Contains(err.Error(), "401 Unauthorized") {
return newHealthResult(backend.HealthStatusError, "401 Unauthorized. check your API key/Access token")
return newHealthResult(backend.HealthStatusError, "401 Unauthorized. Check your API key/Access token")
}
return newHealthResult(backend.HealthStatusError, "Health check failed")
if strings.Contains(err.Error(), "404 Not Found") {
return newHealthResult(backend.HealthStatusError, "404 Not Found. Check the Github Enterprise Server URL")
}
if strings.HasSuffix(err.Error(), "no such host") {
return newHealthResult(backend.HealthStatusError, "Unable to reach the Github Enterprise Server URL from the Grafana server. Check the Github Enterprise Server URL and/or proxy settings")
}
return newHealthResult(backend.HealthStatusError, fmt.Sprintf("Health check failed. %s", err.Error()))
yesoreyeram marked this conversation as resolved.
Show resolved Hide resolved
}

return newHealthResult(backend.HealthStatusOk, "Data source is working")
Expand Down
3 changes: 2 additions & 1 deletion src/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
ScopedVars,
} from '@grafana/data';
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
import { GitHubDataSourceOptions, GitHubQuery, GitHubVariableQuery } from './types';
import { replaceVariables } from './variables';
import { isValid } from './validation';
import { getAnnotationsFromFrame } from 'common/annotationsFromDataFrame';
import { prepareAnnotation } from 'migrations';
import { Observable } from 'rxjs';
import { trackRequest } from 'tracking';
import type { GitHubQuery, GitHubVariableQuery } from './types';
import type { GitHubDataSourceOptions } from './types/config';

export class GitHubDataSource extends DataSourceWithBackend<GitHubQuery, GitHubDataSourceOptions> {
templateSrv = getTemplateSrv();
Expand Down Expand Up @@ -65,7 +66,7 @@
interval: request.interval,
} as DataQueryRequest<GitHubQuery>;

const res = await this.query(query).toPromise();

Check warning on line 69 in src/DataSource.ts

View workflow job for this annotation

GitHub Actions / Build, lint and unit tests

'toPromise' is deprecated. Replaced with {@link firstValueFrom } and {@link lastValueFrom }. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise

if (!res?.data?.length) {
return [];
Expand Down Expand Up @@ -94,7 +95,7 @@
} as DataQueryRequest;

try {
const res = await this.query(request).toPromise();

Check warning on line 98 in src/DataSource.ts

View workflow job for this annotation

GitHub Actions / Build, lint and unit tests

'toPromise' is deprecated. Replaced with {@link firstValueFrom } and {@link lastValueFrom }. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise
const columns = (res?.data[0]?.fields || []).map((f: any) => f.name) || [];
return columns;
} catch (err) {
Expand All @@ -114,7 +115,7 @@
range: options.range,
} as DataQueryRequest;
try {
const res = await this.query(request).toPromise();

Check warning on line 118 in src/DataSource.ts

View workflow job for this annotation

GitHub Actions / Build, lint and unit tests

'toPromise' is deprecated. Replaced with {@link firstValueFrom } and {@link lastValueFrom }. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise
if (!res?.data?.length) {
return [];
}
Expand Down
3 changes: 2 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import ConfigEditor from './views/ConfigEditor';
import QueryEditor from './views/QueryEditor';
import VariableQueryEditor from './views/VariableQueryEditor';
import { GitHubQuery, GitHubDataSourceOptions, GitHubSecureJsonData } from './types';
import type { GitHubQuery } from './types';
import type { GitHubDataSourceOptions, GitHubSecureJsonData } from './types/config';

export const plugin = new DataSourcePlugin<
GitHubDataSource,
Expand All @@ -12,5 +13,5 @@
GitHubSecureJsonData
>(GitHubDataSource)
.setConfigEditor(ConfigEditor)
.setVariableQueryEditor(VariableQueryEditor)

Check warning on line 16 in src/module.ts

View workflow job for this annotation

GitHub Actions / Build, lint and unit tests

'setVariableQueryEditor' is deprecated. -- prefer using {@link StandardVariableSupport} or {@link CustomVariableSupport} or {@link DataSourceVariableSupport} in data source instead
.setQueryEditor(QueryEditor);
38 changes: 2 additions & 36 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DataQuery, DataSourceJsonData } from '@grafana/data';
import { Filter } from 'components/Filters';
yesoreyeram marked this conversation as resolved.
Show resolved Hide resolved
import type { DataQuery } from '@grafana/schema';
import type { Filter } from 'components/Filters';

export interface Label {
color: string;
Expand All @@ -12,40 +12,6 @@ export interface RepositoryOptions {
owner?: string;
}

export interface GitHubEnterpriseOptions {
githubUrl?: string;
}

export interface GitHubAppAuth {
appId?: string;
installationId?: string;
}

export interface GitHubDataSourceOptions
extends DataSourceJsonData,
RepositoryOptions,
GitHubEnterpriseOptions,
GitHubAppAuth {
selectedAuthType?: GitHubAuthType;
}

export interface GitHubSecureJsonData {
// accessToken is set if the user is using a Personal Access Token to connect to GitHub
accessToken?: string;
// privateKey is set if the user is using a GitHub App to connect to GitHub
privateKey?: string;
}

export enum GitHubAuthType {
Personal = 'personal-access-token',
App = 'github-app',
}

export enum GitHubLicenseType {
Basic = 'github-basic',
Enterprise = 'github-enterprise',
}

export enum QueryType {
Commits = 'Commits',
Issues = 'Issues',
Expand Down
19 changes: 19 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { DataSourceJsonData } from '@grafana/data';

export type GitHubLicenseType = 'github-basic' | 'github-enterprise-cloud' | 'github-enterprise-server';

export type GitHubAuthType = 'personal-access-token' | 'github-app';

export type GitHubDataSourceOptions = {
githubPlan?: GitHubLicenseType;
githubUrl?: string;
selectedAuthType?: GitHubAuthType;
appId?: string;
installationId?: string;
} & DataSourceJsonData;

export type GitHubSecureJsonDataKeys =
| 'accessToken' // accessToken is set if the user is using a Personal Access Token to connect to GitHub
| 'privateKey'; // privateKey is set if the user is using a GitHub App to connect to GitHub

export type GitHubSecureJsonData = Partial<Record<GitHubSecureJsonDataKeys, string>>;
ivanahuckova marked this conversation as resolved.
Show resolved Hide resolved
36 changes: 23 additions & 13 deletions src/views/ConfigEditor.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,38 @@ describe('Config Editor', () => {
const onOptionsChange = jest.fn();
const options = { jsonData: {}, secureJsonFields: {} } as any;
render(<ConfigEditor options={options} onOptionsChange={onOptionsChange} />);
await waitFor(() => expect(screen.getByText('Additional Settings')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('Connection')).toBeInTheDocument());
onOptionsChange.mockClear(); // this is called on component render, so we need to clear it to avoid false positives
expect(screen.getByLabelText('Basic')).toBeChecked();
expect(screen.getByLabelText('Enterprise')).not.toBeChecked();
expect(screen.queryByText('GitHub Enterprise URL')).not.toBeInTheDocument();
expect(onOptionsChange).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByLabelText('Enterprise'));
expect(screen.getByLabelText('Free, Pro & Team')).toBeChecked();
expect(screen.getByLabelText('Enterprise Server')).not.toBeChecked();
expect(screen.queryByText('GitHub Enterprise Server URL')).not.toBeInTheDocument();
expect(onOptionsChange).toHaveBeenCalledTimes(0);
expect(screen.queryByText('GitHub Enterprise URL')).toBeInTheDocument();
await userEvent.click(screen.getByLabelText('Enterprise Server'));
expect(onOptionsChange).toHaveBeenCalledTimes(1);
expect(screen.queryByText('GitHub Enterprise Server URL')).toBeInTheDocument();
});
it('should select enterprise license type as checked when the url is not empty', async () => {
const onOptionsChange = jest.fn();
const options = { jsonData: { githubUrl: 'https://foo.bar' }, secureJsonFields: {} } as any;
render(<ConfigEditor options={options} onOptionsChange={onOptionsChange} />);
await waitFor(() => expect(screen.getByText('Additional Settings')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('Connection')).toBeInTheDocument());
onOptionsChange.mockClear();
expect(screen.getByLabelText('Basic')).not.toBeChecked();
expect(screen.getByLabelText('Enterprise')).toBeChecked();
expect(screen.queryByText('GitHub Enterprise URL')).toBeInTheDocument();
expect(screen.getByLabelText('Free, Pro & Team')).not.toBeChecked();
expect(screen.getByLabelText('Enterprise Server')).toBeChecked();
expect(screen.queryByText('GitHub Enterprise Server URL')).toBeInTheDocument();
expect(onOptionsChange).toHaveBeenCalledTimes(0);
await userEvent.click(screen.getByLabelText('Basic'));
expect(onOptionsChange).toHaveBeenNthCalledWith(1, { jsonData: { githubUrl: '' }, secureJsonFields: {} });
await userEvent.click(screen.getByLabelText('Free, Pro & Team'));
expect(onOptionsChange).toHaveBeenNthCalledWith(1, {
jsonData: { githubUrl: '', githubPlan: 'github-basic' },
secureJsonFields: {},
});
expect(screen.queryByText('GitHub Enterprise URL')).not.toBeInTheDocument();
await userEvent.click(screen.getByLabelText('Enterprise Cloud'));
expect(onOptionsChange).toHaveBeenNthCalledWith(2, {
jsonData: { githubUrl: '', githubPlan: 'github-enterprise-cloud' },
secureJsonFields: {},
});
await userEvent.click(screen.getByLabelText('Enterprise Server'));
expect(screen.queryByText('GitHub Enterprise Server URL')).toBeInTheDocument();
});
});
Loading
Loading