From 0f12961c54d558434c4cad433e487a5ad06b8911 Mon Sep 17 00:00:00 2001 From: yadhap Dahal Date: Thu, 21 Nov 2024 10:33:53 -0500 Subject: [PATCH 1/2] Added functionality to bulk start and pause the monitorings --- .../JobMonitoringActionButton.jsx | 54 +++++++++++- .../jobMonitoring/JobMonitoringTable.jsx | 28 ++++-- .../application/jobMonitoring/index.jsx | 1 + .../jobMonitoring/jobMonitoring.css | 3 + .../jobMonitoring/jobMonitoringUtils.js | 7 +- Tombolo/server/routes/jobmonitoring/read.js | 87 +++++++++++++++---- 6 files changed, 153 insertions(+), 27 deletions(-) diff --git a/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx b/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx index c16c7ad91..9d894746a 100644 --- a/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx +++ b/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx @@ -1,9 +1,10 @@ import React from 'react'; -import { Menu, Dropdown, Button, message, Popconfirm } from 'antd'; +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, @@ -13,6 +14,9 @@ const JobMonitoringActionButton = ({ setFiltersVisible, filtersVisible, }) => { + const [bulkStartPauseForm] = Form.useForm(); // Form Instance + + // Handle bulk delete const deleteSelected = async () => { try { const selectedRowIds = selectedRows.map((row) => row.id); @@ -24,6 +28,22 @@ 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(); @@ -49,6 +69,36 @@ const JobMonitoringActionButton = ({ Bulk Edit + + +
+ + + + + + +
+ + } + trigger="hover"> + Bulk start/pause +
+
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'); } @@ -283,6 +293,7 @@ const JobMonitoringTable = ({ loading={filteringJobs} columns={columns} rowKey="id" + rowSelectedBgColor="var(--danger)" size="small" rowSelection={{ type: 'checkbox', @@ -291,9 +302,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; + }} /> ); }; diff --git a/Tombolo/client-reactjs/src/components/application/jobMonitoring/index.jsx b/Tombolo/client-reactjs/src/components/application/jobMonitoring/index.jsx index dab1eb75c..331bf3165 100644 --- a/Tombolo/client-reactjs/src/components/application/jobMonitoring/index.jsx +++ b/Tombolo/client-reactjs/src/components/application/jobMonitoring/index.jsx @@ -650,6 +650,7 @@ function JobMonitoring() { setDisplayAddRejectModal={setDisplayAddRejectModal} applicationId={applicationId} setSelectedRows={setSelectedRows} + selectedRows={selectedRows} domains={domains} productCategories={productCategories} allProductCategories={allProductCategories} diff --git a/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoring.css b/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoring.css index fd1d287b8..553da9531 100644 --- a/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoring.css +++ b/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoring.css @@ -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 { diff --git a/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoringUtils.js b/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoringUtils.js index 7eeae5583..578f29139 100644 --- a/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoringUtils.js +++ b/Tombolo/client-reactjs/src/components/application/jobMonitoring/jobMonitoringUtils.js @@ -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); @@ -140,7 +140,8 @@ export const toggleJobMonitoringStatus = async ({ id }) => { } const data = await response.json(); - return data; + + return data.updatedJobMonitorings; }; // Bulk delete job monitorings diff --git a/Tombolo/server/routes/jobmonitoring/read.js b/Tombolo/server/routes/jobmonitoring/read.js index 192548f36..0c310404b 100644 --- a/Tombolo/server/routes/jobmonitoring/read.js +++ b/Tombolo/server/routes/jobmonitoring/read.js @@ -1,5 +1,6 @@ const express = require("express"); const router = express.Router(); +const Sequelize = require("sequelize"); const { body, check, param } = require("express-validator"); //Local imports @@ -9,6 +10,7 @@ const { validationResult } = require("express-validator"); //Constants const JobMonitoring = models.jobMonitoring; +const Op = Sequelize.Op; // Create new job monitoring router.post( @@ -253,40 +255,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", From 8f9ea0fb0c084fd4cafb0969afa17d256c155451 Mon Sep 17 00:00:00 2001 From: yadhap Dahal Date: Thu, 21 Nov 2024 14:16:03 -0500 Subject: [PATCH 2/2] Changed the job monitoring action button menu triggeron click instead of hover. Also added state that controls the actions drawer visibility depending on what action user selects --- .../JobMonitoringActionButton.jsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx b/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx index 06301df59..d25d98d7d 100644 --- a/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx +++ b/Tombolo/client-reactjs/src/components/application/jobMonitoring/JobMonitoringActionButton.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Menu, Dropdown, Button, message, Popconfirm, Popover, Form, Select, Card, Badge } from 'antd'; import { DownOutlined } from '@ant-design/icons'; @@ -17,6 +17,8 @@ const JobMonitoringActionButton = ({ }) => { const [bulkStartPauseForm] = Form.useForm(); // Form Instance + const [expandActionsDrawer, setExpandActionsDrawer] = useState(false); // Drawer state + // Handle bulk delete const deleteSelected = async () => { try { @@ -48,12 +50,16 @@ const JobMonitoringActionButton = ({ 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); } }; @@ -63,8 +69,17 @@ const JobMonitoringActionButton = ({ setFiltersVisible((prev) => !prev); }; + // Handle dropdown open change + const handleDropDownOpenChange = (nextOpen, info) => { + if (info.source === 'trigger' || nextOpen) { + setExpandActionsDrawer(nextOpen); + } + }; return ( ( handleMenuSelection(key)}> Add Job Monitoring @@ -98,7 +113,7 @@ const JobMonitoringActionButton = ({ } - trigger="hover"> + trigger="click"> Bulk start/pause