From 11594e43231f5cdd97a1389757c73a423e883be7 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Thu, 9 Feb 2023 01:46:56 -0300 Subject: [PATCH 01/22] Add first WIP draft of setup wizard remote repositories step --- client/web/src/setup-wizard/SetupWizard.tsx | 27 ++--- .../RemoteRepositoriesStep.module.scss | 59 ++++++++++ .../RemoteRepositoriesStep.tsx | 77 +++++++++++++ .../CodeHostsNavigation.module.scss | 56 ++++++++++ .../navigation/CodeHostsNavigation.tsx | 102 ++++++++++++++++++ .../components/navigation/index.ts | 2 + .../remote-repositories-step/helpers.ts | 33 ++++++ .../remote-repositories-step/index.ts | 2 + .../setup-steps/SetupSteps.module.scss | 16 ++- .../components/setup-steps/SetupSteps.tsx | 29 +++-- .../src/components/Container/Container.tsx | 22 ++-- 11 files changed, 378 insertions(+), 47 deletions(-) create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/index.ts diff --git a/client/web/src/setup-wizard/SetupWizard.tsx b/client/web/src/setup-wizard/SetupWizard.tsx index 9381284a2b67..d589f112d9b3 100644 --- a/client/web/src/setup-wizard/SetupWizard.tsx +++ b/client/web/src/setup-wizard/SetupWizard.tsx @@ -1,11 +1,12 @@ -import { FC } from 'react' +import { FC, ReactElement } from 'react' import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary' -import { Container, H1, H2 } from '@sourcegraph/wildcard' +import { H1, H2 } from '@sourcegraph/wildcard' import { BrandLogo } from '../components/branding/BrandLogo' -import { SetupStepsRoot, CustomNextButton, StepConfiguration } from './components/setup-steps' +import { RemoteRepositoriesStep } from './components/remote-repositories-step'; +import { SetupStepsRoot, StepConfiguration } from './components/setup-steps' import styles from './Setup.module.scss' @@ -14,24 +15,19 @@ const SETUP_STEPS: StepConfiguration[] = [ id: '001', name: 'Add local repositories', path: '/setup/local-repositories', - render: () =>

Hello local repositories step

, + component: LocalRepositoriesStep, }, { id: '002', name: 'Add remote repositories', - path: '/setup/remote-repositories', - render: () => ( - -

Hello remote repositories step

- -
- ), + path: '/setup/remote-repositories/*', + component: RemoteRepositoriesStep, }, { id: '003', name: 'Sync repositories', path: '/setup/sync-repositories', - render: () =>

Hello sync repositories step

, + component: () =>

Hello sync repositories step

, }, ] @@ -60,3 +56,10 @@ export const SetupWizard: FC = props => { ) } + +function LocalRepositoriesStep(props: any): ReactElement { + return ( +

Hello local repositories step

+ ) +} + diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss new file mode 100644 index 000000000000..6b95200e4fb0 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss @@ -0,0 +1,59 @@ + +.root { + display: flex; + flex-direction: column; +} + +.content { + display: flex; + gap: 0.25rem; + min-height: 30rem; + + &-navigation { + flex-basis: 35%; + padding: 1rem; + } + + &-main { + flex-grow: 1; + padding: 1rem; + } +} + +.navigation { + // Top margin to compensate main and left content first line texts + margin-top: 0.2rem; +} + + +.code-host-picker { + &-header { + display: flex; + flex-direction: column; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--border-color); + } + + &-list { + width: 100%; + list-style: none; + padding: 0; + margin: 1rem 0 0 0; + display: flex; + flex-direction: column; + gap: 0.25rem; + justify-content: stretch; + } + + &-item { + display: flex; + width: 100%; + justify-content: flex-start; + align-items: center; + gap: 0.25rem; + + &:hover { + background-color: var(--btn-light-color-variant) !important; + } + } +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx new file mode 100644 index 000000000000..bad90981de1a --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx @@ -0,0 +1,77 @@ +import { FC, HTMLAttributes, ReactElement } from 'react'; + +import classNames from 'classnames'; +import { Routes, Route } from 'react-router-dom-v5-compat'; + +import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations'; +import { Button, Container, Icon, Text } from '@sourcegraph/wildcard'; + +import { CustomNextButton } from '../setup-steps'; + +import { CodeHostsNavigation } from './components/navigation'; +import { getCodeHostIcon, getCodeHostName } from './helpers'; + +import styles from './RemoteRepositoriesStep.module.scss' + +interface RemoteRepositoriesStepProps extends HTMLAttributes {} + +export const RemoteRepositoriesStep: FC = props => { + const { className, ...attributes } = props + + return ( +
+ Connect remote code hosts where your source code lives. + +
+ + + + + + + } /> + Hello creation UI} /> + + +
+ + +
+ ) +} + +enum ExternalCodeHostType { + GitHub, + GitLab, + BitBucket +} + +const SUPPORT_CODE_HOSTS = [ + ExternalServiceKind.GITHUB, + ExternalServiceKind.GITLAB, + ExternalServiceKind.BITBUCKETCLOUD, +] + +interface CodeHostPickerProps {} + +function CodeHostPicker(props: CodeHostPickerProps): ReactElement { + return ( +
+
+ Add another remote code host + Choose a provider from the list below +
+ +
    + { SUPPORT_CODE_HOSTS.map(codeHostType => +
  • + +
  • + )} +
+
+ ) +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss new file mode 100644 index 000000000000..c9fc4304df96 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss @@ -0,0 +1,56 @@ + +.loading-state { + display: flex; + align-items: center; + gap: 0.25rem; + color: var(--text-muted); +} + +.empty-state { + // Top margin to compensate main and left content first line texts + margin-top: 0.2rem; + + display: flex; + gap: 0.25rem; + align-items: baseline; + color: var(--text-muted); + + &-icon { + flex-shrink: 0; + } +} + +.list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + //gap: 0.25rem; +} + +.item { + padding: 0.25rem 0.5rem; + margin: 0 -0.5rem; + border-radius: 3px; + + &:hover { + background-color: var(--secondary-2); + } +} + +.item-button { + display: flex; + justify-content: flex-start; + align-items: flex-start; + gap: 0.25rem; + width: 100%; + text-align: start; + font-weight: normal; + padding: 0; +} + +.item-description { + display: flex; + flex-direction: column; +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx new file mode 100644 index 000000000000..85fa71b2a512 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx @@ -0,0 +1,102 @@ +import { FC } from 'react'; + +import { gql, useQuery } from '@apollo/client'; +import { mdiInformationOutline } from '@mdi/js'; +import classNames from 'classnames'; + +import { ErrorAlert, Icon, LoadingSpinner, Button } from '@sourcegraph/wildcard'; + +import { GetCodeHostsResult } from '../../../../../graphql-operations'; +import { getCodeHostIcon } from '../../helpers' + +import styles from './CodeHostsNavigation.module.scss' + +const GET_CODE_HOSTS = gql` + query GetCodeHosts { + externalServices { + nodes { + id + kind + repoCount + displayName + lastSyncAt + nextSyncAt + } + } + } +` + +interface CodeHostsNavigationProps { + className?: string +} + +export const CodeHostsNavigation: FC = props => { + const { className } = props + + const { data, loading, error, refetch } = useQuery(GET_CODE_HOSTS, { + fetchPolicy: 'cache-and-network' + }) + + if (error && !loading) { + return ( +
+ + +
+ ) + } + + if (!data || loading) { + return ( + + {' '} + Fetching connected code host... + + ) + } + + if (data.externalServices.nodes.length === 0) { + return ( + + + + + Choose at least one of the code host providers from the list on the right. + + ) + } + + return ( +
    + { data.externalServices.nodes.map(codeHost => +
  • + +
  • + )} +
+ ) +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts new file mode 100644 index 000000000000..6e7dbc82e133 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts @@ -0,0 +1,2 @@ + +export { CodeHostsNavigation} from './CodeHostsNavigation' diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts b/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts new file mode 100644 index 000000000000..ae3e102ed4f5 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts @@ -0,0 +1,33 @@ +import { mdiBitbucket, mdiGithub, mdiGitlab } from '@mdi/js'; + +import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations'; + +export const getCodeHostIcon = (codeHostType: ExternalServiceKind): string => { + switch (codeHostType) { + case ExternalServiceKind.GITHUB: + return mdiGithub + case ExternalServiceKind.GITLAB: + return mdiGitlab + case ExternalServiceKind.BITBUCKETCLOUD: + return mdiBitbucket + + default: + // TODO: Add support for other code host + return '' + } +} + +export const getCodeHostName = (codeHostType: ExternalServiceKind): string => { + switch (codeHostType) { + case ExternalServiceKind.GITHUB: + return 'GitHub' + case ExternalServiceKind.GITLAB: + return 'GitLab' + case ExternalServiceKind.BITBUCKETCLOUD: + return 'BitBucket.org' + + default: + // TODO: Add support for other code host + return 'Unknown' + } +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/index.ts new file mode 100644 index 000000000000..49ccee100da8 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/index.ts @@ -0,0 +1,2 @@ + +export { RemoteRepositoriesStep } from './RemoteRepositoriesStep' diff --git a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss index f9f3079b6ea2..a5ed8bfd43ea 100644 --- a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss +++ b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss @@ -1,16 +1,16 @@ .root { display: flex; flex-direction: column; - gap: 2rem; + gap: 1rem; flex-grow: 1; justify-content: center; } .header { display: flex; - justify-content: space-between; gap: 2rem; - margin: -1.5rem auto 0; + justify-content: space-between; + margin: -1.5rem auto 0.5rem; &-step { display: flex; @@ -67,8 +67,11 @@ } .content { + width: 100%; flex-grow: 1; margin: auto; + max-width: 55rem; + padding: 0.5rem; } .actions { @@ -77,7 +80,8 @@ .navigation { display: flex; - padding: 0.5rem; + position: sticky; + bottom: 0; background-color: var(--gray-02); border-top: 1px solid var(--border-color); @@ -89,8 +93,10 @@ display: flex; flex-grow: 1; gap: 0.5rem; - max-width: 40rem; + width: 100%; + max-width: 55rem; margin: auto; + padding: 0.5rem; } // Hide default next button if the custom button is rendered diff --git a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx index eedf80384454..97f45521d5fd 100644 --- a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx +++ b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx @@ -1,14 +1,13 @@ import { - useRef, Ref, createContext, + ComponentType, FC, - ReactNode, HTMLAttributes, useMemo, useContext, useCallback, - useEffect, + useEffect, useState, } from 'react' import { mdiChevronLeft, mdiChevronRight } from '@mdi/js' @@ -24,7 +23,7 @@ export interface StepConfiguration { id: string path: string name: string - render: () => ReactNode + component: ComponentType<{ className: string }> } interface SetupStepsContextData { @@ -55,7 +54,7 @@ export const SetupStepsRoot: FC = props => { const navigate = useNavigate() const location = useLocation() - const nextButtonPortalRef = useRef(null) + const [nextButtonPortal, setNextButtonPortal] = useState(null) // Resolve current setup step and its index by URL matches const { currentStep, activeStepIndex } = useMemo(() => { @@ -114,28 +113,26 @@ export const SetupStepsRoot: FC = props => { const cachedContext = useMemo( () => ({ steps, - nextButtonPortalElement: nextButtonPortalRef.current, + nextButtonPortalElement: nextButtonPortal, onNextStep: handleGoToNextStep, }), - [handleGoToNextStep, steps] + [handleGoToNextStep, steps, nextButtonPortal] ) return (
-
- - {steps.map(step => ( - - ))} - } /> - -
+ + {steps.map(({ path, component: Component}) => ( + }/> + ))} + } /> + diff --git a/client/wildcard/src/components/Container/Container.tsx b/client/wildcard/src/components/Container/Container.tsx index 527421e18457..bd6646133d41 100644 --- a/client/wildcard/src/components/Container/Container.tsx +++ b/client/wildcard/src/components/Container/Container.tsx @@ -1,20 +1,14 @@ -import React from 'react' +import { forwardRef } from 'react' import classNames from 'classnames' -import styles from './Container.module.scss' +import { ForwardReferenceComponent } from '../../types'; -interface Props extends React.HTMLAttributes { - className?: string -} +import styles from './Container.module.scss' /** A container wrapper. Used for grouping content together. */ -export const Container: React.FunctionComponent> = ({ - children, - className, - ...props -}) => ( -
- {children} -
-) +export const Container = forwardRef((props, ref) => { + const { as: Comp = 'div', className, ...attributes } = props + + return () +}) as ForwardReferenceComponent<'div'> From 2e3fe055b20dd358836680ec5897766c658d688a Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Thu, 9 Feb 2023 21:22:23 -0300 Subject: [PATCH 02/22] Move picker to the separate page --- client/web/src/setup-wizard/SetupWizard.tsx | 4 +- .../RemoteRepositoriesStep.tsx | 46 ++----------------- .../CodeHostsPicker.module.scss | 29 ++++++++++++ .../code-host-picker/CodeHostsPicker.tsx | 34 ++++++++++++++ .../components/code-host-picker/index.ts | 2 + .../components/setup-steps/SetupSteps.tsx | 2 +- 6 files changed, 73 insertions(+), 44 deletions(-) create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.module.scss create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts diff --git a/client/web/src/setup-wizard/SetupWizard.tsx b/client/web/src/setup-wizard/SetupWizard.tsx index d589f112d9b3..dc5ee84932de 100644 --- a/client/web/src/setup-wizard/SetupWizard.tsx +++ b/client/web/src/setup-wizard/SetupWizard.tsx @@ -20,7 +20,7 @@ const SETUP_STEPS: StepConfiguration[] = [ { id: '002', name: 'Add remote repositories', - path: '/setup/remote-repositories/*', + path: '/setup/remote-repositories1/*', component: RemoteRepositoriesStep, }, { @@ -48,7 +48,7 @@ export const SetupWizard: FC = props => {

- Welcome to Sourcegraph! Let's get started. + Welcome to Sourcegraph! Let's get started22.

diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx index bad90981de1a..951bf16c3146 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx @@ -1,15 +1,14 @@ -import { FC, HTMLAttributes, ReactElement } from 'react'; +import { FC, HTMLAttributes } from 'react'; import classNames from 'classnames'; import { Routes, Route } from 'react-router-dom-v5-compat'; -import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations'; -import { Button, Container, Icon, Text } from '@sourcegraph/wildcard'; +import { Container, Text } from '@sourcegraph/wildcard'; import { CustomNextButton } from '../setup-steps'; +import { CodeHostsPicker } from './components/code-host-picker'; import { CodeHostsNavigation } from './components/navigation'; -import { getCodeHostIcon, getCodeHostName } from './helpers'; import styles from './RemoteRepositoriesStep.module.scss' @@ -29,8 +28,9 @@ export const RemoteRepositoriesStep: FC = props => - } /> + } /> Hello creation UI} /> + Hello edit UI} /> @@ -39,39 +39,3 @@ export const RemoteRepositoriesStep: FC = props =>
) } - -enum ExternalCodeHostType { - GitHub, - GitLab, - BitBucket -} - -const SUPPORT_CODE_HOSTS = [ - ExternalServiceKind.GITHUB, - ExternalServiceKind.GITLAB, - ExternalServiceKind.BITBUCKETCLOUD, -] - -interface CodeHostPickerProps {} - -function CodeHostPicker(props: CodeHostPickerProps): ReactElement { - return ( -
-
- Add another remote code host - Choose a provider from the list below -
- -
    - { SUPPORT_CODE_HOSTS.map(codeHostType => -
  • - -
  • - )} -
-
- ) -} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.module.scss new file mode 100644 index 000000000000..caa3e9dbaed7 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.module.scss @@ -0,0 +1,29 @@ +.header { + display: flex; + flex-direction: column; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--border-color); +} + +.list { + width: 100%; + list-style: none; + padding: 0; + margin: 1rem 0 0 0; + display: flex; + flex-direction: column; + gap: 0.25rem; + justify-content: stretch; +} + +.item { + display: flex; + width: 100%; + justify-content: flex-start; + align-items: center; + gap: 0.25rem; + + &:hover { + background-color: var(--btn-light-color-variant) !important; + } +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx new file mode 100644 index 000000000000..3e7e312c37c6 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; + +import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations'; +import { Button, Icon } from '@sourcegraph/wildcard'; + +import { getCodeHostIcon, getCodeHostName } from '../../helpers'; + +import styles from './CodeHostsPicker.module.scss'; + +const SUPPORTED_CODE_HOSTS = [ + ExternalServiceKind.GITHUB, + ExternalServiceKind.GITLAB, + ExternalServiceKind.BITBUCKETCLOUD, +] + +export const CodeHostsPicker: FC = () => ( +
+
+ Add another remote code host + Choose a provider from the list below +
+ +
    + {SUPPORTED_CODE_HOSTS.map(codeHostType => +
  • + +
  • + )} +
+
+) diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts new file mode 100644 index 000000000000..c14fb933cbea --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts @@ -0,0 +1,2 @@ + +export { CodeHostsPicker } from './CodeHostsPicker' diff --git a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx index 97f45521d5fd..2957345759f2 100644 --- a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx +++ b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx @@ -23,7 +23,7 @@ export interface StepConfiguration { id: string path: string name: string - component: ComponentType<{ className: string }> + component: ComponentType<{ className?: string }> } interface SetupStepsContextData { From 1ecfcf1514ee68edef30099470df20aa5e2e0314 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Thu, 9 Feb 2023 21:22:31 -0300 Subject: [PATCH 03/22] Fix repo pattern list styles --- .../configuration/components/RepositoryPatternList.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/web/src/enterprise/codeintel/configuration/components/RepositoryPatternList.module.scss b/client/web/src/enterprise/codeintel/configuration/components/RepositoryPatternList.module.scss index d06318b476e5..dbb677014ec3 100644 --- a/client/web/src/enterprise/codeintel/configuration/components/RepositoryPatternList.module.scss +++ b/client/web/src/enterprise/codeintel/configuration/components/RepositoryPatternList.module.scss @@ -24,7 +24,7 @@ .input-actions { width: 10%; display: flex; - justify-content: start; + justify-content: flex-start; align-self: stretch; } From d23c7c5ef38139fac454b71cdd51ea781955d887 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Thu, 9 Feb 2023 23:17:30 -0300 Subject: [PATCH 04/22] Add WIP workaround link for the setup wizard route --- client/web/src/Layout.tsx | 3 ++- client/web/src/setup-wizard/SetupWizard.tsx | 14 +++++++------- .../components/navigation/CodeHostsNavigation.tsx | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/client/web/src/Layout.tsx b/client/web/src/Layout.tsx index c25de0d30bc0..c18fe1f86c93 100644 --- a/client/web/src/Layout.tsx +++ b/client/web/src/Layout.tsx @@ -16,7 +16,7 @@ import { SettingsCascadeProps, SettingsSubjectCommonFields } from '@sourcegraph/ import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { ThemeProps } from '@sourcegraph/shared/src/theme' import { parseQueryAndHash } from '@sourcegraph/shared/src/util/url' -import { FeedbackPrompt, LoadingSpinner, Panel } from '@sourcegraph/wildcard' +import { FeedbackPrompt, Link, LoadingSpinner, Panel } from '@sourcegraph/wildcard' import { AuthenticatedUser } from './auth' import type { BatchChangesProps } from './batches' @@ -162,6 +162,7 @@ export const Layout: React.FC = props => { enableContrastCompliantSyntaxHighlighting && CONTRAST_COMPLIANT_CLASSNAME )} > + Go to wizard {showHelpShortcut?.keybindings.map((keybinding, index) => ( ))} diff --git a/client/web/src/setup-wizard/SetupWizard.tsx b/client/web/src/setup-wizard/SetupWizard.tsx index dc5ee84932de..99eda353e2d9 100644 --- a/client/web/src/setup-wizard/SetupWizard.tsx +++ b/client/web/src/setup-wizard/SetupWizard.tsx @@ -1,4 +1,4 @@ -import { FC, ReactElement } from 'react' +import { FC, ReactElement, useCallback } from 'react' import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary' import { H1, H2 } from '@sourcegraph/wildcard' @@ -20,7 +20,7 @@ const SETUP_STEPS: StepConfiguration[] = [ { id: '002', name: 'Add remote repositories', - path: '/setup/remote-repositories1/*', + path: '/setup/remote-repositories/*', component: RemoteRepositoriesStep, }, { @@ -34,21 +34,21 @@ const SETUP_STEPS: StepConfiguration[] = [ export const SetupWizard: FC = props => { const [activeStepId, setStepId, status] = useTemporarySetting('setup.activeStepId') + const handleStepChange = useCallback((step: StepConfiguration): void => { + setStepId(step.id) + }, [setStepId]) + if (status !== 'loaded') { return null } - const handleStepChange = (step: StepConfiguration): void => { - setStepId(step.id) - } - return (

- Welcome to Sourcegraph! Let's get started22. + Welcome to Sourcegraph! Let's get started.

diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx index 85fa71b2a512..70e674ea9253 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx @@ -53,7 +53,7 @@ export const CodeHostsNavigation: FC = props => { ) } - if (!data || loading) { + if (!data || (!data && loading)) { return ( {' '} From c8e34dfc8e9f7421096a9be09f14d55449ac123a Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Fri, 10 Feb 2023 14:48:39 -0300 Subject: [PATCH 05/22] Add WIP navigation for different code hosts --- client/web/src/Layout.tsx | 3 +- client/web/src/setup-wizard/SetupWizard.tsx | 16 ++-- .../RemoteRepositoriesStep.module.scss | 2 - .../RemoteRepositoriesStep.tsx | 28 ++++--- .../code-host-picker/CodeHostsPicker.tsx | 26 ++++--- .../components/code-host-picker/index.ts | 1 - .../CodeHostsNavigation.module.scss | 65 ++++++++++++++-- .../navigation/CodeHostsNavigation.tsx | 77 +++++++++++++------ .../components/navigation/index.ts | 3 +- .../remote-repositories-step/helpers.ts | 16 +++- .../remote-repositories-step/index.ts | 1 - .../components/setup-steps/SetupSteps.tsx | 9 ++- 12 files changed, 176 insertions(+), 71 deletions(-) diff --git a/client/web/src/Layout.tsx b/client/web/src/Layout.tsx index c18fe1f86c93..c25de0d30bc0 100644 --- a/client/web/src/Layout.tsx +++ b/client/web/src/Layout.tsx @@ -16,7 +16,7 @@ import { SettingsCascadeProps, SettingsSubjectCommonFields } from '@sourcegraph/ import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { ThemeProps } from '@sourcegraph/shared/src/theme' import { parseQueryAndHash } from '@sourcegraph/shared/src/util/url' -import { FeedbackPrompt, Link, LoadingSpinner, Panel } from '@sourcegraph/wildcard' +import { FeedbackPrompt, LoadingSpinner, Panel } from '@sourcegraph/wildcard' import { AuthenticatedUser } from './auth' import type { BatchChangesProps } from './batches' @@ -162,7 +162,6 @@ export const Layout: React.FC = props => { enableContrastCompliantSyntaxHighlighting && CONTRAST_COMPLIANT_CLASSNAME )} > - Go to wizard {showHelpShortcut?.keybindings.map((keybinding, index) => ( ))} diff --git a/client/web/src/setup-wizard/SetupWizard.tsx b/client/web/src/setup-wizard/SetupWizard.tsx index 99eda353e2d9..2813d3b2e32a 100644 --- a/client/web/src/setup-wizard/SetupWizard.tsx +++ b/client/web/src/setup-wizard/SetupWizard.tsx @@ -5,7 +5,7 @@ import { H1, H2 } from '@sourcegraph/wildcard' import { BrandLogo } from '../components/branding/BrandLogo' -import { RemoteRepositoriesStep } from './components/remote-repositories-step'; +import { RemoteRepositoriesStep } from './components/remote-repositories-step' import { SetupStepsRoot, StepConfiguration } from './components/setup-steps' import styles from './Setup.module.scss' @@ -34,9 +34,12 @@ const SETUP_STEPS: StepConfiguration[] = [ export const SetupWizard: FC = props => { const [activeStepId, setStepId, status] = useTemporarySetting('setup.activeStepId') - const handleStepChange = useCallback((step: StepConfiguration): void => { - setStepId(step.id) - }, [setStepId]) + const handleStepChange = useCallback( + (step: StepConfiguration): void => { + setStepId(step.id) + }, + [setStepId] + ) if (status !== 'loaded') { return null @@ -58,8 +61,5 @@ export const SetupWizard: FC = props => { } function LocalRepositoriesStep(props: any): ReactElement { - return ( -

Hello local repositories step

- ) + return

Hello local repositories step

} - diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss index 6b95200e4fb0..c66337d1a61a 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss @@ -1,4 +1,3 @@ - .root { display: flex; flex-direction: column; @@ -25,7 +24,6 @@ margin-top: 0.2rem; } - .code-host-picker { &-header { display: flex; diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx index 951bf16c3146..eaf7de389ba1 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx @@ -1,14 +1,14 @@ -import { FC, HTMLAttributes } from 'react'; +import { FC, HTMLAttributes } from 'react' -import classNames from 'classnames'; -import { Routes, Route } from 'react-router-dom-v5-compat'; +import classNames from 'classnames' +import { Routes, Route, matchPath, useLocation } from 'react-router-dom-v5-compat' -import { Container, Text } from '@sourcegraph/wildcard'; +import { Container, Text } from '@sourcegraph/wildcard' -import { CustomNextButton } from '../setup-steps'; +import { CustomNextButton } from '../setup-steps' -import { CodeHostsPicker } from './components/code-host-picker'; -import { CodeHostsNavigation } from './components/navigation'; +import { CodeHostsPicker } from './components/code-host-picker' +import { CodeHostsNavigation } from './components/navigation' import styles from './RemoteRepositoriesStep.module.scss' @@ -17,18 +17,26 @@ interface RemoteRepositoriesStepProps extends HTMLAttributes {} export const RemoteRepositoriesStep: FC = props => { const { className, ...attributes } = props + const location = useLocation() + const editConnectionRouteMatch = matchPath('/setup/remote-repositories/:codehostid/edit', location.pathname) + const newConnectionRouteMatch = matchPath('/setup/remote-repositories/:codehost/create', location.pathname) + return (
- Connect remote code hosts where your source code lives. + Connect remote code hosts where your source code lives.
- + - } /> + } /> Hello creation UI} /> Hello edit UI} /> diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx index 3e7e312c37c6..c53b82d4c835 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/CodeHostsPicker.tsx @@ -1,11 +1,11 @@ -import { FC } from 'react'; +import { FC } from 'react' -import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations'; -import { Button, Icon } from '@sourcegraph/wildcard'; +import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations' +import { Button, Icon, Link } from '@sourcegraph/wildcard' -import { getCodeHostIcon, getCodeHostName } from '../../helpers'; +import { getCodeHostIcon, getCodeHostName, getCodeHostURLParam } from '../../helpers' -import styles from './CodeHostsPicker.module.scss'; +import styles from './CodeHostsPicker.module.scss' const SUPPORTED_CODE_HOSTS = [ ExternalServiceKind.GITHUB, @@ -17,18 +17,24 @@ export const CodeHostsPicker: FC = () => (
Add another remote code host - Choose a provider from the list below + Choose a provider from the list below
    - {SUPPORTED_CODE_HOSTS.map(codeHostType => + {SUPPORTED_CODE_HOSTS.map(codeHostType => (
  • -
  • - )} + ))}
) diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts index c14fb933cbea..679f91935a1a 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-host-picker/index.ts @@ -1,2 +1 @@ - export { CodeHostsPicker } from './CodeHostsPicker' diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss index c9fc4304df96..5e1bff1baadf 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss @@ -1,4 +1,3 @@ - .loading-state { display: flex; align-items: center; @@ -26,17 +25,49 @@ margin: 0; display: flex; flex-direction: column; - //gap: 0.25rem; + gap: 0.2rem; } .item { - padding: 0.25rem 0.5rem; + display: flex; margin: 0 -0.5rem; border-radius: 3px; &:hover { background-color: var(--secondary-2); } + + &--creation { + display: flex; + justify-content: flex-start; + align-items: flex-start; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + } + + &--active { + color: var(--light-text) !important; + background-color: var(--primary); + + .item-button, + .item-description-status { + color: var(--light-text); + } + + .delete { + color: var(--light-text); + + &:hover, + &:focus { + color: var(--light-text) !important; + background-color: var(--primary-3) !important; + } + } + + &:hover { + background-color: var(--primary); + } + } } .item-button { @@ -44,13 +75,37 @@ justify-content: flex-start; align-items: flex-start; gap: 0.25rem; - width: 100%; text-align: start; font-weight: normal; - padding: 0; + padding: 0.25rem 0 0.25rem 0.5rem; + flex-grow: 1; } .item-description { display: flex; flex-direction: column; + + &-status { + color: var(--text-muted); + } +} + +.delete { + color: var(--text-muted); + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + &:hover, + &:focus { + color: var(--danger); + background-color: var(--danger-4); + } + + :global(.theme-dark) & { + &:hover, + &:focus { + color: var(--light-text); + background-color: var(--danger-2); + } + } } diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx index 70e674ea9253..90e3fe04db18 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx @@ -1,12 +1,12 @@ -import { FC } from 'react'; +import { FC } from 'react' -import { gql, useQuery } from '@apollo/client'; -import { mdiInformationOutline } from '@mdi/js'; -import classNames from 'classnames'; +import { gql, useQuery } from '@apollo/client' +import { mdiInformationOutline, mdiDelete, mdiPlus } from '@mdi/js' +import classNames from 'classnames' -import { ErrorAlert, Icon, LoadingSpinner, Button } from '@sourcegraph/wildcard'; +import { ErrorAlert, Icon, LoadingSpinner, Button, Tooltip, Link } from '@sourcegraph/wildcard' -import { GetCodeHostsResult } from '../../../../../graphql-operations'; +import { GetCodeHostsResult } from '../../../../../graphql-operations' import { getCodeHostIcon } from '../../helpers' import styles from './CodeHostsNavigation.module.scss' @@ -27,26 +27,23 @@ const GET_CODE_HOSTS = gql` ` interface CodeHostsNavigationProps { + activeConnectionId: string | undefined + addNewCodeHost: boolean className?: string } export const CodeHostsNavigation: FC = props => { - const { className } = props + const { activeConnectionId, addNewCodeHost, className } = props const { data, loading, error, refetch } = useQuery(GET_CODE_HOSTS, { - fetchPolicy: 'cache-and-network' + fetchPolicy: 'cache-and-network', }) if (error && !loading) { return (
- -
@@ -56,8 +53,7 @@ export const CodeHostsNavigation: FC = props => { if (!data || (!data && loading)) { return ( - {' '} - Fetching connected code host... + Fetching connected code host... ) } @@ -81,22 +77,55 @@ export const CodeHostsNavigation: FC = props => { return (
    - { data.externalServices.nodes.map(codeHost => -
  • - + + + +
  • - )} + ))}
) } diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts index 6e7dbc82e133..6656de0c1fac 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/index.ts @@ -1,2 +1 @@ - -export { CodeHostsNavigation} from './CodeHostsNavigation' +export { CodeHostsNavigation } from './CodeHostsNavigation' diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts b/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts index ae3e102ed4f5..106edd9b1263 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts +++ b/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts @@ -1,6 +1,6 @@ -import { mdiBitbucket, mdiGithub, mdiGitlab } from '@mdi/js'; +import { mdiBitbucket, mdiGithub, mdiGitlab, mdiAws } from '@mdi/js' -import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations'; +import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations' export const getCodeHostIcon = (codeHostType: ExternalServiceKind): string => { switch (codeHostType) { @@ -10,6 +10,8 @@ export const getCodeHostIcon = (codeHostType: ExternalServiceKind): string => { return mdiGitlab case ExternalServiceKind.BITBUCKETCLOUD: return mdiBitbucket + case ExternalServiceKind.AWSCODECOMMIT: + return mdiAws default: // TODO: Add support for other code host @@ -25,9 +27,19 @@ export const getCodeHostName = (codeHostType: ExternalServiceKind): string => { return 'GitLab' case ExternalServiceKind.BITBUCKETCLOUD: return 'BitBucket.org' + case ExternalServiceKind.AWSCODECOMMIT: + return 'AWS Code Commit' default: // TODO: Add support for other code host return 'Unknown' } } + +export const getCodeHostURLParam = (codeHostType: ExternalServiceKind): string => codeHostType.toString().toLowerCase() + +export const getCodeHostKindFromURLParam = (possibleCodeHostType: string): ExternalServiceKind | null => { + const possibleKind = ExternalServiceKind[possibleCodeHostType.toUpperCase() as ExternalServiceKind] + + return possibleKind ?? null +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/index.ts index 49ccee100da8..eb6df0dad149 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/index.ts +++ b/client/web/src/setup-wizard/components/remote-repositories-step/index.ts @@ -1,2 +1 @@ - export { RemoteRepositoriesStep } from './RemoteRepositoriesStep' diff --git a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx index 2957345759f2..da436ccb0633 100644 --- a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx +++ b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx @@ -7,7 +7,8 @@ import { useMemo, useContext, useCallback, - useEffect, useState, + useEffect, + useState, } from 'react' import { mdiChevronLeft, mdiChevronRight } from '@mdi/js' @@ -54,7 +55,7 @@ export const SetupStepsRoot: FC = props => { const navigate = useNavigate() const location = useLocation() - const [nextButtonPortal, setNextButtonPortal] = useState(null) + const [nextButtonPortal, setNextButtonPortal] = useState(null) // Resolve current setup step and its index by URL matches const { currentStep, activeStepIndex } = useMemo(() => { @@ -124,8 +125,8 @@ export const SetupStepsRoot: FC = props => {
- {steps.map(({ path, component: Component}) => ( - }/> + {steps.map(({ path, component: Component }) => ( + } /> ))} } /> From bfdebcb327afc5a43fed4bd40a3c0e7aa167ffd3 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Fri, 10 Feb 2023 14:49:08 -0300 Subject: [PATCH 06/22] Add debounce time for the tooltip UI --- .../src/components/Container/Container.tsx | 4 ++-- client/wildcard/src/components/Tooltip/Tooltip.tsx | 14 ++++++++++++-- client/wildcard/src/hooks/useDebounce.ts | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/client/wildcard/src/components/Container/Container.tsx b/client/wildcard/src/components/Container/Container.tsx index bd6646133d41..ad7a6d808595 100644 --- a/client/wildcard/src/components/Container/Container.tsx +++ b/client/wildcard/src/components/Container/Container.tsx @@ -2,7 +2,7 @@ import { forwardRef } from 'react' import classNames from 'classnames' -import { ForwardReferenceComponent } from '../../types'; +import { ForwardReferenceComponent } from '../../types' import styles from './Container.module.scss' @@ -10,5 +10,5 @@ import styles from './Container.module.scss' export const Container = forwardRef((props, ref) => { const { as: Comp = 'div', className, ...attributes } = props - return () + return }) as ForwardReferenceComponent<'div'> diff --git a/client/wildcard/src/components/Tooltip/Tooltip.tsx b/client/wildcard/src/components/Tooltip/Tooltip.tsx index 2836c0eb720d..6b8318457d18 100644 --- a/client/wildcard/src/components/Tooltip/Tooltip.tsx +++ b/client/wildcard/src/components/Tooltip/Tooltip.tsx @@ -44,6 +44,8 @@ export interface TooltipProps { /** The open state of the tooltip when it is initially rendered. Defaults to `false`. */ defaultOpen?: boolean + debounce?: number + /** * The preferred side of the trigger to render against when open. Will be reversed if * a collision is detected. Defaults to `bottom`. @@ -76,7 +78,15 @@ export interface TooltipProps { * it must have an `aria-label`. */ export const Tooltip: FC = props => { - const { children, content, open, defaultOpen = false, placement = 'bottom', onOpenChange = noop } = props + const { + children, + content, + open, + defaultOpen = false, + placement = 'bottom', + debounce = 100, + onOpenChange = noop, + } = props const [target, setTarget] = useState(null) const [tail, setTail] = useState(null) @@ -152,7 +162,7 @@ export const Tooltip: FC = props => { } const tooltipId = `tooltip-${useId()}` - const isOpenDebounced = useDebounce(isOpen, 100) + const isOpenDebounced = useDebounce(isOpen, debounce) return ( <> diff --git a/client/wildcard/src/hooks/useDebounce.ts b/client/wildcard/src/hooks/useDebounce.ts index 2717f8e386ec..ec0ca679276a 100644 --- a/client/wildcard/src/hooks/useDebounce.ts +++ b/client/wildcard/src/hooks/useDebounce.ts @@ -15,5 +15,5 @@ export const useDebounce = (value: T, delay: number): T => { return () => clearTimeout(handler) }, [delay, value]) - return debouncedValue + return delay === 0 ? value : debouncedValue } From d05c110a365138cd2eddfba4b7c34f46f5c5992b Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 15:39:56 -0300 Subject: [PATCH 07/22] Fix combobox className prop --- .../src/components/Combobox/Combobox.module.scss | 2 ++ client/wildcard/src/components/Combobox/Combobox.tsx | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client/wildcard/src/components/Combobox/Combobox.module.scss b/client/wildcard/src/components/Combobox/Combobox.module.scss index e883e43dfd28..8bdb6fc5fa8c 100644 --- a/client/wildcard/src/components/Combobox/Combobox.module.scss +++ b/client/wildcard/src/components/Combobox/Combobox.module.scss @@ -45,6 +45,8 @@ } .item { + border-radius: 3px; + &--disabled { cursor: not-allowed; } diff --git a/client/wildcard/src/components/Combobox/Combobox.tsx b/client/wildcard/src/components/Combobox/Combobox.tsx index 956cf051742d..27bdf5b161c5 100644 --- a/client/wildcard/src/components/Combobox/Combobox.tsx +++ b/client/wildcard/src/components/Combobox/Combobox.tsx @@ -240,7 +240,7 @@ export const ComboboxOption = forwardRef((props, ref) => {
  • {typeof children === 'function' ? children(context) : children ?? value} @@ -249,7 +249,12 @@ export const ComboboxOption = forwardRef((props, ref) => { } return ( - + {children} ) From b9cb39eb64780b7fcbef7f7783d72ef37ad5bf53 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 16:06:19 -0300 Subject: [PATCH 08/22] Fix react node import --- client/wildcard/src/components/Tabs/useShouldPanelRender.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/wildcard/src/components/Tabs/useShouldPanelRender.ts b/client/wildcard/src/components/Tabs/useShouldPanelRender.ts index 83b55bbecf86..823d8c6527b1 100644 --- a/client/wildcard/src/components/Tabs/useShouldPanelRender.ts +++ b/client/wildcard/src/components/Tabs/useShouldPanelRender.ts @@ -1,10 +1,10 @@ -import { useLayoutEffect, useState } from 'react' +import { useLayoutEffect, useState, ReactNode } from 'react' import { useTabsContext as useReachTabsContext } from '@reach/tabs' import { useTablePanelIndex, useTabsState } from './context' -export function useShouldPanelRender(children: React.ReactNode): boolean { +export function useShouldPanelRender(children: ReactNode): boolean { const { selectedIndex } = useReachTabsContext() const index = useTablePanelIndex() const { From ca227bad8115fc695e774687697eaee25729ec56 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 16:07:11 -0300 Subject: [PATCH 09/22] Fix editor UI --- .../settings/DynamicallyImportedMonacoSettingsEditor.tsx | 8 +++++--- client/web/src/settings/EditorActionsGroup.module.scss | 6 +----- client/web/src/settings/EditorActionsGroup.tsx | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/client/web/src/settings/DynamicallyImportedMonacoSettingsEditor.tsx b/client/web/src/settings/DynamicallyImportedMonacoSettingsEditor.tsx index 2488b3a924b7..38db5c2f12a1 100644 --- a/client/web/src/settings/DynamicallyImportedMonacoSettingsEditor.tsx +++ b/client/web/src/settings/DynamicallyImportedMonacoSettingsEditor.tsx @@ -100,7 +100,7 @@ export class DynamicallyImportedMonacoSettingsEditor exte } private get effectiveValue(): string { - return this.state.value === undefined ? this.props.value : this.state.value + return this.props.value } private get isDirty(): boolean { @@ -130,14 +130,16 @@ export class DynamicallyImportedMonacoSettingsEditor exte ) } + const { className, ...otherProps } = this.props + return ( -
    +
    {this.props.actions && ( )} }> {actions.map(({ id, label }) => ( - ))} From 18fb722351dc6c63634b21c04d9e4e234e74e71a Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 16:07:46 -0300 Subject: [PATCH 10/22] Add controlled field hook --- .../components/Form/Form/hooks/useField.ts | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/client/wildcard/src/components/Form/Form/hooks/useField.ts b/client/wildcard/src/components/Form/Form/hooks/useField.ts index fecad529afc5..269f1f70c672 100644 --- a/client/wildcard/src/components/Form/Form/hooks/useField.ts +++ b/client/wildcard/src/components/Form/Form/hooks/useField.ts @@ -320,3 +320,147 @@ function useFieldValidation(props: UseFieldValidationProps): UseFi return { inputRef: inputReference } } + +export type UseControlledFieldProps = { + value: Value + name: string + submitted: boolean + formTouched: boolean + validators?: Validators +} & InputProps + +/** + * React hook to manage validation of a single form input field. + * `useInputValidation` helps with coordinating the constraint validation API + * and custom synchronous and asynchronous validators. + * + * Should be used with useForm hook to connect field and form component's states. + */ +export function useControlledField(props: UseControlledFieldProps): useFieldAPI { + const { value, validators, submitted, formTouched, onChange = noop, name, disabled, ...inputProps } = props + const { sync: syncValidator, async: asyncValidator } = validators ?? {} + + const inputReference = useRef(null) + const [state, setState] = useState>({ + touched: false, + dirty: false, + validState: 'NOT_VALIDATED', + validity: null, + initialValue: value, + }) + + const { start: startAsyncValidation, cancel: cancelAsyncValidation } = useAsyncValidation({ + inputReference, + asyncValidator, + onValidationChange: asyncState => setState(previousState => ({ ...previousState, ...asyncState })), + }) + + useLayoutEffect(() => { + const inputElement = inputReference.current + + // Clear custom validity from the last validation call. + inputElement?.setCustomValidity?.('') + + const validity = inputElement?.validity ?? null + + // If we got error from native attr validation (required, pattern, type) + // we still run validator in order to get some custom error message for + // standard validation error if validator doesn't provide message we fall back + // on standard validationMessage string [1] (ex. Please fill in input.) + const nativeErrorMessage = inputElement?.validationMessage ?? '' + const customValidationResult = syncValidator ? syncValidator(value, validity) : undefined + + const customValidationMessage = getCustomValidationMessage(customValidationResult) + const customValidationContext = getCustomValidationContext(customValidationResult) + + if (customValidationMessage || (!customValidationResult && nativeErrorMessage)) { + // We have to cancel async validation from previous call + // if we got sync validation native or custom. + cancelAsyncValidation() + + // [1] Custom error message or fallback on native error message + const validationMessage = customValidationMessage || nativeErrorMessage + + inputElement?.setCustomValidity?.(validationMessage) + + return setState(state => ({ + ...state, + validState: 'INVALID', + error: validationMessage, + errorContext: customValidationContext, + validity, + })) + } + + if (asyncValidator) { + // Due to the call of start async validation in useLayoutEffect we have to + // schedule the async validation event in the next tick to be able to run + // observable pipeline validation since useAsyncValidation hook use + // useObservable hook internally which calls '.subscribe' in useEffect. + requestAnimationFrame(() => { + startAsyncValidation({ value, validity }) + }) + + return setState(state => ({ + ...state, + validState: 'CHECKING' as const, + error: '', + validity, + })) + } + + // Hide any validation errors by default if the field is in the disabled + // state. + if (disabled && !syncValidator && !asyncValidator) { + return setState(state => ({ + ...state, + validState: 'NOT_VALIDATED', + error: '', + validity, + })) + } + + return setState(state => ({ + ...state, + validState: 'VALID' as const, + error: '', + errorContext: customValidationContext, + validity, + })) + }, [value, syncValidator, startAsyncValidation, asyncValidator, cancelAsyncValidation, setState, disabled]) + + const handleBlur = useCallback(() => setState(state => ({ ...state, touched: true })), [setState]) + const handleChange = useCallback( + (event: ChangeEvent | Value) => { + const value = getEventValue(event) + + onChange(value) + setState(state => ({ ...state, dirty: true })) + }, + [onChange, setState] + ) + + return { + input: { + value, + name: name.toString(), + ref: inputReference, + onBlur: handleBlur, + onChange: handleChange, + disabled, + ...inputProps, + }, + meta: { + ...state, + value, + validationContext: state.errorContext, + touched: state.touched || submitted || formTouched, + // Set state dispatcher gives access to set inner state of useField. + // Useful for complex cases when you need to deal with custom react input + // components. + setState: dispatch => { + setState(state => ({ ...dispatch({ value, ...state }) })) + }, + }, + } +} From 3f26cef11b399c28104f49da5a84e5e4c0dc03c3 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 16:11:49 -0300 Subject: [PATCH 11/22] Ad common components for code host connection UI --- .../components/code-hosts/common/index.ts | 1 + .../RadioGroupSection.module.scss | 28 ++++++++++++ .../radio-group-section/RadioGroupSection.tsx | 44 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.module.scss create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.tsx diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts new file mode 100644 index 000000000000..38ee6fc22b17 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts @@ -0,0 +1 @@ +export { RadioGroupSection } from './radio-group-section/RadioGroupSection' diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.module.scss new file mode 100644 index 000000000000..9dfa145f43b4 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.module.scss @@ -0,0 +1,28 @@ +.radio-group { + display: grid; + column-gap: 0.5rem; + grid-template-rows: min-content auto; + grid-template-columns: min-content auto; + grid-template-areas: + 'input label' + 'input content'; + + &-input { + grid-area: input; + align-self: center; + grid-row-start: 1; + grid-row-end: 2; + margin: 0; + } + + &-label { + grid-area: label; + display: flex; + margin: 0; + } + + &-content { + grid-area: content; + margin-top: 0.5rem; + } +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.tsx new file mode 100644 index 000000000000..86807bd2b79e --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/radio-group-section/RadioGroupSection.tsx @@ -0,0 +1,44 @@ +import { ChangeEvent, PropsWithChildren, ReactElement } from 'react' + +import { Label } from '@sourcegraph/wildcard' + +import styles from './RadioGroupSection.module.scss' + +interface RadioGroupSectionProps { + name: string + label: string + value: string + checked: boolean + labelId: string + className?: string + onChange: (event: ChangeEvent) => void +} + +export function RadioGroupSection(props: PropsWithChildren): ReactElement { + const { name, label, value, checked, labelId, children, onChange } = props + + return ( +
    + {/* + Standard wildcard input doesn't provide a simple layout for the radio element, + in order to have custom layout in the repo control we have to use native input + with custom styles around spacing and layout + */} + {/* eslint-disable-next-line react/forbid-elements */} + + + + {checked &&
    {children}
    } +
    + ) +} From 351fb3714a3bb0aa5cd8a1ff58d95ccbb8593fbf Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 16:14:01 -0300 Subject: [PATCH 12/22] Add code host connection UI github code host --- .../code-hosts/CodeHostCreation.module.scss | 26 ++ .../code-hosts/CodeHostCreation.tsx | 76 ++++ .../components/code-hosts/CodeHostEdit.tsx | 15 + .../github/GithubConnectView.module.scss | 61 ++++ .../code-hosts/github/GithubConnectView.tsx | 334 ++++++++++++++++++ .../code-hosts/github/GithubEntityPickers.tsx | 108 ++++++ .../github/GithubJSONTab.module.scss | 20 ++ .../code-hosts/github/GithubJSONTab.tsx | 121 +++++++ .../components/code-hosts/types.ts | 6 + 9 files changed, 767 insertions(+) create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubEntityPickers.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.module.scss create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss new file mode 100644 index 000000000000..eadf6ac73df8 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss @@ -0,0 +1,26 @@ +.header { + display: flex; + gap: 0.5rem; + align-items: center; + + &-icon { + width: 2rem; + height: 2rem; + flex-shrink: 0; + } +} + +.footer { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + position: sticky; + // Calculated value since we already have sticky footer element for + // the core wizard UI. + bottom: 51px; + margin-top: auto; + padding: 0.5rem 1rem; + background-color: var(--color-bg-1); + border-top: 1px solid var(--border-color); +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx new file mode 100644 index 000000000000..4359fab7d655 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx @@ -0,0 +1,76 @@ +import { FC, ReactNode } from 'react' + +import { useParams } from 'react-router-dom-v5-compat' + +import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations' +import { Alert, H4, Link, Button } from '@sourcegraph/wildcard' + +import { getCodeHostKindFromURLParam } from '../../helpers' + +import { GithubConnectView } from './github/GithubConnectView' +import { CodeHostFormState } from './types' + +import styles from './CodeHostCreation.module.scss' + +/** + * Renders creation UI for any supported code hosts (Github, Gitlab) based on + * "codeHostType" URL param see root component routing logic. + */ +export const CodeHostCreation: FC = () => { + const { codeHostType } = useParams() + const codeHostKind = getCodeHostKindFromURLParam(codeHostType!) + + if (codeHostKind === null) { + return ( + +

    We either couldn't find "{codeHostType}" code host option or we do not support this

    + Pick one of supported code host option here +
    + ) + } + + // We render content inside react fragment because this view is rendered + // within Container UI (avoid unnecessary DOM nesting) + return ( + + {state => ( +
    + + + + {/*After connecting, repositories will start sync in the background.*/} +
    + )} +
    + ) +} + +interface CodeHostCreationFormProps { + codeHostKind: ExternalServiceKind + children: (state: CodeHostFormState) => ReactNode +} + +/** + * Renders specific creation UI form for particular code host type. Most of + * the code hosts have similar UI but some of them (like GitHub) have special enhanced + * UI with pickers and other form UI. + */ +const CodeHostCreationView: FC = props => { + const { codeHostKind, children } = props + + if (codeHostKind === ExternalServiceKind.GITHUB) { + return {children} + } + + // TODO: extend this view with all supported code host connections form UI + return ( + + Oh no, we haven't implemented code host connection form for {codeHostKind} + code host type, Todo: add this type code host connection form! + + ) +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx new file mode 100644 index 000000000000..e2ff29acfefb --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react' + +interface CodeHostEditProps {} + +/** + * Renders edit UI for any supported code host type. (Github, Gitlab, ...) + * Also performs edit, delete actions over opened code host connection + */ +export const CodeHostEdit: FC = () => { + return <>Edit UI +} + +const CodeHostEditView: FC = () => { + return <>Specific code host edit UI +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss new file mode 100644 index 000000000000..84408d475677 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss @@ -0,0 +1,61 @@ +.form { + // Since this form is rendered withing container with padding + // but we want to render some element without this padding we stretch + // the form and control padding on form level instead. + margin: -1rem; + display: flex; + flex-direction: column; + height: calc(100% + 2rem); + background-color: inherit; +} + +.tab-list { + padding: 0.25rem 0.5rem 0 1rem; +} + +.tab { + padding: 0.2rem 0.5rem; +} + +.tab-panels { + padding: 1rem; +} + +.form-view { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.repositories-fields { + margin-top: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; + + &--disabled { + filter: grayscale(100%) opacity(0.4); + pointer-events: none; + } +} + +.configuration-group { + &-label { + font-weight: 500; + } + + &-instruction-button { + margin-left: -0.5rem; + } + + &-instructions { + padding-left: 1.5rem; + margin-top: 0.25rem; + font-size: 0.8rem; + color: var(--body-color); + } + + &-editor { + margin-top: -0.5rem; + } +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx new file mode 100644 index 000000000000..ecf5a082a337 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx @@ -0,0 +1,334 @@ +import { FC, ReactNode, ReactElement, useCallback, useState, useMemo, ChangeEvent } from 'react' + +import classNames from 'classnames' +import { parse as parseJSONC } from 'jsonc-parser' + +import { modify } from '@sourcegraph/common' +import { Tabs, Tab, TabList, TabPanel, TabPanels, Input, Checkbox, useLocalStorage } from '@sourcegraph/wildcard' + +import { + SubmissionErrors, + useField, + useForm, + Form, + getDefaultInputProps, + useFieldAPI, + useControlledField, +} from '../../../../../../enterprise/insights/components' +import { AddExternalServiceInput, ExternalServiceKind } from '../../../../../../graphql-operations' +import { RadioGroupSection } from '../common' + +import { GithubOrganizationsPicker, GithubRepositoriesPicker } from './GithubEntityPickers' +import { GithubJSONTabView } from './GithubJSONTab' + +import styles from './GithubConnectView.module.scss' + +const DEFAULT_CONFIGURATION = ` +{ + "url": "https://github.com", + "token": "" +} +` +const DEFAULT_FORM_VALUES: GithubConnectFormFields = { + displayName: 'GitHub', + configuration: DEFAULT_CONFIGURATION.trim(), +} + +interface GithubConnectFormFields { + displayName: string + configuration: string +} + +interface CodeHostCreationFormState { + submitting: boolean + submitErrors: SubmissionErrors +} + +interface GithubConnectViewProps { + /** + * Render props that is connected to form state, usually is used to render + * form actions UI, like save, cancel, clear fields. Action layout is the same + * for all variations of this form (create, edit UI) but content is different + */ + children: (state: CodeHostCreationFormState) => ReactNode + onGithubConnect: (input: AddExternalServiceInput) => Promise +} + +/** + * GitHub's creation UI panel, it renders GitHub connection form UI and also handles + * form values logic, like saving work-in-progress form values in local + * storage + */ +export const GithubConnectView: FC = props => { + const { children, onGithubConnect } = props + const [initialValues, setInitialValues] = useLocalStorage('github-connection-form', DEFAULT_FORM_VALUES) + + const handleSubmit = useCallback( + async (values: GithubConnectFormFields): Promise => { + // Perform public API code host connection create action + await onGithubConnect({ + kind: ExternalServiceKind.GITHUB, + displayName: values.displayName, + config: values.configuration, + }) + + // Reset initial values after successful connect action + setInitialValues(DEFAULT_FORM_VALUES) + }, + [setInitialValues, onGithubConnect] + ) + + return ( + + {children} + + ) +} + +enum GithubConnectFormTab { + Form, + JSONC, +} + +interface GithubConnectFormProps { + initialValues: GithubConnectFormFields + children: (state: CodeHostCreationFormState) => ReactNode + onChange: (values: GithubConnectFormFields) => void + onSubmit: (values: GithubConnectFormFields) => void +} + +/** + * It renders custom GitHub connect form that provides form UI and plain JSONC + * configuration UI. + */ +export const GithubConnectForm: FC = props => { + const { initialValues, children, onChange, onSubmit } = props + + const [activeTab, setActiveTab] = useState(GithubConnectFormTab.Form) + const form = useForm({ + initialValues, + onSubmit, + onChange: event => onChange(event.values), + }) + + const displayName = useField({ + formApi: form.formAPI, + name: 'displayName', + required: true, + }) + + const configuration = useField({ + formApi: form.formAPI, + name: 'configuration', + }) + + return ( + + + + Settings + + + JSONC editor + + + + + + + + + + + + {children({ submitting: false, submitErrors: undefined })} + + ) +} + +interface GithubFormViewProps { + form: Form + displayNameField: useFieldAPI + configurationField: useFieldAPI + isTabActive: boolean +} + +function GithubFormView(props: GithubFormViewProps): ReactElement { + const { form, displayNameField, configurationField } = props + + const accessTokenField = useControlledField({ + value: getAccessTokenValue(configurationField.input.value), + name: 'accessToken', + submitted: form.formAPI.submitted, + formTouched: form.formAPI.touched, + validators: { sync: syncAccessTokenValidator, async: asyncAccessTokenValidator }, + onChange: value => configurationField.input.onChange(modify(configurationField.input.value, ['token'], value)), + }) + + const { isAffiliatedRepositories, isOrgsRepositories, isSetRepositories, organizations, repositories } = useMemo( + () => getRepositoriesSettings(configurationField.input.value), + [configurationField.input.value] + ) + + const handleAffiliatedModeChange = (event: ChangeEvent): void => { + const parsedConfiguration = parseJSONC(configurationField.input.value) as Record + const reposQuery: string[] = + typeof parsedConfiguration === 'object' ? [...(parsedConfiguration.reposQuery ?? [])] : [] + + const nextReposQuery = event.target.checked + ? [...reposQuery, 'affiliated'] + : reposQuery.filter(token => token !== 'affiliated') + + configurationField.input.onChange(modify(configurationField.input.value, ['repositoryQuery'], nextReposQuery)) + } + + const handleOrganizationsModeChange = (event: ChangeEvent): void => { + const nextConfiguration = event.target.checked + ? modify(configurationField.input.value, ['orgs'], []) + : modify(configurationField.input.value, ['orgs'], undefined) + + configurationField.input.onChange(nextConfiguration) + } + + const handleRepositoriesModeChange = (event: ChangeEvent): void => { + const nextConfiguration = event.target.checked + ? modify(configurationField.input.value, ['repos'], []) + : modify(configurationField.input.value, ['repos'], undefined) + + configurationField.input.onChange(nextConfiguration) + } + + const handleOrganizationsChange = (organizations: string[]): void => { + configurationField.input.onChange(modify(configurationField.input.value, ['orgs'], organizations)) + } + + const handleRepositoriesChange = (repositories: string[]): void => { + configurationField.input.onChange(modify(configurationField.input.value, ['repos'], repositories)) + } + + // Fragment to avoid nesting since it's rendered within TabPanel fieldset + return ( + <> + + + + +
    + + + + + + + + + +
    + + ) +} + +function syncAccessTokenValidator(value: string | undefined): string | undefined { + if (!value || value.length === 0) { + return 'Access token is a required field' + } + + return +} + +async function asyncAccessTokenValidator(value: string | undefined): Promise { + if (!value) { + return + } + + await new Promise(res => setTimeout(res, 1000)) + + return +} + +function getAccessTokenValue(configuration: string): string { + const parsedConfiguration = parseJSONC(configuration) as Record + + if (typeof parsedConfiguration === 'object') { + return parsedConfiguration.token ?? '' + } + + return '' +} + +interface GithubFormConfiguration { + isAffiliatedRepositories: boolean + isOrgsRepositories: boolean + isSetRepositories: boolean + repositories: string[] + organizations: string[] +} + +function getRepositoriesSettings(configuration: string): GithubFormConfiguration { + const parsedConfiguration = parseJSONC(configuration) as Record + + if (typeof parsedConfiguration === 'object') { + const repositoryQuery: string[] = parsedConfiguration.repositoryQuery ?? [] + + return { + isAffiliatedRepositories: repositoryQuery.includes('affiliated'), + isOrgsRepositories: Array.isArray(parsedConfiguration.orgs), + organizations: parsedConfiguration.orgs ?? [], + isSetRepositories: Array.isArray(parsedConfiguration.repos), + repositories: parsedConfiguration.repos ?? [], + } + } + + return { + isAffiliatedRepositories: false, + isOrgsRepositories: false, + organizations: [], + isSetRepositories: false, + repositories: [], + } +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubEntityPickers.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubEntityPickers.tsx new file mode 100644 index 000000000000..1653de0cad1a --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubEntityPickers.tsx @@ -0,0 +1,108 @@ +import { FC, useState } from 'react' + +import { mdiGithub } from '@mdi/js' + +import { + Icon, + MultiCombobox, + MultiComboboxInput, + MultiComboboxList, + MultiComboboxOption, + MultiComboboxOptionText, +} from '@sourcegraph/wildcard' +import { identity } from 'lodash' + +const DEMO_ORGS_SUGGESTIONS = [ + 'Sourcegraph org', + 'My personal organization', + 'Golang working group', + 'University labs', + 'React working group', +] + +interface GithubOrganizationsPickerProps { + organizations: string[] + onChange: (orginaziations: string[]) => void +} + +export const GithubOrganizationsPicker: FC = props => { + const { organizations, onChange } = props + const [searchTerm, setSearchTerm] = useState('') + + const suggestions = DEMO_ORGS_SUGGESTIONS.filter(item => !organizations.find(selectedItem => selectedItem === item)) + + return ( + + setSearchTerm(event.target.value)} + /> + + Pick at least one organization and we clone all repositories that this organzization has + + + + {items => + items.map((item, index) => ( + + + + )) + } + + + ) +} + +const DEMO_REPOS_SUGGESTIONS = [ + 'sourcegraph/sourcegraph', + 'sourcegraph/about', + 'personal/my-project', + 'peraonal/university-labs', + 'facebook/react', +] + +interface GithubRepositoriesPickerProps { + repositories: string[] + onChange: (repositories: string[]) => void +} + +export const GithubRepositoriesPicker: FC = props => { + const { repositories, onChange } = props + + const [searchTerm, setSearchTerm] = useState('') + + const suggestions = DEMO_REPOS_SUGGESTIONS.filter(item => !repositories.find(selectedItem => selectedItem === item)) + + return ( + + setSearchTerm(event.target.value)} + /> + Pick at least one repository + + + {items => + items.map((item, index) => ( + + + + )) + } + + + ) +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.module.scss new file mode 100644 index 000000000000..e1d7a1d2ba8e --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.module.scss @@ -0,0 +1,20 @@ +.configuration-group { + &-label { + font-weight: 500; + } + + &-instruction-button { + margin-left: -0.5rem; + } + + &-instructions { + padding-left: 1.5rem; + margin-top: 0.25rem; + font-size: 0.8rem; + color: var(--body-color); + } + + &-editor { + margin-top: -0.5rem; + } +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx new file mode 100644 index 000000000000..f60aefe32d89 --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx @@ -0,0 +1,121 @@ +import { FC, PropsWithChildren, ReactElement, useState } from 'react' + +import { mdiChevronDown, mdiChevronUp } from '@mdi/js' + +import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService' +import { Button, Code, Collapse, CollapseHeader, CollapsePanel, Icon, Input, Link, Text } from '@sourcegraph/wildcard' + +import { codeHostExternalServices } from '../../../../../../components/externalServices/externalServices' +import { FormGroup, getDefaultInputProps, useFieldAPI } from '../../../../../../enterprise/insights/components' +import { DynamicallyImportedMonacoSettingsEditor } from '../../../../../../settings/DynamicallyImportedMonacoSettingsEditor' + +import styles from './GithubJSONTab.module.scss' + +interface GithubSettingsViewProps { + displayNameField: useFieldAPI + configurationField: useFieldAPI +} + +export function GithubJSONTabView(props: GithubSettingsViewProps): ReactElement { + const { displayNameField, configurationField } = props + + // Fragment to avoid nesting since it's rendered within TabPanel fieldset + return ( + <> + + + } + labelClassName={styles.configurationGroupLabel} + > + + Use Ctrl+Space for completion, and hover over JSON properties for documentation. + + } + /> + + + ) +} + +const GithubInstructions: FC = () => { + const [isInstructionOpen, setInstructionOpen] = useState(false) + + return ( + + + See instructions how to fill out JSONC configuration{' '} + + + +
  • + Set url to the URL of GitHub Enterprise. +
  • +
  • + Create a GitHub access token ( + + instructions + + ) with repo scope. +
  • + Set the value of the token field as your access token, in the configuration + below. +
  • + +
  • + Specify which repositories Sourcegraph should index using one of the following fields: +
      +
    • + orgs: a list of GitHub organizations. +
    • +
    • + repositoryQuery: a list of GitHub search queries. +
      + For example, + "org:sourcegraph created:>2019-11-01" selects all repositories in + organization "sourcegraph" created after November 1, 2019. +
      + You may also use "affiliated" to select all repositories affiliated with the + access token. +
    • +
    • + repos: a list of individual repositories. +
    • +
    +
  • + + + ) +} + +const Field: FC> = props => {props.children} + +const Value: FC> = props => {props.children} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts new file mode 100644 index 000000000000..c613255049ef --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts @@ -0,0 +1,6 @@ +import { SubmissionErrors } from '../../../../../enterprise/insights/components' + +export interface CodeHostFormState { + submitting: boolean + submitErrors: SubmissionErrors +} From 80f0dcd1b59267227b6e104c26e2539652d2f0e6 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 16:15:29 -0300 Subject: [PATCH 13/22] Fix small visuals in core setup UI --- client/web/src/setup-wizard/SetupWizard.tsx | 2 +- .../RemoteRepositoriesStep.module.scss | 2 + .../RemoteRepositoriesStep.tsx | 17 ++++--- .../components/code-hosts/index.ts | 1 + .../CodeHostsNavigation.module.scss | 2 +- .../navigation/CodeHostsNavigation.tsx | 48 ++++++++++++------- .../remote-repositories-step/helpers.ts | 4 +- .../setup-steps/SetupSteps.module.scss | 15 ++++++ .../components/setup-steps/SetupSteps.tsx | 15 +++++- 9 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts diff --git a/client/web/src/setup-wizard/SetupWizard.tsx b/client/web/src/setup-wizard/SetupWizard.tsx index 2813d3b2e32a..0088396d9f53 100644 --- a/client/web/src/setup-wizard/SetupWizard.tsx +++ b/client/web/src/setup-wizard/SetupWizard.tsx @@ -20,7 +20,7 @@ const SETUP_STEPS: StepConfiguration[] = [ { id: '002', name: 'Add remote repositories', - path: '/setup/remote-repositories/*', + path: '/setup/remote-repositories', component: RemoteRepositoriesStep, }, { diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss index c66337d1a61a..44941bf70528 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.module.scss @@ -10,6 +10,8 @@ &-navigation { flex-basis: 35%; + flex-grow: 0; + flex-shrink: 0; padding: 1rem; } diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx index eaf7de389ba1..21d3fc48c1bd 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx @@ -8,6 +8,7 @@ import { Container, Text } from '@sourcegraph/wildcard' import { CustomNextButton } from '../setup-steps' import { CodeHostsPicker } from './components/code-host-picker' +import { CodeHostCreation } from './components/code-hosts' import { CodeHostsNavigation } from './components/navigation' import styles from './RemoteRepositoriesStep.module.scss' @@ -18,18 +19,20 @@ export const RemoteRepositoriesStep: FC = props => const { className, ...attributes } = props const location = useLocation() - const editConnectionRouteMatch = matchPath('/setup/remote-repositories/:codehostid/edit', location.pathname) - const newConnectionRouteMatch = matchPath('/setup/remote-repositories/:codehost/create', location.pathname) + const editConnectionRouteMatch = matchPath('/setup/remote-repositories/:codehostId/edit', location.pathname) + const newConnectionRouteMatch = matchPath('/setup/remote-repositories/:codeHostType/create', location.pathname) return (
    - Connect remote code hosts where your source code lives. + + Connect remote code hosts where your source code lives. +
    @@ -37,8 +40,8 @@ export const RemoteRepositoriesStep: FC = props => } /> - Hello creation UI} /> - Hello edit UI} /> + } /> + Hello edit UI} />
    diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts new file mode 100644 index 000000000000..ff31edea871b --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts @@ -0,0 +1 @@ +export { CodeHostCreation } from './CodeHostCreation' diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss index 5e1bff1baadf..04e075a931fc 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss @@ -22,7 +22,7 @@ .list { list-style: none; padding: 0; - margin: 0; + margin: -0.25rem 0 0 0; display: flex; flex-direction: column; gap: 0.2rem; diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx index 90e3fe04db18..23f9be82b68c 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, ReactElement } from 'react' import { gql, useQuery } from '@apollo/client' import { mdiInformationOutline, mdiDelete, mdiPlus } from '@mdi/js' @@ -7,7 +7,7 @@ import classNames from 'classnames' import { ErrorAlert, Icon, LoadingSpinner, Button, Tooltip, Link } from '@sourcegraph/wildcard' import { GetCodeHostsResult } from '../../../../../graphql-operations' -import { getCodeHostIcon } from '../../helpers' +import { getCodeHostIcon, getCodeHostKindFromURLParam, getCodeHostName } from '../../helpers' import styles from './CodeHostsNavigation.module.scss' @@ -28,12 +28,12 @@ const GET_CODE_HOSTS = gql` interface CodeHostsNavigationProps { activeConnectionId: string | undefined - addNewCodeHost: boolean + createConnectionType: string | undefined className?: string } export const CodeHostsNavigation: FC = props => { - const { activeConnectionId, addNewCodeHost, className } = props + const { activeConnectionId, createConnectionType, className } = props const { data, loading, error, refetch } = useQuery(GET_CODE_HOSTS, { fetchPolicy: 'cache-and-network', @@ -77,19 +77,7 @@ export const CodeHostsNavigation: FC = props => { return (
      - {addNewCodeHost && ( -
    • - - - - - Connect new code host - - New code host will appear in the list as soon as you connect it - - -
    • - )} + {createConnectionType && } {data.externalServices.nodes.map(codeHost => (
    • = props => {
    ) } + +interface CreateCodeHostConnectionCardProps { + codeHostType: string +} + +function CreateCodeHostConnectionCard(props: CreateCodeHostConnectionCardProps): ReactElement { + const { codeHostType } = props + const codeHostKind = getCodeHostKindFromURLParam(codeHostType) + + return ( +
  • + + + + + + Connect {' '} + {getCodeHostName(codeHostKind)} + + + New code host will appear in the list as soon as you connect it + + +
  • + ) +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts b/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts index 106edd9b1263..6fe6b07cddaf 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts +++ b/client/web/src/setup-wizard/components/remote-repositories-step/helpers.ts @@ -2,7 +2,7 @@ import { mdiBitbucket, mdiGithub, mdiGitlab, mdiAws } from '@mdi/js' import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations' -export const getCodeHostIcon = (codeHostType: ExternalServiceKind): string => { +export const getCodeHostIcon = (codeHostType: ExternalServiceKind | null): string => { switch (codeHostType) { case ExternalServiceKind.GITHUB: return mdiGithub @@ -19,7 +19,7 @@ export const getCodeHostIcon = (codeHostType: ExternalServiceKind): string => { } } -export const getCodeHostName = (codeHostType: ExternalServiceKind): string => { +export const getCodeHostName = (codeHostType: ExternalServiceKind | null): string => { switch (codeHostType) { case ExternalServiceKind.GITHUB: return 'GitHub' diff --git a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss index a5ed8bfd43ea..27645b9ffa9d 100644 --- a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss +++ b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss @@ -63,6 +63,21 @@ &-step-label { font-weight: normal; + + // Font normal and font bold have different text size, in order to + // avoid text layout jumps we reserve element width based on bold version + // of label. + &:before { + content: attr(data-label-text); + display: block; + font-weight: bold; + height: 0; + visibility: hidden; + } + + &--active { + font-weight: bold; + } } } diff --git a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx index da436ccb0633..7200e7039dfe 100644 --- a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx +++ b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.tsx @@ -126,7 +126,11 @@ export const SetupStepsRoot: FC = props => { {steps.map(({ path, component: Component }) => ( - } /> + } + /> ))} } /> @@ -162,7 +166,14 @@ export const SetupStepsHeader: FC = props => { > {index + 1} - {step.name} + + {step.name} +
    ))} From e9699b6280e5b1004a92373047c5975ab84f1768 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 17:53:36 -0300 Subject: [PATCH 14/22] Support json-like connection form for all standard external services --- .../externalServices/externalServices.tsx | 4 +- .../code-hosts/CodeHostCreation.tsx | 18 +- .../CodeHostConnection.module.scss} | 16 ++ .../code-hosts/common/CodeHostConnection.tsx | 158 ++++++++++++++++++ .../components/code-hosts/common/index.ts | 4 + .../code-hosts/github/GithubConnectView.tsx | 56 +++---- .../code-hosts/github/GithubJSONTab.tsx | 121 -------------- 7 files changed, 215 insertions(+), 162 deletions(-) rename client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/{github/GithubJSONTab.module.scss => common/CodeHostConnection.module.scss} (51%) create mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx delete mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx diff --git a/client/web/src/components/externalServices/externalServices.tsx b/client/web/src/components/externalServices/externalServices.tsx index 4ab9542d8fc9..75833dba54d1 100644 --- a/client/web/src/components/externalServices/externalServices.tsx +++ b/client/web/src/components/externalServices/externalServices.tsx @@ -15,7 +15,7 @@ import NpmIcon from 'mdi-react/NpmIcon' import { hasProperty } from '@sourcegraph/common' import { PerforceIcon, PhabricatorIcon } from '@sourcegraph/shared/src/components/icons' -import { Link, Code, Text } from '@sourcegraph/wildcard' +import { Link, Code, Text, setLinkComponent, RouterLink } from '@sourcegraph/wildcard' import awsCodeCommitSchemaJSON from '../../../../../schema/aws_codecommit.schema.json' import azureDevOpsSchemaJSON from '../../../../../schema/azuredevops.schema.json' @@ -45,6 +45,8 @@ import { EditorAction } from '../../settings/EditorActionsGroup' import { GerritIcon } from './GerritIcon' +setLinkComponent(RouterLink) + /** * Metadata associated with adding a given external service. */ diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx index 4359fab7d655..d407010acbf5 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx @@ -5,8 +5,10 @@ import { useParams } from 'react-router-dom-v5-compat' import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations' import { Alert, H4, Link, Button } from '@sourcegraph/wildcard' +import { defaultExternalServices } from '../../../../../components/externalServices/externalServices' import { getCodeHostKindFromURLParam } from '../../helpers' +import { CodeHostJSONForm } from './common' import { GithubConnectView } from './github/GithubConnectView' import { CodeHostFormState } from './types' @@ -41,8 +43,6 @@ export const CodeHostCreation: FC = () => { - - {/*After connecting, repositories will start sync in the background.*/} )} @@ -62,15 +62,17 @@ interface CodeHostCreationFormProps { const CodeHostCreationView: FC = props => { const { codeHostKind, children } = props + const handleSubmit = async () => { + console.log('SUBMIT') + } + if (codeHostKind === ExternalServiceKind.GITHUB) { - return {children} + return {children} } - // TODO: extend this view with all supported code host connections form UI return ( - - Oh no, we haven't implemented code host connection form for {codeHostKind} - code host type, Todo: add this type code host connection form! - + + {children} + ) } diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.module.scss similarity index 51% rename from client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.module.scss rename to client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.module.scss index e1d7a1d2ba8e..a576d0333d3f 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.module.scss @@ -1,3 +1,18 @@ +.form-view { + display: flex; + flex-direction: column; + gap: 0.5rem; + height: 100%; +} + +.footer { + margin: auto -1rem -1rem; + position: sticky; + // Calculated value since we already have sticky footer element for + // the core wizard UI. + bottom: 51px; +} + .configuration-group { &-label { font-weight: 500; @@ -12,6 +27,7 @@ margin-top: 0.25rem; font-size: 0.8rem; color: var(--body-color); + margin-left: -1.5rem; } &-editor { diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx new file mode 100644 index 000000000000..d1edbbf8bafb --- /dev/null +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx @@ -0,0 +1,158 @@ +import { FC, ReactElement, ReactNode, useState } from 'react' + +import { mdiChevronDown, mdiChevronUp } from '@mdi/js' + +import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService' +import { + Button, + Collapse, + CollapseHeader, + CollapsePanel, + Icon, + Input, + Text, + useLocalStorage, +} from '@sourcegraph/wildcard' + +import { AddExternalServiceOptions } from '../../../../../../components/externalServices/externalServices' +import { + FormGroup, + getDefaultInputProps, + SubmissionErrors, + useField, + useFieldAPI, + useForm, +} from '../../../../../../enterprise/insights/components' +import { DynamicallyImportedMonacoSettingsEditor } from '../../../../../../settings/DynamicallyImportedMonacoSettingsEditor' + +import styles from './CodeHostConnection.module.scss' + +export interface CodeHostConnectFormFields { + displayName: string + configuration: string +} + +export interface CodeHostJSONFormState { + submitting: boolean + submitErrors: SubmissionErrors +} + +interface CodeHostJSONFormProps { + externalServiceOptions: AddExternalServiceOptions + initialValues?: CodeHostConnectFormFields + children: (state: CodeHostJSONFormState) => ReactNode + onSubmit: (values: CodeHostConnectFormFields) => Promise +} + +export function CodeHostJSONForm(props: CodeHostJSONFormProps): ReactElement { + const { externalServiceOptions, initialValues, onSubmit, children } = props + + const [localValues, setLocalValues] = useLocalStorage( + `${externalServiceOptions.kind}-connect-form`, + { + displayName: externalServiceOptions.defaultDisplayName, + configuration: externalServiceOptions.defaultConfig, + } + ) + + const form = useForm({ + initialValues: initialValues ?? localValues, + onSubmit, + onChange: event => setLocalValues(event.values), + }) + + const displayName = useField({ + formApi: form.formAPI, + name: 'displayName', + required: true, + }) + + const configuration = useField({ + formApi: form.formAPI, + name: 'configuration', + }) + + return ( + // eslint-disable-next-line react/forbid-elements +
    + + +
    {children(form.formAPI)}
    + + ) +} + +interface CodeHostJSONFormContentProps { + displayNameField: useFieldAPI + configurationField: useFieldAPI + externalServiceOptions: AddExternalServiceOptions +} + +export function CodeHostJSONFormContent(props: CodeHostJSONFormContentProps): ReactElement { + const { displayNameField, configurationField, externalServiceOptions } = props + + // Fragment to avoid nesting since it's rendered within TabPanel fieldset + return ( + <> + + + } + labelClassName={styles.configurationGroupLabel} + > + + Use Ctrl+Space for completion, and hover over JSON properties for documentation. + + } + /> + + + ) +} + +interface CodeHostInstructionsProps { + instructions: ReactNode +} + +const CodeHostInstructions: FC = props => { + const { instructions } = props + const [isInstructionOpen, setInstructionOpen] = useState(false) + + return ( + + + See instructions how to fill out JSONC configuration{' '} + + + {instructions} + + ) +} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts index 38ee6fc22b17..c1a009699aea 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/index.ts @@ -1 +1,5 @@ export { RadioGroupSection } from './radio-group-section/RadioGroupSection' +export { CodeHostJSONForm, CodeHostJSONFormContent } from './CodeHostConnection' + +// Types export +export type { CodeHostJSONFormState, CodeHostConnectFormFields } from './CodeHostConnection' diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx index ecf5a082a337..c8d5604d2a9c 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx @@ -6,8 +6,8 @@ import { parse as parseJSONC } from 'jsonc-parser' import { modify } from '@sourcegraph/common' import { Tabs, Tab, TabList, TabPanel, TabPanels, Input, Checkbox, useLocalStorage } from '@sourcegraph/wildcard' +import { codeHostExternalServices } from '../../../../../../components/externalServices/externalServices' import { - SubmissionErrors, useField, useForm, Form, @@ -16,32 +16,20 @@ import { useControlledField, } from '../../../../../../enterprise/insights/components' import { AddExternalServiceInput, ExternalServiceKind } from '../../../../../../graphql-operations' -import { RadioGroupSection } from '../common' +import { CodeHostJSONFormContent, RadioGroupSection, CodeHostConnectFormFields, CodeHostJSONFormState } from '../common' import { GithubOrganizationsPicker, GithubRepositoriesPicker } from './GithubEntityPickers' -import { GithubJSONTabView } from './GithubJSONTab' import styles from './GithubConnectView.module.scss' -const DEFAULT_CONFIGURATION = ` +const DEFAULT_FORM_VALUES: CodeHostConnectFormFields = { + displayName: codeHostExternalServices.github.defaultDisplayName, + configuration: ` { "url": "https://github.com", "token": "" } -` -const DEFAULT_FORM_VALUES: GithubConnectFormFields = { - displayName: 'GitHub', - configuration: DEFAULT_CONFIGURATION.trim(), -} - -interface GithubConnectFormFields { - displayName: string - configuration: string -} - -interface CodeHostCreationFormState { - submitting: boolean - submitErrors: SubmissionErrors +`.trim(), } interface GithubConnectViewProps { @@ -50,8 +38,8 @@ interface GithubConnectViewProps { * form actions UI, like save, cancel, clear fields. Action layout is the same * for all variations of this form (create, edit UI) but content is different */ - children: (state: CodeHostCreationFormState) => ReactNode - onGithubConnect: (input: AddExternalServiceInput) => Promise + children: (state: CodeHostJSONFormState) => ReactNode + onSubmit: (input: AddExternalServiceInput) => Promise } /** @@ -60,13 +48,13 @@ interface GithubConnectViewProps { * storage */ export const GithubConnectView: FC = props => { - const { children, onGithubConnect } = props + const { children, onSubmit } = props const [initialValues, setInitialValues] = useLocalStorage('github-connection-form', DEFAULT_FORM_VALUES) const handleSubmit = useCallback( - async (values: GithubConnectFormFields): Promise => { + async (values: CodeHostConnectFormFields): Promise => { // Perform public API code host connection create action - await onGithubConnect({ + await onSubmit({ kind: ExternalServiceKind.GITHUB, displayName: values.displayName, config: values.configuration, @@ -75,7 +63,7 @@ export const GithubConnectView: FC = props => { // Reset initial values after successful connect action setInitialValues(DEFAULT_FORM_VALUES) }, - [setInitialValues, onGithubConnect] + [setInitialValues, onSubmit] ) return ( @@ -91,10 +79,10 @@ enum GithubConnectFormTab { } interface GithubConnectFormProps { - initialValues: GithubConnectFormFields - children: (state: CodeHostCreationFormState) => ReactNode - onChange: (values: GithubConnectFormFields) => void - onSubmit: (values: GithubConnectFormFields) => void + initialValues: CodeHostConnectFormFields + children: (state: CodeHostJSONFormState) => ReactNode + onChange: (values: CodeHostConnectFormFields) => void + onSubmit: (values: CodeHostConnectFormFields) => void } /** @@ -105,7 +93,7 @@ export const GithubConnectForm: FC = props => { const { initialValues, children, onChange, onSubmit } = props const [activeTab, setActiveTab] = useState(GithubConnectFormTab.Form) - const form = useForm({ + const form = useForm({ initialValues, onSubmit, onChange: event => onChange(event.values), @@ -152,17 +140,21 @@ export const GithubConnectForm: FC = props => { /> - + - {children({ submitting: false, submitErrors: undefined })} + {children(form.formAPI)} ) } interface GithubFormViewProps { - form: Form + form: Form displayNameField: useFieldAPI configurationField: useFieldAPI isTabActive: boolean diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx deleted file mode 100644 index f60aefe32d89..000000000000 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubJSONTab.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { FC, PropsWithChildren, ReactElement, useState } from 'react' - -import { mdiChevronDown, mdiChevronUp } from '@mdi/js' - -import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService' -import { Button, Code, Collapse, CollapseHeader, CollapsePanel, Icon, Input, Link, Text } from '@sourcegraph/wildcard' - -import { codeHostExternalServices } from '../../../../../../components/externalServices/externalServices' -import { FormGroup, getDefaultInputProps, useFieldAPI } from '../../../../../../enterprise/insights/components' -import { DynamicallyImportedMonacoSettingsEditor } from '../../../../../../settings/DynamicallyImportedMonacoSettingsEditor' - -import styles from './GithubJSONTab.module.scss' - -interface GithubSettingsViewProps { - displayNameField: useFieldAPI - configurationField: useFieldAPI -} - -export function GithubJSONTabView(props: GithubSettingsViewProps): ReactElement { - const { displayNameField, configurationField } = props - - // Fragment to avoid nesting since it's rendered within TabPanel fieldset - return ( - <> - - - } - labelClassName={styles.configurationGroupLabel} - > - - Use Ctrl+Space for completion, and hover over JSON properties for documentation. - - } - /> - - - ) -} - -const GithubInstructions: FC = () => { - const [isInstructionOpen, setInstructionOpen] = useState(false) - - return ( - - - See instructions how to fill out JSONC configuration{' '} - - - -
  • - Set url to the URL of GitHub Enterprise. -
  • -
  • - Create a GitHub access token ( - - instructions - - ) with repo scope. -
  • - Set the value of the token field as your access token, in the configuration - below. -
  • - -
  • - Specify which repositories Sourcegraph should index using one of the following fields: -
      -
    • - orgs: a list of GitHub organizations. -
    • -
    • - repositoryQuery: a list of GitHub search queries. -
      - For example, - "org:sourcegraph created:>2019-11-01" selects all repositories in - organization "sourcegraph" created after November 1, 2019. -
      - You may also use "affiliated" to select all repositories affiliated with the - access token. -
    • -
    • - repos: a list of individual repositories. -
    • -
    -
  • -
    -
    - ) -} - -const Field: FC> = props => {props.children} - -const Value: FC> = props => {props.children} From d6d9a5a481d623cf5acb484010643a1f430cb37c Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 13 Feb 2023 18:29:40 -0300 Subject: [PATCH 15/22] Support code host edit UI --- .../RemoteRepositoriesStep.tsx | 3 +- .../code-hosts/CodeHostCreation.module.scss | 11 +- .../code-hosts/CodeHostCreation.tsx | 5 +- .../components/code-hosts/CodeHostEdit.tsx | 131 +++++++++++++++++- .../code-hosts/common/CodeHostConnection.tsx | 1 + .../code-hosts/github/GithubConnectView.tsx | 12 +- .../components/code-hosts/index.ts | 1 + .../components/code-hosts/types.ts | 6 - .../navigation/CodeHostsNavigation.tsx | 3 +- 9 files changed, 147 insertions(+), 26 deletions(-) delete mode 100644 client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx index 21d3fc48c1bd..7ca84e866758 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/RemoteRepositoriesStep.tsx @@ -12,6 +12,7 @@ import { CodeHostCreation } from './components/code-hosts' import { CodeHostsNavigation } from './components/navigation' import styles from './RemoteRepositoriesStep.module.scss' +import { CodeHostEdit } from './components/code-hosts/CodeHostEdit' interface RemoteRepositoriesStepProps extends HTMLAttributes {} @@ -41,7 +42,7 @@ export const RemoteRepositoriesStep: FC = props => } /> } /> - Hello edit UI} /> + } />
    diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss index eadf6ac73df8..cf08e5e97b48 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.module.scss @@ -1,13 +1,8 @@ -.header { +.loading-state { display: flex; - gap: 0.5rem; align-items: center; - - &-icon { - width: 2rem; - height: 2rem; - flex-shrink: 0; - } + gap: 0.25rem; + color: var(--text-muted); } .footer { diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx index d407010acbf5..4df02cc04105 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostCreation.tsx @@ -8,9 +8,8 @@ import { Alert, H4, Link, Button } from '@sourcegraph/wildcard' import { defaultExternalServices } from '../../../../../components/externalServices/externalServices' import { getCodeHostKindFromURLParam } from '../../helpers' -import { CodeHostJSONForm } from './common' +import { CodeHostJSONForm, CodeHostJSONFormState } from './common' import { GithubConnectView } from './github/GithubConnectView' -import { CodeHostFormState } from './types' import styles from './CodeHostCreation.module.scss' @@ -51,7 +50,7 @@ export const CodeHostCreation: FC = () => { interface CodeHostCreationFormProps { codeHostKind: ExternalServiceKind - children: (state: CodeHostFormState) => ReactNode + children: (state: CodeHostJSONFormState) => ReactNode } /** diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx index e2ff29acfefb..76f8a9e64aa0 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/CodeHostEdit.tsx @@ -1,4 +1,32 @@ -import { FC } from 'react' +import { FC, ReactNode } from 'react' + +import { gql, useQuery } from '@apollo/client' +import { useParams } from 'react-router-dom-v5-compat' + +import { ExternalServiceKind } from '@sourcegraph/shared/src/graphql-operations' +import { Alert, Button, ErrorAlert, H4, Link, LoadingSpinner } from '@sourcegraph/wildcard' + +import { defaultExternalServices } from '../../../../../components/externalServices/externalServices' +import { GetExternalServiceByIdResult, GetExternalServiceByIdVariables } from '../../../../../graphql-operations' + +import { CodeHostConnectFormFields, CodeHostJSONForm, CodeHostJSONFormState } from './common' +import { GithubConnectView } from './github/GithubConnectView' + +import styles from './CodeHostCreation.module.scss' + +const GET_EXTERNAL_SERVICE_BY_ID = gql` + query GetExternalServiceById($id: ID!) { + node(id: $id) { + ... on ExternalService { + id + __typename + kind + displayName + config + } + } + } +` interface CodeHostEditProps {} @@ -7,9 +35,104 @@ interface CodeHostEditProps {} * Also performs edit, delete actions over opened code host connection */ export const CodeHostEdit: FC = () => { - return <>Edit UI + const { codehostId } = useParams() + + const { data, loading, error, refetch } = useQuery( + GET_EXTERNAL_SERVICE_BY_ID, + { + fetchPolicy: 'cache-and-network', + variables: { id: codehostId! }, + } + ) + + if (error && !loading) { + return ( +
    + + +
    + ) + } + + if (!data || (!data && loading)) { + return ( + + Fetching connected code host... + + ) + } + + if (data.node?.__typename !== 'ExternalService') { + return ( + +

    We either couldn't find code host

    + Try to connect new code host instead here +
    + ) + } + + return ( + + {state => ( +
    + + + + + +
    + )} +
    + ) +} + +interface CodeHostEditViewProps { + codeHostKind: ExternalServiceKind + displayName: string + configuration: string + children: (state: CodeHostJSONFormState) => ReactNode } -const CodeHostEditView: FC = () => { - return <>Specific code host edit UI +const CodeHostEditView: FC = props => { + const { codeHostKind, displayName, configuration, children } = props + + const handleSubmit = async (): Promise => { + console.log('UPDATE') + } + + const initialValues: CodeHostConnectFormFields = { + displayName, + configuration, + } + + if (codeHostKind === ExternalServiceKind.GITHUB) { + return ( + + {children} + + ) + } + + return ( + + {children} + + ) } diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx index d1edbbf8bafb..2193028326e3 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/common/CodeHostConnection.tsx @@ -118,6 +118,7 @@ export function CodeHostJSONFormContent(props: CodeHostJSONFormContentProps): Re height={400} readOnly={false} isLightTheme={true} + blockNavigationIfDirty={false} onChange={configurationField.input.onChange} telemetryService={NOOP_TELEMETRY_SERVICE} className={styles.configurationGroupEditor} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx index c8d5604d2a9c..7ee0f8ae183b 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.tsx @@ -33,6 +33,8 @@ const DEFAULT_FORM_VALUES: CodeHostConnectFormFields = { } interface GithubConnectViewProps { + initialValues?: CodeHostConnectFormFields + /** * Render props that is connected to form state, usually is used to render * form actions UI, like save, cancel, clear fields. Action layout is the same @@ -48,8 +50,8 @@ interface GithubConnectViewProps { * storage */ export const GithubConnectView: FC = props => { - const { children, onSubmit } = props - const [initialValues, setInitialValues] = useLocalStorage('github-connection-form', DEFAULT_FORM_VALUES) + const { initialValues, children, onSubmit } = props + const [localValues, setInitialValues] = useLocalStorage('github-connection-form', DEFAULT_FORM_VALUES) const handleSubmit = useCallback( async (values: CodeHostConnectFormFields): Promise => { @@ -67,7 +69,11 @@ export const GithubConnectView: FC = props => { ) return ( - + {children} ) diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts index ff31edea871b..6673e24f010d 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/index.ts @@ -1 +1,2 @@ export { CodeHostCreation } from './CodeHostCreation' +export { CodeHostEdit } from './CodeHostEdit' diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts deleted file mode 100644 index c613255049ef..000000000000 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SubmissionErrors } from '../../../../../enterprise/insights/components' - -export interface CodeHostFormState { - submitting: boolean - submitErrors: SubmissionErrors -} diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx index 23f9be82b68c..b4ef090df9ce 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx @@ -15,6 +15,7 @@ const GET_CODE_HOSTS = gql` query GetCodeHosts { externalServices { nodes { + __typename id kind repoCount @@ -41,7 +42,7 @@ export const CodeHostsNavigation: FC = props => { if (error && !loading) { return ( -
    +
    - From 1c38c1ff21b341bc587d2273a7a95bbcb6aaed95 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Thu, 16 Feb 2023 23:50:50 -0300 Subject: [PATCH 19/22] Fix styles --- .../setup-wizard/components/setup-steps/SetupSteps.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss index 27645b9ffa9d..ae2bdbffb3b4 100644 --- a/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss +++ b/client/web/src/setup-wizard/components/setup-steps/SetupSteps.module.scss @@ -67,7 +67,7 @@ // Font normal and font bold have different text size, in order to // avoid text layout jumps we reserve element width based on bold version // of label. - &:before { + &::before { content: attr(data-label-text); display: block; font-weight: bold; From 7e7853ef62297e3c6eadd822717c3c17d3a44d6b Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 20 Feb 2023 13:21:59 -0300 Subject: [PATCH 20/22] Make setup wizard lazy loaded --- client/web/src/Layout.tsx | 16 ++++++++++++++-- client/web/src/LegacyLayout.tsx | 16 ++++++++++++++-- client/web/src/setup-wizard/SetupWizard.tsx | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/client/web/src/Layout.tsx b/client/web/src/Layout.tsx index c25de0d30bc0..9ddfd2634598 100644 --- a/client/web/src/Layout.tsx +++ b/client/web/src/Layout.tsx @@ -15,6 +15,7 @@ import { SearchContextProps } from '@sourcegraph/shared/src/search' import { SettingsCascadeProps, SettingsSubjectCommonFields } from '@sourcegraph/shared/src/settings/settings' import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { ThemeProps } from '@sourcegraph/shared/src/theme' +import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent' import { parseQueryAndHash } from '@sourcegraph/shared/src/util/url' import { FeedbackPrompt, LoadingSpinner, Panel } from '@sourcegraph/wildcard' @@ -39,7 +40,6 @@ import type { NotebookProps } from './notebooks' import { EnterprisePageRoutes, PageRoutes } from './routes.constants' import { parseSearchURLQuery, SearchAggregationProps, SearchStreamingProps } from './search' import { NotepadContainer } from './search/Notepad' -import { SetupWizard } from './setup-wizard' import { useExperimentalFeatures } from './stores' import { ThemePreferenceProps, useTheme } from './theme' import { getExperimentalFeatures } from './util/get-experimental-features' @@ -47,6 +47,8 @@ import { parseBrowserRepoURL } from './util/url' import styles from './Layout.module.scss' +const LazySetupWizard = lazyComponent(() => import('./setup-wizard'), 'SetupWizard') + export interface LegacyLayoutProps extends SettingsCascadeProps, PlatformContextProps, @@ -152,7 +154,17 @@ export const Layout: React.FC = props => { } if (isSetupWizardPage) { - return + return ( + + +
    + } + > + + + ) } return ( diff --git a/client/web/src/LegacyLayout.tsx b/client/web/src/LegacyLayout.tsx index a6d93a0013de..70168e82a6c2 100644 --- a/client/web/src/LegacyLayout.tsx +++ b/client/web/src/LegacyLayout.tsx @@ -15,6 +15,7 @@ import { Settings } from '@sourcegraph/shared/src/schema/settings.schema' import { SearchContextProps } from '@sourcegraph/shared/src/search' import { SettingsCascadeProps, SettingsSubjectCommonFields } from '@sourcegraph/shared/src/settings/settings' import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' +import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent' import { parseQueryAndHash } from '@sourcegraph/shared/src/util/url' import { FeedbackPrompt, LoadingSpinner, Panel } from '@sourcegraph/wildcard' @@ -50,7 +51,6 @@ import type { LegacyLayoutRouteComponentProps, LayoutRouteProps } from './routes import { EnterprisePageRoutes, PageRoutes } from './routes.constants' import { parseSearchURLQuery, SearchAggregationProps, SearchStreamingProps } from './search' import { NotepadContainer } from './search/Notepad' -import { SetupWizard } from './setup-wizard' import type { SiteAdminAreaRoute } from './site-admin/SiteAdminArea' import type { SiteAdminSideBarGroups } from './site-admin/SiteAdminSidebar' import { useExperimentalFeatures } from './stores' @@ -64,6 +64,8 @@ import { parseBrowserRepoURL } from './util/url' import styles from './Layout.module.scss' +const LazySetupWizard = lazyComponent(() => import('./setup-wizard'), 'SetupWizard') + export interface LegacyLayoutProps extends SettingsCascadeProps, PlatformContextProps, @@ -196,7 +198,17 @@ export const LegacyLayout: React.FunctionComponent if (isSetupWizardPage) { - return + return ( + + +
    + } + > + + + ) } return ( diff --git a/client/web/src/setup-wizard/SetupWizard.tsx b/client/web/src/setup-wizard/SetupWizard.tsx index 0088396d9f53..2e1cabea0c53 100644 --- a/client/web/src/setup-wizard/SetupWizard.tsx +++ b/client/web/src/setup-wizard/SetupWizard.tsx @@ -31,7 +31,7 @@ const SETUP_STEPS: StepConfiguration[] = [ }, ] -export const SetupWizard: FC = props => { +export const SetupWizard: FC = () => { const [activeStepId, setStepId, status] = useTemporarySetting('setup.activeStepId') const handleStepChange = useCallback( From dd38dce3e77614abd5da076c57f50027d8442e27 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 20 Feb 2023 18:39:22 -0300 Subject: [PATCH 21/22] Update client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss Co-authored-by: Becca Steinbrecher --- .../components/code-hosts/github/GithubConnectView.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss index 84408d475677..a3dfd992da68 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/code-hosts/github/GithubConnectView.module.scss @@ -1,5 +1,5 @@ .form { - // Since this form is rendered withing container with padding + // Since this form is rendered within container with padding // but we want to render some element without this padding we stretch // the form and control padding on form level instead. margin: -1rem; From a550dcc3becfa3220b066ef03d6f8d372d97e764 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Mon, 20 Feb 2023 18:37:42 -0300 Subject: [PATCH 22/22] Add add more code host link --- .../CodeHostsNavigation.module.scss | 19 +++++++++++++++++++ .../navigation/CodeHostsNavigation.tsx | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss index f2daae1ff65a..4d8c987f3354 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.module.scss @@ -57,6 +57,18 @@ background-color: var(--secondary-2); } + &--with-more-link { + border-top: 1px solid var(--border-color); + position: sticky; + bottom: 51px; + background-color: var(--light-text); + margin-top: 0.5rem; + margin-bottom: -1rem; + margin-left: -1rem; + margin-right: -1rem; + padding: 0 0.5rem; + } + &--creation { display: flex; justify-content: flex-start; @@ -90,6 +102,13 @@ } } +.more-link { + padding: 0.5rem; + display: flex; + align-items: center; + gap: 0.25rem; +} + .item-button { display: flex; justify-content: flex-start; diff --git a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx index 3f6af9d7cf04..02ffa4c1dd79 100644 --- a/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx +++ b/client/web/src/setup-wizard/components/remote-repositories-step/components/navigation/CodeHostsNavigation.tsx @@ -115,6 +115,11 @@ export const CodeHostsNavigation: FC = props => { ))} +
  • + + Add more code hosts + +
  • ) }