feat: add delete and select all button

This commit is contained in:
Jayden Pyles
2024-07-07 16:27:12 -05:00
parent 2b0153e42d
commit 15fe8eb3ea
7 changed files with 130 additions and 23 deletions

View File

@@ -2,8 +2,8 @@ name: ci
on: on:
push: push:
branches: ["master"] branches: ["master"]
pull_request: # pull_request:
branches: ["master"] # branches: ["master"]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -12,8 +12,13 @@ from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
# LOCAL # LOCAL
from api.backend.job import query, insert from api.backend.job import query, insert, delete_jobs
from api.backend.models import DownloadJob, SubmitScrapeJob, RetrieveScrapeJobs from api.backend.models import (
DownloadJob,
SubmitScrapeJob,
DeleteScrapeJobs,
RetrieveScrapeJobs,
)
from api.backend.scraping import scrape from api.backend.scraping import scrape
from api.backend.auth.auth_router import auth_router from api.backend.auth.auth_router import auth_router
@@ -116,3 +121,13 @@ async def download(download_job: DownloadJob):
except Exception as e: except Exception as e:
LOG.error(f"Exception occurred: {e}") LOG.error(f"Exception occurred: {e}")
return {"error": str(e)} return {"error": str(e)}
@app.post("/api/delete-scrape-jobs")
async def delete(delete_scrape_jobs: DeleteScrapeJobs):
result = await delete_jobs(delete_scrape_jobs.ids)
return (
JSONResponse(content={"message": "Jobs successfully deleted."})
if result
else JSONResponse({"error": "Jobs not deleted."})
)

View File

@@ -24,3 +24,11 @@ async def query(filter: dict[str, Any]) -> list[dict[str, Any]]:
results.append(document) results.append(document)
return results return results
async def delete_jobs(jobs: list[str]):
collection = get_job_collection()
result = await collection.delete_many({"id": {"$in": jobs}})
LOG.info(f"RESULT: {result.deleted_count} documents deleted")
return True if result.deleted_count > 0 else False

View File

@@ -32,3 +32,7 @@ class RetrieveScrapeJobs(pydantic.BaseModel):
class DownloadJob(pydantic.BaseModel): class DownloadJob(pydantic.BaseModel):
id: str id: str
class DeleteScrapeJobs(pydantic.BaseModel):
ids: list[str]

View File

@@ -1,19 +1,24 @@
import React from "react"; import React, { useState } from "react";
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableRow, TableRow,
Button, IconButton,
Box, Box,
Typography, Typography,
Accordion, Accordion,
AccordionSummary, AccordionSummary,
AccordionDetails, AccordionDetails,
Checkbox,
Tooltip,
Button,
} from "@mui/material"; } from "@mui/material";
import { useRouter } from "next/router"; import DeleteIcon from "@mui/icons-material/Delete";
import SelectAllIcon from "@mui/icons-material/SelectAll";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { useRouter } from "next/router";
interface Job { interface Job {
id: string; id: string;
@@ -25,13 +30,15 @@ interface Job {
interface JobTableProps { interface JobTableProps {
jobs: Job[]; jobs: Job[];
fetchJobs: () => void;
} }
const JobTable: React.FC<JobTableProps> = ({ jobs }) => { const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
const [selectedJobs, setSelectedJobs] = useState<Set<string>>(new Set());
const [allSelected, setAllSelected] = useState(false);
const router = useRouter(); const router = useRouter();
const handleDownload = async (id: string) => { const handleDownload = async (id: string) => {
console.log(id);
const response = await fetch("/api/download", { const response = await fetch("/api/download", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -63,6 +70,41 @@ const JobTable: React.FC<JobTableProps> = ({ jobs }) => {
}); });
}; };
const handleSelectJob = (id: string) => {
setSelectedJobs((prevSelected) => {
const newSelected = new Set(prevSelected);
if (newSelected.has(id)) {
newSelected.delete(id);
} else {
newSelected.add(id);
}
return newSelected;
});
};
const handleSelectAll = () => {
if (allSelected) {
setSelectedJobs(new Set());
} else {
const allJobIds = new Set(jobs.map((job) => job.id));
setSelectedJobs(allJobIds);
}
setAllSelected(!allSelected);
};
const handleDeleteSelected = async () => {
const response = await fetch("/api/delete-scrape-jobs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ids: Array.from(selectedJobs) }),
});
if (response.ok) {
fetchJobs();
setSelectedJobs(new Set());
}
};
return ( return (
<Box <Box
width="100%" width="100%"
@@ -79,13 +121,37 @@ const JobTable: React.FC<JobTableProps> = ({ jobs }) => {
bgcolor="background.default" bgcolor="background.default"
overflow="auto" overflow="auto"
> >
<Typography variant="h4" gutterBottom sx={{ mt: 3 }}> <Box
className="flex flex-row w-3/4 items-center p-2"
bgcolor="background.paper"
>
<Typography className="mr-2" variant="body1">
Scrape Jobs Scrape Jobs
</Typography> </Typography>
<Tooltip title="Select All">
<span>
<IconButton color="primary" onClick={handleSelectAll}>
<SelectAllIcon />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Delete Selected">
<span>
<IconButton
color="secondary"
onClick={handleDeleteSelected}
disabled={selectedJobs.size === 0}
>
<DeleteIcon />
</IconButton>
</span>
</Tooltip>
</Box>
<Box sx={{ overflow: "auto", width: "75%" }}> <Box sx={{ overflow: "auto", width: "75%" }}>
<Table sx={{ tableLayout: "fixed", width: "100%" }}> <Table sx={{ tableLayout: "fixed", width: "100%" }}>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell>Select</TableCell>
<TableCell>Id</TableCell> <TableCell>Id</TableCell>
<TableCell>Url</TableCell> <TableCell>Url</TableCell>
<TableCell>Elements</TableCell> <TableCell>Elements</TableCell>
@@ -97,6 +163,12 @@ const JobTable: React.FC<JobTableProps> = ({ jobs }) => {
<TableBody> <TableBody>
{jobs.map((row, index) => ( {jobs.map((row, index) => (
<TableRow key={index}> <TableRow key={index}>
<TableCell padding="checkbox">
<Checkbox
checked={selectedJobs.has(row.id)}
onChange={() => handleSelectJob(row.id)}
/>
</TableCell>
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}> <TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
<Box sx={{ maxHeight: 100, overflow: "auto" }}> <Box sx={{ maxHeight: 100, overflow: "auto" }}>
{row.id} {row.id}

View File

@@ -7,8 +7,7 @@ const Jobs = () => {
const { user } = useAuth(); const { user } = useAuth();
const [jobs, setJobs] = useState([]); const [jobs, setJobs] = useState([]);
useEffect(() => { const fetchJobs = () => {
if (user) {
fetch("/api/retrieve-scrape-jobs", { fetch("/api/retrieve-scrape-jobs", {
method: "POST", method: "POST",
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
@@ -19,6 +18,11 @@ const Jobs = () => {
.catch((error) => { .catch((error) => {
console.error("Error fetching jobs:", error); console.error("Error fetching jobs:", error);
}); });
};
useEffect(() => {
if (user) {
fetchJobs();
} else { } else {
setJobs([]); setJobs([]);
} }
@@ -27,7 +31,7 @@ const Jobs = () => {
return ( return (
<> <>
{user ? ( {user ? (
<JobTable jobs={jobs} /> <JobTable jobs={jobs} fetchJobs={fetchJobs} />
) : ( ) : (
<Box <Box
bgcolor="background.default" bgcolor="background.default"

View File

@@ -108,6 +108,10 @@ const darkTheme = createTheme({
h5: { h5: {
color: "#ffffff", color: "#ffffff",
}, },
body1: {
...commonThemeOptions.typography.body1,
color: "#ffffff",
},
}, },
components: { components: {
...commonThemeOptions.components, ...commonThemeOptions.components,