mirror of https://github.com/grafana/grafana.git
				
				
				
			TokenPermissionsInfo: Add token information for GitLab and Bitbucket (#109158)
* TokenPermissionsInfo: add token information for gitlab and bitbucket
This commit is contained in:
		
							parent
							
								
									1b244cd036
								
							
						
					
					
						commit
						1f76765ed7
					
				|  | @ -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} | ||||
|  |  | |||
|  | @ -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', | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
|  |  | |||
|  | @ -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 && ( | ||||
|         <> | ||||
|  |  | |||
|  | @ -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'>; | ||||
|  |  | |||
|  | @ -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'; | ||||
| } | ||||
|  |  | |||
|  | @ -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" | ||||
|     }, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue