Skip to content

Commit

Permalink
Merge pull request #952 from hpcc-systems/yadhap/bulk-start-pause
Browse files Browse the repository at this point in the history
Yadhap/bulk start pause
  • Loading branch information
FancMa01 authored Nov 22, 2024
2 parents 0a9746f + 8f9ea0f commit 070fdfd
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import { Menu, Dropdown, Button, message, Popconfirm } from 'antd';
import React, { useState } from 'react';
import { Menu, Dropdown, Button, message, Popconfirm, Popover, Form, Select, Card, Badge } from 'antd';
import { DownOutlined } from '@ant-design/icons';

import { handleBulkDeleteJobMonitorings } from './jobMonitoringUtils';
import { handleBulkDeleteJobMonitorings, toggleJobMonitoringStatus } from './jobMonitoringUtils';

const { Option } = Select;
const JobMonitoringActionButton = ({
handleAddJobMonitoringButtonClick,
selectedRows,
Expand All @@ -14,6 +15,11 @@ const JobMonitoringActionButton = ({
filtersVisible,
setDisplayAddRejectModal,
}) => {
const [bulkStartPauseForm] = Form.useForm(); // Form Instance

const [expandActionsDrawer, setExpandActionsDrawer] = useState(false); // Drawer state

// Handle bulk delete
const deleteSelected = async () => {
try {
const selectedRowIds = selectedRows.map((row) => row.id);
Expand All @@ -25,15 +31,35 @@ const JobMonitoringActionButton = ({
}
};

// Bulk start/pause job monitorings
const bulkStartPauseJobMonitorings = async () => {
try {
const action = bulkStartPauseForm.getFieldValue('action'); // Ensure correct usage of bulkStartPauseForm
const selectedRowIds = selectedRows.map((row) => row.id);
const updatedMonitorings = await toggleJobMonitoringStatus({ ids: selectedRowIds, action });
setJobMonitorings((prev) =>
prev.map((monitoring) => updatedMonitorings.find((updated) => updated.id === monitoring.id) || monitoring)
);
message.success(`Selected ${action === 'start' ? 'Job Monitorings started' : 'Job Monitorings paused'}`);
} catch (err) {
message.error('Unable to start/pause selected job monitorings');
}
};

// Handle menu selection
const handleMenuSelection = (key) => {
if (key === '1') {
handleAddJobMonitoringButtonClick();
setExpandActionsDrawer(false);
} else if (key === '2') {
setBulkEditModalVisibility(true);
setExpandActionsDrawer(false);
} else if (key === '4') {
changeFilterVisibility();
setExpandActionsDrawer(false);
} else if (key === '5') {
setDisplayAddRejectModal(true);
setExpandActionsDrawer(false);
}
};

Expand All @@ -43,15 +69,54 @@ const JobMonitoringActionButton = ({
setFiltersVisible((prev) => !prev);
};

// Handle dropdown open change
const handleDropDownOpenChange = (nextOpen, info) => {
if (info.source === 'trigger' || nextOpen) {
setExpandActionsDrawer(nextOpen);
}
};
return (
<Dropdown
trigger={['click']}
onOpenChange={handleDropDownOpenChange}
open={expandActionsDrawer}
dropdownRender={() => (
<Menu onClick={({ key }) => handleMenuSelection(key)}>
<Menu.Item key="1">Add Job Monitoring</Menu.Item>

<Menu.Item key="2" disabled={selectedRows.length < 2}>
Bulk Edit
</Menu.Item>
<Menu.Item disabled={selectedRows.length < 2}>
<Popover
placement="left"
content={
<Card size="small">
<Form layout="vertical" form={bulkStartPauseForm}>
<Form.Item label="Select Action" name="action" required>
<Select style={{ width: '18rem' }}>
<Option value="start">
<Badge color="green" style={{ marginRight: '1rem' }}></Badge>
{`Start selected ${selectedRows.length} Job Monitoring`}
</Option>
<Option value="pause">
<Badge color="red" style={{ marginRight: '1rem' }}></Badge>
{`Pause selected ${selectedRows.length} Job Monitoring`}
</Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" style={{ width: '100%' }} onClick={bulkStartPauseJobMonitorings}>
Apply
</Button>
</Form.Item>
</Form>
</Card>
}
trigger="click">
<a>Bulk start/pause</a>
</Popover>
</Menu.Item>
<Menu.Item key="5" disabled={selectedRows.length < 2}>
Bulk Approve / Reject
</Menu.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ const JobMonitoringTable = ({
setDisplayMonitoringDetailsModal,
setDisplayAddRejectModal,
setSelectedRows,
selectedRows,
domains,
allProductCategories,
filteringJobs,
selectedRows,
}) => {
//Redux
const {
Expand Down Expand Up @@ -273,8 +273,17 @@ const JobMonitoringTable = ({
message.error('Monitoring must be in approved state before it can be started');
return;
}
const updatedData = await toggleJobMonitoringStatus({ id: record.id });
setJobMonitorings((prev) => prev.map((monitoring) => (monitoring.id === record.id ? updatedData : monitoring)));

const updatedData = await toggleJobMonitoringStatus({ ids: [record.id] });
const updatedMonitoringIds = updatedData.map((monitoring) => monitoring.id);

setJobMonitorings((prev) =>
prev.map((monitoring) =>
updatedMonitoringIds.includes(monitoring.id)
? updatedData.find((updated) => updated.id === monitoring.id)
: monitoring
)
);
} catch (err) {
message.error('Failed to toggle monitoring status');
}
Expand All @@ -285,6 +294,7 @@ const JobMonitoringTable = ({
loading={filteringJobs}
columns={columns}
rowKey="id"
rowSelectedBgColor="var(--danger)"
size="small"
rowSelection={{
type: 'checkbox',
Expand All @@ -293,9 +303,16 @@ const JobMonitoringTable = ({
},
}}
pagination={{ pageSize: 20 }}
rowClassName={(record) =>
record?.isActive ? 'jobMonitoringTable__active-monitoring' : 'jobMonitoringTable__inactive-monitoring'
}
rowClassName={(record) => {
let className = record?.isActive
? 'jobMonitoringTable__active-monitoring'
: 'jobMonitoringTable__inactive-monitoring';
const idsOfSelectedRows = selectedRows.map((row) => row.id);
if (idsOfSelectedRows.includes(record.id)) {
className += ' jobMonitoringTable__selected-row';
}
return className;
}}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,11 +652,11 @@ function JobMonitoring() {
setDisplayAddRejectModal={setDisplayAddRejectModal}
applicationId={applicationId}
setSelectedRows={setSelectedRows}
selectedRows={selectedRows}
domains={domains}
productCategories={productCategories}
allProductCategories={allProductCategories}
filteringJobs={filteringJobs}
selectedRows={selectedRows}
/>
{displayMonitoringDetailsModal && (
<MonitoringDetailsModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@
background-color: var(--secondary-light);
}

.jobMonitoringTable__selected-row > .ant-table-cell {
background-color: rgba(255, 255, 0, 0.1) !important;
}
/* Filters ------------------------------------------------------------------ */

.notifications__filter-label {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ export const identifyErroneousTabs = ({ erroneousFields }) => {
};

//Toggle job monitoring status, just post the id of the job monitoring to /toggle in the req body
export const toggleJobMonitoringStatus = async ({ id }) => {
export const toggleJobMonitoringStatus = async ({ ids, action }) => {
const payload = {
method: 'PATCH',
headers: authHeader(),
body: JSON.stringify({ id }),
body: JSON.stringify({ ids, action }),
};

const response = await fetch(`/api/jobmonitoring/toggleIsActive`, payload);
Expand All @@ -140,7 +140,8 @@ export const toggleJobMonitoringStatus = async ({ id }) => {
}

const data = await response.json();
return data;

return data.updatedJobMonitorings;
};

// Bulk delete job monitorings
Expand Down
89 changes: 70 additions & 19 deletions Tombolo/server/routes/jobmonitoring/read.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const express = require("express");
const router = express.Router();
const { body, check, param } = require("express-validator");
const Sequelize = require("sequelize");
const { Op } = Sequelize;
const { body, check, param } = require("express-validator");

//Local imports
const logger = require("../../config/logger");
Expand All @@ -11,6 +10,7 @@ const { validationResult } = require("express-validator");

//Constants
const JobMonitoring = models.jobMonitoring;
const Op = Sequelize.Op;

// Create new job monitoring
router.post(
Expand Down Expand Up @@ -265,40 +265,91 @@ router.delete(
router.patch(
"/toggleIsActive",
[
body("id").isUUID().withMessage("ID must be a valid UUID"),
body("ids").isArray().withMessage("Invalid ids"), // Ensure ids is an array
body("ids.*").isUUID().withMessage("Invalid id"), // Ensure each id is a valid UUID
// make action optional and when provided must be either start or pause
body("action")
.optional()
.isIn(["start", "pause"])
.withMessage("Action must be either start or pause"),
],
async (req, res) => {

// Handle the PATCH request here
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(503).send("Failed to toggle");
return res.status(400).send("Failed to toggle"); // Use a valid status code
}

let transaction;

try {
const { id } = req.body;
// Get and toggle
const jobMonitoring = await JobMonitoring.findByPk(id);
if (!jobMonitoring) {
logger.error("Toggle Job monitoring - Job monitoring not found");
return res.status(404).send("Job monitoring not found");
transaction = await JobMonitoring.sequelize.transaction();
const { ids, action } = req.body; // Expecting an array of IDs

// Find all job monitorings with the given IDs
const jobMonitorings = await JobMonitoring.findAll({
where: { id: { [Op.in]: ids } },
});

if (jobMonitorings.length === 0) {
logger.error("Toggle Job monitoring - Job monitorings not found");
return res.status(404).send("Job monitorings not found");
}
const isApproved = jobMonitoring.approvalStatus === "Approved";
if (!isApproved) {
logger.error("Toggle Job monitoring - Job monitoring not approved");
return res.status(503).send("Can't toggle job monitoring that is not in approved state");

// Filter out the job monitorings that are not approved
const approvedJobMonitorings = jobMonitorings.filter(
(jobMonitoring) => jobMonitoring.approvalStatus === "Approved"
);

if (approvedJobMonitorings.length === 0) {
logger.error(
"Toggle Job monitoring - No approved job monitorings found"
);
return res.status(400).send("No approved job monitorings to toggle"); // Use a valid status code
}
const currentStatus = jobMonitoring.isActive;
const data = await jobMonitoring.update({ isActive: !currentStatus });

res.status(200).send(data);
// Get the IDs of the approved job monitorings
const approvedIds = approvedJobMonitorings.map(
(jobMonitoring) => jobMonitoring.id
);

if(action){
// If action is start or pause change isActive to true or false respectively
await JobMonitoring.update(
{ isActive: action === "start" },
{
where: { id: { [Op.in]: approvedIds } },
transaction,
}
);
}else{
// Toggle the isActive status for all approved job monitorings
await JobMonitoring.update(
{ isActive: Sequelize.literal("NOT isActive") },
{
where: { id: { [Op.in]: approvedIds } },
transaction,
}
);
}

await transaction.commit();

// Get all updated job monitorings
const updatedJobMonitorings = await JobMonitoring.findAll({
where: { id: { [Op.in]: approvedIds } },
});

res.status(200).send({ success: true, message: "Toggled successfully", updatedJobMonitorings }); // Send the updated job monitorings
} catch (err) {
logger.error(err);
await transaction.rollback();
logger.error(err.message);
res.status(500).send("Failed to toggle job monitoring");
}
}
);

module.exports = router;
// Bulk update - only primary, secondary and notify contact are part of bulk update for now
router.patch(
"/bulkUpdate",
Expand Down

0 comments on commit 070fdfd

Please sign in to comment.