diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index e423744c3..67eae1e50 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -806,6 +806,7 @@ export interface StringsMap { 'pr.openForReview': string 'pr.outdated': string 'pr.prBranchDeleteInfo': string + 'pr.prBranchForcePushInfo': string 'pr.prBranchPushInfo': string 'pr.prCanBeMerged': string 'pr.prClosed': string @@ -870,7 +871,11 @@ export interface StringsMap { reactivate: string readMe: string reader: string + rebase: string + rebaseBranch: string rebaseMerge: string + 'rebaseSource.message': string + 'rebaseSource.title': string recursiveSearchLabel: string recursiveSearchTooltip: string refresh: string @@ -1089,7 +1094,9 @@ export interface StringsMap { updateLabel: string updateUser: string updateWebhook: string + updateWithRebase: string updated: string + updatedBranchMessageRebase: string upload: string uploadAFileError: string user: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index 3c883d5a5..4b762de6f 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -319,6 +319,7 @@ pr: prMergedInfo: '{user} merged changes from {source} into {target} as {mergeSha} {time}' prRebasedInfo: '{user} rebased changes from branch {source} onto {target}, now at {mergeSha} {time}' prBranchPushInfo: '{user} pushed a new commit {commit}' + prBranchForcePushInfo: '{user} force-pushed the {gitRef} branch from {oldCommit} to {newCommit}' prBranchDeleteInfo: '{user} deleted the source branch with latest commit {commit}' prStateChanged: '{user} changed pull request state from {old} to {new}.' prStateChangedDraft: '{user} {changedToDraft|true:marked pull request as draft.,opened pull request for review.}' @@ -1122,6 +1123,9 @@ checkSection: allReqChecksPassed: All required checks passed someChecksFailed: Some checks have failed someChecksRunning: Some checks are running +rebaseSource: + title: This branch is out-of-date with the base branch + message: Merge the latest changes from {target} into {source} importFailed: Import Failed uploadAFileError: There is no image or video uploaded. Please upload an image or video. securitySettings: @@ -1256,3 +1260,7 @@ labels: addaValue: Add a value stringMax: '{entity} must be 50 characters or less' noNewLine: '{entity} cannot contain new lines' +rebase: Rebase +rebaseBranch: Rebase branch +updateWithRebase: Update with rebase +updatedBranchMessageRebase: Updated branch with rebase diff --git a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx index d89cee67f..531c7114f 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx @@ -118,7 +118,7 @@ export const PullRequestActionsBox: React.FC = ({ const [ruleViolationArr, setRuleViolationArr] = useState<{ data: { rule_violations: TypesRuleViolations[] } }>() const [notBypassable, setNotBypassable] = useState(false) const [bypass, setBypass] = useState(false) - const { mutate: updatePRState, loading: loadingState } = useMutate({ + const { mutate: updatePRState } = useMutate({ verb: 'POST', path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullReqMetadata.number}/state` }) @@ -318,9 +318,6 @@ export const PullRequestActionsBox: React.FC = ({ )} - - - diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss index a8ac19be6..2056e9bbb 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.module.scss @@ -81,6 +81,14 @@ } } +.blueTextColor { + :global { + .bp3-button-text { + color: var(--primary-7) !important; + } + } +} + .requiredText { font-size: 10px !important; font-style: normal !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 7dc539e67..9025c57f8 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 @@ -18,6 +18,7 @@ // This is an auto-generated file export declare const blueCopyContainer: string export declare const blueText: string +export declare const blueTextColor: string export declare const boldText: string export declare const borderContainer: string export declare const borderRadius: string diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx index 0410adf91..fa75e9613 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestOverviewPanel.tsx @@ -46,6 +46,7 @@ import MergeSection from './sections/MergeSection' import CommentsSection from './sections/CommentsSection' import ChangesSection from './sections/ChangesSection' import BranchActionsSection from './sections/BranchActionsSection' +import RebaseSourceSection from './sections/RebaseSourceSection' import css from './PullRequestOverviewPanel.module.scss' interface PullRequestOverviewPanelProps { @@ -139,7 +140,7 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => { path: `/api/v1/repos/${repoMetadata.path}/+/branches`, pathParams: { repo_ref: repoMetadata.path } }) - const { mutate: mergePR } = useMutate({ + const { mutate: mergePR, loading: mergeLoading } = useMutate({ verb: 'POST', path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullReqMetadata.number}/merge` }) @@ -231,6 +232,11 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => { ) // eslint-disable-next-line react-hooks/exhaustive-deps }, [unchecked, pullReqMetadata?.source_sha, activities]) + const rebasePossible = useMemo( + () => pullReqMetadata.merge_target_sha !== pullReqMetadata.merge_base_sha, + [pullReqMetadata] + ) + return ( @@ -298,7 +304,16 @@ const PullRequestOverviewPanel = (props: PullRequestOverviewPanelProps) => { mergeable={mergeable} conflictingFiles={conflictingFiles} /> - ) + ), + [PanelSectionOutletPosition.REBASE_SOURCE_BRANCH]: rebasePossible && + !mergeLoading && + !conflictingFiles?.length && ( + + ) }} /> ) : ( diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestPanelSections.tsx b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestPanelSections.tsx index 5af9385b9..fae5675d5 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestPanelSections.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/PullRequestPanelSections.tsx @@ -30,6 +30,7 @@ const PullRequestPanelSections = (props: PullRequestPanelSectionsProps) => { {outlets[PanelSectionOutletPosition.CHECKS]} {outlets[PanelSectionOutletPosition.MERGEABILITY]} {outlets[PanelSectionOutletPosition.BRANCH_ACTIONS]} + {outlets[PanelSectionOutletPosition.REBASE_SOURCE_BRANCH]} ) } diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx index 8934da9d4..7e72ceafc 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/MergeSection.tsx @@ -78,7 +78,7 @@ const MergeSection = (props: MergeSectionProps) => { ) return ( <> - + {(unchecked && ) || ( diff --git a/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/RebaseSourceSection.tsx b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/RebaseSourceSection.tsx new file mode 100644 index 000000000..c3c7ba432 --- /dev/null +++ b/web/src/pages/PullRequest/Conversation/PullRequestOverviewPanel/sections/RebaseSourceSection.tsx @@ -0,0 +1,141 @@ +/* + * 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 cx from 'classnames' +import { + Button, + ButtonSize, + ButtonVariation, + Container, + Layout, + StringSubstitute, + Text, + useToaster +} from '@harnessio/uicore' +import { useMutate } from 'restful-react' +import type { RebaseBranchRequestBody, RepoRepositoryOutput, TypesPullReq } from 'services/code' +import { useStrings } from 'framework/strings' +import { GitRefLink } from 'components/GitRefLink/GitRefLink' +import { getErrorMessage, permissionProps } from 'utils/Utils' +import { useGetSpaceParam } from 'hooks/useGetSpaceParam' +import { useAppContext } from 'AppContext' +import Fail from '../../../../../icons/code-fail-grey.svg?url' +import css from '../PullRequestOverviewPanel.module.scss' + +interface RebaseSourceSectionProps { + pullReqMetadata: TypesPullReq + repoMetadata: RepoRepositoryOutput + refetchActivities: () => void +} + +const RebaseSourceSection = (props: RebaseSourceSectionProps) => { + const { pullReqMetadata, repoMetadata, refetchActivities } = props + const { getString } = useStrings() + const { showSuccess, showError } = useToaster() + const { mutate: rebase } = useMutate({ + verb: 'POST', + path: `/api/v1/repos/${repoMetadata.path}/+/rebase` + }) + const { + hooks: { usePermissionTranslate }, + standalone, + routes + } = useAppContext() + const space = useGetSpaceParam() + const permPushResult = usePermissionTranslate( + { + resource: { + resourceType: 'CODE_REPOSITORY', + resourceIdentifier: repoMetadata?.identifier as string + }, + permissions: ['code_repo_push'] + }, + [space] + ) + + const rebaseRequestPayload = { + base_branch: pullReqMetadata.target_branch, + bypass_rules: true, + dry_run_rules: false, + head_branch: pullReqMetadata.source_branch, + head_commit_sha: pullReqMetadata.source_sha + } + + return ( + <> + + + + {getString('failed')} + + + {getString('rebaseSource.title')} + + + + ), + source: ( + + ) + }} + /> + + + + +