Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[issue_tracker] Enable editing of site, assignee and watchers in Batch Mode #9425

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -323,6 +327,8 @@ function IssueTrackerBatchMode({options}) {
<IssueCard
key={issue.issueID}
issue={issue}
assignees={assignees}
otherWatchers={otherWatchers}
onUpdate={handleIssueUpdate}
statuses={statuses}
priorities={priorities}
Expand Down
Loading
Loading