Merge branch 'code-213' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#38)

This commit is contained in:
Calvin Lee 2023-04-24 20:15:12 +00:00 committed by Harness
commit 47bca44bc0
12 changed files with 285 additions and 8 deletions

View File

@ -43,6 +43,7 @@ export interface AppProps {
hooks: Partial<{ hooks: Partial<{
useGetToken: Unknown useGetToken: Unknown
usePermissionTranslate: Unknown usePermissionTranslate: Unknown
useGenerateToken: Unknown
}> }>
currentUser: Required<TypesUser> currentUser: Required<TypesUser>

View File

@ -13,8 +13,12 @@
background-color: var(--grey-50) !important; background-color: var(--grey-50) !important;
border-radius: 4px; border-radius: 4px;
padding-left: var(--spacing-small) !important; padding-left: var(--spacing-small) !important;
max-width: 300px;
.url { .url {
width: 250px;
white-space: nowrap !important;
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--font-size-small) !important; font-size: var(--font-size-small) !important;
} }

View File

@ -1,8 +1,17 @@
import React from 'react' import React, { useState } from 'react'
import { Container, Layout, Text } from '@harness/uicore' import {
Button,
ButtonVariation,
Color,
Container,
FontVariation,
Layout,
Text,
} from '@harness/uicore'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { CopyButton } from 'components/CopyButton/CopyButton' import { CopyButton } from 'components/CopyButton/CopyButton'
import { CodeIcon } from 'utils/GitUtils' import { CodeIcon } from 'utils/GitUtils'
import CloneCredentialDialog from 'components/CloneCredentialDialog/CloneCredentialDialog'
import css from './CloneButtonTooltip.module.scss' import css from './CloneButtonTooltip.module.scss'
interface CloneButtonTooltipProps { interface CloneButtonTooltipProps {
@ -11,18 +20,36 @@ interface CloneButtonTooltipProps {
export function CloneButtonTooltip({ httpsURL }: CloneButtonTooltipProps) { export function CloneButtonTooltip({ httpsURL }: CloneButtonTooltipProps) {
const { getString } = useStrings() const { getString } = useStrings()
const [flag, setFlag] = useState(false)
return ( return (
<Container className={css.container} padding="xlarge"> <Container className={css.container} padding="xlarge">
<Layout.Vertical spacing="small"> <Layout.Vertical spacing="small">
<Text className={css.label}>{getString('cloneHTTPS')}</Text> <Text font={{ variation: FontVariation.H4 }}>{getString('cloneHTTPS')}</Text>
<Text
icon={'code-info'}
iconProps={{ size: 16 }}
color={Color.GREY_700}
font={{ variation: FontVariation.BODY2_SEMI, size: 'small' }}>
{getString('generateCloneText')}
</Text>
<Container> <Container>
<Layout.Horizontal className={css.layout}> <Layout.Horizontal className={css.layout}>
<Text className={css.url}>{httpsURL}</Text> <Text className={css.url}>{httpsURL}</Text>
<CopyButton content={httpsURL} id={css.cloneCopyButton} icon={CodeIcon.Copy} iconProps={{ size: 14 }} /> <CopyButton content={httpsURL} id={css.cloneCopyButton} icon={CodeIcon.Copy} iconProps={{ size: 14 }} />
</Layout.Horizontal> </Layout.Horizontal>
</Container> </Container>
<Button
onClick={() => {
setFlag(true)
}}
variation={ButtonVariation.SECONDARY}>
{getString('generateCloneCred')}
</Button>
</Layout.Vertical> </Layout.Vertical>
<CloneCredentialDialog flag={flag} setFlag={setFlag}/>
</Container> </Container>
) )
} }

View File

@ -0,0 +1,25 @@
.layout {
height: 33px;
display: inline-flex;
justify-content: center;
align-items: center;
border: 1px solid var(--grey-200);
background-color: var(--grey-50) !important;
border-radius: 4px;
padding-left: var(--spacing-small) !important;
max-width: 100%;
.url {
// width: 80%;
white-space: nowrap !important;
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--font-size-small) !important;
}
button#cloneCopyButton {
--button-height: 24px !important;
border-radius: 0 !important;
border-left: 1px solid var(--grey-200) !important;
margin-left: var(--spacing-small) !important;
}
}

View File

@ -0,0 +1,8 @@
/* eslint-disable */
// this is an auto-generated file
declare const styles: {
readonly layout: string
readonly url: string
readonly cloneCopyButton: string
}
export default styles

View File

@ -0,0 +1,97 @@
import React, { useEffect, useState } from 'react'
import {
Button,
ButtonVariation,
Container,
Dialog,
FlexExpander,
FontVariation,
Layout,
Text,
useToaster
} from '@harness/uicore'
import { useStrings } from 'framework/strings'
import { CopyButton } from 'components/CopyButton/CopyButton'
import { CodeIcon } from 'utils/GitUtils'
import { useAppContext } from 'AppContext'
import { generateAlphaNumericHash } from 'utils/Utils'
import css from './CloneCredentialDialog.module.scss'
import { useHistory } from 'react-router-dom'
interface CloneCredentialDialogProps {
setFlag: (val: boolean) => void
flag: boolean
}
const CloneCredentialDialog = (props: CloneCredentialDialogProps) => {
const { setFlag, flag } = props
const history = useHistory()
const { getString } = useStrings()
const { hooks, currentUser,currentUserProfileURL } = useAppContext()
const [token, setToken] = useState('')
const { showError } = useToaster()
const hash = generateAlphaNumericHash(6)
const tokenData = hooks?.useGenerateToken(hash, currentUser.uid, flag)
useEffect(() => {
if (tokenData) {
if (tokenData && tokenData?.status !== 400) {
setToken(tokenData?.data)
} else if (tokenData?.status === 400 && flag) {
setToken('N/A')
showError(tokenData?.data?.message || tokenData?.message)
}
}
}, [flag, tokenData])
return (
<Dialog
isOpen={flag}
enforceFocus={false}
onClose={() => {
setFlag(false)
}}
title={
<Text font={{ variation: FontVariation.H3 }} icon={'success-tick'} iconProps={{ size: 26 }}>
{getString('getMyCloneTitle')}
</Text>
}
style={{ width: 490, maxHeight: '95vh', overflow: 'auto' }}>
<Layout.Vertical width={380}>
<Text padding={{ bottom: 'small' }} font={{ variation: FontVariation.FORM_LABEL, size: 'small' }}>
{getString('userName')}
</Text>
<Container padding={{ bottom: 'medium' }}>
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{currentUser.display_name}</Text>
<FlexExpander />
<CopyButton
content={currentUser.display_name}
id={css.cloneCopyButton}
icon={CodeIcon.Copy}
iconProps={{ size: 14 }}
/>
</Layout.Horizontal>
</Container>
<Text padding={{ bottom: 'small' }} font={{ variation: FontVariation.FORM_LABEL, size: 'small' }}>
{getString('passwordApi')}
</Text>
<Container padding={{ bottom: 'medium' }}>
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{token}</Text>
<FlexExpander />
<CopyButton content={token} id={css.cloneCopyButton} icon={CodeIcon.Copy} iconProps={{ size: 14 }} />
</Layout.Horizontal>
</Container>
<Text padding={{ bottom: 'medium' }} font={{ variation: FontVariation.BODY2_SEMI, size: 'small' }}>
{getString('cloneText')}
</Text>
<Button onClick={()=>{
history.push(currentUserProfileURL)
}} variation={ButtonVariation.TERTIARY} text={getString('manageApiToken')} />
</Layout.Vertical>
</Dialog>
)
}
export default CloneCredentialDialog

View File

@ -42,6 +42,7 @@ export interface StringsMap {
checks: string checks: string
clone: string clone: string
cloneHTTPS: string cloneHTTPS: string
cloneText: string
closed: string closed: string
comment: string comment: string
commentDeleted: string commentDeleted: string
@ -122,7 +123,11 @@ export interface StringsMap {
findATag: string findATag: string
findBranch: string findBranch: string
findOrCreateBranch: string findOrCreateBranch: string
firstTimeTitle: string
general: string general: string
generateCloneCred: string
generateCloneText: string
getMyCloneTitle: string
gitIgnore: string gitIgnore: string
history: string history: string
in: string in: string
@ -133,6 +138,8 @@ export interface StringsMap {
loading: string loading: string
makeOptional: string makeOptional: string
makeRequired: string makeRequired: string
manageApiToken: string
manageCredText: string
merged: string merged: string
missingPerms: string missingPerms: string
missingPermsContent: string missingPermsContent: string
@ -172,6 +179,7 @@ export interface StringsMap {
pageLoading: string pageLoading: string
pageNotFound: string pageNotFound: string
password: string password: string
passwordApi: string
payloadUrl: string payloadUrl: string
payloadUrlLabel: string payloadUrlLabel: string
pending: string pending: string
@ -251,6 +259,7 @@ export interface StringsMap {
repoDeleted: string repoDeleted: string
repoEmptyMarkdown: string repoEmptyMarkdown: string
repoEmptyMarkdownClone: string repoEmptyMarkdownClone: string
repoEmptyMarkdownClonePush: string
repoEmptyMarkdownExisting: string repoEmptyMarkdownExisting: string
repoUpdate: string repoUpdate: string
'repos.activities': string 'repos.activities': string
@ -292,6 +301,7 @@ export interface StringsMap {
updateFile: string updateFile: string
updateWebhook: string updateWebhook: string
updated: string updated: string
userName: string
'validation.gitBranchNameInvalid': string 'validation.gitBranchNameInvalid': string
'validation.repoNamePatternIsNotValid': string 'validation.repoNamePatternIsNotValid': string
viewAllBranches: string viewAllBranches: string

View File

@ -279,6 +279,7 @@ repoEmptyMarkdownClone: |
```sh ```sh
git clone REPO_URL git clone REPO_URL
``` ```
repoEmptyMarkdownClonePush: |
Then push some content into it. Then push some content into it.
```sh ```sh
@ -362,3 +363,12 @@ repoUpdate: Repository Updated
deleteRepoText: Are you sure you want to delete the repository '{REPONAME}'? deleteRepoText: Are you sure you want to delete the repository '{REPONAME}'?
deleteRepoTitle: Delete the repository deleteRepoTitle: Delete the repository
resolve: Resolve resolve: Resolve
generateCloneCred: + Generate Clone Credential
generateCloneText: "Please generate clone credential if its your first time"
getMyCloneTitle: Get My Clone Credential
cloneText: Your clone credentials have been generated. Please make sure to copy and store your password somewhere safe, you won't be able to see it again.
manageApiToken: Manage Api Token
userName: User Name
passwordApi: Password (API Token)
firstTimeTitle: Please generate Git Credentials if its your first time to clone the repository
manageCredText: You can also manage your git credential {URL}

View File

@ -28,3 +28,33 @@
margin: var(--spacing-small) var(--spacing-xlarge) !important; margin: var(--spacing-small) var(--spacing-xlarge) !important;
border-radius: 5px; border-radius: 5px;
} }
.layout {
height: 33px;
display: inline-flex;
justify-content: center;
align-items: center;
border: 1px solid var(--grey-200);
background-color: var(--grey-50) !important;
border-radius: 4px;
padding-left: var(--spacing-small) !important;
max-width: 100%;
.url {
// width: 80%;
white-space: nowrap !important;
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--font-size-small) !important;
}
button#cloneCopyButton {
--button-height: 24px !important;
border-radius: 0 !important;
border-left: 1px solid var(--grey-200) !important;
margin-left: var(--spacing-small) !important;
}
}
.text {
font-size: 16px !important;
}

View File

@ -6,5 +6,9 @@ declare const styles: {
readonly divContainer: string readonly divContainer: string
readonly textContainer: string readonly textContainer: string
readonly bannerContainer: string readonly bannerContainer: string
readonly layout: string
readonly url: string
readonly cloneCopyButton: string
readonly text: string
} }
export default styles export default styles

View File

@ -4,6 +4,7 @@ import {
Button, Button,
ButtonVariation, ButtonVariation,
Container, Container,
FlexExpander,
FontVariation, FontVariation,
Layout, Layout,
PageBody, PageBody,
@ -19,7 +20,7 @@ import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import type { OpenapiGetContentOutput, TypesRepository } from 'services/code' import type { OpenapiGetContentOutput, TypesRepository } from 'services/code'
import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer' import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
import type { GitInfoProps } from 'utils/GitUtils' import { CodeIcon, GitInfoProps } from 'utils/GitUtils'
import { useDisableCodeMainLinks } from 'hooks/useDisableCodeMainLinks' import { useDisableCodeMainLinks } from 'hooks/useDisableCodeMainLinks'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import { Images } from 'images' import { Images } from 'images'
@ -27,6 +28,8 @@ import { RepositoryContent } from './RepositoryContent/RepositoryContent'
import { RepositoryHeader } from './RepositoryHeader/RepositoryHeader' import { RepositoryHeader } from './RepositoryHeader/RepositoryHeader'
import { ContentHeader } from './RepositoryContent/ContentHeader/ContentHeader' import { ContentHeader } from './RepositoryContent/ContentHeader/ContentHeader'
import css from './Repository.module.scss' import css from './Repository.module.scss'
import { CopyButton } from 'components/CopyButton/CopyButton'
import CloneCredentialDialog from 'components/CloneCredentialDialog/CloneCredentialDialog'
export default function Repository() { export default function Repository() {
const { gitRef, resourcePath, repoMetadata, error, loading, refetch } = useGetRepositoryMetadata() const { gitRef, resourcePath, repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
@ -128,6 +131,7 @@ const EmptyRepositoryInfo: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourc
const { standalone } = useAppContext() const { standalone } = useAppContext()
const { hooks } = useAppContext() const { hooks } = useAppContext()
const space = useGetSpaceParam() const space = useGetSpaceParam()
const [flag, setFlag] = useState(false)
const permPushResult = hooks?.usePermissionTranslate?.( const permPushResult = hooks?.usePermissionTranslate?.(
{ {
@ -173,15 +177,58 @@ const EmptyRepositoryInfo: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourc
</Container> </Container>
</Layout.Horizontal> </Layout.Horizontal>
</Container> </Container>
<Container
margin={{ bottom: 'xxlarge' }}
padding={{ top: 'xxlarge', bottom: 'xxlarge', left: 'xxlarge', right: 'xxlarge' }}
className={css.divContainer}>
<Text font={{ variation: FontVariation.H4 }}>{getString('firstTimeTitle')}</Text>
<Text className={css.text} padding={{ top: 'medium', bottom: 'small' }} font={{ variation: FontVariation.BODY }}>
{getString('cloneHTTPS')}
</Text>
<Layout.Horizontal>
<Container padding={{ bottom: 'medium' }} width={400} margin={{ right: 'small' }}>
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{repoMetadata.git_url}</Text>
<FlexExpander />
<CopyButton
content={repoMetadata?.git_url as string}
id={css.cloneCopyButton}
icon={CodeIcon.Copy}
iconProps={{ size: 14 }}
/>
</Layout.Horizontal>
</Container>
<Button
onClick={() => {
setFlag(true)
}}
variation={ButtonVariation.SECONDARY}>
{getString('generateCloneCred')}
</Button>
</Layout.Horizontal>
<Text font={{ variation: FontVariation.BODY, size: 'small' }}>
<StringSubstitute
str={getString('manageCredText')}
vars={{
URL: (
<a
onClick={() => {
history.push(currentUserProfileURL)
}}>
here
</a>
)
}}
/>
</Text>
</Container>
<Container <Container
margin={{ bottom: 'xxlarge' }} margin={{ bottom: 'xxlarge' }}
padding={{ top: 'xxlarge', bottom: 'xxlarge', left: 'xxlarge', right: 'xxlarge' }} padding={{ top: 'xxlarge', bottom: 'xxlarge', left: 'xxlarge', right: 'xxlarge' }}
className={css.divContainer}> className={css.divContainer}>
<MarkdownViewer <MarkdownViewer
getString={getString} getString={getString}
source={getString('repoEmptyMarkdownClone') source={getString('repoEmptyMarkdownClonePush').replace(/REPO_NAME/g, repoMetadata.uid || '')}
.replace(/REPO_URL/g, repoMetadata.git_url || '')
.replace(/REPO_NAME/g, repoMetadata.uid || '')}
/> />
</Container> </Container>
<Container <Container
@ -196,6 +243,7 @@ const EmptyRepositoryInfo: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourc
.replace(/CREATE_API_TOKEN_URL/g, currentUserProfileURL || '')} .replace(/CREATE_API_TOKEN_URL/g, currentUserProfileURL || '')}
/> />
</Container> </Container>
<CloneCredentialDialog flag={flag} setFlag={setFlag} />
</Container> </Container>
) )
} }

View File

@ -26,6 +26,19 @@ export function permissionProps(permResult: { disabled: boolean; tooltip: JSX.El
return !perm ? permResult : undefined return !perm ? permResult : undefined
} }
export function generateAlphaNumericHash(length: number) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
export const dayAgoInMS = 86400000 export const dayAgoInMS = 86400000
export const getErrorMessage = (error: Unknown): string => export const getErrorMessage = (error: Unknown): string =>