mirror of
https://github.com/harness/drone.git
synced 2025-05-19 02:20:03 +08:00
Add routingId to support Harness gateway + Prototype for Commit modal (#68)
* Implement file path edit input * Implement file path edit input - cont * Prototype for Commit modal * Add routingId to support Harness gateway
This commit is contained in:
parent
c8cee4d250
commit
31a58e3172
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react'
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react'
|
||||
import { RestfulProvider } from 'restful-react'
|
||||
import { TooltipContextProvider } from '@harness/uicore'
|
||||
import { ModalProvider } from '@harness/use-modal'
|
||||
@ -34,6 +34,11 @@ const App: React.FC<AppProps> = React.memo(function App({
|
||||
(): Partial<RequestInit> => buildResfulReactRequestOptions(hooks?.useGetToken?.() || token),
|
||||
[token, hooks]
|
||||
)
|
||||
const queryParams = useMemo(
|
||||
() => (!standalone ? { routingId: space.split('/').shift() } : {}),
|
||||
|
||||
[space, standalone]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
languageLoader(lang).then(setStrings)
|
||||
@ -46,7 +51,7 @@ const App: React.FC<AppProps> = React.memo(function App({
|
||||
<RestfulProvider
|
||||
base={standalone ? '/' : getConfigNew('scm')}
|
||||
requestOptions={getRequestOptions}
|
||||
queryParams={{}}
|
||||
queryParams={queryParams}
|
||||
queryParamStringifyOptions={{ skipNulls: true }}
|
||||
onResponse={response => {
|
||||
if (!response.ok && response.status === 401) {
|
||||
|
@ -0,0 +1,58 @@
|
||||
.main {
|
||||
padding-left: var(--spacing-xxlarge) !important;
|
||||
height: 100%;
|
||||
|
||||
.extendedDescription textarea {
|
||||
height: 100px !important;
|
||||
}
|
||||
|
||||
.radioGroup {
|
||||
> div {
|
||||
margin-bottom: var(--spacing-small) !important;
|
||||
}
|
||||
|
||||
&.directly {
|
||||
label[class*='RadioButton']:first-of-type {
|
||||
background: rgba(220, 238, 255, 0.5);
|
||||
mix-blend-mode: normal;
|
||||
border: 1px solid var(--primary-6);
|
||||
}
|
||||
}
|
||||
|
||||
&.newBranch {
|
||||
label[class*='RadioButton']:last-of-type {
|
||||
background: rgba(220, 238, 255, 0.5);
|
||||
mix-blend-mode: normal;
|
||||
border: 1px solid var(--primary-6);
|
||||
}
|
||||
}
|
||||
|
||||
label[class*='RadioButton'] {
|
||||
border-radius: var(--spacing-2);
|
||||
border: 1px solid var(--bp3-intent-color, var(--grey-200));
|
||||
padding: var(--spacing-small) !important;
|
||||
|
||||
&:first-of-type {
|
||||
margin-bottom: var(--spacing-small);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: var(--form-input-font-size);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.newBranchContainer {
|
||||
align-items: center;
|
||||
padding-left: var(--spacing-medium);
|
||||
|
||||
> div {
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
input {
|
||||
width: 588px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
web/src/components/CommitModalButton/CommitModalButton.module.scss.d.ts
vendored
Normal file
11
web/src/components/CommitModalButton/CommitModalButton.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/* eslint-disable */
|
||||
// this is an auto-generated file
|
||||
declare const styles: {
|
||||
readonly main: string
|
||||
readonly extendedDescription: string
|
||||
readonly radioGroup: string
|
||||
readonly directly: string
|
||||
readonly newBranch: string
|
||||
readonly newBranchContainer: string
|
||||
}
|
||||
export default styles
|
206
web/src/components/CommitModalButton/CommitModalButton.tsx
Normal file
206
web/src/components/CommitModalButton/CommitModalButton.tsx
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2021 Harness Inc. All rights reserved.
|
||||
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
|
||||
* that can be found in the licenses directory at the root of this repository, also available at
|
||||
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { Dialog, Intent } from '@blueprintjs/core'
|
||||
import * as yup from 'yup'
|
||||
import {
|
||||
Button,
|
||||
ButtonProps,
|
||||
Container,
|
||||
Layout,
|
||||
FlexExpander,
|
||||
Icon,
|
||||
Formik,
|
||||
FormikForm,
|
||||
Heading,
|
||||
useToaster,
|
||||
FormInput
|
||||
} from '@harness/uicore'
|
||||
import cx from 'classnames'
|
||||
import { FontVariation } from '@harness/design-system'
|
||||
import { useMutate } from 'restful-react'
|
||||
import { get } from 'lodash-es'
|
||||
import { useModalHook } from '@harness/use-modal'
|
||||
import { String, useStrings } from 'framework/strings'
|
||||
import { DEFAULT_BRANCH_NAME, getErrorMessage, Unknown } from 'utils/Utils'
|
||||
import type { TypesRepository, OpenapiCreateRepositoryRequest } from 'services/scm'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import css from './CommitModalButton.module.scss'
|
||||
|
||||
enum CommitToGitRefOption {
|
||||
DIRECTLY = 'directly',
|
||||
NEW_BRANCH = 'new-branch'
|
||||
}
|
||||
|
||||
interface RepoFormData {
|
||||
message: string
|
||||
extendedDescription: string
|
||||
newBranch: string
|
||||
branch: string
|
||||
commitToGitRefOption: CommitToGitRefOption
|
||||
}
|
||||
|
||||
const formInitialValues: RepoFormData = {
|
||||
message: '',
|
||||
extendedDescription: '',
|
||||
newBranch: '',
|
||||
branch: '',
|
||||
commitToGitRefOption: CommitToGitRefOption.DIRECTLY
|
||||
}
|
||||
|
||||
export interface CommitModalButtonProps extends Omit<ButtonProps, 'onClick' | 'onSubmit'> {
|
||||
gitRef: string
|
||||
commitMessagePlaceHolder: string
|
||||
resourcePath: string
|
||||
onSubmit: (data: TypesRepository) => void
|
||||
}
|
||||
|
||||
export const CommitModalButton: React.FC<CommitModalButtonProps> = ({
|
||||
gitRef,
|
||||
resourcePath,
|
||||
commitMessagePlaceHolder,
|
||||
onSubmit,
|
||||
...props
|
||||
}) => {
|
||||
const ModalComponent: React.FC = () => {
|
||||
const { standalone } = useAppContext()
|
||||
const { getString } = useStrings()
|
||||
const [targetBranchOption, setTargetBranchOption] = useState(CommitToGitRefOption.DIRECTLY)
|
||||
const [branchName, setBranchName] = useState(gitRef)
|
||||
const { showError } = useToaster()
|
||||
const { mutate: createRepo, loading: submitLoading } = useMutate<TypesRepository>({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos?spacePath=${gitRef}`
|
||||
})
|
||||
const loading = submitLoading
|
||||
|
||||
console.log('Commit to', { targetBranchOption, branchName })
|
||||
|
||||
const handleSubmit = (formData?: Unknown): void => {
|
||||
try {
|
||||
createRepo({
|
||||
message: branchName || get(formData, 'message', DEFAULT_BRANCH_NAME),
|
||||
extendedDescription: get(formData, 'extendedDescription', '').trim(),
|
||||
commitToGitRefOption: get(formData, 'commitToGitRefOption') === CommitToGitRefOption.DIRECTLY,
|
||||
uid: get(formData, 'name', '').trim(),
|
||||
parentId: standalone ? gitRef : 0
|
||||
} as OpenapiCreateRepositoryRequest)
|
||||
.then(response => {
|
||||
hideModal()
|
||||
onSubmit(response)
|
||||
})
|
||||
.catch(_error => {
|
||||
showError(getErrorMessage(_error), 0, 'failedToCreateRepo')
|
||||
})
|
||||
} catch (exception) {
|
||||
showError(getErrorMessage(exception), 0, 'failedToCreateRepo')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
enforceFocus={false}
|
||||
onClose={hideModal}
|
||||
title={''}
|
||||
style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}>
|
||||
<Layout.Vertical className={cx(css.main)}>
|
||||
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
|
||||
{getString('commitChanges')}
|
||||
</Heading>
|
||||
|
||||
<Container margin={{ right: 'xxlarge' }}>
|
||||
<Formik
|
||||
initialValues={formInitialValues}
|
||||
formName="commitChanges"
|
||||
enableReinitialize={true}
|
||||
validationSchema={yup.object().shape({})}
|
||||
validateOnChange
|
||||
validateOnBlur
|
||||
onSubmit={handleSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text
|
||||
name="message"
|
||||
label={getString('commitMessage')}
|
||||
placeholder={commitMessagePlaceHolder}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'commitMessage'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
/>
|
||||
<FormInput.TextArea
|
||||
className={css.extendedDescription}
|
||||
name="extendedDescription"
|
||||
placeholder={getString('optionalExtendedDescription')}
|
||||
/>
|
||||
<Container
|
||||
className={cx(
|
||||
css.radioGroup,
|
||||
targetBranchOption === CommitToGitRefOption.DIRECTLY ? css.directly : css.newBranch
|
||||
)}>
|
||||
<FormInput.RadioGroup
|
||||
name="commitToGitRefOption"
|
||||
label=""
|
||||
onChange={e => {
|
||||
setTargetBranchOption(get(e.target, 'defaultValue'))
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
label: <String stringID="commitDirectlyTo" vars={{ gitRef }} useRichText />,
|
||||
value: CommitToGitRefOption.DIRECTLY
|
||||
},
|
||||
{
|
||||
label: <String stringID="commitToNewBranch" useRichText />,
|
||||
value: CommitToGitRefOption.NEW_BRANCH
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{targetBranchOption === CommitToGitRefOption.NEW_BRANCH && (
|
||||
<Container>
|
||||
<Layout.Horizontal spacing="medium" className={css.newBranchContainer}>
|
||||
<Icon name="git-branch" />
|
||||
<FormInput.Text
|
||||
name="newBranch"
|
||||
placeholder={getString('enterNewBranchName')}
|
||||
tooltipProps={{
|
||||
dataTooltipId: 'enterNewBranchName'
|
||||
}}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
<Layout.Horizontal
|
||||
spacing="small"
|
||||
padding={{ right: 'xxlarge', top: 'xxlarge', bottom: 'large' }}
|
||||
style={{ alignItems: 'center' }}>
|
||||
<Button type="submit" text={getString('commitChanges')} intent={Intent.PRIMARY} disabled={loading} />
|
||||
<Button text={getString('cancel')} minimal onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
const [openModal, hideModal] = useModalHook(ModalComponent, [
|
||||
onSubmit,
|
||||
gitRef,
|
||||
resourcePath,
|
||||
commitMessagePlaceHolder
|
||||
])
|
||||
|
||||
return <Button onClick={openModal} {...props} />
|
||||
}
|
@ -96,16 +96,12 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
|
||||
data: gitignores,
|
||||
loading: gitIgnoreLoading,
|
||||
error: gitIgnoreError
|
||||
} = useGet({
|
||||
path: '/api/v1/resources/gitignore'
|
||||
})
|
||||
} = useGet({ path: '/api/v1/resources/gitignore' })
|
||||
const {
|
||||
data: licences,
|
||||
loading: licenseLoading,
|
||||
error: licenseError
|
||||
} = useGet({
|
||||
path: '/api/v1/resources/license'
|
||||
})
|
||||
} = useGet({ path: '/api/v1/resources/license' })
|
||||
const loading = submitLoading || gitIgnoreLoading || licenseLoading
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -11,9 +11,14 @@ export interface StringsMap {
|
||||
branch: string
|
||||
branches: string
|
||||
cancel: string
|
||||
cancelChanges: string
|
||||
clone: string
|
||||
cloneHTTPS: string
|
||||
commit: string
|
||||
commitChanges: string
|
||||
commitDirectlyTo: string
|
||||
commitMessage: string
|
||||
commitToNewBranch: string
|
||||
commits: string
|
||||
commitsOn: string
|
||||
content: string
|
||||
@ -31,6 +36,7 @@ export interface StringsMap {
|
||||
editFile: string
|
||||
email: string
|
||||
enterDescription: string
|
||||
enterNewBranchName: string
|
||||
enterRepoName: string
|
||||
existingAccount: string
|
||||
failedToCreateRepo: string
|
||||
@ -47,6 +53,8 @@ export interface StringsMap {
|
||||
noAccount: string
|
||||
none: string
|
||||
ok: string
|
||||
optional: string
|
||||
optionalExtendedDescription: string
|
||||
pageNotFound: string
|
||||
password: string
|
||||
private: string
|
||||
|
@ -15,9 +15,11 @@ export function useGetResourceContent({
|
||||
includeCommit = false
|
||||
}: UseGetResourceContentParams) {
|
||||
const { data, error, loading, refetch, response } = useGet<OpenapiGetContentOutput>({
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/content${
|
||||
resourcePath ? '/' + resourcePath : ''
|
||||
}?include_commit=${String(includeCommit)}${gitRef ? `&git_ref=${gitRef}` : ''}`
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/content${resourcePath ? '/' + resourcePath : ''}`,
|
||||
queryParams: {
|
||||
include_commit: String(includeCommit),
|
||||
git_ref: gitRef
|
||||
}
|
||||
})
|
||||
|
||||
return { data, error, loading, refetch, response }
|
||||
|
@ -15,6 +15,8 @@ repositories: Repositories
|
||||
files: Files
|
||||
commit: Commit
|
||||
commits: Commits
|
||||
commitChanges: Commit Changes
|
||||
cancelChanges: Cancel Changes
|
||||
pullRequests: Pull Requests
|
||||
settings: Settings
|
||||
newFile: New File
|
||||
@ -69,3 +71,9 @@ createRepoModal:
|
||||
validation:
|
||||
repoNamePatternIsNotValid: "Name can only contain alphanumerics, '-', '_', '.', and '$'"
|
||||
gitBranchNameInvalid: Branch name is invalid.
|
||||
commitMessage: Commit message
|
||||
optionalExtendedDescription: Optional extended description
|
||||
optional: Optional
|
||||
commitDirectlyTo: 'Commit directly to the <strong style="color: var(--primary-7)">{{gitRef}}</strong> branch'
|
||||
commitToNewBranch: Create a <strong>new branch</strong> for this commit and start a pull request
|
||||
enterNewBranchName: New branch name
|
||||
|
@ -38,14 +38,9 @@ export default function RepositoriesListing() {
|
||||
const [searchTerm, setSearchTerm] = useState<string | undefined>()
|
||||
const { routes } = useAppContext()
|
||||
const [pageIndex, setPageIndex] = usePageIndex()
|
||||
const path = useMemo(
|
||||
() =>
|
||||
`/api/v1/spaces/${space}/+/repos?page=${pageIndex + 1}&per_page=${LIST_FETCHING_PER_PAGE}${
|
||||
searchTerm ? `&query=${searchTerm}` : ''
|
||||
}`,
|
||||
[space, searchTerm, pageIndex]
|
||||
)
|
||||
const { data: repositories, error, loading, refetch, response } = useGet<TypesRepository[]>({ path })
|
||||
const path = useMemo(() => `/api/v1/spaces/${space}/+/repos?page=${pageIndex + 1}`, [space, pageIndex])
|
||||
const queryParams = useMemo(() => ({ per_page: LIST_FETCHING_PER_PAGE, query: searchTerm }), [searchTerm])
|
||||
const { data: repositories, error, loading, refetch, response } = useGet<TypesRepository[]>({ path, queryParams })
|
||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -35,12 +35,10 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
|
||||
const history = useHistory()
|
||||
const [query, setQuery] = useState('')
|
||||
const [activeBranch, setActiveBranch] = useState(gitRef || repoMetadata.defaultBranch)
|
||||
const path = useMemo(
|
||||
() =>
|
||||
`/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=${BRANCH_PER_PAGE}&page=1&query=${query}`,
|
||||
[query, repoMetadata.path]
|
||||
)
|
||||
const { data, loading } = useGet<RepoBranch[]>({ path })
|
||||
const { data, loading } = useGet<RepoBranch[]>({
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
||||
queryParams: { sort: 'date', direction: 'desc', per_page: BRANCH_PER_PAGE, page: 1, query }
|
||||
})
|
||||
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
|
||||
const defaultBranches = useMemo(
|
||||
() => [repoMetadata.defaultBranch].concat(gitRef ? gitRef : []),
|
||||
@ -145,7 +143,7 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
|
||||
history.push(
|
||||
routes.toSCMRepositoryFileEdit({
|
||||
repoPath: repoMetadata.path as string,
|
||||
resourcePath: '',
|
||||
resourcePath,
|
||||
gitRef: gitRef || (repoMetadata.defaultBranch as string)
|
||||
})
|
||||
)
|
||||
|
@ -1,13 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Container, Color, Layout, Button, FlexExpander, ButtonVariation, Heading } from '@harness/uicore'
|
||||
// import { useHistory } from 'react-router-dom'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { useGet } from 'restful-react'
|
||||
// import { useStrings } from 'framework/strings'
|
||||
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
||||
// import { useAppContext } from 'AppContext'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import type { OpenapiContentInfo, OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
|
||||
import { GitIcon } from 'utils/GitUtils'
|
||||
import type {} from 'services/scm'
|
||||
import css from './Readme.module.scss'
|
||||
|
||||
interface FolderContentProps {
|
||||
@ -17,14 +15,15 @@ interface FolderContentProps {
|
||||
}
|
||||
|
||||
export function Readme({ metadata, gitRef, readmeInfo }: FolderContentProps) {
|
||||
// const { getString } = useStrings()
|
||||
// const history = useHistory()
|
||||
// const { routes } = useAppContext()
|
||||
const history = useHistory()
|
||||
const { routes } = useAppContext()
|
||||
|
||||
const { data /*error, loading, refetch, response */ } = useGet<OpenapiGetContentOutput>({
|
||||
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}?include_commit=false${
|
||||
gitRef ? `&git_ref=${gitRef}` : ''
|
||||
}`
|
||||
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}`,
|
||||
queryParams: {
|
||||
include_commit: false,
|
||||
git_ref: gitRef
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
@ -32,7 +31,19 @@ export function Readme({ metadata, gitRef, readmeInfo }: FolderContentProps) {
|
||||
<Layout.Horizontal padding="small" className={css.heading}>
|
||||
<Heading level={5}>{readmeInfo.name}</Heading>
|
||||
<FlexExpander />
|
||||
<Button variation={ButtonVariation.ICON} icon={GitIcon.EDIT} />
|
||||
<Button
|
||||
variation={ButtonVariation.ICON}
|
||||
icon={GitIcon.EDIT}
|
||||
onClick={() => {
|
||||
history.push(
|
||||
routes.toSCMRepositoryFileEdit({
|
||||
repoPath: metadata.path as string,
|
||||
gitRef: gitRef || (metadata.defaultBranch as string),
|
||||
resourcePath: readmeInfo.path as string
|
||||
})
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
|
||||
{/* TODO: Loading and Error handling */}
|
||||
|
@ -21,9 +21,14 @@ export function RepositoryBranchesContent({ repoMetadata }: RepositoryBranchesCo
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [pageIndex, setPageIndex] = usePageIndex()
|
||||
const { data: branches, response /*error, loading, refetch */ } = useGet<RepoBranch[]>({
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches?per_page=${LIST_FETCHING_PER_PAGE}&page=${
|
||||
pageIndex + 1
|
||||
}&direction=desc&include_commit=true&query=${searchTerm}`
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
||||
queryParams: {
|
||||
per_page: LIST_FETCHING_PER_PAGE,
|
||||
page: pageIndex + 1,
|
||||
direction: 'desc',
|
||||
include_commit: true,
|
||||
query: searchTerm
|
||||
}
|
||||
})
|
||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
||||
|
||||
|
@ -17,12 +17,16 @@ export function CommitsContentHeader({ repoMetadata, onSwitch }: CommitsContentH
|
||||
const { getString } = useStrings()
|
||||
const [query, setQuery] = useState('')
|
||||
const [activeBranch, setActiveBranch] = useState(repoMetadata.defaultBranch)
|
||||
const path = useMemo(
|
||||
() =>
|
||||
`/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=${BRANCH_PER_PAGE}&page=1&query=${query}`,
|
||||
[query, repoMetadata.path]
|
||||
)
|
||||
const { data, loading } = useGet<RepoBranch[]>({ path })
|
||||
const { data, loading } = useGet<RepoBranch[]>({
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
||||
queryParams: {
|
||||
sort: 'date',
|
||||
direction: 'desc',
|
||||
per_page: BRANCH_PER_PAGE,
|
||||
page: 1,
|
||||
query
|
||||
}
|
||||
})
|
||||
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
|
||||
const defaultBranches = useMemo(() => [repoMetadata.defaultBranch].concat([]), [repoMetadata])
|
||||
const [branches, setBranches] = useState<SelectOption[]>(
|
||||
|
@ -21,9 +21,12 @@ export function RepositoryCommitsContent({ repoMetadata, commitRef }: Repository
|
||||
const history = useHistory()
|
||||
const [pageIndex, setPageIndex] = usePageIndex()
|
||||
const { data: commits, response /*error, loading, refetch */ } = useGet<RepoCommit[]>({
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/commits?per_page=${LIST_FETCHING_PER_PAGE}&page=${
|
||||
pageIndex + 1
|
||||
}&git_ref=${commitRef || repoMetadata.defaultBranch}`
|
||||
path: `/api/v1/repos/${repoMetadata.path}/+/commits`,
|
||||
queryParams: {
|
||||
per_page: LIST_FETCHING_PER_PAGE,
|
||||
page: pageIndex + 1,
|
||||
git_ref: commitRef || repoMetadata.defaultBranch
|
||||
}
|
||||
})
|
||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
// border-top-right-radius: 4px;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-xlarge) !important;
|
||||
height: 52px;
|
||||
height: 58px;
|
||||
background-color: var(--grey-100);
|
||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
||||
border-bottom: 1px solid var(--grey-200);
|
||||
@ -26,8 +26,21 @@
|
||||
input {
|
||||
padding: var(--spacing-xsmall) var(--spacing-small);
|
||||
height: 28px;
|
||||
width: 200px;
|
||||
|
||||
&:focus {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.refLink {
|
||||
font-weight: 600;
|
||||
border-radius: 4px;
|
||||
color: var(--primary-7);
|
||||
background: var(--primary-1);
|
||||
padding: 2px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +49,7 @@
|
||||
overflow: hidden;
|
||||
|
||||
.editorContainer {
|
||||
height: calc(100vh - 96px - 52px);
|
||||
height: calc(100vh - 96px - 58px);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ declare const styles: {
|
||||
readonly heading: string
|
||||
readonly path: string
|
||||
readonly inputContainer: string
|
||||
readonly refLink: string
|
||||
readonly content: string
|
||||
readonly editorContainer: string
|
||||
}
|
||||
|
@ -1,38 +1,56 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Button,
|
||||
ButtonSize,
|
||||
ButtonVariation,
|
||||
Color,
|
||||
Container,
|
||||
FlexExpander,
|
||||
Icon,
|
||||
Layout,
|
||||
Text,
|
||||
TextInput
|
||||
} from '@harness/uicore'
|
||||
import { Link } from 'react-router-dom'
|
||||
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react'
|
||||
import { Button, ButtonVariation, Color, Container, FlexExpander, Icon, Layout, Text, TextInput } from '@harness/uicore'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
import ReactJoin from 'react-join'
|
||||
import cx from 'classnames'
|
||||
import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor'
|
||||
import type { OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { GitIcon } from 'utils/GitUtils'
|
||||
import { isDir } from 'utils/GitUtils'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { filenameToLanguage } from 'utils/Utils'
|
||||
import { filenameToLanguage, FILE_SEPERATOR } from 'utils/Utils'
|
||||
import { CommitModalButton } from 'components/CommitModalButton/CommitModalButton'
|
||||
import css from './FileEditor.module.scss'
|
||||
|
||||
interface FileEditorProps {
|
||||
repoMetadata: TypesRepository
|
||||
gitRef: string
|
||||
resourcePath?: string
|
||||
resourcePath: string
|
||||
contentInfo: OpenapiGetContentOutput
|
||||
}
|
||||
|
||||
export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '' }: FileEditorProps) {
|
||||
const PathSeparator = () => <Text color={Color.GREY_900}>/</Text>
|
||||
|
||||
function Editor({ contentInfo, repoMetadata, gitRef, resourcePath }: FileEditorProps) {
|
||||
const history = useHistory()
|
||||
const isNew = useMemo(() => isDir(contentInfo), [contentInfo])
|
||||
const [fileName, setFileName] = useState(isNew ? '' : (contentInfo.name as string))
|
||||
const [parentPath, setParentPath] = useState(
|
||||
isNew ? resourcePath : resourcePath.split(FILE_SEPERATOR).slice(0, -1).join(FILE_SEPERATOR)
|
||||
)
|
||||
const { getString } = useStrings()
|
||||
const { routes } = useAppContext()
|
||||
const language = filenameToLanguage(contentInfo?.name)
|
||||
const rebuildPaths = useCallback(() => {
|
||||
const _tokens = fileName.split(FILE_SEPERATOR).filter(part => !!part.trim())
|
||||
const _fileName = _tokens.pop() as string
|
||||
const _parentPath = parentPath
|
||||
.split(FILE_SEPERATOR)
|
||||
.concat(_tokens)
|
||||
.map(p => p.trim())
|
||||
.filter(part => !!part.trim())
|
||||
.join(FILE_SEPERATOR)
|
||||
|
||||
if (_fileName && _fileName !== fileName) {
|
||||
setFileName(_fileName.trim())
|
||||
}
|
||||
|
||||
setParentPath(_parentPath)
|
||||
}, [fileName, setFileName, parentPath, setParentPath])
|
||||
const fileResourcePath = useMemo(
|
||||
() => [parentPath, fileName].filter(p => !!p.trim()).join(FILE_SEPERATOR),
|
||||
[parentPath, fileName]
|
||||
)
|
||||
|
||||
return (
|
||||
<Container className={css.container}>
|
||||
@ -40,52 +58,87 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
|
||||
<Container>
|
||||
<Layout.Horizontal spacing="small" className={css.path}>
|
||||
<Link to={routes.toSCMRepository({ repoPath: repoMetadata.path as string, gitRef })}>
|
||||
<Icon name="main-folder" />
|
||||
<Icon name="main-folder" padding={{ right: 'small' }} />
|
||||
<Text color={Color.GREY_900} inline>
|
||||
{repoMetadata.uid}
|
||||
</Text>
|
||||
</Link>
|
||||
<Text color={Color.GREY_900}>/</Text>
|
||||
<ReactJoin separator={<Text color={Color.GREY_900}>/</Text>}>
|
||||
{resourcePath.split('/').map((_path, index, paths) => {
|
||||
const pathAtIndex = paths.slice(0, index + 1).join('/')
|
||||
<PathSeparator />
|
||||
{parentPath && (
|
||||
<>
|
||||
<ReactJoin separator={<PathSeparator />}>
|
||||
{parentPath.split(FILE_SEPERATOR).map((_path, index, paths) => {
|
||||
const pathAtIndex = paths.slice(0, index + 1).join('/')
|
||||
|
||||
return index < paths.length - 1 ? (
|
||||
<Link
|
||||
key={_path + index}
|
||||
to={routes.toSCMRepository({
|
||||
repoPath: repoMetadata.path as string,
|
||||
gitRef,
|
||||
resourcePath: pathAtIndex
|
||||
})}>
|
||||
<Text color={Color.GREY_900}>{_path}</Text>
|
||||
</Link>
|
||||
) : (
|
||||
<TextInput
|
||||
key={_path + index}
|
||||
autoFocus
|
||||
value={_path || ''}
|
||||
wrapperClassName={css.inputContainer}
|
||||
placeholder={getString('nameYourFile')}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ReactJoin>
|
||||
return (
|
||||
<Link
|
||||
key={_path + index}
|
||||
to={routes.toSCMRepository({
|
||||
repoPath: repoMetadata.path as string,
|
||||
gitRef,
|
||||
resourcePath: pathAtIndex
|
||||
})}>
|
||||
<Text color={Color.GREY_900}>{_path}</Text>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</ReactJoin>
|
||||
<PathSeparator />
|
||||
</>
|
||||
)}
|
||||
<TextInput
|
||||
autoFocus
|
||||
value={fileName}
|
||||
wrapperClassName={css.inputContainer}
|
||||
placeholder={getString('nameYourFile')}
|
||||
onInput={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setFileName(event.currentTarget.value)
|
||||
}}
|
||||
onBlur={rebuildPaths}
|
||||
onFocus={event => {
|
||||
const value = (parentPath ? parentPath + FILE_SEPERATOR : '') + fileName
|
||||
setFileName(value)
|
||||
setParentPath('')
|
||||
setTimeout(() => event.target.setSelectionRange(value.length, value.length), 0)
|
||||
}}
|
||||
/>
|
||||
<Text color={Color.GREY_900}>{getString('in')}</Text>
|
||||
<Link
|
||||
to={routes.toSCMRepository({
|
||||
repoPath: repoMetadata.path as string,
|
||||
gitRef
|
||||
})}>
|
||||
})}
|
||||
className={css.refLink}>
|
||||
{gitRef}
|
||||
</Link>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
<FlexExpander />
|
||||
<Button
|
||||
text={getString('commit')}
|
||||
icon={GitIcon.COMMIT}
|
||||
iconProps={{ size: 10 }}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
size={ButtonSize.SMALL}
|
||||
/>
|
||||
<Container>
|
||||
<Layout.Horizontal spacing="small">
|
||||
<CommitModalButton
|
||||
text={getString('commitChanges')}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
commitMessagePlaceHolder={'Update file...'}
|
||||
gitRef={gitRef}
|
||||
resourcePath={fileResourcePath}
|
||||
onSubmit={data => console.log({ data })}
|
||||
/>
|
||||
<Button
|
||||
text={getString('cancelChanges')}
|
||||
variation={ButtonVariation.TERTIARY}
|
||||
onClick={() => {
|
||||
history.push(
|
||||
routes.toSCMRepository({
|
||||
repoPath: repoMetadata.path as string,
|
||||
gitRef,
|
||||
resourcePath
|
||||
})
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
|
||||
<Container className={cx(css.content, language)}>
|
||||
@ -99,3 +152,5 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const FileEditor = React.memo(Editor)
|
||||
|
@ -14,6 +14,7 @@ export type Unknown = any // eslint-disable-line @typescript-eslint/no-explicit-
|
||||
export const DEFAULT_BRANCH_NAME = 'main'
|
||||
export const REGEX_VALID_REPO_NAME = /^[a-zA-Z_][0-9a-zA-Z-_.$]*$/
|
||||
export const SUGGESTED_BRANCH_NAMES = [DEFAULT_BRANCH_NAME, 'master']
|
||||
export const FILE_SEPERATOR = '/'
|
||||
|
||||
/** This utility shows a toaster without being bound to any component.
|
||||
* It's useful to show cross-page/component messages */
|
||||
|
Loading…
Reference in New Issue
Block a user