Skip to content

Commit

Permalink
Feature 1566 - Add merge/update branch support (#1570)
Browse files Browse the repository at this point in the history
* Added merging and updating functionality for master

* added icons for merge/update

* removed unused var

* added merge/update feature

* added missing hook dependencies

* added merge dialog

* removed unnecesary useMemo deps

* removed unused vars

* removed unused import

* changed update button tooltip

* added dialogs for merge/update

* added loading state on save

* console log fixes

* removed unnecessary log

* fixed FileChip tooltip

* removed unneeded prop

* Removed unnecessary prop

---------

Co-authored-by: jincypjose <[email protected]>
  • Loading branch information
abelpz and jincypjose authored Apr 25, 2023
1 parent fa60dae commit 21bd958
Show file tree
Hide file tree
Showing 20 changed files with 2,565 additions and 1,684 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"@material-ui/styles": "^4.11.3",
"axios": "^0.21.0",
"bible-reference-range": "^1.1.0",
"datatable-translatable": "^1.0.23",
"datatable-translatable": "^1.0.24",
"dcs-branch-merger": "^1.3.0",
"deep-freeze": "^0.0.1",
"gitea-react-toolkit": "2.2.4",
"lodash.isequal": "^4.5.0",
Expand All @@ -44,6 +45,7 @@
"react-beforeunload": "^2.5.1",
"react-dom": "^16.12.0",
"react-headroom": "^3.0.0",
"react-icons": "^4.8.0",
"react-select": "^4.1.0",
"react-waypoint": "^10.1.0",
"scripture-resources-rcl": "5.3.8",
Expand Down
2 changes: 1 addition & 1 deletion public/build_number
Original file line number Diff line number Diff line change
@@ -1 +1 @@
235-a32731d
251-fcff0e4
17 changes: 16 additions & 1 deletion src/App.context.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { useStateReducer } from './hooks/useStateReducer';
import { useGiteaReactToolkit } from './hooks/useGiteaReactToolkit';
import { useWarning } from './hooks/useWarning';
import BranchMergerProvider from './components/branch-merger/context/BranchMergerProvider';

export const AppContext = React.createContext();

Expand Down Expand Up @@ -40,7 +41,21 @@ export function AppContextProvider({
warning,
};

return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
const params = {
server: state.authentication?.config?.server,
owner: state.targetRepository?.owner?.login,
repo: state.targetRepository?.name,
userBranch: state.targetRepository?.branch,
tokenid: state.authentication?.token?.sha1,
}

return (
<AppContext.Provider value={value}>
<BranchMergerProvider {...params}>
{children}
</BranchMergerProvider>
</AppContext.Provider>
);
};

AppContextProvider.propTypes = {
Expand Down
45 changes: 45 additions & 0 deletions src/components/branch-merger/components/MergeBranchButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import IconButton from '@material-ui/core/IconButton';
import Badge from '@material-ui/core/Badge';
import CircularProgress from '@material-ui/core/CircularProgress';
import { Tooltip } from '@material-ui/core';
import { MdOutlinePublish, MdPublish } from "react-icons/md";

export function MergeBranchButton({
onClick = () => undefined,
isLoading = false,
blocked = false,
pending = false,
loadingProps,
title = "Merge my work",
...props
}) {
const Icon = blocked ? MdOutlinePublish : MdPublish
return (
<Tooltip title={title}>
<span>
<IconButton
key='merge-to-master'
onClick={onClick}
aria-label={title}
style={{ cursor: 'pointer', ...props.style}}
// disabled={!pending | blocked}
>
{isLoading ? (
<CircularProgress
size={18}
style={{
margin: "3px 0px",
}}
{...loadingProps}
/>
) : (
<Badge color="error" variant="dot" invisible={!pending}>
<Icon id='merge-to-master-icon'/>
</Badge>
)}
</IconButton>
</span>
</Tooltip>
)
}
57 changes: 57 additions & 0 deletions src/components/branch-merger/components/MergeDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { useRef } from 'react';
import CircularProgress from '@material-ui/core/CircularProgress';

export default function MergeDialog({ onSubmit, onCancel, open, isLoading, loadingProps }) {
const descriptionRef = useRef(null);

return (
<Dialog open={open} onClose={onCancel} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Merge</DialogTitle>
<DialogContent>
<DialogContentText>
Clicking submit will merge your current work with your team's work. Please add a comment below about the changes that you are submitting.
</DialogContentText>
<TextField
inputRef={descriptionRef}
autoFocus
margin="dense"
id="merge-description"
label="Description"
type="text"
minRows={2}
fullWidth
multiline
disabled={isLoading}
/>
</DialogContent>
<DialogActions>
<Button onClick={onCancel} color="primary" disabled={isLoading}>
Cancel
</Button>
<Button onClick={() => {
onSubmit(descriptionRef.current.value)
}}
color="primary"
disabled={isLoading}
>
{isLoading ? (
<>
<CircularProgress size='1rem' style={{ marginRight: '0.5rem' }} {...loadingProps} />{' '}
{' Sending...'}
</>
) : (
'Submit'
)}
</Button>
</DialogActions>
</Dialog>
);
}
46 changes: 46 additions & 0 deletions src/components/branch-merger/components/UpdateBranchButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
import { MdUpdate, MdUpdateDisabled } from 'react-icons/md';
import IconButton from '@material-ui/core/IconButton';
import Badge from '@material-ui/core/Badge';
import CircularProgress from '@material-ui/core/CircularProgress';
import { Tooltip } from '@material-ui/core';

export function UpdateBranchButton({
onClick = () => undefined,
isLoading = false,
blocked = false,
pending = false,
loadingProps,
title = "Update my content",
...props
}) {
const Icon = blocked ? MdUpdateDisabled : MdUpdate
return (
<Tooltip title={title}>
<span>
<IconButton
key='update-from-master'
onClick={onClick}
aria-label={title}
style={{ cursor: 'pointer', ...props.style, ...(!pending | blocked ? {color: "rgba(0, 0, 0, 0.26)"} : {})}}
// disabled={!pending | blocked}
>
{isLoading ? (
<CircularProgress

size={18}
style={{
margin: "3px 0px",
}}
{...loadingProps}
/>
) : (
<Badge color="error" variant="dot" overlap="circular" invisible={!pending}>
<Icon id='update-from-master-icon'/>
</Badge>
)}
</IconButton>
</span>
</Tooltip>
)
}
25 changes: 25 additions & 0 deletions src/components/branch-merger/context/BranchMergerProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { createContext } from 'react';
import PropTypes from 'prop-types';
import useBranchMerger from '../hooks/useBranchMerger';

export const BranchMergerContext = createContext();

const BranchMergerProvider = ({ children, server, owner, repo, userBranch, tokenid }) => {
const {state,actions} = useBranchMerger({server, owner, repo, userBranch, tokenid})
return (
<BranchMergerContext.Provider value={{state,actions}}>
{children}
</BranchMergerContext.Provider>
);
};

BranchMergerProvider.propTypes = {
children: PropTypes.element,
server: PropTypes.string,
owner: PropTypes.string,
repo: PropTypes.string,
userBranch: PropTypes.string,
tokenid: PropTypes.string,
};

export default BranchMergerProvider;
126 changes: 126 additions & 0 deletions src/components/branch-merger/hooks/useBranchMerger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useCallback, useEffect, useState, useMemo } from 'react'
import {
mergeDefaultIntoUserBranch,
checkMergeDefaultIntoUserBranch,
checkMergeUserIntoDefaultBranch,
mergeUserIntoDefaultBranch
} from "dcs-branch-merger"

const defaultStatus = {
"mergeNeeded": false,
"conflict": false,
"success": false,
"userBranchDeleted": false,
"error": false,
"message": "",
"pullRequest": "",
}

/**
* Legend:
* merge = push from user branch to master branch
* update = pull from master branch to user branch
**/

//Adapters
const update = (params) => mergeDefaultIntoUserBranch(params);
const checkUpdate = (params) => checkMergeDefaultIntoUserBranch(params);
const merge = (params) => mergeUserIntoDefaultBranch(params);
const checkMerge = (params) => checkMergeUserIntoDefaultBranch(params);

export default function useBranchMerger({ server, owner, repo, userBranch, tokenid }) {

const [mergeStatus, setMergeStatus] = useState(defaultStatus);
const [updateStatus, setUpdateStatus] = useState(defaultStatus);
const [loadingUpdate, setLoadingUpdate] = useState(false);
const [loadingMerge, setLoadingMerge] = useState(false);

const params = useMemo(() => ({ server, owner, repo, userBranch, tokenid }), [server, owner, repo, userBranch, tokenid])

const setStatus = useCallback((setter, newStatus) => setter( prevStatus => {
if (Object.keys(prevStatus).some(key => prevStatus[key] !== newStatus[key])) return {...prevStatus, ...newStatus};
return prevStatus;
}), [])

const isInvalid = useCallback((setter, {server, owner, repo, userBranch, tokenid}) => {
if (![server, owner, repo, userBranch, tokenid].every(Boolean)) {
setStatus(setter, defaultStatus);
return Promise.resolve(defaultStatus);
};
}, [setStatus])

const runMergeHandler = useCallback(async ({setter,handler,params}) => isInvalid(setter, params) ?? handler(params)
.then((newStatus) => {
setStatus(setter, newStatus)
return newStatus
}).catch(e => {
const newStatus = {...defaultStatus, error:true, message: e.message}
setStatus(setter, newStatus)
return newStatus
}),[isInvalid,setStatus]);

/**
* updates the updateStatus state
*/
const checkUpdateStatus = useCallback(() => {
setLoadingUpdate(true);
const setter = setUpdateStatus;
const handler = checkUpdate
return runMergeHandler({setter,handler,params}).then(r => { setLoadingUpdate(false); return r})
},[params,runMergeHandler])

/**
* pulls master branch from user branch
*/
const updateUserBranch = useCallback(() => {
setLoadingUpdate(true);
const setter = setUpdateStatus;
const handler = update;
return runMergeHandler({setter,handler,params}).then(r => { setLoadingUpdate(false); return r})
},[params,runMergeHandler]);

/**
* updates the mergeStatus state
*/
const checkMergeStatus = useCallback(() => {
setLoadingMerge(true);
const setter = setMergeStatus;
const handler = checkMerge;
return runMergeHandler({setter,handler,params}).then(r => { setLoadingMerge(false); return r})
}, [params,runMergeHandler])

/**
* pushes user branch to master branch
*/
const mergeMasterBranch = useCallback((prDescription) => {
setLoadingMerge(true);
const setter = setMergeStatus;
const handler = merge;
return runMergeHandler({ setter, handler, params: { ...params, prDescription } }).then(r => {
setLoadingMerge(false);
return r
})
},[params,runMergeHandler]);

useEffect(() => {
checkMergeStatus();
}, [checkMergeStatus])

useEffect(() => {
checkUpdateStatus();
}, [checkUpdateStatus])

return {
state: {
mergeStatus,
updateStatus,
loadingUpdate,
loadingMerge
}, actions: {
checkUpdateStatus,
checkMergeStatus,
updateUserBranch,
mergeMasterBranch
}
}
}
28 changes: 28 additions & 0 deletions src/components/dialogs/ErrorDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import LinkChip from './LinkChip';

export default function ErrorDialog({ onClose, title, open, isLoading = false, content, link, linkTooltip = "" }) {

return (
<Dialog open={open} onClose={onClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText>
{content}
</DialogContentText>
{link ? <LinkChip link={link} linkTooltip={linkTooltip} /> : null}
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary" disabled={isLoading}>
Accept
</Button>
</DialogActions>
</Dialog>
);
}
Loading

0 comments on commit 21bd958

Please sign in to comment.