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 { RestfulProvider } from 'restful-react'
|
||||||
import { TooltipContextProvider } from '@harness/uicore'
|
import { TooltipContextProvider } from '@harness/uicore'
|
||||||
import { ModalProvider } from '@harness/use-modal'
|
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),
|
(): Partial<RequestInit> => buildResfulReactRequestOptions(hooks?.useGetToken?.() || token),
|
||||||
[token, hooks]
|
[token, hooks]
|
||||||
)
|
)
|
||||||
|
const queryParams = useMemo(
|
||||||
|
() => (!standalone ? { routingId: space.split('/').shift() } : {}),
|
||||||
|
|
||||||
|
[space, standalone]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
languageLoader(lang).then(setStrings)
|
languageLoader(lang).then(setStrings)
|
||||||
@ -46,7 +51,7 @@ const App: React.FC<AppProps> = React.memo(function App({
|
|||||||
<RestfulProvider
|
<RestfulProvider
|
||||||
base={standalone ? '/' : getConfigNew('scm')}
|
base={standalone ? '/' : getConfigNew('scm')}
|
||||||
requestOptions={getRequestOptions}
|
requestOptions={getRequestOptions}
|
||||||
queryParams={{}}
|
queryParams={queryParams}
|
||||||
queryParamStringifyOptions={{ skipNulls: true }}
|
queryParamStringifyOptions={{ skipNulls: true }}
|
||||||
onResponse={response => {
|
onResponse={response => {
|
||||||
if (!response.ok && response.status === 401) {
|
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,
|
data: gitignores,
|
||||||
loading: gitIgnoreLoading,
|
loading: gitIgnoreLoading,
|
||||||
error: gitIgnoreError
|
error: gitIgnoreError
|
||||||
} = useGet({
|
} = useGet({ path: '/api/v1/resources/gitignore' })
|
||||||
path: '/api/v1/resources/gitignore'
|
|
||||||
})
|
|
||||||
const {
|
const {
|
||||||
data: licences,
|
data: licences,
|
||||||
loading: licenseLoading,
|
loading: licenseLoading,
|
||||||
error: licenseError
|
error: licenseError
|
||||||
} = useGet({
|
} = useGet({ path: '/api/v1/resources/license' })
|
||||||
path: '/api/v1/resources/license'
|
|
||||||
})
|
|
||||||
const loading = submitLoading || gitIgnoreLoading || licenseLoading
|
const loading = submitLoading || gitIgnoreLoading || licenseLoading
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -11,9 +11,14 @@ export interface StringsMap {
|
|||||||
branch: string
|
branch: string
|
||||||
branches: string
|
branches: string
|
||||||
cancel: string
|
cancel: string
|
||||||
|
cancelChanges: string
|
||||||
clone: string
|
clone: string
|
||||||
cloneHTTPS: string
|
cloneHTTPS: string
|
||||||
commit: string
|
commit: string
|
||||||
|
commitChanges: string
|
||||||
|
commitDirectlyTo: string
|
||||||
|
commitMessage: string
|
||||||
|
commitToNewBranch: string
|
||||||
commits: string
|
commits: string
|
||||||
commitsOn: string
|
commitsOn: string
|
||||||
content: string
|
content: string
|
||||||
@ -31,6 +36,7 @@ export interface StringsMap {
|
|||||||
editFile: string
|
editFile: string
|
||||||
email: string
|
email: string
|
||||||
enterDescription: string
|
enterDescription: string
|
||||||
|
enterNewBranchName: string
|
||||||
enterRepoName: string
|
enterRepoName: string
|
||||||
existingAccount: string
|
existingAccount: string
|
||||||
failedToCreateRepo: string
|
failedToCreateRepo: string
|
||||||
@ -47,6 +53,8 @@ export interface StringsMap {
|
|||||||
noAccount: string
|
noAccount: string
|
||||||
none: string
|
none: string
|
||||||
ok: string
|
ok: string
|
||||||
|
optional: string
|
||||||
|
optionalExtendedDescription: string
|
||||||
pageNotFound: string
|
pageNotFound: string
|
||||||
password: string
|
password: string
|
||||||
private: string
|
private: string
|
||||||
|
@ -15,9 +15,11 @@ export function useGetResourceContent({
|
|||||||
includeCommit = false
|
includeCommit = false
|
||||||
}: UseGetResourceContentParams) {
|
}: UseGetResourceContentParams) {
|
||||||
const { data, error, loading, refetch, response } = useGet<OpenapiGetContentOutput>({
|
const { data, error, loading, refetch, response } = useGet<OpenapiGetContentOutput>({
|
||||||
path: `/api/v1/repos/${repoMetadata.path}/+/content${
|
path: `/api/v1/repos/${repoMetadata.path}/+/content${resourcePath ? '/' + resourcePath : ''}`,
|
||||||
resourcePath ? '/' + resourcePath : ''
|
queryParams: {
|
||||||
}?include_commit=${String(includeCommit)}${gitRef ? `&git_ref=${gitRef}` : ''}`
|
include_commit: String(includeCommit),
|
||||||
|
git_ref: gitRef
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { data, error, loading, refetch, response }
|
return { data, error, loading, refetch, response }
|
||||||
|
@ -15,6 +15,8 @@ repositories: Repositories
|
|||||||
files: Files
|
files: Files
|
||||||
commit: Commit
|
commit: Commit
|
||||||
commits: Commits
|
commits: Commits
|
||||||
|
commitChanges: Commit Changes
|
||||||
|
cancelChanges: Cancel Changes
|
||||||
pullRequests: Pull Requests
|
pullRequests: Pull Requests
|
||||||
settings: Settings
|
settings: Settings
|
||||||
newFile: New File
|
newFile: New File
|
||||||
@ -69,3 +71,9 @@ createRepoModal:
|
|||||||
validation:
|
validation:
|
||||||
repoNamePatternIsNotValid: "Name can only contain alphanumerics, '-', '_', '.', and '$'"
|
repoNamePatternIsNotValid: "Name can only contain alphanumerics, '-', '_', '.', and '$'"
|
||||||
gitBranchNameInvalid: Branch name is invalid.
|
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 [searchTerm, setSearchTerm] = useState<string | undefined>()
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [pageIndex, setPageIndex] = usePageIndex()
|
||||||
const path = useMemo(
|
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])
|
||||||
`/api/v1/spaces/${space}/+/repos?page=${pageIndex + 1}&per_page=${LIST_FETCHING_PER_PAGE}${
|
const { data: repositories, error, loading, refetch, response } = useGet<TypesRepository[]>({ path, queryParams })
|
||||||
searchTerm ? `&query=${searchTerm}` : ''
|
|
||||||
}`,
|
|
||||||
[space, searchTerm, pageIndex]
|
|
||||||
)
|
|
||||||
const { data: repositories, error, loading, refetch, response } = useGet<TypesRepository[]>({ path })
|
|
||||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -35,12 +35,10 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
|
|||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const [activeBranch, setActiveBranch] = useState(gitRef || repoMetadata.defaultBranch)
|
const [activeBranch, setActiveBranch] = useState(gitRef || repoMetadata.defaultBranch)
|
||||||
const path = useMemo(
|
const { data, loading } = useGet<RepoBranch[]>({
|
||||||
() =>
|
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
||||||
`/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=${BRANCH_PER_PAGE}&page=1&query=${query}`,
|
queryParams: { sort: 'date', direction: 'desc', per_page: BRANCH_PER_PAGE, page: 1, query }
|
||||||
[query, repoMetadata.path]
|
})
|
||||||
)
|
|
||||||
const { data, loading } = useGet<RepoBranch[]>({ path })
|
|
||||||
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
|
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
|
||||||
const defaultBranches = useMemo(
|
const defaultBranches = useMemo(
|
||||||
() => [repoMetadata.defaultBranch].concat(gitRef ? gitRef : []),
|
() => [repoMetadata.defaultBranch].concat(gitRef ? gitRef : []),
|
||||||
@ -145,7 +143,7 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
|
|||||||
history.push(
|
history.push(
|
||||||
routes.toSCMRepositoryFileEdit({
|
routes.toSCMRepositoryFileEdit({
|
||||||
repoPath: repoMetadata.path as string,
|
repoPath: repoMetadata.path as string,
|
||||||
resourcePath: '',
|
resourcePath,
|
||||||
gitRef: gitRef || (repoMetadata.defaultBranch as string)
|
gitRef: gitRef || (repoMetadata.defaultBranch as string)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Container, Color, Layout, Button, FlexExpander, ButtonVariation, Heading } from '@harness/uicore'
|
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 { useGet } from 'restful-react'
|
||||||
// import { useStrings } from 'framework/strings'
|
|
||||||
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
|
||||||
// import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import type { OpenapiContentInfo, OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
|
import type { OpenapiContentInfo, OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
|
||||||
import { GitIcon } from 'utils/GitUtils'
|
import { GitIcon } from 'utils/GitUtils'
|
||||||
import type {} from 'services/scm'
|
|
||||||
import css from './Readme.module.scss'
|
import css from './Readme.module.scss'
|
||||||
|
|
||||||
interface FolderContentProps {
|
interface FolderContentProps {
|
||||||
@ -17,14 +15,15 @@ interface FolderContentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Readme({ metadata, gitRef, readmeInfo }: FolderContentProps) {
|
export function Readme({ metadata, gitRef, readmeInfo }: FolderContentProps) {
|
||||||
// const { getString } = useStrings()
|
const history = useHistory()
|
||||||
// const history = useHistory()
|
const { routes } = useAppContext()
|
||||||
// const { routes } = useAppContext()
|
|
||||||
|
|
||||||
const { data /*error, loading, refetch, response */ } = useGet<OpenapiGetContentOutput>({
|
const { data /*error, loading, refetch, response */ } = useGet<OpenapiGetContentOutput>({
|
||||||
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}?include_commit=false${
|
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}`,
|
||||||
gitRef ? `&git_ref=${gitRef}` : ''
|
queryParams: {
|
||||||
}`
|
include_commit: false,
|
||||||
|
git_ref: gitRef
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -32,7 +31,19 @@ export function Readme({ metadata, gitRef, readmeInfo }: FolderContentProps) {
|
|||||||
<Layout.Horizontal padding="small" className={css.heading}>
|
<Layout.Horizontal padding="small" className={css.heading}>
|
||||||
<Heading level={5}>{readmeInfo.name}</Heading>
|
<Heading level={5}>{readmeInfo.name}</Heading>
|
||||||
<FlexExpander />
|
<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>
|
</Layout.Horizontal>
|
||||||
|
|
||||||
{/* TODO: Loading and Error handling */}
|
{/* TODO: Loading and Error handling */}
|
||||||
|
@ -21,9 +21,14 @@ export function RepositoryBranchesContent({ repoMetadata }: RepositoryBranchesCo
|
|||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [pageIndex, setPageIndex] = usePageIndex()
|
||||||
const { data: branches, response /*error, loading, refetch */ } = useGet<RepoBranch[]>({
|
const { data: branches, response /*error, loading, refetch */ } = useGet<RepoBranch[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata.path}/+/branches?per_page=${LIST_FETCHING_PER_PAGE}&page=${
|
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
||||||
pageIndex + 1
|
queryParams: {
|
||||||
}&direction=desc&include_commit=true&query=${searchTerm}`
|
per_page: LIST_FETCHING_PER_PAGE,
|
||||||
|
page: pageIndex + 1,
|
||||||
|
direction: 'desc',
|
||||||
|
include_commit: true,
|
||||||
|
query: searchTerm
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
||||||
|
|
||||||
|
@ -17,12 +17,16 @@ export function CommitsContentHeader({ repoMetadata, onSwitch }: CommitsContentH
|
|||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const [activeBranch, setActiveBranch] = useState(repoMetadata.defaultBranch)
|
const [activeBranch, setActiveBranch] = useState(repoMetadata.defaultBranch)
|
||||||
const path = useMemo(
|
const { data, loading } = useGet<RepoBranch[]>({
|
||||||
() =>
|
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
|
||||||
`/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=${BRANCH_PER_PAGE}&page=1&query=${query}`,
|
queryParams: {
|
||||||
[query, repoMetadata.path]
|
sort: 'date',
|
||||||
)
|
direction: 'desc',
|
||||||
const { data, loading } = useGet<RepoBranch[]>({ path })
|
per_page: BRANCH_PER_PAGE,
|
||||||
|
page: 1,
|
||||||
|
query
|
||||||
|
}
|
||||||
|
})
|
||||||
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
|
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
|
||||||
const defaultBranches = useMemo(() => [repoMetadata.defaultBranch].concat([]), [repoMetadata])
|
const defaultBranches = useMemo(() => [repoMetadata.defaultBranch].concat([]), [repoMetadata])
|
||||||
const [branches, setBranches] = useState<SelectOption[]>(
|
const [branches, setBranches] = useState<SelectOption[]>(
|
||||||
|
@ -21,9 +21,12 @@ export function RepositoryCommitsContent({ repoMetadata, commitRef }: Repository
|
|||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const [pageIndex, setPageIndex] = usePageIndex()
|
const [pageIndex, setPageIndex] = usePageIndex()
|
||||||
const { data: commits, response /*error, loading, refetch */ } = useGet<RepoCommit[]>({
|
const { data: commits, response /*error, loading, refetch */ } = useGet<RepoCommit[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata.path}/+/commits?per_page=${LIST_FETCHING_PER_PAGE}&page=${
|
path: `/api/v1/repos/${repoMetadata.path}/+/commits`,
|
||||||
pageIndex + 1
|
queryParams: {
|
||||||
}&git_ref=${commitRef || repoMetadata.defaultBranch}`
|
per_page: LIST_FETCHING_PER_PAGE,
|
||||||
|
page: pageIndex + 1,
|
||||||
|
git_ref: commitRef || repoMetadata.defaultBranch
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
// border-top-right-radius: 4px;
|
// border-top-right-radius: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 var(--spacing-xlarge) !important;
|
padding: 0 var(--spacing-xlarge) !important;
|
||||||
height: 52px;
|
height: 58px;
|
||||||
background-color: var(--grey-100);
|
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);
|
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);
|
border-bottom: 1px solid var(--grey-200);
|
||||||
@ -26,8 +26,21 @@
|
|||||||
input {
|
input {
|
||||||
padding: var(--spacing-xsmall) var(--spacing-small);
|
padding: var(--spacing-xsmall) var(--spacing-small);
|
||||||
height: 28px;
|
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;
|
overflow: hidden;
|
||||||
|
|
||||||
.editorContainer {
|
.editorContainer {
|
||||||
height: calc(100vh - 96px - 52px);
|
height: calc(100vh - 96px - 58px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ declare const styles: {
|
|||||||
readonly heading: string
|
readonly heading: string
|
||||||
readonly path: string
|
readonly path: string
|
||||||
readonly inputContainer: string
|
readonly inputContainer: string
|
||||||
|
readonly refLink: string
|
||||||
readonly content: string
|
readonly content: string
|
||||||
readonly editorContainer: string
|
readonly editorContainer: string
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,56 @@
|
|||||||
import React from 'react'
|
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react'
|
||||||
import {
|
import { Button, ButtonVariation, Color, Container, FlexExpander, Icon, Layout, Text, TextInput } from '@harness/uicore'
|
||||||
Button,
|
import { Link, useHistory } from 'react-router-dom'
|
||||||
ButtonSize,
|
|
||||||
ButtonVariation,
|
|
||||||
Color,
|
|
||||||
Container,
|
|
||||||
FlexExpander,
|
|
||||||
Icon,
|
|
||||||
Layout,
|
|
||||||
Text,
|
|
||||||
TextInput
|
|
||||||
} from '@harness/uicore'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import ReactJoin from 'react-join'
|
import ReactJoin from 'react-join'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor'
|
import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor'
|
||||||
import type { OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
|
import type { OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { GitIcon } from 'utils/GitUtils'
|
import { isDir } from 'utils/GitUtils'
|
||||||
import { useStrings } from 'framework/strings'
|
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'
|
import css from './FileEditor.module.scss'
|
||||||
|
|
||||||
interface FileEditorProps {
|
interface FileEditorProps {
|
||||||
repoMetadata: TypesRepository
|
repoMetadata: TypesRepository
|
||||||
gitRef: string
|
gitRef: string
|
||||||
resourcePath?: string
|
resourcePath: string
|
||||||
contentInfo: OpenapiGetContentOutput
|
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 { getString } = useStrings()
|
||||||
const { routes } = useAppContext()
|
const { routes } = useAppContext()
|
||||||
const language = filenameToLanguage(contentInfo?.name)
|
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 (
|
return (
|
||||||
<Container className={css.container}>
|
<Container className={css.container}>
|
||||||
@ -40,52 +58,87 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
|
|||||||
<Container>
|
<Container>
|
||||||
<Layout.Horizontal spacing="small" className={css.path}>
|
<Layout.Horizontal spacing="small" className={css.path}>
|
||||||
<Link to={routes.toSCMRepository({ repoPath: repoMetadata.path as string, gitRef })}>
|
<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>
|
</Link>
|
||||||
<Text color={Color.GREY_900}>/</Text>
|
<PathSeparator />
|
||||||
<ReactJoin separator={<Text color={Color.GREY_900}>/</Text>}>
|
{parentPath && (
|
||||||
{resourcePath.split('/').map((_path, index, paths) => {
|
<>
|
||||||
const pathAtIndex = paths.slice(0, index + 1).join('/')
|
<ReactJoin separator={<PathSeparator />}>
|
||||||
|
{parentPath.split(FILE_SEPERATOR).map((_path, index, paths) => {
|
||||||
|
const pathAtIndex = paths.slice(0, index + 1).join('/')
|
||||||
|
|
||||||
return index < paths.length - 1 ? (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={_path + index}
|
key={_path + index}
|
||||||
to={routes.toSCMRepository({
|
to={routes.toSCMRepository({
|
||||||
repoPath: repoMetadata.path as string,
|
repoPath: repoMetadata.path as string,
|
||||||
gitRef,
|
gitRef,
|
||||||
resourcePath: pathAtIndex
|
resourcePath: pathAtIndex
|
||||||
})}>
|
})}>
|
||||||
<Text color={Color.GREY_900}>{_path}</Text>
|
<Text color={Color.GREY_900}>{_path}</Text>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
)
|
||||||
<TextInput
|
})}
|
||||||
key={_path + index}
|
</ReactJoin>
|
||||||
autoFocus
|
<PathSeparator />
|
||||||
value={_path || ''}
|
</>
|
||||||
wrapperClassName={css.inputContainer}
|
)}
|
||||||
placeholder={getString('nameYourFile')}
|
<TextInput
|
||||||
/>
|
autoFocus
|
||||||
)
|
value={fileName}
|
||||||
})}
|
wrapperClassName={css.inputContainer}
|
||||||
</ReactJoin>
|
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>
|
<Text color={Color.GREY_900}>{getString('in')}</Text>
|
||||||
<Link
|
<Link
|
||||||
to={routes.toSCMRepository({
|
to={routes.toSCMRepository({
|
||||||
repoPath: repoMetadata.path as string,
|
repoPath: repoMetadata.path as string,
|
||||||
gitRef
|
gitRef
|
||||||
})}>
|
})}
|
||||||
|
className={css.refLink}>
|
||||||
{gitRef}
|
{gitRef}
|
||||||
</Link>
|
</Link>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Container>
|
</Container>
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
<Button
|
<Container>
|
||||||
text={getString('commit')}
|
<Layout.Horizontal spacing="small">
|
||||||
icon={GitIcon.COMMIT}
|
<CommitModalButton
|
||||||
iconProps={{ size: 10 }}
|
text={getString('commitChanges')}
|
||||||
variation={ButtonVariation.PRIMARY}
|
variation={ButtonVariation.PRIMARY}
|
||||||
size={ButtonSize.SMALL}
|
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>
|
</Layout.Horizontal>
|
||||||
|
|
||||||
<Container className={cx(css.content, language)}>
|
<Container className={cx(css.content, language)}>
|
||||||
@ -99,3 +152,5 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
|
|||||||
</Container>
|
</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 DEFAULT_BRANCH_NAME = 'main'
|
||||||
export const REGEX_VALID_REPO_NAME = /^[a-zA-Z_][0-9a-zA-Z-_.$]*$/
|
export const REGEX_VALID_REPO_NAME = /^[a-zA-Z_][0-9a-zA-Z-_.$]*$/
|
||||||
export const SUGGESTED_BRANCH_NAMES = [DEFAULT_BRANCH_NAME, 'master']
|
export const SUGGESTED_BRANCH_NAMES = [DEFAULT_BRANCH_NAME, 'master']
|
||||||
|
export const FILE_SEPERATOR = '/'
|
||||||
|
|
||||||
/** This utility shows a toaster without being bound to any component.
|
/** This utility shows a toaster without being bound to any component.
|
||||||
* It's useful to show cross-page/component messages */
|
* It's useful to show cross-page/component messages */
|
||||||
|
Loading…
Reference in New Issue
Block a user