feat: [code-288]: file commit history

This commit is contained in:
calvin 2023-06-05 15:52:15 -06:00
parent 8cf58dbde9
commit 24b3ec9fe9
21 changed files with 345 additions and 26 deletions

View File

@ -43,7 +43,7 @@
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.9.6",
"@harness/design-system": "1.4.0",
"@harness/icons": "1.143.0",
"@harness/icons": "1.149.0",
"@harness/ng-tooltip": ">=1.31.25",
"@harness/telemetry": ">=1.0.42",
"@harness/uicore": "3.131.1",

View File

@ -44,3 +44,45 @@
padding-left: var(--spacing-xsmall) !important;
padding-right: var(--spacing-xsmall);
}
.fileButton {
svg {
> path:first-child {
fill: unset !important;
}
}
}
.layout {
height: 22px;
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;
&.noCopy {
padding-right: var(--spacing-small) !important;
}
a span {
font-size: var(--font-size-small) !important;
color: var(--primary-7);
font-family: var(--font-family-mono) !important;
}
span#commitFileButton {
--button-height: 22px !important;
border-radius: 0 !important;
border-right: 1px solid var(--grey-200) !important;
padding-right: 4px !important;
padding-bottom: 2px !important ;
cursor: pointer;
}
button#commitRepoButton {
padding-bottom: 2px !important;
padding-right: 4px !important;
}
}

View File

@ -7,5 +7,10 @@ declare const styles: {
readonly rowText: string
readonly label: string
readonly refreshIcon: string
readonly fileButton: string
readonly layout: string
readonly noCopy: string
readonly commitFileButton: string
readonly commitRepoButton: string
}
export default styles

View File

@ -10,18 +10,20 @@ import {
ButtonSize,
Button,
FlexExpander,
StringSubstitute
StringSubstitute,
Icon,
Popover
} from '@harness/uicore'
import type { CellProps, Column } from 'react-table'
import { noop, orderBy } from 'lodash-es'
import { Link } from 'react-router-dom'
import { Link, useHistory } from 'react-router-dom'
import { useStrings } from 'framework/strings'
import { useAppContext } from 'AppContext'
import type { TypesCommit } from 'services/code'
import { CommitActions } from 'components/CommitActions/CommitActions'
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
import { formatDate } from 'utils/Utils'
import { FileSection, formatDate } from 'utils/Utils'
import { CodeIcon, GitInfoProps } from 'utils/GitUtils'
import type { CODERoutes } from 'RouteDefinitions'
import css from './CommitsView.module.scss'
@ -32,6 +34,10 @@ interface CommitsViewProps extends Pick<GitInfoProps, 'repoMetadata'> {
emptyMessage: string
prHasChanged?: boolean
handleRefresh?: () => void
showFileHistoryIcons?: boolean
gitRef?: string
resourcePath?: string
setActiveTab?: React.Dispatch<React.SetStateAction<string>>
}
export function CommitsView({
@ -40,8 +46,14 @@ export function CommitsView({
emptyTitle,
emptyMessage,
handleRefresh = noop,
prHasChanged
prHasChanged,
showFileHistoryIcons = false,
gitRef = '',
resourcePath = '',
setActiveTab
}: CommitsViewProps) {
console.log(repoMetadata, commits, gitRef, resourcePath)
const history = useHistory()
const { getString } = useStrings()
const { routes } = useAppContext()
const columns: Column<TypesCommit>[] = useMemo(
@ -86,9 +98,66 @@ export function CommitsView({
/>
)
}
},
{
id: 'buttons',
width: showFileHistoryIcons ? '60px' : '0px',
Cell: ({ row }: CellProps<TypesCommit>) => {
if (showFileHistoryIcons) {
return (
<Container padding={{ left: 'small' }}>
<Layout.Horizontal className={css.layout}>
<Popover
content={
<Text color={Color.BLACK} padding="medium">
{getString('viewFile')}
</Text>
}
interactionKind="hover">
<Icon
id={css.commitFileButton}
className={css.fileButton}
name={'code-content'}
size={14}
onClick={() => {
history.push(
routes.toCODERepository({
repoPath: repoMetadata.path as string,
gitRef: row.original.sha,
resourcePath
})
)
if(setActiveTab){
setActiveTab(FileSection.CONTENT)
}
}}
/>
</Popover>
<Button
id={css.commitRepoButton}
variation={ButtonVariation.ICON}
text={'<>'}
onClick={() => {
// console.log(gitRef)
history.push(
routes.toCODERepository({
repoPath: repoMetadata.path as string,
gitRef: row.original.sha
})
)
}}
tooltip={getString('viewRepo')}
/>
</Layout.Horizontal>
</Container>
)
} else {
return <Container width={0}></Container>
}
}
}
],
[repoMetadata, routes]
[repoMetadata, routes] // eslint-disable-line react-hooks/exhaustive-deps
)
const commitsGroupedByDate: Record<string, TypesCommit[]> = useMemo(
() =>

View File

@ -16,7 +16,6 @@ const ImageCarousel = (props: ImageCarouselProps) => {
const { getString } = useStrings()
const { isOpen, setIsOpen, setZoomLevel, zoomLevel, imgEvent } = props
const [imgTitle, setImageTitle] = useState(imgEvent[0])
return (
<Dialog
portalClassName={css.portalContainer}

View File

@ -20,6 +20,9 @@
&.hideGutter > ::before {
display: none;
}
&.hideTitleGutter > ::before {
display: none;
}
}
}
@ -38,6 +41,10 @@
opacity: 0.7;
z-index: 2;
}
&.hideTitleGutter > ::before {
display: none;
}
}
.inCommentBox {
padding-left: 25px;
@ -79,5 +86,8 @@
&.hideGutter > ::before {
display: none;
}
&.hideTitleGutter > ::before {
display: none;
}
}
}

View File

@ -4,6 +4,7 @@ declare const styles: {
readonly thread: string
readonly content: string
readonly hideGutter: string
readonly hideTitleGutter: string
readonly titleContent: string
readonly inCommentBox: string
readonly threadLessSpace: string

View File

@ -8,6 +8,7 @@ interface ThreadSectionProps {
className?: string
contentClassName?: string
hideGutter?: boolean
hideTitleGutter?: boolean
onlyTitle?: boolean
inCommentBox?: boolean
lastItem?: boolean
@ -19,13 +20,14 @@ export const ThreadSection: React.FC<ThreadSectionProps> = ({
className,
contentClassName,
hideGutter,
hideTitleGutter,
onlyTitle,
inCommentBox = false,
lastItem
}) => {
return (
<Container
className={cx(inCommentBox ? css.thread : css.threadLessSpace, className, {
className={cx(inCommentBox ? css.thread : css.threadLessSpace,hideTitleGutter ? css.hideTitleGutter : '', className, {
[css.titleContent]: onlyTitle && !inCommentBox && !lastItem,
[css.inCommentBox]: inCommentBox && !lastItem
})}>

View File

@ -140,6 +140,7 @@ export interface StringsMap {
generateCloneText: string
getMyCloneTitle: string
gitIgnore: string
hideCommitHistory: string
history: string
in: string
inactiveBranches: string
@ -316,6 +317,7 @@ export interface StringsMap {
selectBranchPlaceHolder: string
selectToViewMore: string
settings: string
showCommitHistory: string
showEverything: string
signIn: string
signUp: string
@ -348,8 +350,10 @@ export interface StringsMap {
viewAllBranches: string
viewAllTags: string
viewCommitDetails: string
viewFile: string
viewFiles: string
viewRaw: string
viewRepo: string
viewed: string
webhook: string
webhookAllEventsSelected: string

View File

@ -417,3 +417,7 @@ tagger: Tagger
confirmDelete: Confirm delete
tagEmpty: Here is no Tag. Try to
newTag: New Tag
viewFile: View the file at this point in the history
viewRepo: View the repository at this point in the history
hideCommitHistory: Hide Rename History for {{file}}
showCommitHistory: Show Rename History for {{file}}

View File

@ -65,7 +65,7 @@ export const Conversation: React.FC<ConversationProps> = ({
// Determine all parent activities
const parentActivities = orderBy(
activities?.filter(activity => !activity.parent_id) || [],
'edited',
'created',
dateOrderSort
).map(_comment => [_comment])

View File

@ -16,7 +16,7 @@ import { EmptyRepositoryInfo } from './EmptyRepositoryInfo'
import css from './Repository.module.scss'
export default function Repository() {
const { gitRef, resourcePath, repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
const { gitRef, resourcePath, repoMetadata, error, loading, refetch, commitRef } = useGetRepositoryMetadata()
const {
data: resourceContent,
error: resourceError,
@ -82,6 +82,7 @@ export default function Repository() {
gitRef={gitRef}
resourcePath={resourcePath}
resourceContent={resourceContent}
commitRef={commitRef}
/>
)}

View File

@ -35,6 +35,10 @@
padding: var(--spacing-xlarge) !important;
}
.gitHistory {
padding: var(--spacing-small) var(--spacing-xlarge) !important;
}
.tabsContainer {
flex-grow: 1;
display: flex;

View File

@ -6,6 +6,7 @@ declare const styles: {
readonly content: string
readonly fileContent: string
readonly gitBlame: string
readonly gitHistory: string
readonly tabsContainer: string
readonly tabTitle: string
readonly count: string

View File

@ -1,4 +1,5 @@
import React, { useMemo } from 'react'
import { useGet } from 'restful-react'
import {
ButtonSize,
ButtonVariation,
@ -13,7 +14,7 @@ import {
import { Render } from 'react-jsx-match'
import { useHistory } from 'react-router-dom'
import { SourceCodeViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
import type { OpenapiContentInfo, RepoFileContent } from 'services/code'
import type { OpenapiContentInfo, RepoFileContent, TypesCommit } from 'services/code'
import {
decodeGitContent,
findMarkdownInfo,
@ -22,33 +23,34 @@ import {
isRefATag,
makeDiffRefs
} from 'utils/GitUtils'
import { filenameToLanguage, permissionProps } from 'utils/Utils'
import { filenameToLanguage, permissionProps, LIST_FETCHING_LIMIT, RenameDetails, FileSection } from 'utils/Utils'
import { useAppContext } from 'AppContext'
import { LatestCommitForFile } from 'components/LatestCommit/LatestCommit'
import { useCommitModal } from 'components/CommitModalButton/CommitModalButton'
import { useStrings } from 'framework/strings'
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
import { PlainButton } from 'components/PlainButton/PlainButton'
import { CommitsView } from 'components/CommitsView/CommitsView'
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import { usePageIndex } from 'hooks/usePageIndex'
import { Readme } from '../FolderContent/Readme'
import { GitBlame } from './GitBlame'
import RenameContentHistory from './RenameContentHistory'
import css from './FileContent.module.scss'
enum FileSection {
CONTENT = 'content',
BLAME = 'blame',
HISTORY = 'history'
}
export function FileContent({
repoMetadata,
gitRef,
resourcePath,
resourceContent
}: Pick<GitInfoProps, 'repoMetadata' | 'gitRef' | 'resourcePath' | 'resourceContent'>) {
resourceContent,
commitRef
}: Pick<GitInfoProps, 'repoMetadata' | 'gitRef' | 'resourcePath' | 'resourceContent' | 'commitRef'>) {
const { routes } = useAppContext()
const { getString } = useStrings()
const history = useHistory()
const [activeTab, setActiveTab] = React.useState<string>(FileSection.CONTENT)
const content = useMemo(
() => decodeGitContent((resourceContent?.content as RepoFileContent)?.data),
[resourceContent?.content]
@ -105,13 +107,27 @@ export function FileContent({
}
return { disabled: isRefATag(gitRef) || false, tooltip: undefined }
}, [permPushResult, gitRef]) // eslint-disable-line react-hooks/exhaustive-deps
const [page, setPage] = usePageIndex()
const { data: commits, response } = useGet<{ commits: TypesCommit[]; rename_details: RenameDetails[] }>({
path: `/api/v1/repos/${repoMetadata?.path}/+/commitsV2`,
queryParams: {
limit: LIST_FETCHING_LIMIT,
page,
git_ref: commitRef || repoMetadata?.default_branch,
path: resourcePath
},
lazy: !repoMetadata
})
return (
<Container className={css.tabsContainer}>
<Tabs
id="fileTabs"
selectedTabId={activeTab}
defaultSelectedTabId={FileSection.CONTENT}
large={false}
onChange={(id: string) => setActiveTab(id)}
tabList={[
{
id: FileSection.CONTENT,
@ -233,6 +249,38 @@ export function FileContent({
))}
</Container>
)
},
{
id: FileSection.HISTORY,
title: getString('history'),
panel: (
<>
{repoMetadata && !!commits?.commits?.length && (
<>
<Container className={css.gitBlame}>
<CommitsView
commits={commits.commits}
repoMetadata={repoMetadata}
emptyTitle={getString('noCommits')}
emptyMessage={getString('noCommitsMessage')}
showFileHistoryIcons={true}
gitRef={gitRef}
resourcePath={resourcePath}
setActiveTab={setActiveTab}
/>
{/* <ThreadSection></ThreadSection> */}
<ResourceListingPagination response={response} page={page} setPage={setPage} />
</Container>
<Container className={css.gitHistory}>
{commits?.rename_details && repoMetadata ? (
<RenameContentHistory rename_details={commits.rename_details} repoMetadata={repoMetadata} />
) : null}
</Container>
</>
)}
</>
)
}
]}
/>

View File

@ -0,0 +1,8 @@
.contentSection {
padding-left: unset !important;
}
.hideText {
text-align: center !important;
width: 100% !important;
}

View File

@ -0,0 +1,7 @@
/* eslint-disable */
// this is an auto-generated file
declare const styles: {
readonly contentSection: string
readonly hideText: string
}
export default styles

View File

@ -0,0 +1,99 @@
import React, { useState } from 'react'
import { Text } from '@harness/uicore'
import { useGet } from 'restful-react'
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
import { LIST_FETCHING_LIMIT, RenameDetails } from 'utils/Utils'
import { usePageIndex } from 'hooks/usePageIndex'
import type { TypesCommit, TypesRepository } from 'services/code'
import { useStrings } from 'framework/strings'
import { CommitsView } from 'components/CommitsView/CommitsView'
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import css from './RenameContentHistory.module.scss'
const RenameContentHistory = (props: { rename_details: RenameDetails[], repoMetadata: TypesRepository, fileVisibility?: { [key: string]: boolean } }) => {
const { rename_details, repoMetadata, fileVisibility: initialFileVisibility } = props;
const { getString } = useStrings();
const [fileVisibility, setFileVisibility] = useState(initialFileVisibility || {});
const [page, setPage] = usePageIndex();
const { data: commits, response, refetch: getCommitHistory } = useGet<{ commits: TypesCommit[]; rename_details: RenameDetails[] }>({
path: `/api/v1/repos/${repoMetadata?.path}/+/commitsV2`,
lazy: true
});
const toggleCommitHistory = async (details: RenameDetails) => {
setFileVisibility(prevVisibility => ({
...prevVisibility,
[details.old_path]: !prevVisibility[details.old_path]
}));
if (!fileVisibility[details.old_path]) {
await getCommitHistory({
queryParams: {
limit: LIST_FETCHING_LIMIT,
page,
git_ref: details.commit_sha_before,
path: details.old_path
}
});
}
};
return (
<>
{rename_details.map((details, index) => {
const isFileShown = fileVisibility[details.old_path];
const commitsData = commits?.commits;
const showCommitHistory = isFileShown && commitsData && commitsData.length > 0;
return (
<ThreadSection
key={index}
hideGutter
hideTitleGutter
contentClassName={css.contentSection}
title={
<Text
hidden={showCommitHistory}
className={css.hideText}
padding={{top:"large"}}
onClick={() => toggleCommitHistory(details)}
>
{showCommitHistory ?getString('hideCommitHistory',{file:details.old_path}) :getString('showCommitHistory',{file:details.old_path})}
</Text>
}
onlyTitle={showCommitHistory}
>
{showCommitHistory && (
<>
<CommitsView
commits={commits.commits}
repoMetadata={repoMetadata}
emptyTitle={getString('noCommits')}
emptyMessage={getString('noCommitsMessage')}
showFileHistoryIcons={true}
resourcePath={details.old_path}
/>
<Text
className={css.hideText}
padding={{ left: 'xxxlarge', right: 'xxxlarge', top: 'large' }}
onClick={() => toggleCommitHistory(details)}
>
{getString('hideCommitHistory',{file:details.old_path})}
</Text>
<ResourceListingPagination response={response} page={page} setPage={setPage} />
<RenameContentHistory
rename_details={commits.rename_details.filter(file => file.old_path !== details.old_path)}
repoMetadata={repoMetadata}
fileVisibility={fileVisibility}
/>
</>
)}
</ThreadSection>
);
})}
</>
);
};
export default RenameContentHistory;

View File

@ -10,8 +10,9 @@ export function RepositoryContent({
repoMetadata,
gitRef,
resourcePath,
resourceContent
}: Pick<GitInfoProps, 'repoMetadata' | 'gitRef' | 'resourcePath' | 'resourceContent'>) {
resourceContent,
commitRef
}: Pick<GitInfoProps, 'repoMetadata' | 'gitRef' | 'resourcePath' | 'resourceContent' | 'commitRef' >) {
useEffect(() => {
window.scroll({ top: 0 })
}, [gitRef, resourcePath])
@ -36,6 +37,7 @@ export function RepositoryContent({
gitRef={gitRef}
resourcePath={resourcePath}
resourceContent={resourceContent}
commitRef={commitRef}
/>
)}
</Container>

View File

@ -47,6 +47,13 @@ export interface PageBrowserProps {
page: string
}
export interface RenameDetails {
commit_sha_after: string
commit_sha_before: string
new_path: string
old_path: string
}
export interface SourceCodeEditorProps {
source: string
language?: string
@ -135,6 +142,12 @@ export enum CodeCommentState {
RESOLVED = 'resolved'
}
export enum FileSection {
CONTENT = 'content',
BLAME = 'blame',
HISTORY = 'history'
}
const MONACO_SUPPORTED_LANGUAGES = [
'abap',
'apex',

View File

@ -686,10 +686,10 @@
resolved "https://npm.pkg.github.com/download/@harness/design-system/1.4.0/b2a77f73696d71a53765c71efd0a5b28039fa1cf#b2a77f73696d71a53765c71efd0a5b28039fa1cf"
integrity sha512-LuzuPEHPkE6xgIuXxn16RCCvPY1NDXF3o1JWlIjxmepoDTkgFuwnV1OhBdQftvAVBawJ5wJP10IIKUL161LdYg==
"@harness/icons@1.143.0":
version "1.143.0"
resolved "https://npm.pkg.github.com/download/@harness/icons/1.143.0/fb412c51530b7d113694738bfa682db8e141d169#fb412c51530b7d113694738bfa682db8e141d169"
integrity sha512-RfIcmJkc7vXWCAHVti07Zc54FMxu54uPW77SkEL9IbRhxM8qEof9Jmcgq7CsEfHTHlCk2+oFeXQKsCFZjv34SA==
"@harness/icons@1.149.0":
version "1.149.0"
resolved "https://npm.pkg.github.com/download/@harness/icons/1.149.0/0f2e1faba8d5e1a651b5b8f2c03582d30702d81f#0f2e1faba8d5e1a651b5b8f2c03582d30702d81f"
integrity sha512-kfcGY7t+V1NoSRAoDJofZpKAY79UN+NI8XWf0OLYtRI1JnVF37ZyJgvJ3Vk4VgLbMElsPRXopcx9ADjCWz/RZw==
"@harness/jarvis@^0.12.0":
version "0.12.0"