mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
Implement UI for Github Apps <> Batch Changes integration (#63597)
Closes SRCH-660 Closes SRCH-661 This PR adds the UI flow for adding a GitHub app as a Batch Changes credential. The actual functionality to create the GitHub app and store it's information will be done in a follow up PR. https://github.com/sourcegraph/sourcegraph/assets/25608335/1089a363-070d-4fd8-8a03-af63cd378947 ## Test plan <!-- REQUIRED; info at https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles --> Manual testing. ## Changelog <!-- OPTIONAL; info at https://www.notion.so/sourcegraph/Writing-a-changelog-entry-dd997f411d524caabf0d8d38a24a878c -->
This commit is contained in:
parent
fcff7a218b
commit
2eafe0fcfe
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -73,7 +73,5 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"cody.codebase": "github.com/sourcegraph/sourcegraph",
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"docker-images/syntax-highlighter/Cargo.toml"
|
||||
],
|
||||
"rust-analyzer.linkedProjects": ["docker-images/syntax-highlighter/Cargo.toml"]
|
||||
}
|
||||
|
||||
@ -1,23 +1,15 @@
|
||||
import React, { type FC, useState, useCallback, useRef, useEffect } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { noop } from 'lodash'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import { EVENT_LOGGER } from '@sourcegraph/shared/src/telemetry/web/eventLogger'
|
||||
import {
|
||||
Alert,
|
||||
Container,
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
Text,
|
||||
PageHeader,
|
||||
ButtonLink,
|
||||
Checkbox,
|
||||
Link,
|
||||
} from '@sourcegraph/wildcard'
|
||||
import { Alert, Container, Button, Input, Label, Text, PageHeader, Checkbox, Link } from '@sourcegraph/wildcard'
|
||||
|
||||
import { GitHubAppDomain } from '../../graphql-operations'
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import type { GitHubAppDomain, GitHubAppKind } from '../../graphql-operations'
|
||||
import { PageTitle } from '../PageTitle'
|
||||
|
||||
interface StateResponse {
|
||||
@ -51,6 +43,8 @@ export interface CreateGitHubAppPageProps extends TelemetryV2Props {
|
||||
headerAnnotation?: React.ReactNode
|
||||
/** The domain the new GitHub App is meant to be used for in Sourcegraph. */
|
||||
appDomain: GitHubAppDomain
|
||||
/** The purpose of the GitHub App to be created. This is only applicable when the appDomain is BATCHES. */
|
||||
appKind: GitHubAppKind
|
||||
/** The name to use for the new GitHub App. Defaults to "Sourcegraph". */
|
||||
defaultAppName?: string
|
||||
/*
|
||||
@ -63,6 +57,13 @@ export interface CreateGitHubAppPageProps extends TelemetryV2Props {
|
||||
* or a string with an error message reason if not.
|
||||
*/
|
||||
validateURL?: (url: string) => true | string
|
||||
/** The currently authenticated user */
|
||||
authenticatedUser: AuthenticatedUser
|
||||
/**
|
||||
* Whether or not the page is being rendered in a minimized mode.
|
||||
* Minimized mode is when this component is rendered in a modal.
|
||||
*/
|
||||
minimizedMode?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,7 +80,11 @@ export const CreateGitHubAppPage: FC<CreateGitHubAppPageProps> = ({
|
||||
baseURL,
|
||||
validateURL,
|
||||
telemetryRecorder,
|
||||
appKind,
|
||||
authenticatedUser,
|
||||
minimizedMode,
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const ref = useRef<HTMLFormElement>(null)
|
||||
const formInput = useRef<HTMLInputElement>(null)
|
||||
const [name, setName] = useState<string>(defaultAppName)
|
||||
@ -143,24 +148,30 @@ export const CreateGitHubAppPage: FC<CreateGitHubAppPageProps> = ({
|
||||
const createState = useCallback(async () => {
|
||||
setError(undefined)
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/githubapp/new-app-state?appName=${name}&webhookURN=${url}&domain=${appDomain}&baseURL=${url}`
|
||||
)
|
||||
let appStateUrl = `/githubapp/new-app-state?appName=${encodeURIComponent(
|
||||
name
|
||||
)}&webhookURN=${url}&domain=${appDomain}&baseURL=${encodeURIComponent(url)}&kind=${appKind}`
|
||||
if (authenticatedUser) {
|
||||
appStateUrl = `${appStateUrl}&userID=${authenticatedUser.id}`
|
||||
}
|
||||
// We encode the name and url here so that special characters like `#` are interpreted as
|
||||
// part of the URL and not the fragment.
|
||||
const response = await fetch(appStateUrl)
|
||||
if (!response.ok) {
|
||||
if (response.body instanceof ReadableStream) {
|
||||
const error = await response.text()
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
const state = (await response.json()) as StateResponse
|
||||
const jsonResponse = (await response.json()) as StateResponse
|
||||
let webhookURL: string | undefined
|
||||
if (state.webhookUUID?.length) {
|
||||
webhookURL = new URL(`/.api/webhooks/${state.webhookUUID}`, originURL).href
|
||||
if (jsonResponse.webhookUUID?.length) {
|
||||
webhookURL = new URL(`/.api/webhooks/${jsonResponse.webhookUUID}`, originURL).href
|
||||
}
|
||||
if (!state.state?.length) {
|
||||
if (!jsonResponse.state?.length) {
|
||||
throw new Error('Response from server missing state parameter')
|
||||
}
|
||||
submitForm({ state: state.state, webhookURL, name })
|
||||
submitForm({ state: jsonResponse.state, webhookURL, name })
|
||||
} catch (error_) {
|
||||
if (error_ instanceof Error) {
|
||||
setError(error_.message)
|
||||
@ -170,7 +181,7 @@ export const CreateGitHubAppPage: FC<CreateGitHubAppPageProps> = ({
|
||||
setError('Unknown error occurred.')
|
||||
}
|
||||
}
|
||||
}, [submitForm, name, appDomain, url, originURL])
|
||||
}, [submitForm, name, appDomain, url, originURL, appKind, authenticatedUser])
|
||||
|
||||
const handleNameChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value)
|
||||
@ -207,34 +218,38 @@ export const CreateGitHubAppPage: FC<CreateGitHubAppPageProps> = ({
|
||||
|
||||
const handleOrgChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => setOrg(event.target.value), [])
|
||||
const toggleIsPublic = useCallback(() => setIsPublic(isPublic => !isPublic), [])
|
||||
const cancelUrl = `/site-admin/${appDomain === GitHubAppDomain.BATCHES ? 'batch-changes' : 'github-apps'}`
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={pageTitle} />
|
||||
<PageHeader
|
||||
path={[{ text: pageTitle }]}
|
||||
headingElement="h2"
|
||||
description={
|
||||
headerDescription || (
|
||||
<>
|
||||
Register a GitHub App to better manage GitHub code host connections.{' '}
|
||||
<Link to="/help/admin/code_hosts/github#using-a-github-app" target="_blank">
|
||||
See how GitHub App configuration works.
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
annotation={headerAnnotation}
|
||||
className="mb-3"
|
||||
/>
|
||||
{!minimizedMode && (
|
||||
<>
|
||||
<PageTitle title={pageTitle} />
|
||||
<PageHeader
|
||||
path={[{ text: pageTitle }]}
|
||||
headingElement="h2"
|
||||
description={
|
||||
headerDescription || (
|
||||
<>
|
||||
Register a GitHub App to better manage GitHub code host connections.{' '}
|
||||
<Link to="/help/admin/external_service/github#using-a-github-app" target="_blank">
|
||||
See how GitHub App configuration works.
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
annotation={headerAnnotation}
|
||||
className="mb-3"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Container className="mb-3">
|
||||
{error && <Alert variant="danger">Error creating GitHub App: {error}</Alert>}
|
||||
<Text>
|
||||
Provide the details for a new GitHub App with the form below. Once you click "Create GitHub App",
|
||||
you will be routed to {baseURL || 'GitHub'} to create the App and choose which repositories to grant
|
||||
it access to. Once created on {baseURL || 'GitHub'}, you'll be redirected back here to finish
|
||||
connecting it to Sourcegraph.
|
||||
you will be routed to <strong>{baseURL || 'GitHub'}</strong> to create the App and choose which
|
||||
repositories to grant it access to. Once created on <strong>{baseURL || 'GitHub'}</strong>, you'll
|
||||
be redirected back here to finish connecting it to Sourcegraph.
|
||||
</Text>
|
||||
<Label className="w-100">
|
||||
<Text alignment="left" className="mb-2">
|
||||
@ -305,7 +320,7 @@ export const CreateGitHubAppPage: FC<CreateGitHubAppPageProps> = ({
|
||||
Your GitHub App must be public if you want to install it on multiple organizations or user
|
||||
accounts.{' '}
|
||||
<Link
|
||||
to="/help/admin/code_hosts/github#multiple-installations"
|
||||
to="/help/admin/external_service/github#multiple-installations"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@ -320,13 +335,24 @@ export const CreateGitHubAppPage: FC<CreateGitHubAppPageProps> = ({
|
||||
<input ref={formInput} name="manifest" onChange={noop} hidden={true} />
|
||||
</form>
|
||||
</Container>
|
||||
<div>
|
||||
<div
|
||||
className={classNames({
|
||||
'd-flex flex-row-reverse': minimizedMode,
|
||||
})}
|
||||
>
|
||||
<Button variant="primary" onClick={createState} disabled={!!nameError || !!urlError}>
|
||||
Create Github App
|
||||
</Button>
|
||||
<ButtonLink className="ml-2" to={cancelUrl} variant="secondary">
|
||||
<Button
|
||||
className={classNames({
|
||||
'ml-2': !minimizedMode,
|
||||
'mr-2': minimizedMode,
|
||||
})}
|
||||
onClick={() => navigate(-1)}
|
||||
variant="secondary"
|
||||
>
|
||||
Cancel
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
.add-credential-modal {
|
||||
&__modal-step-ruler {
|
||||
&__container {
|
||||
max-height: 40rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__step-ruler {
|
||||
height: 0.125rem;
|
||||
width: 100%;
|
||||
border-radius: 0.125rem;
|
||||
|
||||
@ -6,7 +6,7 @@ import { getDocumentNode } from '@sourcegraph/http-client'
|
||||
import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo'
|
||||
|
||||
import { WebStory } from '../../../components/WebStory'
|
||||
import { ExternalServiceKind } from '../../../graphql-operations'
|
||||
import { ExternalServiceKind, type UserAreaUserFields } from '../../../graphql-operations'
|
||||
|
||||
import { AddCredentialModal } from './AddCredentialModal'
|
||||
import { CREATE_BATCH_CHANGES_CREDENTIAL } from './backend'
|
||||
@ -24,6 +24,14 @@ const config: Meta = {
|
||||
},
|
||||
}
|
||||
|
||||
const user = {
|
||||
__typename: 'User',
|
||||
id: '123',
|
||||
username: 'alice',
|
||||
avatarURL: null,
|
||||
viewerCanAdminister: true,
|
||||
} as UserAreaUserFields
|
||||
|
||||
export default config
|
||||
|
||||
export const RequiresSSHstep1: StoryFn = args => (
|
||||
@ -54,7 +62,7 @@ export const RequiresSSHstep1: StoryFn = args => (
|
||||
>
|
||||
<AddCredentialModal
|
||||
{...props}
|
||||
userID="user-id-1"
|
||||
user={user}
|
||||
externalServiceKind={args.externalServiceKind}
|
||||
externalServiceURL="https://github.com/"
|
||||
requiresSSH={true}
|
||||
@ -83,7 +91,7 @@ export const RequiresSSHstep2: StoryFn = args => (
|
||||
{props => (
|
||||
<AddCredentialModal
|
||||
{...props}
|
||||
userID="user-id-1"
|
||||
user={user}
|
||||
externalServiceKind={args.externalServiceKind}
|
||||
externalServiceURL="https://github.com/"
|
||||
requiresSSH={true}
|
||||
@ -112,7 +120,7 @@ export const GitHub: StoryFn = () => (
|
||||
{props => (
|
||||
<AddCredentialModal
|
||||
{...props}
|
||||
userID="user-id-1"
|
||||
user={user}
|
||||
externalServiceKind={ExternalServiceKind.GITHUB}
|
||||
externalServiceURL="https://github.com/"
|
||||
requiresSSH={false}
|
||||
@ -131,7 +139,7 @@ export const GitLab: StoryFn = () => (
|
||||
{props => (
|
||||
<AddCredentialModal
|
||||
{...props}
|
||||
userID="user-id-1"
|
||||
user={user}
|
||||
externalServiceKind={ExternalServiceKind.GITLAB}
|
||||
externalServiceURL="https://gitlab.com/"
|
||||
requiresSSH={false}
|
||||
@ -150,7 +158,7 @@ export const BitbucketServer: StoryFn = () => (
|
||||
{props => (
|
||||
<AddCredentialModal
|
||||
{...props}
|
||||
userID="user-id-1"
|
||||
user={user}
|
||||
externalServiceKind={ExternalServiceKind.BITBUCKETSERVER}
|
||||
externalServiceURL="https://bitbucket.sgdev.org/"
|
||||
requiresSSH={false}
|
||||
@ -167,7 +175,7 @@ export const BitbucketCloud: StoryFn = () => (
|
||||
{props => (
|
||||
<AddCredentialModal
|
||||
{...props}
|
||||
userID="user-id-1"
|
||||
user={user}
|
||||
externalServiceKind={ExternalServiceKind.BITBUCKETCLOUD}
|
||||
externalServiceURL="https://bitbucket.org/"
|
||||
requiresSSH={false}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import React, { useCallback, useState, type FC } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { logger } from '@sourcegraph/common'
|
||||
import { Button, Modal, Link, Code, Label, Text, Input, ErrorAlert, Form } from '@sourcegraph/wildcard'
|
||||
import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
|
||||
import { Button, Modal, Link, Code, Label, Text, Input, ErrorAlert, Form, Select } from '@sourcegraph/wildcard'
|
||||
|
||||
import { LoaderButton } from '../../../components/LoaderButton'
|
||||
import { ExternalServiceKind, type Scalars } from '../../../graphql-operations'
|
||||
import { useFeatureFlag } from '../../../featureFlags/useFeatureFlag'
|
||||
import { ExternalServiceKind, GitHubAppKind, type UserAreaUserFields } from '../../../graphql-operations'
|
||||
|
||||
import { useCreateBatchChangesCredential } from './backend'
|
||||
import { BatchChangesCreateGitHubAppPage } from './BatchChangesCreateGitHubAppPage'
|
||||
import { CodeHostSshPublicKey } from './CodeHostSshPublicKey'
|
||||
import { ModalHeader } from './ModalHeader'
|
||||
|
||||
@ -17,7 +20,7 @@ import styles from './AddCredentialModal.module.scss'
|
||||
export interface AddCredentialModalProps {
|
||||
onCancel: () => void
|
||||
afterCreate: () => void
|
||||
userID: Scalars['ID'] | null
|
||||
user: UserAreaUserFields | null
|
||||
externalServiceKind: ExternalServiceKind
|
||||
externalServiceURL: string
|
||||
requiresSSH: boolean
|
||||
@ -77,10 +80,17 @@ const scopeRequirements: Record<ExternalServiceKind, JSX.Element> = {
|
||||
|
||||
type Step = 'add-token' | 'get-ssh-key'
|
||||
|
||||
export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren<AddCredentialModalProps>> = ({
|
||||
const AuthenticationStrategy = {
|
||||
PERSONAL_ACCESS_TOKEN: 'PERSONAL_ACCESS_TOKEN',
|
||||
GITHUB_APP: 'GITHUB_APP',
|
||||
} as const
|
||||
|
||||
type AuthenticationStrategyType = typeof AuthenticationStrategy[keyof typeof AuthenticationStrategy]
|
||||
|
||||
export const AddCredentialModal: FC<React.PropsWithChildren<AddCredentialModalProps>> = ({
|
||||
onCancel,
|
||||
afterCreate,
|
||||
userID,
|
||||
user,
|
||||
externalServiceKind,
|
||||
externalServiceURL,
|
||||
requiresSSH,
|
||||
@ -92,6 +102,10 @@ export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren
|
||||
const [sshPublicKey, setSSHPublicKey] = useState<string>()
|
||||
const [username, setUsername] = useState<string>('')
|
||||
const [step, setStep] = useState<Step>(initialStep)
|
||||
const [authStrategy, setAuthStrategy] = useState<AuthenticationStrategyType>(
|
||||
AuthenticationStrategy.PERSONAL_ACCESS_TOKEN
|
||||
)
|
||||
const [isGithubAppIntegrationEnabled] = useFeatureFlag('batches-github-app-integration')
|
||||
|
||||
const onChangeCredential = useCallback<React.ChangeEventHandler<HTMLInputElement>>(event => {
|
||||
setCredential(event.target.value)
|
||||
@ -110,7 +124,7 @@ export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren
|
||||
try {
|
||||
const { data } = await createBatchChangesCredential({
|
||||
variables: {
|
||||
user: userID,
|
||||
user: user?.id || null,
|
||||
credential,
|
||||
username: requiresUsername ? username : null,
|
||||
externalServiceKind,
|
||||
@ -130,7 +144,7 @@ export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren
|
||||
},
|
||||
[
|
||||
createBatchChangesCredential,
|
||||
userID,
|
||||
user?.id,
|
||||
credential,
|
||||
requiresUsername,
|
||||
username,
|
||||
@ -141,21 +155,32 @@ export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren
|
||||
]
|
||||
)
|
||||
|
||||
const patLabel =
|
||||
externalServiceKind === ExternalServiceKind.PERFORCE
|
||||
? 'Ticket'
|
||||
: externalServiceKind === ExternalServiceKind.BITBUCKETCLOUD
|
||||
? 'App password'
|
||||
: 'Personal access token'
|
||||
const isTokenSection = step === 'add-token'
|
||||
const isGitHubKind = externalServiceKind === ExternalServiceKind.GITHUB
|
||||
|
||||
// addCredentialModalStepRuler
|
||||
return (
|
||||
<Modal onDismiss={onCancel} aria-labelledby={labelId}>
|
||||
<div className="test-add-credential-modal">
|
||||
<Modal onDismiss={onCancel} aria-labelledby={labelId} position="center">
|
||||
<div className={classNames('test-add-credential-modal', styles.addCredentialModalContainer)}>
|
||||
<ModalHeader
|
||||
id={labelId}
|
||||
externalServiceKind={externalServiceKind}
|
||||
externalServiceURL={externalServiceURL}
|
||||
/>
|
||||
{isGitHubKind && isGithubAppIntegrationEnabled && isTokenSection && (
|
||||
<Select
|
||||
id="credential-kind"
|
||||
selectSize="sm"
|
||||
label="Select an Authentication strategy for your credential"
|
||||
value={authStrategy}
|
||||
onChange={event => setAuthStrategy(event.target.value as AuthenticationStrategyType)}
|
||||
>
|
||||
<option value={AuthenticationStrategy.PERSONAL_ACCESS_TOKEN} defaultChecked={true}>
|
||||
Personal Access Token
|
||||
</option>
|
||||
<option value={AuthenticationStrategy.GITHUB_APP}>GitHub App</option>
|
||||
</Select>
|
||||
)}
|
||||
{requiresSSH && (
|
||||
<div className="d-flex w-100 justify-content-between mb-4">
|
||||
<div className="flex-grow-1 mr-2">
|
||||
@ -164,8 +189,8 @@ export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren
|
||||
</Text>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.addCredentialModalModalStepRuler,
|
||||
styles.addCredentialModalModalStepRulerPurple
|
||||
styles.addCredentialModalStepRuler,
|
||||
styles.addCredentialModalStepRulerPurple
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@ -175,83 +200,32 @@ export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren
|
||||
</Text>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.addCredentialModalModalStepRuler,
|
||||
step === 'add-token' && styles.addCredentialModalModalStepRulerGray,
|
||||
step === 'get-ssh-key' && styles.addCredentialModalModalStepRulerBlue
|
||||
styles.addCredentialModalStepRuler,
|
||||
step === 'add-token' && styles.addCredentialModalStepRulerGray,
|
||||
step === 'get-ssh-key' && styles.addCredentialModalStepRulerBlue
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{step === 'add-token' && (
|
||||
<>
|
||||
{error && <ErrorAlert error={error} />}
|
||||
<Form onSubmit={onSubmit}>
|
||||
<div className="form-group">
|
||||
{requiresUsername && (
|
||||
<>
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
autoComplete="off"
|
||||
inputClassName="mb-2"
|
||||
className="mb-0"
|
||||
required={true}
|
||||
spellCheck="false"
|
||||
minLength={1}
|
||||
value={username}
|
||||
onChange={onChangeUsername}
|
||||
label="Username"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Label htmlFor="token">{patLabel}</Label>
|
||||
<Input
|
||||
id="token"
|
||||
name="token"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
data-testid="test-add-credential-modal-input"
|
||||
required={true}
|
||||
spellCheck="false"
|
||||
minLength={1}
|
||||
value={credential}
|
||||
onChange={onChangeCredential}
|
||||
/>
|
||||
<Text className="form-text">
|
||||
<Link
|
||||
to={HELP_TEXT_LINK_URL}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
aria-label={`Follow our docs to learn how to create a new ${patLabel.toLocaleLowerCase()} on this code host`}
|
||||
>
|
||||
Create a new {patLabel.toLocaleLowerCase()}
|
||||
</Link>{' '}
|
||||
{scopeRequirements[externalServiceKind]}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="d-flex justify-content-end">
|
||||
<Button
|
||||
disabled={loading}
|
||||
className="mr-2"
|
||||
onClick={onCancel}
|
||||
outline={true}
|
||||
variant="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<LoaderButton
|
||||
type="submit"
|
||||
disabled={loading || credential.length === 0}
|
||||
className="test-add-credential-modal-submit"
|
||||
variant="primary"
|
||||
loading={loading}
|
||||
alwaysShowLabel={true}
|
||||
label={requiresSSH ? 'Next' : 'Add credential'}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
<AddToken
|
||||
step={step}
|
||||
error={error}
|
||||
credential={credential}
|
||||
onChangeCredential={onChangeCredential}
|
||||
username={username}
|
||||
onChangeUsername={onChangeUsername}
|
||||
requiresUsername={requiresUsername}
|
||||
externalServiceKind={externalServiceKind}
|
||||
onSubmit={onSubmit}
|
||||
requiresSSH={requiresSSH}
|
||||
loading={loading}
|
||||
onCancel={onCancel}
|
||||
authStrategy={authStrategy}
|
||||
externalServiceURL={externalServiceURL}
|
||||
user={user}
|
||||
/>
|
||||
)}
|
||||
{step === 'get-ssh-key' && (
|
||||
<>
|
||||
@ -273,3 +247,149 @@ export const AddCredentialModal: React.FunctionComponent<React.PropsWithChildren
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const computeCredentialLabel = (
|
||||
externalServiceKind: ExternalServiceKind,
|
||||
authStrategy: AuthenticationStrategyType
|
||||
): string => {
|
||||
if (externalServiceKind === ExternalServiceKind.PERFORCE) {
|
||||
return 'Ticket'
|
||||
}
|
||||
|
||||
if (externalServiceKind === ExternalServiceKind.BITBUCKETCLOUD) {
|
||||
return 'App password'
|
||||
}
|
||||
|
||||
if (externalServiceKind === ExternalServiceKind.GITHUB && authStrategy === AuthenticationStrategy.GITHUB_APP) {
|
||||
return 'Create GitHub App'
|
||||
}
|
||||
|
||||
return 'Personal access token'
|
||||
}
|
||||
|
||||
interface AddTokenProps {
|
||||
step: Step
|
||||
error: unknown
|
||||
onSubmit: React.FormEventHandler<Element>
|
||||
requiresUsername: boolean
|
||||
credential: string
|
||||
username: string
|
||||
onChangeUsername: React.ChangeEventHandler<HTMLInputElement>
|
||||
onChangeCredential: React.ChangeEventHandler<HTMLInputElement>
|
||||
externalServiceKind: ExternalServiceKind
|
||||
requiresSSH: boolean
|
||||
loading: boolean
|
||||
onCancel: () => void
|
||||
authStrategy: AuthenticationStrategyType
|
||||
externalServiceURL: string
|
||||
user: UserAreaUserFields | null
|
||||
}
|
||||
|
||||
const AddToken: FC<AddTokenProps> = ({
|
||||
step,
|
||||
error,
|
||||
onSubmit,
|
||||
requiresUsername,
|
||||
credential,
|
||||
username,
|
||||
onChangeUsername,
|
||||
onChangeCredential,
|
||||
externalServiceKind,
|
||||
requiresSSH,
|
||||
loading,
|
||||
onCancel,
|
||||
authStrategy,
|
||||
externalServiceURL,
|
||||
user,
|
||||
}) => {
|
||||
const patLabel = computeCredentialLabel(externalServiceKind, authStrategy)
|
||||
const isStrategyPAT = authStrategy === AuthenticationStrategy.PERSONAL_ACCESS_TOKEN
|
||||
const kind = user ? GitHubAppKind.USER_CREDENTIAL : GitHubAppKind.SITE_CREDENTIAL
|
||||
|
||||
if (step === 'add-token') {
|
||||
return (
|
||||
<>
|
||||
{error && <ErrorAlert error={error} />}
|
||||
{isStrategyPAT ? (
|
||||
<Form onSubmit={onSubmit}>
|
||||
<div className="form-group">
|
||||
{requiresUsername && (
|
||||
<>
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
autoComplete="off"
|
||||
inputClassName="mb-2"
|
||||
className="mb-0"
|
||||
required={true}
|
||||
spellCheck="false"
|
||||
minLength={1}
|
||||
value={username}
|
||||
onChange={onChangeUsername}
|
||||
label="Username"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Label htmlFor="token">{patLabel}</Label>
|
||||
<Input
|
||||
id="token"
|
||||
name="token"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
data-testid="test-add-credential-modal-input"
|
||||
required={true}
|
||||
spellCheck="false"
|
||||
minLength={1}
|
||||
value={credential}
|
||||
onChange={onChangeCredential}
|
||||
/>
|
||||
<Text className="form-text">
|
||||
<Link
|
||||
to={HELP_TEXT_LINK_URL}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
aria-label={`Follow our docs to learn how to create a new ${patLabel.toLocaleLowerCase()} on this code host`}
|
||||
>
|
||||
Create a new {patLabel.toLocaleLowerCase()}
|
||||
</Link>{' '}
|
||||
{scopeRequirements[externalServiceKind]}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="d-flex justify-content-end align-items-center">
|
||||
{isStrategyPAT && (
|
||||
<>
|
||||
<Button
|
||||
disabled={loading}
|
||||
className="mr-2"
|
||||
onClick={onCancel}
|
||||
outline={true}
|
||||
variant="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<LoaderButton
|
||||
type="submit"
|
||||
disabled={loading || credential.length === 0}
|
||||
className="test-add-credential-modal-submit"
|
||||
variant="primary"
|
||||
loading={loading}
|
||||
alwaysShowLabel={true}
|
||||
label={requiresSSH ? 'Next' : 'Add credential'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
) : (
|
||||
<BatchChangesCreateGitHubAppPage
|
||||
authenticatedUser={user as unknown as AuthenticatedUser}
|
||||
minimizedMode={true}
|
||||
kind={kind}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, type FC } from 'react'
|
||||
|
||||
import { capitalize } from 'lodash'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
|
||||
import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
|
||||
import { FeedbackBadge, Link } from '@sourcegraph/wildcard'
|
||||
import { Link, FeedbackBadge } from '@sourcegraph/wildcard'
|
||||
|
||||
import { CreateGitHubAppPage } from '../../../components/gitHubApps/CreateGitHubAppPage'
|
||||
import { GitHubAppDomain } from '../../../graphql-operations'
|
||||
import { GitHubAppDomain, GitHubAppKind } from '../../../graphql-operations'
|
||||
|
||||
import { useGlobalBatchChangesCodeHostConnection } from './backend'
|
||||
|
||||
@ -17,9 +19,22 @@ const DEFAULT_PERMISSIONS = {
|
||||
metadata: 'read',
|
||||
}
|
||||
|
||||
export const BatchChangesCreateGitHubAppPage: React.FunctionComponent = () => {
|
||||
interface BatchChangesCreateGitHubAppPageProps {
|
||||
authenticatedUser: AuthenticatedUser
|
||||
minimizedMode?: boolean
|
||||
kind: GitHubAppKind
|
||||
}
|
||||
|
||||
export const BatchChangesCreateGitHubAppPage: FC<BatchChangesCreateGitHubAppPageProps> = ({
|
||||
minimizedMode,
|
||||
kind,
|
||||
authenticatedUser,
|
||||
}) => {
|
||||
const location = useLocation()
|
||||
const baseURL = new URLSearchParams(location.search).get('baseURL')
|
||||
const searchParams = new URLSearchParams(location.search)
|
||||
const baseURL = searchParams.get('baseURL')
|
||||
|
||||
const isGitHubAppKindCredential = kind === GitHubAppKind.USER_CREDENTIAL || kind === GitHubAppKind.SITE_CREDENTIAL
|
||||
|
||||
const { connection } = useGlobalBatchChangesCodeHostConnection()
|
||||
// validateURL compares a provided URL against the URLs of existing commit signing
|
||||
@ -35,26 +50,48 @@ export const BatchChangesCreateGitHubAppPage: React.FunctionComponent = () => {
|
||||
// assume this call will succeed.
|
||||
const asURL = new URL(url)
|
||||
const isDuplicate = connection.nodes.some(node => {
|
||||
const existingURL = node.commitSigningConfiguration?.baseURL
|
||||
const existingURL = isGitHubAppKindCredential
|
||||
? node.externalServiceURL
|
||||
: node.commitSigningConfiguration?.baseURL
|
||||
if (!existingURL) {
|
||||
return false
|
||||
}
|
||||
|
||||
return new URL(existingURL).hostname === asURL.hostname
|
||||
})
|
||||
return isDuplicate ? 'A commit signing integration for the code host at this URL already exists.' : true
|
||||
const errorMsg = `A ${
|
||||
isGitHubAppKindCredential ? 'GitHub app' : 'commit signing'
|
||||
} integration for the code host at this URL already exists.`
|
||||
return isDuplicate ? errorMsg : true
|
||||
},
|
||||
[connection]
|
||||
[connection, isGitHubAppKindCredential]
|
||||
)
|
||||
const pageTitle = isGitHubAppKindCredential
|
||||
? `Create GitHub app for ${
|
||||
kind === GitHubAppKind.USER_CREDENTIAL ? authenticatedUser.username : 'Global'
|
||||
} Batch Changes credential`
|
||||
: 'Create GitHub app for commit signing'
|
||||
const defaultAppName = computeAppName(kind, authenticatedUser?.username)
|
||||
|
||||
// COMMIT SIGNING apps do not need permissions to create pull request, we duplicate the
|
||||
// commit using the GraphQL request and the changeset is created with the PAT.
|
||||
const permissions = {
|
||||
...DEFAULT_PERMISSIONS,
|
||||
...(isGitHubAppKindCredential ? { pull_requests: 'write' } : {}),
|
||||
}
|
||||
return (
|
||||
<CreateGitHubAppPage
|
||||
minimizedMode={minimizedMode}
|
||||
authenticatedUser={authenticatedUser}
|
||||
appKind={kind}
|
||||
defaultEvents={DEFAULT_EVENTS}
|
||||
defaultPermissions={DEFAULT_PERMISSIONS}
|
||||
pageTitle="Create GitHub App for commit signing"
|
||||
defaultPermissions={permissions}
|
||||
pageTitle={pageTitle}
|
||||
headerDescription={
|
||||
<>
|
||||
Register a GitHub App to enable Sourcegraph to sign commits for Batch Change changesets on your
|
||||
behalf.
|
||||
Register a GitHub App to enable Sourcegraph{' '}
|
||||
{isGitHubAppKindCredential ? 'create' : 'sign commits for'} Batch Change changesets on your behalf.
|
||||
{/* TODO (@BolajiOlajide/@bahrmichael) update link here for credential github app */}
|
||||
<Link to="/help/admin/config/batch_changes#commit-signing-for-github" className="ml-1">
|
||||
See how GitHub App configuration works.
|
||||
</Link>
|
||||
@ -62,10 +99,26 @@ export const BatchChangesCreateGitHubAppPage: React.FunctionComponent = () => {
|
||||
}
|
||||
headerAnnotation={<FeedbackBadge status="beta" feedback={{ mailto: 'support@sourcegraph.com' }} />}
|
||||
appDomain={GitHubAppDomain.BATCHES}
|
||||
defaultAppName="Sourcegraph Commit Signing"
|
||||
defaultAppName={defaultAppName}
|
||||
baseURL={baseURL?.length ? baseURL : undefined}
|
||||
validateURL={validateURL}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const computeAppName = (kind: GitHubAppKind, username?: string): string => {
|
||||
switch (kind) {
|
||||
case GitHubAppKind.COMMIT_SIGNING: {
|
||||
return 'Sourcegraph Commit Signing'
|
||||
}
|
||||
|
||||
case GitHubAppKind.USER_CREDENTIAL: {
|
||||
return `${capitalize(username)}'s Batch Changes GitHub App`
|
||||
}
|
||||
|
||||
default: {
|
||||
return 'Batch Changes GitHub App'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
type BatchChangesCredentialFields,
|
||||
ExternalServiceKind,
|
||||
type UserBatchChangesCodeHostsResult,
|
||||
type UserAreaUserFields,
|
||||
} from '../../../graphql-operations'
|
||||
import { BATCH_CHANGES_SITE_CONFIGURATION } from '../backend'
|
||||
import { noRolloutWindowMockResult, rolloutWindowConfigMockResult } from '../mocks'
|
||||
@ -133,7 +134,7 @@ export const Overview: StoryFn = () => (
|
||||
},
|
||||
]}
|
||||
>
|
||||
<BatchChangesSettingsArea {...props} user={{ id: 'user-id-1' }} />
|
||||
<BatchChangesSettingsArea {...props} user={{ id: 'user-id-1' } as UserAreaUserFields} />
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -214,7 +215,7 @@ export const ConfigAdded: StoryFn = () => (
|
||||
},
|
||||
]}
|
||||
>
|
||||
<BatchChangesSettingsArea {...props} user={{ id: 'user-id-2' }} />
|
||||
<BatchChangesSettingsArea {...props} user={{ id: 'user-id-2' } as UserAreaUserFields} />
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -297,7 +298,7 @@ export const RolloutWindowsConfigurationStory: StoryFn = () => (
|
||||
},
|
||||
]}
|
||||
>
|
||||
<BatchChangesSettingsArea {...props} user={{ id: 'user-id-2' }} />
|
||||
<BatchChangesSettingsArea {...props} user={{ id: 'user-id-2' } as UserAreaUserFields} />
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
</WebStory>
|
||||
|
||||
@ -10,7 +10,7 @@ import { UserCommitSigningIntegrations } from './CommitSigningIntegrations'
|
||||
import { RolloutWindowsConfiguration } from './RolloutWindowsConfiguration'
|
||||
|
||||
export interface BatchChangesSettingsAreaProps {
|
||||
user: Pick<UserAreaUserFields, 'id'>
|
||||
user: UserAreaUserFields
|
||||
}
|
||||
|
||||
/** The page area for all batch changes settings. It's shown in the user settings sidebar. */
|
||||
@ -23,7 +23,7 @@ export const BatchChangesSettingsArea: React.FunctionComponent<
|
||||
<RolloutWindowsConfiguration />
|
||||
<UserCodeHostConnections
|
||||
headerLine={<Text>Add access tokens to enable Batch Changes changeset creation on your code hosts.</Text>}
|
||||
userID={props.user.id}
|
||||
user={props.user}
|
||||
/>
|
||||
<UserCommitSigningIntegrations userID={props.user.id} />
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import { PageHeader, Alert, Text } from '@sourcegraph/wildcard'
|
||||
|
||||
import { PageTitle } from '../../../components/PageTitle'
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
type BatchChangesCredentialFields,
|
||||
type CheckBatchChangesCredentialResult,
|
||||
ExternalServiceKind,
|
||||
type UserAreaUserFields,
|
||||
} from '../../../graphql-operations'
|
||||
|
||||
import { CHECK_BATCH_CHANGES_CREDENTIAL } from './backend'
|
||||
@ -67,7 +68,7 @@ export const Overview: StoryFn = () => (
|
||||
commitSigningConfiguration: null,
|
||||
}}
|
||||
refetchAll={() => {}}
|
||||
userID="123"
|
||||
user={{ id: '123' } as UserAreaUserFields}
|
||||
/>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
|
||||
@ -13,7 +13,7 @@ import type {
|
||||
BatchChangesCodeHostFields,
|
||||
CheckBatchChangesCredentialResult,
|
||||
CheckBatchChangesCredentialVariables,
|
||||
Scalars,
|
||||
UserAreaUserFields,
|
||||
} from '../../../graphql-operations'
|
||||
|
||||
import { AddCredentialModal } from './AddCredentialModal'
|
||||
@ -27,7 +27,7 @@ import styles from './CodeHostConnectionNode.module.scss'
|
||||
export interface CodeHostConnectionNodeProps {
|
||||
node: BatchChangesCodeHostFields
|
||||
refetchAll: () => void
|
||||
userID: Scalars['ID'] | null
|
||||
user: UserAreaUserFields | null
|
||||
}
|
||||
|
||||
type OpenModal = 'add' | 'view' | 'delete'
|
||||
@ -35,7 +35,7 @@ type OpenModal = 'add' | 'view' | 'delete'
|
||||
export const CodeHostConnectionNode: React.FunctionComponent<React.PropsWithChildren<CodeHostConnectionNodeProps>> = ({
|
||||
node,
|
||||
refetchAll,
|
||||
userID,
|
||||
user,
|
||||
}) => {
|
||||
const [checkCredError, setCheckCredError] = useState<ApolloError | undefined>()
|
||||
const ExternalServiceIcon = defaultExternalServices[node.externalServiceKind].icon
|
||||
@ -75,7 +75,7 @@ export const CodeHostConnectionNode: React.FunctionComponent<React.PropsWithChil
|
||||
refetchAll()
|
||||
}, [refetchAll, buttonReference])
|
||||
|
||||
const isEnabled = node.credential !== null && (userID === null || !node.credential.isSiteCredential)
|
||||
const isEnabled = node.credential !== null && (user === null || !node.credential.isSiteCredential)
|
||||
|
||||
const headingAriaLabel = `Sourcegraph ${
|
||||
isEnabled ? 'has credentials configured' : 'does not have credentials configured'
|
||||
@ -203,7 +203,7 @@ export const CodeHostConnectionNode: React.FunctionComponent<React.PropsWithChil
|
||||
<AddCredentialModal
|
||||
onCancel={closeModal}
|
||||
afterCreate={afterAction}
|
||||
userID={userID}
|
||||
user={user}
|
||||
externalServiceKind={node.externalServiceKind}
|
||||
externalServiceURL={node.externalServiceURL}
|
||||
requiresSSH={node.requiresSSH}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { Container, Link, H3, Text } from '@sourcegraph/wildcard'
|
||||
|
||||
import { DismissibleAlert } from '../../../components/DismissibleAlert'
|
||||
import type { UseShowMorePaginationResult } from '../../../components/FilteredConnection/hooks/useShowMorePagination'
|
||||
import {
|
||||
ConnectionContainer,
|
||||
@ -12,11 +15,13 @@ import {
|
||||
ShowMoreButton,
|
||||
SummaryContainer,
|
||||
} from '../../../components/FilteredConnection/ui'
|
||||
import type {
|
||||
BatchChangesCodeHostFields,
|
||||
GlobalBatchChangesCodeHostsResult,
|
||||
Scalars,
|
||||
UserBatchChangesCodeHostsResult,
|
||||
import { GitHubAppFailureAlert } from '../../../components/gitHubApps/GitHubAppFailureAlert'
|
||||
import {
|
||||
type BatchChangesCodeHostFields,
|
||||
GitHubAppKind,
|
||||
type GlobalBatchChangesCodeHostsResult,
|
||||
type UserBatchChangesCodeHostsResult,
|
||||
type UserAreaUserFields,
|
||||
} from '../../../graphql-operations'
|
||||
|
||||
import { useGlobalBatchChangesCodeHostConnection, useUserBatchChangesCodeHostConnection } from './backend'
|
||||
@ -28,20 +33,24 @@ export interface GlobalCodeHostConnectionsProps {
|
||||
|
||||
export const GlobalCodeHostConnections: React.FunctionComponent<
|
||||
React.PropsWithChildren<GlobalCodeHostConnectionsProps>
|
||||
> = props => (
|
||||
<CodeHostConnections userID={null} connectionResult={useGlobalBatchChangesCodeHostConnection()} {...props} />
|
||||
)
|
||||
> = props => <CodeHostConnections user={null} connectionResult={useGlobalBatchChangesCodeHostConnection()} {...props} />
|
||||
|
||||
export interface UserCodeHostConnectionsProps extends GlobalCodeHostConnectionsProps {
|
||||
userID: Scalars['ID']
|
||||
user: UserAreaUserFields
|
||||
}
|
||||
|
||||
export const UserCodeHostConnections: React.FunctionComponent<
|
||||
React.PropsWithChildren<UserCodeHostConnectionsProps>
|
||||
> = props => <CodeHostConnections connectionResult={useUserBatchChangesCodeHostConnection(props.userID)} {...props} />
|
||||
> = ({ user, headerLine }) => (
|
||||
<CodeHostConnections
|
||||
connectionResult={useUserBatchChangesCodeHostConnection(user.id)}
|
||||
headerLine={headerLine}
|
||||
user={user}
|
||||
/>
|
||||
)
|
||||
|
||||
interface CodeHostConnectionsProps extends GlobalCodeHostConnectionsProps {
|
||||
userID: Scalars['ID'] | null
|
||||
user: UserAreaUserFields | null
|
||||
connectionResult: UseShowMorePaginationResult<
|
||||
GlobalBatchChangesCodeHostsResult | UserBatchChangesCodeHostsResult,
|
||||
BatchChangesCodeHostFields
|
||||
@ -49,11 +58,17 @@ interface CodeHostConnectionsProps extends GlobalCodeHostConnectionsProps {
|
||||
}
|
||||
|
||||
const CodeHostConnections: React.FunctionComponent<React.PropsWithChildren<CodeHostConnectionsProps>> = ({
|
||||
userID,
|
||||
user,
|
||||
headerLine,
|
||||
connectionResult,
|
||||
}) => {
|
||||
const { loading, hasNextPage, fetchMore, connection, error, refetchAll } = connectionResult
|
||||
const location = useLocation()
|
||||
const kind = new URLSearchParams(location.search).get('kind')
|
||||
const success = new URLSearchParams(location.search).get('success') === 'true'
|
||||
const appName = new URLSearchParams(location.search).get('app_name')
|
||||
const setupError = new URLSearchParams(location.search).get('error')
|
||||
const shouldShowError = !success && setupError && kind !== GitHubAppKind.COMMIT_SIGNING
|
||||
return (
|
||||
<Container className="mb-3">
|
||||
<H3>Code host tokens</H3>
|
||||
@ -61,13 +76,23 @@ const CodeHostConnections: React.FunctionComponent<React.PropsWithChildren<CodeH
|
||||
<ConnectionContainer className="mb-3">
|
||||
{error && <ConnectionError errors={[error.message]} />}
|
||||
{loading && !connection && <ConnectionLoading />}
|
||||
{success && (
|
||||
<DismissibleAlert
|
||||
className="mb-3"
|
||||
variant="success"
|
||||
partialStorageKey="batch-changes-github-app-integration-success"
|
||||
>
|
||||
GitHub App {appName?.length ? `"${appName}" ` : ''}successfully connected.
|
||||
</DismissibleAlert>
|
||||
)}
|
||||
{shouldShowError && <GitHubAppFailureAlert error={setupError} />}
|
||||
<ConnectionList as="ul" className="list-group" aria-label="code host connections">
|
||||
{connection?.nodes?.map(node => (
|
||||
<CodeHostConnectionNode
|
||||
key={node.externalServiceURL}
|
||||
node={node}
|
||||
refetchAll={refetchAll}
|
||||
userID={userID}
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
</ConnectionList>
|
||||
|
||||
@ -29,6 +29,7 @@ export const FEATURE_FLAGS = [
|
||||
'sourcegraph-operator-site-admin-hide-maintenance',
|
||||
'sourcegraph-cloud-managed-feature-flags-warning-shown',
|
||||
'ab-shortened-install-first-signup-flow-cody-2024-04',
|
||||
'batches-github-app-integration',
|
||||
] as const
|
||||
|
||||
export type FeatureFlagName = typeof FEATURE_FLAGS[number]
|
||||
|
||||
@ -11,6 +11,7 @@ import { LoadingSpinner, ErrorAlert } from '@sourcegraph/wildcard'
|
||||
|
||||
import {
|
||||
GitHubAppDomain,
|
||||
GitHubAppKind,
|
||||
type SiteExternalServiceConfigResult,
|
||||
type SiteExternalServiceConfigVariables,
|
||||
} from '../graphql-operations'
|
||||
@ -82,6 +83,7 @@ export const SiteAdminGitHubAppsArea: FC<Props> = props => {
|
||||
path="new"
|
||||
element={
|
||||
<CreateGitHubAppPage
|
||||
appKind={GitHubAppKind.REPO_SYNC}
|
||||
defaultEvents={DEFAULT_EVENTS}
|
||||
defaultPermissions={DEFAULT_PERMISSIONS}
|
||||
appDomain={GitHubAppDomain.REPOS}
|
||||
|
||||
@ -8,6 +8,7 @@ import { SHOW_BUSINESS_FEATURES } from '../enterprise/dotcom/productSubscription
|
||||
import { OwnAnalyticsPage } from '../enterprise/own/admin-ui/OwnAnalyticsPage'
|
||||
import type { SiteAdminRolesPageProps } from '../enterprise/rbac/SiteAdminRolesPage'
|
||||
import type { RoleAssignmentModalProps } from '../enterprise/site-admin/UserManagement/components/RoleAssignmentModal'
|
||||
import { GitHubAppKind } from '../graphql-operations'
|
||||
import { checkRequestAccessAllowed } from '../util/checkRequestAccessAllowed'
|
||||
|
||||
import { isPackagesEnabled } from './flags'
|
||||
@ -408,7 +409,12 @@ export const otherSiteAdminRoutes: readonly SiteAdminAreaRoute[] = [
|
||||
},
|
||||
{
|
||||
path: '/batch-changes/github-apps/new',
|
||||
render: () => <BatchChangesCreateGitHubAppPage />,
|
||||
render: ({ authenticatedUser }) => (
|
||||
<BatchChangesCreateGitHubAppPage
|
||||
authenticatedUser={authenticatedUser}
|
||||
kind={GitHubAppKind.COMMIT_SIGNING}
|
||||
/>
|
||||
),
|
||||
condition: ({ batchChangesEnabled }) => batchChangesEnabled,
|
||||
},
|
||||
{
|
||||
|
||||
@ -41,6 +41,28 @@ enum GitHubAppDomain {
|
||||
BATCHES
|
||||
}
|
||||
|
||||
"""
|
||||
GitHubAppKind enumerates the domains in which GitHub Apps can be used.
|
||||
"""
|
||||
enum GitHubAppKind {
|
||||
"""
|
||||
GitHub Apps that are configured for commit signing.
|
||||
"""
|
||||
COMMIT_SIGNING
|
||||
"""
|
||||
GitHub Apps that are configured for a user's batch changes credential.
|
||||
"""
|
||||
USER_CREDENTIAL
|
||||
"""
|
||||
GitHub Apps that are configured for repo syncing.
|
||||
"""
|
||||
REPO_SYNC
|
||||
"""
|
||||
GitHub Apps that are configured for a site's batch changes credential.
|
||||
"""
|
||||
SITE_CREDENTIAL
|
||||
}
|
||||
|
||||
"""
|
||||
A list of GitHub Apps.
|
||||
"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user