From 3bbd9a8e998cbe95f65b750956da8a0f712fe15b Mon Sep 17 00:00:00 2001
From: raheeqi <raheeqi@bu.edu>
Date: Thu, 31 Oct 2024 21:32:11 -0400
Subject: [PATCH 1/3] ui progress bar added

---
 .../graphql/resolvers/graphql_resolvers.ts    | 193 ++++++-----
 backend/graphql/schemas/type_definitions.ts   |  26 +-
 backend/graphql/types/types.ts                |  16 +-
 frontend/src/contexts/upload_context.tsx      |   9 +
 .../src/pages/UploadPage/CSVUploadPage.css    |  20 +-
 .../src/pages/UploadPage/CSVUploadPage.tsx    | 302 ++++++++++++------
 package-lock.json                             |  32 ++
 package.json                                  |   1 +
 8 files changed, 409 insertions(+), 190 deletions(-)

diff --git a/backend/graphql/resolvers/graphql_resolvers.ts b/backend/graphql/resolvers/graphql_resolvers.ts
index ec88ea3..d0b799b 100644
--- a/backend/graphql/resolvers/graphql_resolvers.ts
+++ b/backend/graphql/resolvers/graphql_resolvers.ts
@@ -10,7 +10,7 @@ import { Collection } from "mongodb";
 import { authMiddleware } from "../../authMiddleware.js";
 import axios from "axios";
 import { error } from "console";
-const proxy_Url = "http://35.229.106.189:80/upload_csv";
+const proxy_Url = process.env.REACT_APP_ML_PIP_URL || "";
 import FormData from 'form-data';
 import {createWriteStream} from 'fs';
 import { GraphQLUpload } from "graphql-upload-minimal";
@@ -18,6 +18,12 @@ import { GraphQLScalarType, GraphQLError} from 'graphql';
 import { FileUpload } from 'graphql-upload-minimal';
 import path from 'path';
 import { gql } from "@apollo/client";
+import { Readable } from 'stream';
+import { PubSub } from 'graphql-subscriptions';
+
+const pubsub = new PubSub();
+const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS';
+
 
 
 
@@ -31,119 +37,147 @@ interface UploadCSVArgs {
   file: Promise<FileUpload>; // `file` should be a promise that resolves to a FileUpload type
   userId: string;
 }
+// Helper function to simulate delayed progress
+const simulateProgressUpdate = (userId, filename, progress) => {
+  pubsub.publish("UPLOAD_PROGRESS", {
+    uploadProgress: { userId, filename, progress, status: "Uploading" },
+  });
+};
+
+const getLastTenUploads = async (userId, db) => {
+  const uploadData = db.collection("uploads");
+  return await uploadData
+      .find({ userId })
+      .sort({ timestamp: -1 })
+      .limit(10)
+      .toArray();
+};
 
 export const resolvers = {
   Upload: GraphQLUpload,
   Mutation: {
-      uploadCSV: async (_, { file, userId }: UploadCSVArgs, { db, req, res }) => {
-        
+    uploadCSV: async (_, { file, userId }, { db }) => {
       try {
-     //   await authMiddleware(req, res, () => {});
-  
-        const upload = await file; // Resolve the file promise to get Upload object
-
+        const upload = await file; // Resolve the file promise to get the Upload object
         if (!upload) {
           throw new Error("No file uploaded");
         }
 
-        // Destructure the resolved `upload` object to get file properties
         const { createReadStream, filename, mimetype } = upload;
-
-        // Check if `createReadStream` is defined
         if (!createReadStream) {
           throw new Error("Invalid file upload. `createReadStream` is not defined.");
         }
 
         console.log(`Uploading file: ${filename} (Type: ${mimetype}) for user: ${userId}`);
 
-        // Step 2: Create a read stream from the file
+        // Step 1: Buffer the file to calculate size
         const stream = createReadStream();
+        const chunks = [];
+        let fileSize = 0;
 
-        // Step 3: Prepare FormData for sending to an external service (optional)
+        for await (const chunk of stream) {
+          chunks.push(chunk);
+          fileSize += chunk.length;
+        }
+
+        const fileBuffer = Buffer.concat(chunks); // File is now fully buffered
+        const fileSizeKB = (fileSize / 1024).toFixed(1);
+        console.log(`File size: ${fileSizeKB} KB`);
+
+        // Step 2: Create a new stream from the buffer for uploading
+        const uploadStream = Readable.from(fileBuffer);
         const formData = new FormData();
+        formData.append("file", uploadStream, { filename });
+        formData.append("user_id", userId);
+
+        // Step 3: Upload with progress updates
+        const response = await axios.post(proxy_Url, formData, {
+          headers: {
+            ...formData.getHeaders(),
+            "X-API-KEY": "beri-stronk-key",
+          },
+          onUploadProgress: function (progressEvent) {
+            const progress = Math.min(
+              Math.round((progressEvent.loaded / fileSize) * 100),
+              100
+            );
 
+            // Emit progress update for the client
+            simulateProgressUpdate(userId, filename, progress);
 
-          const map = JSON.stringify({ "1": ["variables.file"] });
-          formData.append("operations", JSON.stringify({
-          query: `mutation UploadCSV($file: Upload!, $userId: String!): UploadStatus! {
-            uploadCSV(file: $file, user_id: $userId) {
-              filename
-              status
-            }
-          }`,
-          variables: { file: null, userId},
-          }));
-        formData.append("map", map);
-        formData.append("1", stream, { filename, contentType: mimetype });
-
-          formData.append('file', stream, { filename });
-          formData.append('user_id', userId);
-
-          // Step 4: Send the file to an external API 
-
-          console.log('URL being used in production:' , proxy_Url);
-        const response = await axios.post(
-            proxy_Url,
-          formData,
-          {
-              headers: {
-                ...formData.getHeaders(),
-                "X-API-KEY": "beri-stronk-key"
-              },
-          }
-        );
+            console.log(`Upload Progress: ${progress}%`);
+          },
+        });
 
         // Handle API response
         if (response.status === 200) {
-          console.log('File uploaded successfully to external API.');
+          console.log("File uploaded successfully to external API.");
+
+          // Publish completion message
+          pubsub.publish("UPLOAD_PROGRESS", {
+            uploadProgress: {
+              userId,
+              filename,
+              progress: 100,
+              status: "Upload complete!",
+            },
+          });
 
-          // Step 5: Save upload metadata to the database (if needed)
-          const uploadData = db.collection('uploads');
-            const result = await uploadData.insertOne({
+          const uploadData = db.collection("uploads");
+          const result = await uploadData.insertOne({
             userId,
             filename,
             timestamp: new Date(),
-            status: 'Success',
+            status: "Success",
+            size: fileSizeKB, 
           });
 
-          return { filename: filename, status: 'Success' };
+          return { filename, status: "Success" };
         } else {
-            throw new GraphQLError('Failed to upload CSV.');
+          throw new GraphQLError("Failed to upload CSV.");
         }
       } catch (error) {
-        console.error('Error uploading CSV:', error);
-          throw new GraphQLError('Error uploading CSV.');
+        console.error("Error uploading CSV:", error);
+        throw new GraphQLError("Error uploading CSV.");
       }
-  },
-	  addRssFeed: async (_, { url, userID }, { db, req, res }) => {
-		await authMiddleware(req, res, () => {});
-  
-		const decodedToken = JSON.parse(req.headers.user as string);
-		if (!decodedToken) {
-		  throw new Error('Unauthorized');
-		}
-  
-		if (decodedToken.sub !== userID) {
-		  throw new Error('Forbidden');
-		}
-  
-		const rss_data = db.collection("rss_links");
-  
-		// Create or update the RSS feed for the given userID
-		const filter = { userID: userID };
-		const update = {
-		  $set: { url: url, userID: userID },
-		};
-		const options = {
-		  upsert: true, // create a new document if no document matches the filter
-		  returnDocument: "after", // return the modified document
-		};
+    },
+
+    addRssFeed: async (_, { url, userID }, { db, req, res }) => {
+      await authMiddleware(req, res, () => {});
+    
+      const decodedToken = JSON.parse(req.headers.user as string);
+      if (!decodedToken) {
+        throw new Error('Unauthorized');
+      }
+    
+      if (decodedToken.sub !== userID) {
+        throw new Error('Forbidden');
+      }
+    
+      const rss_data = db.collection("rss_links");
+    
+      // Create or update the RSS feed for the given userID
+      const filter = { userID: userID };
+      const update = {
+        $set: { url: url, userID: userID },
+      };
+      const options = {
+        upsert: true, // create a new document if no document matches the filter
+        returnDocument: "after", // return the modified document
+      };
+    
+      const result = await rss_data.findOneAndUpdate(filter, update, options);
   
-		const result = await rss_data.findOneAndUpdate(filter, update, options);
+      return result.value;
+      },
+    },
+
+  Subscription: {
+    uploadProgress: {
+      subscribe: (_, { userId }) => pubsub.asyncIterator("UPLOAD_PROGRESS"),
+    },
+  },
 
-    return result.value;
-	  },
-	},
   Query: {
     // RSS Resolver
     getRssLinkByUserId: async (_, args, { db, req, res }) => {
@@ -163,6 +197,9 @@ export const resolvers = {
       const queryResult = await rss_data.find({ userID: args.userID }).toArray();
       return queryResult;
     },
+    lastTenUploads: async (_, { userId }, { db }) => {
+      return getLastTenUploads(userId, db);
+    },
     // CSV Upload Resolver
     getUploadByUserId: async (_, args, { db, req, res }) => {
       await authMiddleware(req, res, () => {});
diff --git a/backend/graphql/schemas/type_definitions.ts b/backend/graphql/schemas/type_definitions.ts
index 5a590ba..b05979c 100644
--- a/backend/graphql/schemas/type_definitions.ts
+++ b/backend/graphql/schemas/type_definitions.ts
@@ -9,6 +9,17 @@ export const typeDefs = gql`
 		userID: String!
 	}
 
+	type Subscription {
+  	uploadProgress(userId: String!): UploadProgress
+	}
+
+	type UploadProgress {
+  		userId: String!
+  		filename: String!
+  		progress: Int!
+  		status: String
+	}
+
 	type Rss_data {
 		userID: String!
 		title: String!
@@ -46,6 +57,19 @@ export const typeDefs = gql`
 		filename: String!
   		status: String!
 	}
+	
+	type Query {
+    lastTenUploads(userId: String!): [UploadHistory!]!
+}
+
+type UploadHistory {
+    uploadID: String
+    timestamp: String
+    article_cnt: Int
+    status: String
+    filename: String
+}
+
 
 
 	type Article {
@@ -135,4 +159,4 @@ export const typeDefs = gql`
 	}
 
 
-`;
+`;
\ No newline at end of file
diff --git a/backend/graphql/types/types.ts b/backend/graphql/types/types.ts
index bc1f66a..c5f6bef 100644
--- a/backend/graphql/types/types.ts
+++ b/backend/graphql/types/types.ts
@@ -18,23 +18,15 @@ export interface DemographicsByTractsArgs {
     tract: string;F
 }
 
-export interface IUploadedFile {
-    name: string;
-    size: number;
-    progress: number;
-    status: string;
-    file: FileUpload;
-    error?: string;
-}
 
 type UploadedFile = {
     name: string;
     size: number;
-    progress: number;
+    progress: number; // Track upload progress here
     status: string;
-    error?: string; // fail to pass test
-    file: File; // store a reference to the File object
-};	
+    error?: string;
+    file: File;
+};
 
 // *** Data Types derived from the collections ***
 
diff --git a/frontend/src/contexts/upload_context.tsx b/frontend/src/contexts/upload_context.tsx
index ef2157c..774a5f8 100644
--- a/frontend/src/contexts/upload_context.tsx
+++ b/frontend/src/contexts/upload_context.tsx
@@ -24,6 +24,15 @@ const UPLOAD_DATA_QUERY = gql`
   }
 `;
 
+export const GET_UPLOAD_PROGRESS = gql`
+    query GetUploadProgress($userId: String!) {
+        uploadProgress(userId: $userId) {
+            progress
+            status
+        }
+    }
+`;
+
 export const UPLOAD_CSV_MUTATION = gql`
 mutation UploadCSV($file: Upload!, $userId: String!) {
   uploadCSV(file: $file, userId: $userId) {
diff --git a/frontend/src/pages/UploadPage/CSVUploadPage.css b/frontend/src/pages/UploadPage/CSVUploadPage.css
index e221e6e..7d23326 100644
--- a/frontend/src/pages/UploadPage/CSVUploadPage.css
+++ b/frontend/src/pages/UploadPage/CSVUploadPage.css
@@ -96,10 +96,6 @@
     font-weight: bold;
 }
 
-.file-upload-status {
-    display: flex;
-    margin-bottom: 10px;
-}
 
 .file-name {
     display: flex;
@@ -141,6 +137,22 @@
     align-items: center;
 }
 
+.file-progress {
+    width: 100%;
+    padding: 0 10px;
+}
+.file-upload-status {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px 0;
+    border-bottom: 1px solid #e0e0e0;
+}
+.file-name, .file-size, .file-status, .file-actions, .file-progress {
+    flex: 1;
+    text-align: center;
+}
+
 .alert-message {
     position: fixed;
     top: 0;
diff --git a/frontend/src/pages/UploadPage/CSVUploadPage.tsx b/frontend/src/pages/UploadPage/CSVUploadPage.tsx
index a6e3637..666a565 100644
--- a/frontend/src/pages/UploadPage/CSVUploadPage.tsx
+++ b/frontend/src/pages/UploadPage/CSVUploadPage.tsx
@@ -13,10 +13,37 @@ import HelpIcon from '@mui/icons-material/Help';
 import Tooltip from '@mui/material/Tooltip';
 import Modal from '@mui/material/Modal';
 import Box from '@mui/material/Box';
-import { gql } from "@apollo/client";
+import { gql, useSubscription } from "@apollo/client";
 import ArrowBackIcon from '@mui/icons-material/ArrowBack';
+import LinearProgress from '@mui/material/LinearProgress';
 
 
+
+
+
+
+const UPLOAD_PROGRESS_SUBSCRIPTION = gql`
+  subscription uploadProgress($userId: String!) {
+    uploadProgress(userId: $userId) {
+      filename
+      progress
+      status
+    }
+  }
+`;
+
+const LAST_TEN_UPLOADS_QUERY = gql`
+    query lastTenUploads($userId: String!) {
+        lastTenUploads(userId: $userId) {
+            uploadID
+            timestamp
+            article_cnt
+            status
+            filename
+        }
+    }
+`;
+
 // Define a type for the file with progress information
 type UploadedFile = {
     name: string;
@@ -40,6 +67,7 @@ export const CSVUploadBox = () => {
     const [uploads, setUpload] = useState<Uploads[]>([]);
     const { user, isSignedIn } = useUser();
     const { organization } = useOrganization();
+    
 
     
     
@@ -52,6 +80,24 @@ export const CSVUploadBox = () => {
     const handleOpen = () => setOpen(true);
     const handleClose = () => setOpen(false);   
 
+    const { data: progressData } = useSubscription(UPLOAD_PROGRESS_SUBSCRIPTION, {
+        variables: { userId: organization ? organization.id : user?.id },
+        shouldResubscribe: true,
+        onSubscriptionData: ({ subscriptionData }) => {
+          const uploadProgress = subscriptionData.data.uploadProgress;
+          if (uploadProgress) {
+            const { filename, progress, status } = uploadProgress;
+            setUploadedFiles((prevFiles) =>
+              prevFiles.map((file) =>
+                file.name === filename
+                  ? { ...file, progress, status: status || file.status }
+                  : file
+              )
+            );
+          }
+        },
+      });
+
     useEffect(() => {
         if (isSignedIn && user) {
             if (organization) {
@@ -201,62 +247,138 @@ export const CSVUploadBox = () => {
 		},
 	  });
     
-    const submitFile = () => {
-        console.log("submitting file");
-      for (let i = 0; i < submittedFiles.length; i++) {
-        const file = submittedFiles[i];
-        console.log("Type of file:", file instanceof File); 
-
-        if (user && isSignedIn) {
-          const variables = {
-            file,
-            userId: organization ? organization.id : user.id, 
-          };
-          console.log( typeof user.id);
-
-          console.log("logging variables: ", variables);
-    
-          uploadCSV({ variables })
-          .then((response) => {
-            // Check if the response contains the expected data
-            if (response.data && response.data.uploadCSV && response.data.uploadCSV.status === 'Success') {
-                setSuccessMessage("Successfully submitted!");
-                setTimeout(() => setSuccessMessage(""), 3000);
-              } else if (response.errors) {
-                // Handle GraphQL errors
-                console.error("GraphQL Errors:", response.errors);
-                const errorMessage = response.errors[0]?.message || "Failed to upload CSV."; // Extract the error message
-                setAlertMessage(errorMessage);
-                setTimeout(() => setAlertMessage(""), 3000);
-              } else {
-                // Handle unexpected responses
-                console.error("Unexpected response:", response);
-                setAlertMessage("Failed to upload CSV: Unexpected response.");
-                setTimeout(() => setAlertMessage(""), 3000);
-              }
-          })
-          .catch((error) => {
-            console.error("Error during CSV upload:", error);
-            setAlertMessage("Failed to upload CSV.");
-            setTimeout(() => setAlertMessage(""), 3000);
-          });
-      } else {
-        console.error("User is not signed in");
-      }
-      }
+      const submitFile = () => {
+        for (let i = 0; i < submittedFiles.length; i++) {
+            const file = submittedFiles[i];
+    
+            if (user && isSignedIn) {
+                const variables = {
+                    file,
+                    userId: organization ? organization.id : user.id,
+                };
+    
+                // Set initial progress to indicate the upload has started
+                setUploadedFiles((prevFiles) =>
+                    prevFiles.map((f) =>
+                        f.name === file.name ? { ...f, progress: 10, status: 'Uploading...' } : f
+                    )
+                );
+    
+                const reader = new FileReader();
+                reader.onload = () => {
+                    const text = reader.result as string;
+                    const lines = text.split(/\r?\n/);
+                    const articleCount = lines.length - 1;
+    
+                    uploadCSV({ variables })
+                        .then((response) => {
+                            if (response.data && response.data.uploadCSV && response.data.uploadCSV.status === 'Success') {
+                                // Simulate gradual progress update
+                                let progress = 10;
+                                const interval = setInterval(() => {
+                                    progress += 15;
+                                    setUploadedFiles((prevFiles) =>
+                                        prevFiles.map((f) =>
+                                            f.name === file.name
+                                                ? { ...f, progress: Math.min(progress, 100) }
+                                                : f
+                                        )
+                                    );
+    
+                                    // Finalize when progress reaches 100%
+                                    if (progress >= 100) {
+                                        clearInterval(interval);
+    
+                                        // Set final status to complete
+                                        setUploadedFiles((prevFiles) =>
+                                            prevFiles.map((f) =>
+                                                f.name === file.name ? { ...f, progress: 100, status: 'Upload complete!' } : f
+                                            )
+                                        );
+    
+                                        // Display success message
+                                        setSuccessMessage("File uploaded successfully!");
+    
+                                        // Delay file removal from 'uploaded files' table
+                                        setTimeout(() => {
+                                            setSuccessMessage("");
+    
+                                            // Add to upload history
+                                            setUpload((prevUploads) => [
+                                                ...prevUploads,
+                                                {
+                                                    uploadID: Math.random().toString(36).substr(2, 9),
+                                                    timestamp: new Date().toISOString(),
+                                                    article_cnt: articleCount,
+                                                    status: 'Success',
+                                                    userID: user.id,
+                                                    message: '',
+                                                },
+                                            ]);
+    
+                                            // Now remove the file from the 'uploaded files' table
+                                            setUploadedFiles((prevFiles) =>
+                                                prevFiles.filter((f) => f.name !== file.name)
+                                            );
+                                        }, 3000); // Delay for the green success message to display
+                                    }
+                                }, 500); // Adjust interval for progress update frequency
+                            } else {
+                                throw new Error("Unexpected response"); // Force catch for unexpected responses
+                            }
+                        })
+                        .catch((error) => {
+                            console.error("Error during CSV upload:", error);
+    
+                            // Update progress to stop at failure and set status to failed
+                            setUploadedFiles((prevFiles) =>
+                                prevFiles.map((f) =>
+                                    f.name === file.name
+                                        ? { ...f, progress: 0, status: 'Upload failed!' }
+                                        : f
+                                )
+                            );
+    
+                            // Show alert message
+                            setAlertMessage("Failed to upload CSV.");
+                            setTimeout(() => setAlertMessage(""), 3000);
+                        });
+                };
+    
+                // Start reading the file to calculate line count
+                reader.readAsText(file);
+            } else {
+                console.error("User is not signed in");
+            }
+        }
     };
+    
+    
+    
+    
 
+
+    
+    
+    
     // Function that simulates file upload and updates progress
     // Need to change this to real func converting csv into json as input to backend
+
+
+    
     const uploadFile = (file: File) => {
+        const sizeKB = (file.size / 1024).toFixed(1); // Convert size to KB
+    
         const newFile: UploadedFile = {
             name: file.name,
-            size: file.size,
+            size: Number(sizeKB), // Store size in KB directly for easier rendering
             progress: 0,
             status: "Uploading...",
             file: file,
         };
     
+        console.log(`File name: ${file.name}, File size in KB: ${sizeKB}`); // Confirm formatted size here
+    
         setUploadedFiles((prevFiles) => [...prevFiles, newFile]);
 
         validateCsvHeaders(file, (missingHeaders, missingDataWarnings) => {
@@ -296,8 +418,14 @@ export const CSVUploadBox = () => {
 
     // Handle file selection
     const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-        if (event.target.files) {
-            Array.from(event.target.files).forEach(uploadFile);
+        const files = event.target.files;
+        if (files && files.length > 0) {
+            const file = files[0];
+            const fileSize = file.size; // Get the file size directly
+            console.log(`File size: ${fileSize} bytes`);
+
+            // Call your upload function with the file and its size
+            uploadFile(file);
         }
     };
 
@@ -315,6 +443,7 @@ export const CSVUploadBox = () => {
     // Handle file submit
     const handleFileSubmit = (files: File[]) => {
         submitFile();
+        /*
         // clear out uploaded Files, validated files (submitted files listen onto validated files)
         for (let i = 0; i < files.length; i++) {
             setUploadedFiles((prevFiles) =>
@@ -324,6 +453,7 @@ export const CSVUploadBox = () => {
                 prevFiles.filter((f) => f.name != files[i].name),
             );
         }
+            */
         // need some logic to handle file history
     };
     //If the user is does not have access to the upload page
@@ -557,45 +687,38 @@ export const CSVUploadBox = () => {
                             <span>ACTIONS</span>
                         </div>
                         {uploadedFiles.map((file, index) => (
-                            <div key={index} className='file-upload-status'>
-                                {/* File Name Column */}
-                                <div className='file-name'>
-                                    <span>{file.name}</span>
-                                </div>
+                        <div key={index} className='file-upload-status'>
+                            {/* File Name Column */}
+                            <div className='file-name'>
+                                <span>{file.name}</span>
+                            </div>
 
-                                {/* File Size Column */}
-                                <div className='file-size'>
-                                    <span>
-                                        {(file.size / (1024 * 1024)).toFixed(1)}{" "}
-                                        MB
-                                    </span>
-                                </div>
+                            {/* File Size Column */}
+                            <div className='file-size'>
+    <span>{file.size} KB</span>
+</div>
 
-                                {/* Upload Status Column */}
-                                <div className='file-upload-progress-wrapper'>
-                                    <div className='file-status'>
-                                        {file.status}
-                                    </div>
-                                {file.error && (
-                                    <div className='error-message'>
-                                        {file.error}
-                                    </div>
-                                )}
-                                </div>
-                                {/* Action Column */}
-                                <div className='file-actions'>
-                                    {(file.status === "Passed" ||
-                                        file.error) && (
-                                        <Button
+                            {/* Validation Status Column */}
+                            <div className='file-status'>
+                                {file.status}
+                            </div>
+
+                            {/* Progress Column */}
+                            <div className='file-progress'>
+                                <LinearProgress variant="determinate" value={file.progress} />
+                            </div>
+
+                            {/* Action Column */}
+                            <div className='file-actions'>
+                                {(file.status === "Passed" || file.error) && (
+                                    <Button
                                         variant="outlined"
                                         color="error"
                                         size="small"
-                                        onClick={() =>
-                                            handleFileRemoval(file.name)
-                                        }
-                                        >
-                                            Delete
-                                        </Button>
+                                        onClick={() => handleFileRemoval(file.name)}
+                                    >
+                                        Delete
+                                    </Button>
                                     )}
                                 </div>
                             </div>
@@ -624,30 +747,19 @@ export const CSVUploadBox = () => {
                         </div>
                         {uploads.map((file, index) => (
                             <div key={index} className='file-upload-status'>
-                                {/* Upload ID Column */}
                                 <div className='file-uploadId'>
                                     <span>{file.uploadID}</span>
                                 </div>
-
-                                {/* Time Stamp Column */}
                                 <div className='file-timeStamp'>
                                     <span>
-                                        {new Date(
-                                            file.timestamp,
-                                        ).toLocaleString()}
+                                        {new Date(file.timestamp).toLocaleString()}
                                     </span>
                                 </div>
-
-                                {/* Article cnt Column */}
                                 <div className='file-articleCnt'>
                                     <span>
-                                        {file.article_cnt === -1
-                                            ? 0
-                                            : file.article_cnt}
+                                        {file.article_cnt === -1 ? 0 : file.article_cnt}
                                     </span>
                                 </div>
-
-                                {/* http status Column */}
                                 <div className='file-httpStatus'>
                                     <span>{file.status}</span>
                                 </div>
@@ -660,4 +772,4 @@ export const CSVUploadBox = () => {
     );
 };
 
-export default CSVUploadBox;
+export default CSVUploadBox;
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 766b667..18dcb78 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
         "express-fileupload": "^1.5.1",
         "font-awesome": "^4.7.0",
         "graphql": "^16.8.1",
+        "graphql-subscriptions": "^2.0.0",
         "graphql-upload-minimal": "^1.6.1",
         "koa": "^2.15.3",
         "node-fetch": "^2.7.0",
@@ -3643,6 +3644,18 @@
         "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
       }
     },
+    "node_modules/graphql-subscriptions": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz",
+      "integrity": "sha512-s6k2b8mmt9gF9pEfkxsaO1lTxaySfKoEJzEfmwguBbQ//Oq23hIXCfR1hm4kdh5hnR20RdwB+s3BCb+0duHSZA==",
+      "license": "MIT",
+      "dependencies": {
+        "iterall": "^1.3.0"
+      },
+      "peerDependencies": {
+        "graphql": "^15.7.2 || ^16.0.0"
+      }
+    },
     "node_modules/graphql-tag": {
       "version": "2.12.6",
       "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
@@ -4131,6 +4144,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/iterall": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz",
+      "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==",
+      "license": "MIT"
+    },
     "node_modules/jest": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
@@ -10661,6 +10680,14 @@
       "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
       "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw=="
     },
+    "graphql-subscriptions": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz",
+      "integrity": "sha512-s6k2b8mmt9gF9pEfkxsaO1lTxaySfKoEJzEfmwguBbQ//Oq23hIXCfR1hm4kdh5hnR20RdwB+s3BCb+0duHSZA==",
+      "requires": {
+        "iterall": "^1.3.0"
+      }
+    },
     "graphql-tag": {
       "version": "2.12.6",
       "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
@@ -11002,6 +11029,11 @@
         "istanbul-lib-report": "^3.0.0"
       }
     },
+    "iterall": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz",
+      "integrity": "sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg=="
+    },
     "jest": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
diff --git a/package.json b/package.json
index 9c60817..090da92 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
     "express-fileupload": "^1.5.1",
     "font-awesome": "^4.7.0",
     "graphql": "^16.8.1",
+    "graphql-subscriptions": "^2.0.0",
     "graphql-upload-minimal": "^1.6.1",
     "koa": "^2.15.3",
     "node-fetch": "^2.7.0",

From d1e6b0289b9c5cadf9599dc5756b8157dd61eb36 Mon Sep 17 00:00:00 2001
From: raheeqi <raheeqi@bu.edu>
Date: Sun, 3 Nov 2024 12:10:54 -0500
Subject: [PATCH 2/3] upload history works

---
 .../graphql/resolvers/graphql_resolvers.ts    |  55 +++++---
 backend/graphql/schemas/type_definitions.ts   |  16 ++-
 frontend/src/contexts/upload_context.tsx      |  30 ++--
 .../src/pages/UploadPage/CSVUploadPage.tsx    | 129 +++++++++++-------
 package-lock.json                             |  29 ++++
 package.json                                  |   2 +
 6 files changed, 170 insertions(+), 91 deletions(-)

diff --git a/backend/graphql/resolvers/graphql_resolvers.ts b/backend/graphql/resolvers/graphql_resolvers.ts
index d0b799b..6439e40 100644
--- a/backend/graphql/resolvers/graphql_resolvers.ts
+++ b/backend/graphql/resolvers/graphql_resolvers.ts
@@ -23,6 +23,7 @@ import { PubSub } from 'graphql-subscriptions';
 
 const pubsub = new PubSub();
 const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS';
+const UPLOAD_STATUS_UPDATED = 'UPLOAD_STATUS_UPDATED';
 
 
 
@@ -44,14 +45,17 @@ const simulateProgressUpdate = (userId, filename, progress) => {
   });
 };
 
-const getLastTenUploads = async (userId, db) => {
-  const uploadData = db.collection("uploads");
-  return await uploadData
-      .find({ userId })
-      .sort({ timestamp: -1 })
-      .limit(10)
-      .toArray();
-};
+// const getLastTenUploads = async (userId, db) => {
+//   const uploadData = db.collection("uploads");
+//   return await uploadData
+//     .find({ userId })
+//     .sort({ timestamp: -1 })
+//     .limit(10)
+//     .map(upload => ({ ...upload, article_cnt: upload.article_cnt || 0 })) // Set default value to 0 if null
+//     .toArray();
+
+    
+// };
 
 export const resolvers = {
   Upload: GraphQLUpload,
@@ -124,12 +128,19 @@ export const resolvers = {
           });
 
           const uploadData = db.collection("uploads");
-          const result = await uploadData.insertOne({
-            userId,
-            filename,
-            timestamp: new Date(),
-            status: "Success",
-            size: fileSizeKB, 
+          // const result = await uploadData.insertOne({
+          //   userId,
+          //   filename,
+          //   timestamp: new Date(),
+          //   status: "Success",
+          //   size: fileSizeKB, 
+          // });
+
+          await db.collection("uploads").watch().on('change', (change) => {
+            const updatedUpload = change.fullDocument;
+            pubsub.publish(UPLOAD_STATUS_UPDATED, {
+              uploadStatusUpdated: updatedUpload,
+            });
           });
 
           return { filename, status: "Success" };
@@ -176,6 +187,9 @@ export const resolvers = {
     uploadProgress: {
       subscribe: (_, { userId }) => pubsub.asyncIterator("UPLOAD_PROGRESS"),
     },
+    uploadStatusUpdated: {
+      subscribe: () => pubsub.asyncIterator([UPLOAD_STATUS_UPDATED]),
+    },
   },
 
   Query: {
@@ -197,8 +211,17 @@ export const resolvers = {
       const queryResult = await rss_data.find({ userID: args.userID }).toArray();
       return queryResult;
     },
-    lastTenUploads: async (_, { userId }, { db }) => {
-      return getLastTenUploads(userId, db);
+      lastTenUploads: async (_, { userId }, { db }) => {
+        const uploads = await db.collection("uploads")
+          .find({ userID: userId })
+          .sort({ timestamp: -1 })
+          .limit(10)
+          .toArray();
+  
+        return uploads.map(upload => ({
+          ...upload,
+          uploadID: upload.uploadID  
+        }));
     },
     // CSV Upload Resolver
     getUploadByUserId: async (_, args, { db, req, res }) => {
diff --git a/backend/graphql/schemas/type_definitions.ts b/backend/graphql/schemas/type_definitions.ts
index b05979c..c412b12 100644
--- a/backend/graphql/schemas/type_definitions.ts
+++ b/backend/graphql/schemas/type_definitions.ts
@@ -40,6 +40,10 @@ export const typeDefs = gql`
 		message: String!
 	}
 
+	type Subscription {
+  uploadStatusUpdated: Uploads
+}
+
 	type Mutation {
   		# Define a new mutation for uploading a CSV file
   		uploadCSV(file: Upload!, userId: String!): UploadStatus!
@@ -63,11 +67,13 @@ export const typeDefs = gql`
 }
 
 type UploadHistory {
-    uploadID: String
-    timestamp: String
-    article_cnt: Int
-    status: String
-    filename: String
+    uploadID: String!
+	article_cnt: Int!
+	message: String!
+	status: String!
+    timestamp: String!
+    userID: String!
+	
 }
 
 
diff --git a/frontend/src/contexts/upload_context.tsx b/frontend/src/contexts/upload_context.tsx
index 774a5f8..4289d94 100644
--- a/frontend/src/contexts/upload_context.tsx
+++ b/frontend/src/contexts/upload_context.tsx
@@ -1,5 +1,5 @@
-import React from "react";
-import { useLazyQuery, gql, useMutation } from "@apollo/client";
+import React, { useEffect } from "react";
+import { useQuery, gql, useMutation } from "@apollo/client";
 import { Uploads } from "../__generated__/graphql";
 
 type UploadContextType = {
@@ -9,16 +9,12 @@ type UploadContextType = {
   uploadCSV: (file: File, userID: string) => void;
 };
 
-/* Upload Queries */
-// We will pass what we need in here
 const UPLOAD_DATA_QUERY = gql`
   query GetUploadByUserId($userId: String!) {
     getUploadByUserId(user_id: $userId) {
-      article_cnt
-      message
+      uploadID
       status
       timestamp
-      uploadID
       userID
     }
   }
@@ -63,11 +59,11 @@ const UploadProvider: React.FC = ({ children }: any) => {
 
   const [uploadCSVMutation, { loading: uploadingCSV, error: uploadCSVError }] = useMutation(UPLOAD_CSV_MUTATION);
 
+  const { data: uploadData, loading: uploadDataLoading, error: uploadDataError } = useQuery(UPLOAD_DATA_QUERY, {
+    variables: { userId: "user-id-placeholder" }, // Replace with dynamic user ID as needed
+    fetchPolicy: "network-only", // Ensures data is fresh on each load
+  });
 
-  const [
-    queryUploadData,
-    { data: uploadData, loading: uploadDataLoading, error: uploadDataError },
-  ] = useLazyQuery(UPLOAD_DATA_QUERY);
   const [uploads, setUploadData] = React.useState<Uploads[] | null>(null);
 
   React.useEffect(() => {
@@ -96,18 +92,8 @@ const UploadProvider: React.FC = ({ children }: any) => {
       });
   };
 
-
   const queryUploadDataType = (queryType: "UPLOAD_DATA", options?: any) => {
-    switch (queryType) {
-      case "UPLOAD_DATA":
-        queryUploadData({
-          variables: options,
-        });
-        break;
-      default:
-        console.log("ERROR: Fetch Upload Data does not have this query Type!");
-        break;
-    }
+    // No longer needed as useQuery will handle loading on component mount
   };
 
   return (
diff --git a/frontend/src/pages/UploadPage/CSVUploadPage.tsx b/frontend/src/pages/UploadPage/CSVUploadPage.tsx
index 666a565..ce0ad8e 100644
--- a/frontend/src/pages/UploadPage/CSVUploadPage.tsx
+++ b/frontend/src/pages/UploadPage/CSVUploadPage.tsx
@@ -8,7 +8,7 @@ import "./CSVUploadPage.css";
 import { UploadContext, UPLOAD_CSV_MUTATION } from "../../contexts/upload_context";
 import { Uploads } from "../../__generated__/graphql";
 import { useOrganization, useUser, useAuth } from "@clerk/clerk-react";
-import { useMutation } from "@apollo/client";
+import { useMutation, useQuery } from "@apollo/client";
 import HelpIcon from '@mui/icons-material/Help';
 import Tooltip from '@mui/material/Tooltip';
 import Modal from '@mui/material/Modal';
@@ -16,9 +16,7 @@ import Box from '@mui/material/Box';
 import { gql, useSubscription } from "@apollo/client";
 import ArrowBackIcon from '@mui/icons-material/ArrowBack';
 import LinearProgress from '@mui/material/LinearProgress';
-
-
-
+import { DateTime } from "luxon";
 
 
 
@@ -36,14 +34,28 @@ const LAST_TEN_UPLOADS_QUERY = gql`
     query lastTenUploads($userId: String!) {
         lastTenUploads(userId: $userId) {
             uploadID
-            timestamp
             article_cnt
+            message
             status
-            filename
+            timestamp
+            userID
+            
         }
     }
 `;
 
+
+const UPLOAD_STATUS_UPDATED_SUBSCRIPTION = gql`
+  subscription OnUploadStatusUpdated {
+    uploadStatusUpdated {
+      uploadID
+      status
+      message
+      article_cnt
+    }
+  }
+`;
+
 // Define a type for the file with progress information
 type UploadedFile = {
     name: string;
@@ -98,6 +110,48 @@ export const CSVUploadBox = () => {
         },
       });
 
+      const { data, loading, error, refetch: refetchLastTenUploads } = useQuery(LAST_TEN_UPLOADS_QUERY, {
+        variables: { userId: organization ? organization.id : user?.id },
+        skip: !isSignedIn,
+        onCompleted: (data) => {
+            console.log("Fetched data for last ten uploads:", data);
+        },
+    });
+    
+
+    useSubscription(UPLOAD_STATUS_UPDATED_SUBSCRIPTION, {
+        onSubscriptionData: ({ subscriptionData }) => {
+            const updatedUpload = subscriptionData.data.uploadStatusUpdated;
+            setUpload((prevUploads: any) =>
+                prevUploads.map((upload: any) =>
+                    upload.uploadID === updatedUpload.uploadID
+                        ? {
+                            ...upload,
+                            status: updatedUpload.status,
+                            message: updatedUpload.message,
+                            article_cnt: updatedUpload.article_cnt,
+                        }
+                        : upload
+                )
+            );
+        },
+    });
+
+    const extractProgress = (message: any) => {
+        const match = message?.match(/\[(\d+\/\d+)\]/);
+        return match ? match[0] : "[0/0]";
+    };
+    
+
+
+      useEffect(() => {
+        if (data) {
+          setUpload(data.lastTenUploads);
+        }
+      }, [data]);
+
+
+
     useEffect(() => {
         if (isSignedIn && user) {
             if (organization) {
@@ -169,7 +223,6 @@ export const CSVUploadBox = () => {
                 "Headline",
                 "Publisher",
                 "Byline",
-                "content_id",
                 "Paths",
                 "Publish Date",
                 "Body",
@@ -257,7 +310,6 @@ export const CSVUploadBox = () => {
                     userId: organization ? organization.id : user.id,
                 };
     
-                // Set initial progress to indicate the upload has started
                 setUploadedFiles((prevFiles) =>
                     prevFiles.map((f) =>
                         f.name === file.name ? { ...f, progress: 10, status: 'Uploading...' } : f
@@ -266,14 +318,9 @@ export const CSVUploadBox = () => {
     
                 const reader = new FileReader();
                 reader.onload = () => {
-                    const text = reader.result as string;
-                    const lines = text.split(/\r?\n/);
-                    const articleCount = lines.length - 1;
-    
                     uploadCSV({ variables })
                         .then((response) => {
-                            if (response.data && response.data.uploadCSV && response.data.uploadCSV.status === 'Success') {
-                                // Simulate gradual progress update
+                            if (response.data?.uploadCSV?.status === 'Success') {
                                 let progress = 10;
                                 const interval = setInterval(() => {
                                     progress += 15;
@@ -285,67 +332,42 @@ export const CSVUploadBox = () => {
                                         )
                                     );
     
-                                    // Finalize when progress reaches 100%
                                     if (progress >= 100) {
                                         clearInterval(interval);
-    
-                                        // Set final status to complete
                                         setUploadedFiles((prevFiles) =>
                                             prevFiles.map((f) =>
                                                 f.name === file.name ? { ...f, progress: 100, status: 'Upload complete!' } : f
                                             )
                                         );
     
-                                        // Display success message
                                         setSuccessMessage("File uploaded successfully!");
-    
-                                        // Delay file removal from 'uploaded files' table
                                         setTimeout(() => {
                                             setSuccessMessage("");
-    
-                                            // Add to upload history
-                                            setUpload((prevUploads) => [
-                                                ...prevUploads,
-                                                {
-                                                    uploadID: Math.random().toString(36).substr(2, 9),
-                                                    timestamp: new Date().toISOString(),
-                                                    article_cnt: articleCount,
-                                                    status: 'Success',
-                                                    userID: user.id,
-                                                    message: '',
-                                                },
-                                            ]);
-    
-                                            // Now remove the file from the 'uploaded files' table
                                             setUploadedFiles((prevFiles) =>
                                                 prevFiles.filter((f) => f.name !== file.name)
                                             );
-                                        }, 3000); // Delay for the green success message to display
+    
+                                            // Refetch the latest uploads from MongoDB after each successful upload
+                                            refetchLastTenUploads();
+                                        }, 3000);
                                     }
-                                }, 500); // Adjust interval for progress update frequency
+                                }, 500);
                             } else {
-                                throw new Error("Unexpected response"); // Force catch for unexpected responses
+                                throw new Error("Unexpected response");
                             }
                         })
                         .catch((error) => {
                             console.error("Error during CSV upload:", error);
-    
-                            // Update progress to stop at failure and set status to failed
                             setUploadedFiles((prevFiles) =>
                                 prevFiles.map((f) =>
-                                    f.name === file.name
-                                        ? { ...f, progress: 0, status: 'Upload failed!' }
-                                        : f
+                                    f.name === file.name ? { ...f, progress: 0, status: 'Upload failed!' } : f
                                 )
                             );
-    
-                            // Show alert message
                             setAlertMessage("Failed to upload CSV.");
                             setTimeout(() => setAlertMessage(""), 3000);
                         });
                 };
     
-                // Start reading the file to calculate line count
                 reader.readAsText(file);
             } else {
                 console.error("User is not signed in");
@@ -356,6 +378,8 @@ export const CSVUploadBox = () => {
     
     
     
+    
+    
 
 
     
@@ -470,6 +494,12 @@ export const CSVUploadBox = () => {
         );
     }
 
+    function parseTimestamp(timestamp: any) {
+        // Insert "T" between date and time to make it ISO-compliant
+        const isoTimestamp = timestamp.replace(" ", "T");
+        return new Date(isoTimestamp + "Z");
+    }
+
     return (
         <div>
             <div className='RSS-link'>
@@ -752,7 +782,7 @@ export const CSVUploadBox = () => {
                                 </div>
                                 <div className='file-timeStamp'>
                                     <span>
-                                        {new Date(file.timestamp).toLocaleString()}
+                                    {file.timestamp ? parseTimestamp(file.timestamp).toLocaleString() : "No Timestamp Available"}
                                     </span>
                                 </div>
                                 <div className='file-articleCnt'>
@@ -761,7 +791,10 @@ export const CSVUploadBox = () => {
                                     </span>
                                 </div>
                                 <div className='file-httpStatus'>
-                                    <span>{file.status}</span>
+                                    <span>{file.status === "PROCESSING"
+                                            ? `PROCESSING ${extractProgress(file.message)} ARTICLES`
+                                            : file.status}
+                                    </span>
                                 </div>
                             </div>
                         ))}
diff --git a/package-lock.json b/package-lock.json
index 18dcb78..dbb3a57 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
         "graphql-subscriptions": "^2.0.0",
         "graphql-upload-minimal": "^1.6.1",
         "koa": "^2.15.3",
+        "luxon": "^3.5.0",
         "node-fetch": "^2.7.0",
         "react-loader-spinner": "^5.4.5",
         "react-router-dom": "^6.22.3",
@@ -23,6 +24,7 @@
         "@testing-library/jest-dom": "^6.4.2",
         "@testing-library/react": "^15.0.5",
         "@types/jest": "^29.5.12",
+        "@types/luxon": "^3.4.2",
         "@types/xmldom": "^0.1.33",
         "jest": "^29.7.0",
         "ts-jest": "^29.1.2"
@@ -2131,6 +2133,13 @@
       "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
       "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
     },
+    "node_modules/@types/luxon": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
+      "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/mime": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
@@ -6272,6 +6281,15 @@
         "node": ">=12"
       }
     },
+    "node_modules/luxon": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
+      "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/lz-string": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@@ -9542,6 +9560,12 @@
       "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
       "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
     },
+    "@types/luxon": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
+      "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
+      "dev": true
+    },
     "@types/mime": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
@@ -12616,6 +12640,11 @@
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
       "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
     },
+    "luxon": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
+      "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ=="
+    },
     "lz-string": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
diff --git a/package.json b/package.json
index 090da92..ff4b077 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
     "graphql-subscriptions": "^2.0.0",
     "graphql-upload-minimal": "^1.6.1",
     "koa": "^2.15.3",
+    "luxon": "^3.5.0",
     "node-fetch": "^2.7.0",
     "react-loader-spinner": "^5.4.5",
     "react-router-dom": "^6.22.3",
@@ -18,6 +19,7 @@
     "@testing-library/jest-dom": "^6.4.2",
     "@testing-library/react": "^15.0.5",
     "@types/jest": "^29.5.12",
+    "@types/luxon": "^3.4.2",
     "@types/xmldom": "^0.1.33",
     "jest": "^29.7.0",
     "ts-jest": "^29.1.2"

From 6761732adee4e12ed1456f8184ef4cfbf234a6cd Mon Sep 17 00:00:00 2001
From: Nikhil Ramchandani <ramchandaninikhil01@gmail.com>
Date: Sun, 3 Nov 2024 15:23:30 -0500
Subject: [PATCH 3/3] removed import

---
 frontend/src/pages/UploadPage/CSVUploadPage.tsx | 1 -
 1 file changed, 1 deletion(-)

diff --git a/frontend/src/pages/UploadPage/CSVUploadPage.tsx b/frontend/src/pages/UploadPage/CSVUploadPage.tsx
index ce0ad8e..ba306a7 100644
--- a/frontend/src/pages/UploadPage/CSVUploadPage.tsx
+++ b/frontend/src/pages/UploadPage/CSVUploadPage.tsx
@@ -16,7 +16,6 @@ import Box from '@mui/material/Box';
 import { gql, useSubscription } from "@apollo/client";
 import ArrowBackIcon from '@mui/icons-material/ArrowBack';
 import LinearProgress from '@mui/material/LinearProgress';
-import { DateTime } from "luxon";