diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 65d1dbd935..7ed6a02008 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -250,6 +250,18 @@ export interface StringsMap { cloneText: string close: string closed: string + 'cmdlineInfo.content': string + 'cmdlineInfo.stepFive': string + 'cmdlineInfo.stepFiveSub': string + 'cmdlineInfo.stepFour': string + 'cmdlineInfo.stepFourSub': string + 'cmdlineInfo.stepOne': string + 'cmdlineInfo.stepOneSub': string + 'cmdlineInfo.stepThree': string + 'cmdlineInfo.stepThreeSub': string + 'cmdlineInfo.stepTwo': string + 'cmdlineInfo.stepTwoSub': string + 'cmdlineInfo.title': string code: string 'codeOwner.approvalCompleted': string 'codeOwner.changesRequested': string @@ -259,6 +271,7 @@ export interface StringsMap { codeSearch: string codeSearchModal: string comingSoon: string + commandLine: string comment: string commentDeleted: string commit: string @@ -286,6 +299,7 @@ export interface StringsMap { confirmRepoVisButton: string confirmStrat: string confirmation: string + conflictsFoundInThisBranch: string content: string contents: string contributor: string @@ -802,6 +816,7 @@ export interface StringsMap { 'pr.titlePlaceHolder': string 'pr.toggleComments': string 'pr.unified': string + 'pr.useCmdLineToResolveConflicts': string 'prChecks.error': string 'prChecks.failure': string 'prChecks.killed': string @@ -973,6 +988,7 @@ export interface StringsMap { status: string 'step.select': string 'stepCategory.select': string + stepNum: string submitReview: string success: string suggestion: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index 7bda9a0d87..e9690e5fd4 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -252,6 +252,7 @@ webhook: Webhook diff: Diff draft: Draft conversation: Conversation +commandLine: command line pr: toggleComments: Toggle comments suggestedChange: Suggested change @@ -267,6 +268,7 @@ pr: expandFullFile: Expand all collapseFullFile: Collapse expanded lines ableToMerge: Able to merge. + useCmdLineToResolveConflicts: Use the {cmd} to resolve conflicts cantBeMerged: This branch has conflicts with the {{name}} branch. cantMerge: Can't be merged. You can still create the pull request. failedToCreate: Failed to create Pull Request. @@ -1043,6 +1045,7 @@ resolveComments: There are {{n}} unresolved comments view: View mergeCheckInProgress: Merge check in progress... allConflictsNeedToBeResolved: All conflicts have to be resolved before merging +conflictsFoundInThisBranch: Conflicts found in this branch details: Details showCheckAll: Show all checks showLessCheck: Show less checks @@ -1053,7 +1056,7 @@ customizeMergeCommitMessage: Customize merge commit message mergeStrategy: Merge strategy selectMergeStrat: Select merge strategy writeDownCommit: Write down commit message here -prHasNoConflicts: This branch has no conflicts with {{name}} branch +prHasNoConflicts: This branch has no conflicts with {name} branch checkStatus: succeeded: Succeeded in {time} failed: Failed in {time} @@ -1181,3 +1184,17 @@ changesRequestedBy: CHANGES REQUESTED BY mergeBranchTitle: Merge branch {{branchName}} of {{repoPath}} (#{{prNum}}) http: HTTP ssh: SSH +stepNum: STEP {{num}} +cmdlineInfo: + title: Resolve conflicts via command line + content: If the conflicts on this branch are too complex to resolve in the web editor, you can check it out via command line to resolve the conflicts + stepOne: Clone the repository or update your local repository with the latest changes + stepOneSub: git pull origin {target} + stepTwo: Switch to the head branch of the pull request + stepTwoSub: git checkout {source} + stepThree: Merge the base branch into the head branch + stepThreeSub: git merge {target} + stepFour: Fix the conflicts and commit the result + stepFourSub: See Resolving a merge conflict using the command line for step-by-step instruction on resolving merge conflicts + stepFive: Push the changes + stepFiveSub: git push -u origin {source} diff --git a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx index af47972c45..e8d8729783 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx @@ -38,11 +38,10 @@ import cx from 'classnames' import ReactTimeago from 'react-timeago' import type { OpenapiStatePullReqRequest, TypesPullReq, TypesRuleViolations } from 'services/code' import { useStrings } from 'framework/strings' -import { CodeIcon, MergeStrategy, PullRequestFilterOption, PullRequestState } from 'utils/GitUtils' +import { CodeIcon, MergeStrategy, PullRequestFilterOption, PullRequestState, dryMerge } from 'utils/GitUtils' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { useAppContext } from 'AppContext' import { - dryMerge, extractInfoFromRuleViolationArr, getErrorMessage, inlineMergeFormRefType, @@ -65,7 +64,8 @@ export const PullRequestActionsBox: React.FC = ({ onPRStateChanged, allowedStrategy, pullReqCommits, - PRStateLoading + PRStateLoading, + setConflictingFiles }) => { const { getString } = useStrings() const { showError } = useToaster() @@ -126,7 +126,8 @@ export const PullRequestActionsBox: React.FC = ({ setRuleViolationArr, setAllowedStrats, pullRequestSection, - showError + showError, + setConflictingFiles ) // eslint-disable-next-line react-hooks/exhaustive-deps }, [unchecked, pullReqMetadata?.source_sha]) const [prMerged, setPrMerged] = useState(false) @@ -144,7 +145,8 @@ export const PullRequestActionsBox: React.FC = ({ setRuleViolationArr, setAllowedStrats, pullRequestSection, - showError + showError, + setConflictingFiles ) } }, POLLING_INTERVAL) // Poll every 20 seconds diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/CommandLineInfo.tsx b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/CommandLineInfo.tsx new file mode 100644 index 0000000000..3dab6c2842 --- /dev/null +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/CommandLineInfo.tsx @@ -0,0 +1,209 @@ +/* + * Copyright 2023 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react' +import { Color, FontVariation } from '@harnessio/design-system' +import { Container, Layout, StringSubstitute, Text } from '@harnessio/uicore' +import type { TypesPullReq } from 'services/code' +import { useStrings } from 'framework/strings' +import { CopyButton } from 'components/CopyButton/CopyButton' +import { CodeIcon } from 'utils/GitUtils' +import css from './PullRequestOverviewPanel.module.scss' + +interface CommandLineInfoProps { + pullReqMetadata: TypesPullReq +} + +const CommandLineInfo = (props: CommandLineInfoProps) => { + const { pullReqMetadata } = props + + const { getString } = useStrings() + const stepOneCopy = getString('cmdlineInfo.stepOneSub').replace('{target}', pullReqMetadata.target_branch as string) + const stepTwoCopy = getString('cmdlineInfo.stepTwoSub').replace('{source}', pullReqMetadata.source_branch as string) + const stepThreeCopy = getString('cmdlineInfo.stepThreeSub').replace( + '{target}', + pullReqMetadata.target_branch as string + ) + const stepFiveCopy = getString('cmdlineInfo.stepFiveSub').replace('{source}', pullReqMetadata.source_branch as string) + return ( + + + + + {getString('cmdlineInfo.title')} + + + + {getString('cmdlineInfo.content')} + + + + + {getString('stepNum', { num: 1 }).toUpperCase()} + + + {getString('cmdlineInfo.stepOne')} + + + + + + + + + + + + + {getString('stepNum', { num: 2 }).toUpperCase()} + + + {getString('cmdlineInfo.stepTwo')} + + + + + + + + + + + + + + {getString('stepNum', { num: 3 }).toUpperCase()} + + + {getString('cmdlineInfo.stepThree')} + + + + + + + + + + + + + {getString('stepNum', { num: 4 }).toUpperCase()} + + + {getString('cmdlineInfo.stepFour')} + + + + + + {getString('cmdlineInfo.stepFourSub')} + + + + + + {getString('stepNum', { num: 5 }).toUpperCase()} + + + {getString('cmdlineInfo.stepFive')} + + + + + + + + + + + + + + ) +} + +export default CommandLineInfo diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss index 6bdd9706b5..720e0291f4 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss @@ -204,8 +204,9 @@ .gridContainer { display: grid !important; - grid-template-columns: 1fr 1fr !important; + grid-template-columns: 1fr minmax(0, 1fr) !important; position: relative !important; + min-width: 0; } .row { @@ -225,6 +226,7 @@ margin-left: var(--spacing-small) !important; grid-column: 2 !important; text-align: right !important; + min-width: 0; } /* Pseudo-elements for placeholders to maintain grid structure */ @@ -251,3 +253,107 @@ padding-right: unset !important; } } +.conflictingFilesTable { + .row { + font-size: 12px !important; + box-shadow: unset !important; + border-bottom: 1px solid rgb(217, 218, 229, 0.6) !important; + padding: var(--spacing-small) !important; + margin-bottom: unset !important; + background: #f6f8fa !important; + + &:last-child { + border-bottom: none !important; + } + } + + :global { + [class*='TableV2--header'] { + border-bottom: 1px solid rgb(217, 218, 229, 0.6) !important; + padding-top: var(--spacing-small) !important; + padding-left: var(--spacing-medium) !important; + padding-right: var(--spacing-medium) !important; + padding-bottom: var(--spacing-xsmall) !important; + font-size: 12px !important; + } + .conflictingFileName { + font-family: 'Roboto Mono' !important; + } + [class*='TableV2--cell'] { + p { + font-size: 12px !important; + } + } + + .bp3-icon-double-caret-vertical { + fill: var(--grey-200) !important; + } + } +} + +.conflictingContainer { + border-top: 1px solid var(--grey-100) !important; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + padding: var(--spacing-5) 2rem !important; + padding-right: 4.05rem !important; + background: #f6f8fa !important; + max-height: 150px; + overflow: auto !important; +} + +.cmdText { + color: var(--primary-7) !important; + &:hover { + text-decoration: underline; + cursor: pointer; + } +} + +.cmdInfoContainer { + background: var(--primary-bg) !important; + border: 1px solid var(--grey-100) !important; + + .cmdTextTitleContainer { + border-bottom: 1px solid var(--grey-100) !important; + > p { + font-size: 14px !important; + font-weight: 600 !important; + } + } +} + +.copyIconContainer { + background-color: var(--primary-1) !important; + color: var(--primary-7) !important; + border-radius: 4px !important; + --button-height: unset !important; + padding: unset !important ; + --padding-right: 2px !important; + padding-left: 4px !important; + min-width: unset !important; + padding-bottom: 1px !important; + --background-color: var(--primary-7); + --text-color: var(--primary-7) !important; +} + +.blueCopyContainer { + border: 0.5px solid var(--primary-3) !important; + background: var(--primary-1) !important; + width: 70% !important; +} + +.stepFont { + font-size: 12px !important; + color: var(--black) !important; + font-family: 'Courier New', Courier, monospace !important; +} + +.stepText { + font-size: 12px !important; + font-weight: 400 !important; +} +.conflictingFileName { + font-family: 'Roboto Mono' !important; + font-weight: 600 !important; +} diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss.d.ts b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss.d.ts index 2869fded9c..393cc05ba0 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss.d.ts +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss.d.ts @@ -16,6 +16,7 @@ /* eslint-disable */ // This is an auto-generated file +export declare const blueCopyContainer: string export declare const blueText: string export declare const borderContainer: string export declare const borderRadius: string @@ -23,7 +24,14 @@ export declare const buttonPadding: string export declare const changeContainerPadding: string export declare const checkContainerPadding: string export declare const checkName: string +export declare const cmdInfoContainer: string +export declare const cmdText: string +export declare const cmdTextTitleContainer: string export declare const codeOwnerContainer: string +export declare const conflictingContainer: string +export declare const conflictingFileName: string +export declare const conflictingFilesTable: string +export declare const copyIconContainer: string export declare const details: string export declare const executionIcon: string export declare const greyContainer: string @@ -48,6 +56,8 @@ export declare const sectionTitle: string export declare const showMore: string export declare const statusCircleContainer: string export declare const statusIcon: string +export declare const stepFont: string +export declare const stepText: string export declare const successIcon: string export declare const textSize: string export declare const timeoutIcon: string diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx index 6c6dbaf238..fdf64c3fe4 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx @@ -28,8 +28,8 @@ import type { TypesRuleViolations } from 'services/code' import { PanelSectionOutletPosition } from 'pages/PullRequest/PullRequestUtils' -import { MergeCheckStatus, PRMergeOption, dryMerge } from 'utils/Utils' -import { PullRequestState } from 'utils/GitUtils' +import { MergeCheckStatus, PRMergeOption } from 'utils/Utils' +import { PullRequestState, dryMerge } from 'utils/GitUtils' import { useStrings } from 'framework/strings' import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision' import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata' @@ -79,6 +79,7 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => { () => pullReqMetadata.merge_check_status === MergeCheckStatus.UNCHECKED && !isClosed, [pullReqMetadata, isClosed] ) + const [conflictingFiles, setConflictingFiles] = useState() const [ruleViolation, setRuleViolation] = useState(false) const [ruleViolationArr, setRuleViolationArr] = useState<{ data: { rule_violations: TypesRuleViolations[] } }>() const [requiresCommentApproval, setRequiresCommentApproval] = useState(false) @@ -172,6 +173,7 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => { setAllowedStrats, pullRequestSection, showError, + setConflictingFiles, setRequiresCommentApproval, setAtLeastOneReviewerRule, setReqCodeOwnerApproval, @@ -186,6 +188,8 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => { { ), [PanelSectionOutletPosition.MERGEABILITY]: !pullReqMetadata.merged && ( - - - + ) }} /> diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx index b527d91337..b0a393b9a8 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx @@ -13,63 +13,156 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react' +import React, { useMemo } from 'react' import { Color, FontVariation } from '@harnessio/design-system' -import { Container, Layout, Text } from '@harnessio/uicore' +import cx from 'classnames' +import { + Button, + ButtonSize, + ButtonVariation, + Container, + Layout, + StringSubstitute, + TableV2, + Text, + useToggle +} from '@harnessio/uicore' +import { Render } from 'react-jsx-match' +import type { CellProps, Column } from 'react-table' import { Images } from 'images' import type { TypesPullReq } from 'services/code' import { useStrings } from 'framework/strings' import Success from '../../../../../icons/code-success.svg?url' import Fail from '../../../../../icons/code-fail.svg?url' +import CommandLineInfo from '../CommandLineInfo' import css from '../PullRequestOverviewPanel.module.scss' interface MergeSectionProps { mergeable: boolean unchecked: boolean pullReqMetadata: TypesPullReq + conflictingFiles: string[] | undefined } +interface ConflictingFilesInterface { + name: string +} const MergeSection = (props: MergeSectionProps) => { - const { mergeable, unchecked, pullReqMetadata } = props + const { mergeable, unchecked, pullReqMetadata, conflictingFiles } = props const { getString } = useStrings() - + const [isExpanded, toggleExpanded] = useToggle(false) + const [showCommandLineInfo, toggleShowCommandLineInfo] = useToggle(false) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const columns: Column[] = useMemo( + () => [ + { + id: 'conflictingFiles', + width: '45%', + sort: true, + Header: `Conflicting Files (${conflictingFiles?.length})`, + accessor: 'conflictingFiles', + Cell: ({ row }: CellProps) => { + return ( + + {row.original} + + ) + } + } + ], // eslint-disable-next-line react-hooks/exhaustive-deps + [conflictingFiles] + ) return ( - - {(unchecked && ) || ( - <> - {mergeable ? ( - {getString('success')} - ) : ( - {getString('failed')} + + + + {(unchecked && ) || ( + <> + {mergeable ? ( + {getString('success')} + ) : ( + {getString('failed')} + )} + )} - - )} - {(unchecked && ( - - - {getString('mergeCheckInProgress')} - - {getString('pr.checkingToMerge')} - - )) || ( - - {!mergeable && ( - - {getString('allConflictsNeedToBeResolved')} - + {(unchecked && ( + + + {getString('mergeCheckInProgress')} + + {getString('pr.checkingToMerge')} + + )) || ( + + {!mergeable && ( + + {getString('conflictsFoundInThisBranch')} + + )} + + + {getString('commandLine')} + + ) + }} + /> + + )} - - {getString(mergeable ? 'prHasNoConflicts' : 'pr.cantBeMerged', { name: pullReqMetadata?.target_branch })} - - - )} - + + {!mergeable && ( +