From 077d4a5faf01d731b5a28ac9a7c4c29221a6e5a8 Mon Sep 17 00:00:00 2001 From: calvin Date: Mon, 24 Apr 2023 12:37:01 -0600 Subject: [PATCH] feat: [code-213]: clone credential dialog --- web/src/AppProps.ts | 1 + .../CloneButtonTooltip.module.scss | 6 +- .../CloneButtonTooltip/CloneButtonTooltip.tsx | 33 ++++++- .../CloneCredentialDialog.module.scss | 25 +++++ .../CloneCredentialDialog.module.scss.d.ts | 8 ++ .../CloneCredentialDialog.tsx | 97 +++++++++++++++++++ web/src/framework/strings/stringTypes.ts | 10 ++ web/src/i18n/strings.en.yaml | 10 ++ .../pages/Repository/Repository.module.scss | 30 ++++++ .../Repository/Repository.module.scss.d.ts | 4 + web/src/pages/Repository/Repository.tsx | 56 ++++++++++- web/src/utils/Utils.ts | 13 +++ 12 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss create mode 100644 web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss.d.ts create mode 100644 web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx diff --git a/web/src/AppProps.ts b/web/src/AppProps.ts index 8fa19becc..9a036e4a8 100644 --- a/web/src/AppProps.ts +++ b/web/src/AppProps.ts @@ -43,6 +43,7 @@ export interface AppProps { hooks: Partial<{ useGetToken: Unknown usePermissionTranslate: Unknown + useGenerateToken: Unknown }> currentUser: Required diff --git a/web/src/components/CloneButtonTooltip/CloneButtonTooltip.module.scss b/web/src/components/CloneButtonTooltip/CloneButtonTooltip.module.scss index 41598a042..64926feca 100644 --- a/web/src/components/CloneButtonTooltip/CloneButtonTooltip.module.scss +++ b/web/src/components/CloneButtonTooltip/CloneButtonTooltip.module.scss @@ -13,8 +13,12 @@ background-color: var(--grey-50) !important; border-radius: 4px; padding-left: var(--spacing-small) !important; - + max-width: 300px; .url { + width: 250px; + white-space: nowrap !important; + overflow: hidden; + text-overflow: ellipsis; font-size: var(--font-size-small) !important; } diff --git a/web/src/components/CloneButtonTooltip/CloneButtonTooltip.tsx b/web/src/components/CloneButtonTooltip/CloneButtonTooltip.tsx index 2adad0a9d..60cc2f81d 100644 --- a/web/src/components/CloneButtonTooltip/CloneButtonTooltip.tsx +++ b/web/src/components/CloneButtonTooltip/CloneButtonTooltip.tsx @@ -1,8 +1,17 @@ -import React from 'react' -import { Container, Layout, Text } from '@harness/uicore' +import React, { useState } from 'react' +import { + Button, + ButtonVariation, + Color, + Container, + FontVariation, + Layout, + Text, +} from '@harness/uicore' import { useStrings } from 'framework/strings' import { CopyButton } from 'components/CopyButton/CopyButton' import { CodeIcon } from 'utils/GitUtils' +import CloneCredentialDialog from 'components/CloneCredentialDialog/CloneCredentialDialog' import css from './CloneButtonTooltip.module.scss' interface CloneButtonTooltipProps { @@ -11,18 +20,36 @@ interface CloneButtonTooltipProps { export function CloneButtonTooltip({ httpsURL }: CloneButtonTooltipProps) { const { getString } = useStrings() + const [flag, setFlag] = useState(false) return ( - {getString('cloneHTTPS')} + {getString('cloneHTTPS')} + + {getString('generateCloneText')} + + {httpsURL} + + + ) } diff --git a/web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss new file mode 100644 index 000000000..633488546 --- /dev/null +++ b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss @@ -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; + } +} diff --git a/web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss.d.ts b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss.d.ts new file mode 100644 index 000000000..368426fb2 --- /dev/null +++ b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.module.scss.d.ts @@ -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 diff --git a/web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx new file mode 100644 index 000000000..87bc4257b --- /dev/null +++ b/web/src/components/CloneCredentialDialog/CloneCredentialDialog.tsx @@ -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 ( + { + setFlag(false) + }} + title={ + + {getString('getMyCloneTitle')} + + } + style={{ width: 490, maxHeight: '95vh', overflow: 'auto' }}> + + + {getString('userName')} + + + + {currentUser.display_name} + + + + + + {getString('passwordApi')} + + + + + {token} + + + + + + {getString('cloneText')} + + + ) +} + +export default CloneCredentialDialog diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index e690582c0..30e4f638a 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -42,6 +42,7 @@ export interface StringsMap { checks: string clone: string cloneHTTPS: string + cloneText: string closed: string comment: string commentDeleted: string @@ -122,7 +123,11 @@ export interface StringsMap { findATag: string findBranch: string findOrCreateBranch: string + firstTimeTitle: string general: string + generateCloneCred: string + generateCloneText: string + getMyCloneTitle: string gitIgnore: string history: string in: string @@ -133,6 +138,8 @@ export interface StringsMap { loading: string makeOptional: string makeRequired: string + manageApiToken: string + manageCredText: string merged: string missingPerms: string missingPermsContent: string @@ -172,6 +179,7 @@ export interface StringsMap { pageLoading: string pageNotFound: string password: string + passwordApi: string payloadUrl: string payloadUrlLabel: string pending: string @@ -251,6 +259,7 @@ export interface StringsMap { repoDeleted: string repoEmptyMarkdown: string repoEmptyMarkdownClone: string + repoEmptyMarkdownClonePush: string repoEmptyMarkdownExisting: string repoUpdate: string 'repos.activities': string @@ -292,6 +301,7 @@ export interface StringsMap { updateFile: string updateWebhook: string updated: string + userName: string 'validation.gitBranchNameInvalid': string 'validation.repoNamePatternIsNotValid': string viewAllBranches: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index 46c46ffaf..144fdd3c4 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -279,6 +279,7 @@ repoEmptyMarkdownClone: | ```sh git clone REPO_URL ``` +repoEmptyMarkdownClonePush: | Then push some content into it. ```sh @@ -362,3 +363,12 @@ repoUpdate: Repository Updated deleteRepoText: Are you sure you want to delete the repository '{REPONAME}'? deleteRepoTitle: Delete the repository resolve: Resolve +generateCloneCred: + Generate Clone Credential +generateCloneText: "Please generate clone credential if it’s 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 it’s your first time to clone the repository +manageCredText: You can also manage your git credential {URL} diff --git a/web/src/pages/Repository/Repository.module.scss b/web/src/pages/Repository/Repository.module.scss index 7d8f9e5fe..1ae6c1fe7 100644 --- a/web/src/pages/Repository/Repository.module.scss +++ b/web/src/pages/Repository/Repository.module.scss @@ -28,3 +28,33 @@ margin: var(--spacing-small) var(--spacing-xlarge) !important; 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; +} diff --git a/web/src/pages/Repository/Repository.module.scss.d.ts b/web/src/pages/Repository/Repository.module.scss.d.ts index c559ce986..c4e238149 100644 --- a/web/src/pages/Repository/Repository.module.scss.d.ts +++ b/web/src/pages/Repository/Repository.module.scss.d.ts @@ -6,5 +6,9 @@ declare const styles: { readonly divContainer: string readonly textContainer: string readonly bannerContainer: string + readonly layout: string + readonly url: string + readonly cloneCopyButton: string + readonly text: string } export default styles diff --git a/web/src/pages/Repository/Repository.tsx b/web/src/pages/Repository/Repository.tsx index fdba61136..bd972794b 100644 --- a/web/src/pages/Repository/Repository.tsx +++ b/web/src/pages/Repository/Repository.tsx @@ -4,6 +4,7 @@ import { Button, ButtonVariation, Container, + FlexExpander, FontVariation, Layout, PageBody, @@ -19,7 +20,7 @@ import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { useStrings } from 'framework/strings' import type { OpenapiGetContentOutput, TypesRepository } from 'services/code' import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer' -import type { GitInfoProps } from 'utils/GitUtils' +import { CodeIcon, GitInfoProps } from 'utils/GitUtils' import { useDisableCodeMainLinks } from 'hooks/useDisableCodeMainLinks' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { Images } from 'images' @@ -27,6 +28,8 @@ import { RepositoryContent } from './RepositoryContent/RepositoryContent' import { RepositoryHeader } from './RepositoryHeader/RepositoryHeader' import { ContentHeader } from './RepositoryContent/ContentHeader/ContentHeader' import css from './Repository.module.scss' +import { CopyButton } from 'components/CopyButton/CopyButton' +import CloneCredentialDialog from 'components/CloneCredentialDialog/CloneCredentialDialog' export default function Repository() { const { gitRef, resourcePath, repoMetadata, error, loading, refetch } = useGetRepositoryMetadata() @@ -128,6 +131,7 @@ const EmptyRepositoryInfo: React.FC + + {getString('firstTimeTitle')} + + {getString('cloneHTTPS')} + + + + + {repoMetadata.git_url} + + + + + + + + { + history.push(currentUserProfileURL) + }}> + here + + ) + }} + /> + + + ) } diff --git a/web/src/utils/Utils.ts b/web/src/utils/Utils.ts index a8c90000e..6db62b499 100644 --- a/web/src/utils/Utils.ts +++ b/web/src/utils/Utils.ts @@ -26,6 +26,19 @@ export function permissionProps(permResult: { disabled: boolean; tooltip: JSX.El 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 getErrorMessage = (error: Unknown): string =>