TokenPermissionsInfo: Add token information for GitLab and Bitbucket (#109158)

* TokenPermissionsInfo: add token information for gitlab and bitbucket
This commit is contained in:
Yunwen Zheng 2025-08-06 13:04:24 -04:00 committed by GitHub
parent 1b244cd036
commit 1f76765ed7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 133 additions and 27 deletions

View File

@ -22,6 +22,7 @@ import { getGitProviderFields, getLocalProviderFields } from '../Wizard/fields';
import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository';
import { RepositoryFormData } from '../types';
import { dataToSpec } from '../utils/data';
import { getHasTokenInstructions } from '../utils/git';
import { getRepositoryTypeConfig, isGitProvider } from '../utils/repositoryTypes';
import { ConfigFormGithubCollapse } from './ConfigFormGithubCollapse';
@ -62,6 +63,7 @@ export function ConfigForm({ data }: ConfigFormProps) {
// Get field configurations based on provider type
const gitFields = isGitBased ? getGitProviderFields(type) : null;
const localFields = type === 'local' ? getLocalProviderFields(type) : null;
const hasTokenInstructions = getHasTokenInstructions(type);
useEffect(() => {
if (request.isSuccess) {
@ -153,7 +155,7 @@ export function ConfigForm({ data }: ConfigFormProps) {
/>
</Field>
)}
{type === 'github' && <TokenPermissionsInfo />}
{hasTokenInstructions && <TokenPermissionsInfo type={type} />}
<Field
noMargin
label={gitFields.urlConfig.label}

View File

@ -1,44 +1,31 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { t, Trans } from '@grafana/i18n';
import { Stack, TextLink, useStyles2 } from '@grafana/ui';
export function TokenPermissionsInfo() {
import { InstructionAvailability } from '../Wizard/types';
export function TokenPermissionsInfo({ type }: { type: InstructionAvailability }) {
const styles = useStyles2(getStyles);
const { tokenText, createTokenLink, createTokenButtonText } = connectStepInstruction()[type];
return (
<div className={styles.container}>
{/* GitHub UI is English only, so these strings are not translated */}
{/* eslint-disable @grafana/i18n/no-untranslated-strings */}
<Stack gap={0.5} wrap={'wrap'}>
<Trans i18nKey="provisioning.token-permissions-info.go-to">Go to</Trans>
<TextLink external href="https://github.com/settings/personal-access-tokens/new">
GitHub Personal Access Tokens
<TextLink external href={createTokenLink}>
{tokenText}
</TextLink>
<Trans i18nKey="provisioning.token-permissions-info.and-click">and click</Trans>
<strong>"Fine-grained token".</strong>
<strong>"{createTokenButtonText}".</strong>
<Trans i18nKey="provisioning.token-permissions-info.make-sure">Make sure to include these permissions</Trans>:
</Stack>
{/* eslint-enable @grafana/i18n/no-untranslated-strings */}
<ul className={styles.permissionsList}>
{/* eslint-disable-next-line @grafana/i18n/no-untranslated-strings */}
<li>
Content: <span className={styles.accessLevel}>Read and write</span>
</li>
{/* eslint-disable-next-line @grafana/i18n/no-untranslated-strings */}
<li>
Metadata: <span className={styles.accessLevel}>Read only</span>
</li>
{/* eslint-disable-next-line @grafana/i18n/no-untranslated-strings */}
<li>
Pull requests: <span className={styles.accessLevel}>Read and write</span>
</li>
{/* eslint-disable-next-line @grafana/i18n/no-untranslated-strings */}
<li>
Webhooks: <span className={styles.accessLevel}>Read and write</span>
</li>
{getPermissionsForProvider(type).map((permission) => (
<AccessLevelField key={permission.name} label={permission.name} access={permission.access} />
))}
</ul>
</div>
);
@ -72,3 +59,87 @@ function getStyles(theme: GrafanaTheme2) {
}),
};
}
type Permission = {
name: string;
access: string;
};
function getPermissionsForProvider(type: InstructionAvailability): Permission[] {
switch (type) {
case 'github':
// GitHub UI is English only, so these strings are not translated
return [
{ name: 'Contents', access: 'Read and write' },
{ name: 'Metadata', access: 'Read only' },
{ name: 'Pull requests', access: 'Read and write' },
{ name: 'Webhooks', access: 'Read and write' },
];
case 'gitlab':
return [
{
name: t('provisioning.gitlab.permissions.repository-label', 'Repository'),
access: t('provisioning.gitlab.permissions.repository-read-write', 'Read and write'),
},
{
name: t('provisioning.gitlab.permissions.user-label', 'User'),
access: t('provisioning.gitlab.permissions.user-read', 'Read only'),
},
{
name: t('provisioning.gitlab.permissions.api', 'API'),
access: t('provisioning.gitlab.permissions.api-read-write', 'Read and write'),
},
];
case 'bitbucket':
return [
{
name: t('provisioning.bitbucket.permissions.repository-label', 'Repositories'),
access: t('provisioning.bitbucket.permissions.repository-read-write-admin', 'Read, and write'),
},
{
name: t('provisioning.bitbucket.permissions.pull-requests-label', 'Pull requests'),
access: t('provisioning.bitbucket.permissions.pull-requests-read-write', 'Read and write'),
},
{
name: t('provisioning.bitbucket.permissions.webhooks-label', 'Webhooks'),
access: t('provisioning.bitbucket.permissions.webhooks-read-write', 'Read and write'),
},
];
default:
return [];
}
}
function AccessLevelField({ label, access }: { label: string; access: string }) {
const styles = useStyles2(getStyles);
return (
<li>
{label}: <span className={styles.accessLevel}>{access}</span>
</li>
);
}
function connectStepInstruction() {
return {
bitbucket: {
// Bitbucket App password will be replaced by API tokens on Sep 9 2025
createTokenLink: 'https://bitbucket.org/account/settings/app-passwords/',
tokenText: t('provisioning.token-permissions-info.bitbucket.token-text', 'Bitbucket Personal Access Token'),
createTokenButtonText: t(
'provisioning.token-permissions-info.bitbucket.create-token-button',
'Create App passwords'
),
},
gitlab: {
createTokenLink: 'https://gitlab.com/-/user_settings/personal_access_tokens',
tokenText: t('provisioning.token-permissions-info.gitlab.token-text', 'GitLab Personal Access Token'),
createTokenButtonText: t('provisioning.token-permissions-info.gitlab.create-token-button', 'Add new token'),
},
// GitHub UI is English only, so these strings are not translated
github: {
createTokenLink: 'https://github.com/settings/personal-access-tokens/new',
tokenText: 'GitHub Personal Access Token',
createTokenButtonText: 'Fine-grained token',
},
};
}

View File

@ -4,6 +4,7 @@ import { Controller, useFormContext } from 'react-hook-form';
import { Field, Input, SecretInput, Stack } from '@grafana/ui';
import { TokenPermissionsInfo } from '../Shared/TokenPermissionsInfo';
import { getHasTokenInstructions } from '../utils/git';
import { isGitProvider } from '../utils/repositoryTypes';
import { getGitProviderFields, getLocalProviderFields } from './fields';
@ -26,11 +27,11 @@ export function ConnectStep() {
// Get field configurations based on provider type
const gitFields = isGitBased ? getGitProviderFields(type) : null;
const localFields = !isGitBased ? getLocalProviderFields(type) : null;
const hasTokenInstructions = getHasTokenInstructions(type);
return (
<Stack direction="column" gap={2}>
{/*TODO: Add same permission info for other providers*/}
{type === 'github' && <TokenPermissionsInfo />}
{hasTokenInstructions && <TokenPermissionsInfo type={type} />}
{gitFields && (
<>

View File

@ -41,3 +41,5 @@ export type StepStatusInfo =
| { status: 'success'; success?: string | StatusInfo }
| { status: 'error'; error: string | StatusInfo }
| { status: 'warning'; warning: string | StatusInfo };
export type InstructionAvailability = Extract<RepoType, 'bitbucket' | 'gitlab' | 'github'>;

View File

@ -1,5 +1,7 @@
import { RepositorySpec } from 'app/api/clients/provisioning/v0alpha1';
import { InstructionAvailability, RepoType } from '../Wizard/types';
/**
* Validates a Git branch name according to the following rules:
* 1. The branch name cannot start with `/`, end with `/`, `.`, or whitespace.
@ -64,3 +66,7 @@ export const getRepoHrefForProvider = (spec?: RepositorySpec) => {
return undefined;
}
};
export function getHasTokenInstructions(type: RepoType): type is InstructionAvailability {
return type === 'github' || type === 'gitlab' || type === 'bitbucket';
}

View File

@ -11098,6 +11098,14 @@
"branch-required": "Branch is required",
"path-description": "Optional subdirectory path within the repository",
"path-label": "Path",
"permissions": {
"pull-requests-label": "Pull requests",
"pull-requests-read-write": "Read and write",
"repository-label": "Repositories",
"repository-read-write-admin": "Read, and write",
"webhooks-label": "Webhooks",
"webhooks-read-write": "Read and write"
},
"pr-workflow-description": "Allows users to choose whether to open a pull request when saving changes. If the repository does not allow direct changes to the main branch, a pull request may still be required.",
"pr-workflow-label": "Enable pull request option when saving",
"token-description": "Bitbucket App Password with repository permissions",
@ -11311,6 +11319,14 @@
"branch-required": "Branch is required",
"path-description": "Optional subdirectory path within the repository",
"path-label": "Path",
"permissions": {
"api": "API",
"api-read-write": "Read and write",
"repository-label": "Repository",
"repository-read-write": "Read and write",
"user-label": "User",
"user-read": "Read only"
},
"pr-workflow-description": "Allows users to choose whether to open a merge request when saving changes. If the repository does not allow direct changes to the main branch, a merge request may still be required.",
"pr-workflow-label": "Enable merge request option when saving",
"token-description": "GitLab Project Access Token with repository permissions",
@ -11517,6 +11533,14 @@
},
"token-permissions-info": {
"and-click": "and click",
"bitbucket": {
"create-token-button": "Create App passwords",
"token-text": "Bitbucket Personal Access Token"
},
"gitlab": {
"create-token-button": "Add new token",
"token-text": "GitLab Personal Access Token"
},
"go-to": "Go to",
"make-sure": "Make sure to include these permissions"
},