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 { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository'; | ||||||
| import { RepositoryFormData } from '../types'; | import { RepositoryFormData } from '../types'; | ||||||
| import { dataToSpec } from '../utils/data'; | import { dataToSpec } from '../utils/data'; | ||||||
|  | import { getHasTokenInstructions } from '../utils/git'; | ||||||
| import { getRepositoryTypeConfig, isGitProvider } from '../utils/repositoryTypes'; | import { getRepositoryTypeConfig, isGitProvider } from '../utils/repositoryTypes'; | ||||||
| 
 | 
 | ||||||
| import { ConfigFormGithubCollapse } from './ConfigFormGithubCollapse'; | import { ConfigFormGithubCollapse } from './ConfigFormGithubCollapse'; | ||||||
|  | @ -62,6 +63,7 @@ export function ConfigForm({ data }: ConfigFormProps) { | ||||||
|   // Get field configurations based on provider type
 |   // Get field configurations based on provider type
 | ||||||
|   const gitFields = isGitBased ? getGitProviderFields(type) : null; |   const gitFields = isGitBased ? getGitProviderFields(type) : null; | ||||||
|   const localFields = type === 'local' ? getLocalProviderFields(type) : null; |   const localFields = type === 'local' ? getLocalProviderFields(type) : null; | ||||||
|  |   const hasTokenInstructions = getHasTokenInstructions(type); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (request.isSuccess) { |     if (request.isSuccess) { | ||||||
|  | @ -153,7 +155,7 @@ export function ConfigForm({ data }: ConfigFormProps) { | ||||||
|                 /> |                 /> | ||||||
|               </Field> |               </Field> | ||||||
|             )} |             )} | ||||||
|             {type === 'github' && <TokenPermissionsInfo />} |             {hasTokenInstructions && <TokenPermissionsInfo type={type} />} | ||||||
|             <Field |             <Field | ||||||
|               noMargin |               noMargin | ||||||
|               label={gitFields.urlConfig.label} |               label={gitFields.urlConfig.label} | ||||||
|  |  | ||||||
|  | @ -1,44 +1,31 @@ | ||||||
| import { css } from '@emotion/css'; | import { css } from '@emotion/css'; | ||||||
| 
 | 
 | ||||||
| import { GrafanaTheme2 } from '@grafana/data'; | import { GrafanaTheme2 } from '@grafana/data'; | ||||||
| import { Trans } from '@grafana/i18n'; | import { t, Trans } from '@grafana/i18n'; | ||||||
| import { Stack, TextLink, useStyles2 } from '@grafana/ui'; | 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 styles = useStyles2(getStyles); | ||||||
|  |   const { tokenText, createTokenLink, createTokenButtonText } = connectStepInstruction()[type]; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className={styles.container}> |     <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'}> |       <Stack gap={0.5} wrap={'wrap'}> | ||||||
|         <Trans i18nKey="provisioning.token-permissions-info.go-to">Go to</Trans> |         <Trans i18nKey="provisioning.token-permissions-info.go-to">Go to</Trans> | ||||||
|         <TextLink external href="https://github.com/settings/personal-access-tokens/new"> |         <TextLink external href={createTokenLink}> | ||||||
|           GitHub Personal Access Tokens |           {tokenText} | ||||||
|         </TextLink> |         </TextLink> | ||||||
|         <Trans i18nKey="provisioning.token-permissions-info.and-click">and click</Trans> |         <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>: |         <Trans i18nKey="provisioning.token-permissions-info.make-sure">Make sure to include these permissions</Trans>: | ||||||
|       </Stack> |       </Stack> | ||||||
|       {/* eslint-enable @grafana/i18n/no-untranslated-strings */} |  | ||||||
| 
 | 
 | ||||||
|       <ul className={styles.permissionsList}> |       <ul className={styles.permissionsList}> | ||||||
|         {/* eslint-disable-next-line @grafana/i18n/no-untranslated-strings */} |         {getPermissionsForProvider(type).map((permission) => ( | ||||||
|         <li> |           <AccessLevelField key={permission.name} label={permission.name} access={permission.access} /> | ||||||
|           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> |  | ||||||
|       </ul> |       </ul> | ||||||
|     </div> |     </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 { Field, Input, SecretInput, Stack } from '@grafana/ui'; | ||||||
| 
 | 
 | ||||||
| import { TokenPermissionsInfo } from '../Shared/TokenPermissionsInfo'; | import { TokenPermissionsInfo } from '../Shared/TokenPermissionsInfo'; | ||||||
|  | import { getHasTokenInstructions } from '../utils/git'; | ||||||
| import { isGitProvider } from '../utils/repositoryTypes'; | import { isGitProvider } from '../utils/repositoryTypes'; | ||||||
| 
 | 
 | ||||||
| import { getGitProviderFields, getLocalProviderFields } from './fields'; | import { getGitProviderFields, getLocalProviderFields } from './fields'; | ||||||
|  | @ -26,11 +27,11 @@ export function ConnectStep() { | ||||||
|   // Get field configurations based on provider type
 |   // Get field configurations based on provider type
 | ||||||
|   const gitFields = isGitBased ? getGitProviderFields(type) : null; |   const gitFields = isGitBased ? getGitProviderFields(type) : null; | ||||||
|   const localFields = !isGitBased ? getLocalProviderFields(type) : null; |   const localFields = !isGitBased ? getLocalProviderFields(type) : null; | ||||||
|  |   const hasTokenInstructions = getHasTokenInstructions(type); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Stack direction="column" gap={2}> |     <Stack direction="column" gap={2}> | ||||||
|       {/*TODO: Add same permission info for other providers*/} |       {hasTokenInstructions && <TokenPermissionsInfo type={type} />} | ||||||
|       {type === 'github' && <TokenPermissionsInfo />} |  | ||||||
| 
 | 
 | ||||||
|       {gitFields && ( |       {gitFields && ( | ||||||
|         <> |         <> | ||||||
|  |  | ||||||
|  | @ -41,3 +41,5 @@ export type StepStatusInfo = | ||||||
|   | { status: 'success'; success?: string | StatusInfo } |   | { status: 'success'; success?: string | StatusInfo } | ||||||
|   | { status: 'error'; error: string | StatusInfo } |   | { status: 'error'; error: string | StatusInfo } | ||||||
|   | { status: 'warning'; warning: 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 { RepositorySpec } from 'app/api/clients/provisioning/v0alpha1'; | ||||||
| 
 | 
 | ||||||
|  | import { InstructionAvailability, RepoType } from '../Wizard/types'; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Validates a Git branch name according to the following rules: |  * Validates a Git branch name according to the following rules: | ||||||
|  * 1. The branch name cannot start with `/`, end with `/`, `.`, or whitespace. |  * 1. The branch name cannot start with `/`, end with `/`, `.`, or whitespace. | ||||||
|  | @ -64,3 +66,7 @@ export const getRepoHrefForProvider = (spec?: RepositorySpec) => { | ||||||
|       return undefined; |       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", |       "branch-required": "Branch is required", | ||||||
|       "path-description": "Optional subdirectory path within the repository", |       "path-description": "Optional subdirectory path within the repository", | ||||||
|       "path-label": "Path", |       "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-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", |       "pr-workflow-label": "Enable pull request option when saving", | ||||||
|       "token-description": "Bitbucket App Password with repository permissions", |       "token-description": "Bitbucket App Password with repository permissions", | ||||||
|  | @ -11311,6 +11319,14 @@ | ||||||
|       "branch-required": "Branch is required", |       "branch-required": "Branch is required", | ||||||
|       "path-description": "Optional subdirectory path within the repository", |       "path-description": "Optional subdirectory path within the repository", | ||||||
|       "path-label": "Path", |       "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-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", |       "pr-workflow-label": "Enable merge request option when saving", | ||||||
|       "token-description": "GitLab Project Access Token with repository permissions", |       "token-description": "GitLab Project Access Token with repository permissions", | ||||||
|  | @ -11517,6 +11533,14 @@ | ||||||
|     }, |     }, | ||||||
|     "token-permissions-info": { |     "token-permissions-info": { | ||||||
|       "and-click": "and click", |       "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", |       "go-to": "Go to", | ||||||
|       "make-sure": "Make sure to include these permissions" |       "make-sure": "Make sure to include these permissions" | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue