Skip to content

Commit

Permalink
[issue_tracker] Enable editing of site, assignee and watchers in Batc…
Browse files Browse the repository at this point in the history
…h Mode (#9425)

## Brief summary of changes
- Added site selector to Issue Card edit mode
- Enhanced Add Comment modal with assignee and watchers selectors
- Updated backend endpoint to fetch otherWatchers and assignees
  • Loading branch information
ay-bh authored Dec 13, 2024
1 parent 416240f commit 452218c
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 39 deletions.
128 changes: 115 additions & 13 deletions modules/issue_tracker/jsx/IssueCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const IssueCard = React.memo(function IssueCard({
priorities,
categories,
sites,
assignees,
otherWatchers,
}) {
const [isEditing, setIsEditing] = useState(false);
const [editedIssue, setEditedIssue] = useState({...issue});
Expand All @@ -21,17 +23,20 @@ const IssueCard = React.memo(function IssueCard({
const [newComment, setNewComment] = useState('');
const [isSubmittingComment, setIsSubmittingComment] = useState(false);

const [newAssignee, setNewAssignee] = useState(issue.assignee || '');
const [newWatchers, setNewWatchers] = useState(issue.othersWatching || []);

const handleInputChange = (field, value) => {
setTempEditedIssue((prev) => ({
...prev,
[field]: value,
[field]: value === '' ? null : value,
}));
};

const handleSubmit = (e) => {
e.preventDefault();

if (!tempEditedIssue.title.trim()) {
if (!tempEditedIssue.title || !tempEditedIssue.title.trim()) {
showAlertMessage('error', 'Title cannot be empty');
return;
}
Expand Down Expand Up @@ -100,23 +105,46 @@ const IssueCard = React.memo(function IssueCard({
};

const handleOpenAddCommentModal = () => {
setNewAssignee(issue.assignee || '');
setNewWatchers(issue.othersWatching || []);
setShowAddCommentModal(true);
};

const handleCloseAddCommentModal = () => {
setShowAddCommentModal(false);
setNewComment('');
setNewAssignee(issue.assignee || '');
setNewWatchers(issue.othersWatching || []);
};

const handleAddCommentChange = (e) => {
setNewComment(e.target.value);
};

const handleNewAssigneeChange = (e) => {
setNewAssignee(e.target.value);
};

const handleNewWatchersChange = (e) => {
const options = e.target.options;
const selectedWatchers = [];
for (let i = 0; i < options.length; i++) {
if (options[i].selected) {
selectedWatchers.push(options[i].value);
}
}
setNewWatchers(selectedWatchers);
};

const handleAddCommentSubmit = (e) => {
e.preventDefault();

if (!newComment.trim()) {
showAlertMessage('error', 'Comment cannot be empty');
const trimmedComment = newComment.trim();
const hasAssigneeChanged = newAssignee !== issue.assignee;
const hasWatchersChanged = JSON.stringify(newWatchers) !==
JSON.stringify(issue.othersWatching);
if (!trimmedComment && !hasAssigneeChanged && !hasWatchersChanged) {
showAlertMessage('info', 'Please add a comment or make changes');
return;
}

Expand All @@ -129,7 +157,16 @@ const IssueCard = React.memo(function IssueCard({
formData.append(key, value === null ? 'null' : value);
});

formData.append('comment', newComment.trim());
// Only append comment if it's not empty
if (trimmedComment) {
formData.append('comment', newComment.trim());
}

formData.append('assignee', newAssignee || 'null');
formData.append(
'othersWatching',
newWatchers.length > 0 ? newWatchers.join(',') : ''
);

fetch(`${loris.BaseURL}/issue_tracker/Edit/`, {
method: 'POST',
Expand All @@ -145,7 +182,7 @@ const IssueCard = React.memo(function IssueCard({
return response.json();
})
.then((data) => {
showAlertMessage('success', 'Comment added successfully');
showAlertMessage('success', 'Issue updated successfully');
handleCloseAddCommentModal();
onUpdate();
})
Expand Down Expand Up @@ -174,10 +211,48 @@ const IssueCard = React.memo(function IssueCard({
value={newComment}
onChange={handleAddCommentChange}
className="textarea"
required
disabled={isSubmittingComment}
/>
</div>
<div className="form-group">
<label htmlFor="newAssignee" className="small">
Assignee
</label>
<select
id="newAssignee"
value={newAssignee}
onChange={handleNewAssigneeChange}
className="form-control"
disabled={isSubmittingComment}
>
<option value="">Unassigned</option>
{Object.entries(assignees).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
</div>
<div className="form-group">
<label htmlFor="newWatchers" className="small">
Watchers
</label>
<select
id="newWatchers"
value={newWatchers}
onChange={handleNewWatchersChange}
className="form-control"
multiple
disabled={isSubmittingComment}
>
{Object.entries(otherWatchers).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
</div>

<div className="modal-actions">
<button
type="submit"
Expand Down Expand Up @@ -218,12 +293,7 @@ const IssueCard = React.memo(function IssueCard({
<div className="issue-dates">
<span>Created: {issue.dateCreated}</span>
<span>Last Updated: {issue.lastUpdate}</span>
<span>Assignee: {issue.assignee}</span>
<span>
Site: {issue.centerID
? sites[String(issue.centerID)]
: 'No Site'}
</span>
<span>Assignee: {issue.assignee || 'None'}</span>
</div>
</div>
<form onSubmit={handleSubmit} className="issue-form">
Expand Down Expand Up @@ -296,6 +366,28 @@ const IssueCard = React.memo(function IssueCard({
))}
</select>
</div>
<div className="control-group">
<label htmlFor="centerID">
Site:&nbsp;
</label>
<select
id="centerID"
value={tempEditedIssue.centerID || ''}
onChange={(e) =>
handleInputChange('centerID', e.target.value)
}
>
<option value="">All Sites</option>
{Object.entries(sites).map(([id, name]) => (
<option
key={id}
value={id}
>
{name}
</option>
))}
</select>
</div>
</>
) : (
<>
Expand All @@ -320,6 +412,13 @@ const IssueCard = React.memo(function IssueCard({
'Uncategorized'}
</span>
</div>
<div className="control-group">
<label>Site:&nbsp;</label>
<span>
{sites[String(tempEditedIssue.centerID)] ||
'All Sites'}
</span>
</div>
</>
)}
</div>
Expand Down Expand Up @@ -407,6 +506,7 @@ IssueCard.propTypes = {
title: PropTypes.string.isRequired,
reporter: PropTypes.string.isRequired,
assignee: PropTypes.string,
othersWatching: PropTypes.arrayOf(PropTypes.string),
status: PropTypes.string.isRequired,
priority: PropTypes.string.isRequired,
module: PropTypes.number,
Expand Down Expand Up @@ -434,6 +534,8 @@ IssueCard.propTypes = {
priorities: PropTypes.object.isRequired,
categories: PropTypes.object.isRequired,
sites: PropTypes.object.isRequired,
assignees: PropTypes.object.isRequired,
otherWatchers: PropTypes.object.isRequired,
};

export default IssueCard;
10 changes: 8 additions & 2 deletions modules/issue_tracker/jsx/IssueTrackerBatchMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ function IssueTrackerBatchMode({options}) {
const [filteredIssues, setFilteredIssues] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [assignees, setAssignees] = useState({});
const [otherWatchers, setOtherWatchers] = useState({});

// Pagination state
const [page, setPage] = useState({
Expand Down Expand Up @@ -63,7 +65,9 @@ function IssueTrackerBatchMode({options}) {
throw new Error('Network response was not ok');
}
const data = await response.json();
setIssues(data);
setIssues(data.issues || []);
setAssignees(data.assignees || {});
setOtherWatchers(data.otherWatchers || {});
setIsLoading(false);
} catch (error) {
console.error('Error fetching issues:', error);
Expand All @@ -73,7 +77,7 @@ function IssueTrackerBatchMode({options}) {
}

/**
* Filters issues based on selected categories, priorities, and statuses
* Filters issues based on selected categories, priorities, statuses, and sites
*/
function filterIssues() {
setFilteredIssues(issues.filter((issue) =>
Expand Down Expand Up @@ -331,6 +335,8 @@ function IssueTrackerBatchMode({options}) {
<IssueCard
key={issue.issueID}
issue={issue}
assignees={assignees}
otherWatchers={otherWatchers}
onUpdate={handleIssueUpdate}
statuses={statuses}
priorities={priorities}
Expand Down
Loading

0 comments on commit 452218c

Please sign in to comment.