Merge remote-tracking branch 'upstream/master' into feat/form-validation

This commit is contained in:
Daniel Cojocea committed 2024-07-16 17:12:32 -04:00
commit 7fa1fecb9e
10 files changed
+218 -123

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
language: "en-CA"
tone_instructions: "Tale as old as time (ooh-ooh) Tune as old as song Bittersweet and strange Finding you can change Learning you were wrong Certain as the sun Rising in the east Tale as old as time Song as old as rhyme Beauty and the beast"
tone_instructions: "His palms are sweaty, knees weak, arms are heavy There’s vomit on his sweater already, mom’s spaghetti"
early_access: false
reviews:
profile: "assertive"
+62 -76
View File
@@ -17,40 +17,52 @@ import PropTypes from "prop-types";
*
* @component
* @param {Object} props - Component props.
* @param {Array} props.headers - Array of header titles for the table.
* @param {Array} props.rowItems - Array of row data objects for the table.
* @param {boolean} props.paginated - Flag to enable pagination.
* @param {Object} props.data - Data for the table including columns and rows.
* @param {Array} props.data.cols - Array of objects for column headers.
* @param {number} props.data.cols[].id - Unique identifier for the column.
* @param {string} props.data.cols[].name - Name of the column to display as header.
* @param {Array} props.data.rows - Array of row objects.
* @param {number} props.data.rows[].id - Unique identifier for the row.
* @param {Array} props.data.rows[].data - Array of cell data objects for the row.
* @param {number} props.data.rows[].data[].id - Unique identifier for the cell.
* @param {JSX.Element} props.data.rows[].data[].data - The content to display in the cell.
* @param {function} props.data.rows.data.handleClick - Function to call when the row is clicked.
* @param {boolean} [props.paginated=false] - Flag to enable pagination.
*
* @example
* const data = {
* cols: [
* { id: 1, name: "First Col" },
* { id: 2, name: "Second Col" },
* { id: 3, name: "Third Col" },
* { id: 4, name: "Fourth Col" },
* ],
* rows: [
* {
* id: 1,
* data: [
* { id: 1, data: <div>Data for Row 1 Col 1</div> },
* { id: 2, data: <div>Data for Row 1 Col 2</div> },
* { id: 3, data: <div>Data for Row 1 Col 3</div> },
* { id: 4, data: <div>Data for Row 1 Col 4</div> },
* ],
* },
* {
* id: 2,
* data: [
* { id: 5, data: <div>Data for Row 2 Col 1</div> },
* { id: 6, data: <div>Data for Row 2 Col 2</div> },
* { id: 7, data: <div>Data for Row 2 Col 3</div> },
* { id: 8, data: <div>Data for Row 2 Col 4</div> },
* ],
* },
* ],
* };
*
* Headers is an array of objets that have a unique ID and a name
* Rows is an array of objects that have a unique ID and some data to display
* Row data can be any valid React node
* Rows are split into groups according to the number of headers, so if you have 3 headers then
* Rows will go under the index % 3 column.
* ie: Row 0 -> col 0 % 3 = 0
* ie: Row 1 -> col 1 % 3 = 1
* ie: Row 2 -> col 2 % 3 = 2
* ie: Row 3 -> col 3 % 3 = 0
*
* Thus items are put in their correct columns
*
*
* const headers = [
* { id: 1, name: "ID" },
* { id: 2, name: "Name" },
* { id: 3, name: "Age" },
* ];
* const rows = [
* { id: 1, data: <span>999</span> },
* { id: 2, data: <div>Alex Holliday</div> },
* { id: 3, data: <div>5</div> },
* // More rows...
* ];
* <BasicTable headers={headers} rows={rows} paginated={true} />
* <BasicTable data={data} rows={rows} paginated={true} />
*/
const BasicTable = ({ headers, rowItems, paginated }) => {
const BasicTable = ({ data, paginated }) => {
// Add headers to props validation
const [page, setPage] = useState(0);
@@ -65,19 +77,9 @@ const BasicTable = ({ headers, rowItems, paginated }) => {
setPage(0); // Reset to first page after changing rows per page
};
const numOfCols = headers.length;
const groupedRowItems = rowItems.reduce((acc, item, idx) => {
const rowIndex = Math.floor((item.id - 1) / numOfCols);
if (!acc[rowIndex]) acc[rowIndex] = { items: [], idx };
acc[rowIndex].items.push({ item });
return acc;
}, {});
const paginatedRowsKeys = Object.keys(groupedRowItems).slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage
);
let displayData = paginated
? data.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: data.rows;
return (
<>
@@ -85,42 +87,37 @@ const BasicTable = ({ headers, rowItems, paginated }) => {
<Table>
<TableHead>
<TableRow>
{headers.map((header) => {
return <TableCell key={header.id}>{header.name}</TableCell>;
})}
{data.cols.map((col) => (
<TableCell key={col.id}>{col.name}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{paginated === false &&
Object.keys(groupedRowItems).map((key) => {
return (
<TableRow key={groupedRowItems[key].idx}>
{groupedRowItems[key].items.map(({ item }) => {
return <TableCell key={item.id}>{item.data}</TableCell>;
})}
</TableRow>
);
})}
{paginated === true &&
paginatedRowsKeys.map((key) => (
<TableRow key={groupedRowItems[key].idx}>
{groupedRowItems[key].items.map(({ item }) => (
<TableCell key={item.id}>{item.data}</TableCell>
))}
{displayData.map((row) => {
return (
<TableRow
sx={{ cursor: row.handleClick ? "pointer" : "default" }}
key={row.id}
onClick={row.handleClick ? row.handleClick : null}
>
{row.data.map((cell) => {
return <TableCell key={cell.id}>{cell.data}</TableCell>;
})}
</TableRow>
))}
);
})}
</TableBody>
</Table>
</TableContainer>
{paginated === true && (
<TablePagination
rowsPerPageOptions={[1, 5, 10, 25]}
component="div"
count={Object.keys(groupedRowItems).length}
count={data.rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
rowsPerPageOptions={[1, 5, 10]}
/>
)}
</>
@@ -128,18 +125,7 @@ const BasicTable = ({ headers, rowItems, paginated }) => {
};
BasicTable.propTypes = {
headers: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
name: PropTypes.string.isRequired,
})
).isRequired,
rowItems: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
data: PropTypes.node,
})
).isRequired,
data: PropTypes.object.isRequired,
paginated: PropTypes.bool,
};
+26 -16
View File
@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import ResponseTimeChart from "../Charts/ResponseTimeChart";
import BasicTable from "../BasicTable";
import OpenInNewPage from "../../assets/icons/open-in-new-page.svg?react";
import { useNavigate } from "react-router-dom";
/**
* Host component.
@@ -80,14 +81,19 @@ const Status = ({ params }) => {
* @returns {React.Component} Returns a table with the monitor data.
*/
const MonitorTable = ({ monitors = [] }) => {
const headers = [
{ id: 1, name: "Host" },
{ id: 2, name: "Status" },
{ id: 3, name: "Response Time" },
{ id: 4, name: "Actions" },
];
const data = [];
monitors.forEach((monitor) => {
const navigate = useNavigate();
const data = {
cols: [
{ id: 1, name: "Host" },
{ id: 2, name: "Status" },
{ id: 3, name: "Response Time" },
{ id: 4, name: "Actions" },
],
rows: [],
};
data.rows = monitors.map((monitor, idx) => {
const params = {
url: monitor.url,
title: monitor.name,
@@ -106,16 +112,20 @@ const MonitorTable = ({ monitors = [] }) => {
? "var(--env-var-color-17)"
: "var(--env-var-color-19)",
};
data.push({ id: data.length + 1, data: <Host params={params} /> });
data.push({ id: data.length + 1, data: <Status params={params} /> });
data.push({
id: data.length + 1,
data: <ResponseTimeChart checks={monitor.checks} />,
});
data.push({ id: data.length + 1, data: "TODO" });
return {
id: monitor._id,
handleClick: () => navigate(`/monitors/${monitor._id}`),
data: [
{ id: idx, data: <Host params={params} /> },
{ id: idx + 1, data: <Status params={params} /> },
{ id: idx + 2, data: <ResponseTimeChart checks={monitor.checks} /> },
{ id: idx + 3, data: "TODO" },
],
};
});
return <BasicTable headers={headers} rowItems={data} paginated={true} />;
return <BasicTable data={data} paginated={true} />;
};
MonitorTable.propTypes = {
+25 -27
View File
@@ -27,64 +27,62 @@ const DetailsPage = () => {
const theme = useTheme();
// Example monitor data (replace with actual data or fetch from API)
/**
* Function to calculate uptime duration based on the most recent check.
* @param {Array} checks Array of check objects.
* @returns {string} Uptime duration in hours, minutes, and seconds.
* TODO - NEED TO REVISIT THIS FOR PROPER LOGIC.
* @returns {number} Uptime duration in ms.
*/
const calculateUptimeDuration = (checks) => {
if (!checks || checks.length === 0) {
return "N/A";
return 0;
}
const mostRecentCheck = checks[0];
const currentTime = new Date();
const lastCheckedTime = new Date(mostRecentCheck.createdAt);
let uptimeDuration = currentTime - lastCheckedTime;
let longestDuration = 0;
let lastDownTimestamp = null;
let currentDuration = longestDuration;
// Calculate hours, minutes, and seconds from uptime duration
// TODO - NEED TO REVISIT THIS FOR PROPER LOGIC.
let uptimeHours = Math.floor(uptimeDuration / (1000 * 60 * 60));
uptimeDuration %= 1000 * 60 * 60;
let uptimeMinutes = Math.floor(uptimeDuration / (1000 * 60));
uptimeDuration %= 1000 * 60;
let uptimeSeconds = Math.floor(uptimeDuration / 1000);
return `${uptimeHours}h ${uptimeMinutes}m ${uptimeSeconds}s`;
// Loop over the checks to find the most recent uptime duration
checks.forEach((check) => {
if (check.status === false) {
lastDownTimestamp = check.createdAt;
} else if (check.status === true && lastDownTimestamp) {
currentDuration = check.createdAt - lastDownTimestamp;
if (currentDuration > longestDuration) {
longestDuration = currentDuration;
}
lastDownTimestamp = null;
}
});
lastDownTimestamp = null;
return longestDuration;
};
/**
* Helper function to get timestamp of the most recent check.
* @param {Array} checks Array of check objects.
* @returns {string} Timestamp of the most recent check.
* TODO - NEED TO REVISIT THIS FOR PROPER LOGIC.
* @returns {number} Timestamp of the most recent check.
*/
const getLastCheckedTimestamp = (checks) => {
if (!checks || checks.length === 0) {
return "N/A"; // Handle case when no checks are available
return 0; // Handle case when no checks are available
}
const mostRecentCheck = checks[0];
return new Date(mostRecentCheck.createdAt).toLocaleString();
return mostRecentCheck.createdAt;
};
/**
* Helper function to count incidents (checks with status === false).
* @param {Array} checks Array of check objects.
* @returns {number} Number of incidents.
* TODO - NEED TO REVISIT THIS FOR PROPER LOGIC.
*/
const countIncidents = (checks) => {
if (!checks || checks.length === 0) {
return 0; // Handle case when no checks are available
}
// Count checks with status === false
// TODO - NEED TO REVISIT THIS FOR PROPER LOGIC.
const incidentCount = checks.filter((check) => !check.status).length;
return incidentCount;
return checks.reduce((acc, check) => {
check.status === false ? (acc += 1) : acc;
}, 0);
};
return (
+2 -2
View File
@@ -309,11 +309,11 @@ const validateRecoveryTokenController = async (req, res, next) => {
const resetPasswordController = async (req, res, next) => {
try {
await newPasswordValidation.validateAsync(req.body);
user = await req.db.resetPassword(req, res);
const token = await req.db.resetPassword(req, res);
res.status(200).json({
success: true,
msg: successMessages.AUTH_RESET_PASSWORD,
data: user,
data: token,
});
} catch (error) {
error.service = SERVICE_NAME;
@@ -0,0 +1,60 @@
const PageSpeedCheck = require("../models/PageSpeedCheck");
const { successMessages } = require("../utils/messages");
const SERVICE_NAME = "pagespeed";
/**
* Gets all PageSpeedChecks for a monitor
* @async
* @param {Express.Request} req - The request object
* @param {Express.Response} res - The response object
* @param {Function} next - The next middleware function
* @returns {Promise<Express.Response>}
*/
const getPageSpeedChecks = async (req, res, next) => {
try {
return res.status(200).json({ msg: "Hit getPageSpeedChecks" });
} catch (error) {
error.service = SERVICE_NAME;
next(error);
}
};
/**
* Creates a new PageSpeedCheck for a monitor
* @async
* @param {Express.Request} req - The request object
* @param {Express.Response} res - The response object
* @param {Function} next - The next middleware function
* @returns {Promise<Express.Response>}
*/
const createPageSpeedCheck = async (req, res, next) => {
try {
return res.status(200).json({ msg: "Hit createPageSpeedCheck" });
} catch (error) {
error.service = SERVICE_NAME;
next(error);
}
};
/**
* Deletes all PageSpeedChecks for a monitor
* @async
* @param {Express.Request} req - The request object
* @param {Express.Response} res - The response object
* @param {Function} next - The next middleware function
* @returns {Promise<Express.Response>}
*/
const deletePageSpeedCheck = async (req, res, next) => {
try {
return res.status(200).json({ msg: "Hit deletePageSpeedCheck" });
} catch (error) {
error.service = SERVICE_NAME;
next(error);
}
};
module.exports = {
getPageSpeedChecks,
createPageSpeedCheck,
deletePageSpeedCheck,
};
+2 -1
View File
@@ -220,7 +220,8 @@ const resetPassword = async (req, res) => {
})
.select("-password")
.select("-profileImage");
return userWithoutPassword;
const token = issueToken(userWithoutPassword);
return token;
} else {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
+2
View File
@@ -12,6 +12,7 @@ const { verifyJWT } = require("./middleware/verifyJWT");
const { handleErrors } = require("./middleware/handleErrors");
const queueRouter = require("./routes/queueRoute");
const JobQueue = require("./service/jobQueue");
const pageSpeedCheckRouter = require("./routes/pageSpeedCheckRoute");
// Need to wrap server setup in a function to handle async nature of JobQueue
const startApp = async () => {
@@ -71,6 +72,7 @@ const startApp = async () => {
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
app.use("/api/v1/checks", verifyJWT, checkRouter);
app.use("/api/v1/alerts", verifyJWT, alertRouter);
app.use("/api/v1/pagespeed", verifyJWT, pageSpeedCheckRouter);
//Temporary route for testing, remove later
app.use("/api/v1/job", queueRouter);
File renamed without changes.
+38
View File
@@ -0,0 +1,38 @@
const router = require("express").Router();
const { verifyOwnership } = require("../middleware/verifyOwnership");
const Monitor = require("../models/Monitor");
/**
* @route POST /:monitorId
* @description Create a new PageSpeedCheck for a monitor
* @access Private
* @param {string} monitorId - The ID of the monitor
* @middleware verifyOwnership - Ensures the user owns the monitor
*/
router.post("/:monitorId", verifyOwnership(Monitor, "monitorId"), () => {
console.log("Create check");
});
/**
* @route GET /:monitorId
* @description Get all PageSpeedChecks for a monitor
* @access Private
* @param {string} monitorId - The ID of the monitor
* @middleware verifyOwnership - Ensures the user owns the monitor
*/
router.get("/:monitorId", verifyOwnership(Monitor, "monitorId"), () => {
console.log("Get checks");
});
/**
* @route POST /delete/:monitorId
* @description Delete all PageSpeedChecks for a monitor
* @access Private
* @param {string} monitorId - The ID of the monitor
* @middleware verifyOwnership - Ensures the user owns the monitor
*/
router.delete("/:monitorId", verifyOwnership(Monitor, "monitorId"), () => {
console.log("Delete checks");
});
module.exports = router;