From 111a507db174a94922fd79091435b675a005adeb Mon Sep 17 00:00:00 2001 From: jacoblurie29 Date: Wed, 11 Oct 2023 22:55:09 -0500 Subject: [PATCH] Issues are completed, descriptions are formatted, users are redirected after submitting, links to github --- .../Organizer/BugReportsTab/BugReportsTab.tsx | 100 +++++++++++++- models/Report.ts | 11 ++ pages/api/report.ts | 89 +++++++++++-- pages/report.tsx | 126 +++++++++++++----- styles/Report.module.css | 58 ++++++++ types/client.ts | 4 + 6 files changed, 336 insertions(+), 52 deletions(-) diff --git a/components/Organizer/BugReportsTab/BugReportsTab.tsx b/components/Organizer/BugReportsTab/BugReportsTab.tsx index 685b6053..191e6429 100644 --- a/components/Organizer/BugReportsTab/BugReportsTab.tsx +++ b/components/Organizer/BugReportsTab/BugReportsTab.tsx @@ -2,17 +2,20 @@ import { Button, Input, InputRef, Skeleton, Space, Table, Tag } from 'antd'; import { RequestType, useCustomSWR } from '../../../utils/request-utils'; import { Report } from '../../../types/client'; import { ColumnsType } from 'antd/lib/table'; -import { SearchOutlined } from '@ant-design/icons'; +import { DeleteOutlined, SearchOutlined } from '@ant-design/icons'; import { FilterConfirmProps } from 'antd/es/table/interface'; import Highlighter from 'react-highlight-words'; import { useRef, useState } from 'react'; import { FilterValue } from 'antd/lib/table/interface'; +import styles from '../../../styles/Report.module.css'; const BugReportsTab = () => { const searchInput = useRef(null); const [searchText, setSearchText] = useState(''); const [searchedColumn, setSearchedColumn] = useState(''); - const [filteredInfo, setFilteredInfo] = useState>({}); + const [filteredInfo, setFilteredInfo] = useState>({ + status: ['OPEN', 'IN_PROGRESS'], + }); const { data: bugReports, error: bugReportsError } = useCustomSWR({ url: '/api/report', @@ -26,6 +29,26 @@ const BugReportsTab = () => { const bugReportsForTable = bugReports?.map(x => ({ ...x, key: x._id })) || ([] as Report[]); + const handleDeleteIssue = async (id: string | undefined) => { + if (!id) return; + + if (confirm('Are you sure you want to delete this issue?\nThis will NOT delete the issue in GitHub!')) { + // delete the issue + const res = await fetch('/api/report', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id }), + }); + if (res.status === 200) { + window.location.reload(); + } else { + alert('Failed to delete issue!'); + } + } + }; + const handleSearch = ( selectedKeys: string[], confirm: (param?: FilterConfirmProps) => void, @@ -85,7 +108,7 @@ const BugReportsTab = () => { return recordValue.toString().toLowerCase().includes(value.toString().toLowerCase()); }, filteredValue: - (dataIndex in filteredInfo ? filteredInfo[dataIndex] : filteredInfo['application.' + dataIndex]) || null, + (dataIndex in filteredInfo ? filteredInfo[dataIndex] : filteredInfo['report.' + dataIndex]) || null, onFilterDropdownOpenChange: (open: boolean) => { if (open) { setTimeout(() => searchInput.current?.select(), 100); @@ -118,7 +141,7 @@ const BugReportsTab = () => { title: 'Name', dataIndex: 'name', key: 'name', - width: '15%', + width: '10%', ...getColumnSearchProps('name'), }, { @@ -132,7 +155,7 @@ const BugReportsTab = () => { title: 'Description', dataIndex: 'description', key: 'description', - width: '40%', + width: '25%', ...getColumnSearchProps('description'), }, { @@ -142,7 +165,7 @@ const BugReportsTab = () => { render: (date: string) => { return
{new Date(date).toLocaleString()}
; }, - width: '20%', + width: '10%', sorter: (a: Report, b: Report) => { const aStart = new Date(a.date.toString()); const bStart = new Date(b.date.toString()); @@ -161,9 +184,72 @@ const BugReportsTab = () => { filteredValue: filteredInfo['role'] || null, onFilter: (value: string | number | boolean, record: any): boolean => record.role === value, render: (role?: string) => { - return role !== undefined ? {role === 'HACKER' ? 'Hacker' : 'Judge'} : ''; + return role !== undefined ? ( + {role === 'HACKER' ? 'Hacker' : role === 'JUDGE' ? 'Judge' : 'Organizer'} + ) : ( + '' + ); + }, + }, + { + title: 'Status', + dataIndex: 'status', + key: 'status', + width: '10%', + defaultSortOrder: 'ascend', + filters: [ + { text: 'Open', value: 'OPEN' }, + { text: 'Closed', value: 'CLOSED' }, + { text: 'In Progress', value: 'IN_PROGRESS' }, + ], + filteredValue: filteredInfo['status'] || null, + onFilter: (value: string | number | boolean, record: any): boolean => record.status === value, + render: (status?: string) => { + return status !== undefined ? ( + + {status === 'OPEN' ? 'Open' : status === 'CLOSED' ? 'Closed' : 'In Progress'} + + ) : ( + '' + ); + }, + }, + { + title: 'Issue #', + dataIndex: 'ghIssueNumber', + key: 'issueNumber', + width: '5%', + render: (issueNumber: number) => { + return issueNumber; }, }, + { + // github link + title: 'GitHub', + key: 'github', + width: '10%', + render: (text: string, record: Report) => + record.ghUrl && ( + + + + ), + }, + { + // delete button + title: 'Actions', + key: 'action', + width: '5%', + render: (text: string, record: Report) => ( + + ), + }, ]; return ( diff --git a/models/Report.ts b/models/Report.ts index 27703bed..90e0a252 100644 --- a/models/Report.ts +++ b/models/Report.ts @@ -26,6 +26,17 @@ export const ReportSchema = new Schema({ status: { type: String, }, + ghIssueNumber: { + type: Number, + required: true, + }, + ghAssignee: { + type: String, + }, + ghUrl: { + type: String, + required: true, + }, }); // prevent recompilation of model if it already exists diff --git a/pages/api/report.ts b/pages/api/report.ts index bc5b6eec..8c1b3aab 100644 --- a/pages/api/report.ts +++ b/pages/api/report.ts @@ -14,27 +14,72 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< return res.status(200).send(reports); case 'POST': - const { email, name, role, description, date, status } = req.body; - - console.log(process.env.GITHUB_TOKEN); + const { email, name, role, description, date, status, ghAssignee } = req.body; + // Use the GitHub API to create an issue const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, }); + // make a more in depth description + // tell the developer this is a bug report, provide the email, name, role, time, and description. + // tell them that can track this issue in the organizer dashboard + + const fullDescription = `# Bug Report +This is a bug report. You can track this issue in the organizer dashboard. +### Reporter Information +${email} - ${name} - ${role === 'ORGANIZER' ? 'Organizer' : role === 'JUDGE' ? 'Judge' : 'Hacker'} +Issue was reported on ${ + new Date().toLocaleString('en-US', { + timeZone: 'America/Chicago', + }) + ' CT' + } +### Description +${description}`; + + // Make a request to the GitHub API to create an issue const response = await octokit.request('POST /repos/VandyHacks/witness/issues', { owner: 'VandyHacks', repo: 'witness', headers: { 'X-GitHub-Api-Version': '2022-11-28', }, - title: 'Found a bug', - body: "I'm having a problem with this.", - assignees: ['jacoblurie29'], + title: `[BUG REPORT] Bug found by ${ + role === 'ORGANIZER' ? 'an organizer' : role === 'JUDGE' ? 'a judge' : 'a hacker' + } on ${ + new Date() + .toLocaleString('en-US', { + timeZone: 'America/Chicago', + }) + .split(',')[0] + } at + ${ + new Date() + .toLocaleString('en-US', { + timeZone: 'America/Chicago', + }) + .split(',')[1] + .split(':')[0] + + ':' + + new Date() + .toLocaleString('en-US', { + timeZone: 'America/Chicago', + }) + .split(',')[1] + .split(':')[1] + } ${ + new Date() + .toLocaleString('en-US', { + timeZone: 'America/Chicago', + }) + .split(' ')[2] + .split(' ')[0] + }`, + body: fullDescription, + assignees: [ghAssignee], }); - console.log(response); - + // Create a new report in the database const report = await Report.create({ email, name, @@ -42,18 +87,42 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< description, date, status, + ghIssueNumber: response.data.number, + ghAssignee: response.data.assignee.login, + ghUrl: response.data.html_url, }); return res.status(200).send(report); case 'PATCH': - const { id } = req.query; + const { id } = req.body; + + if (!id) { + return res.status(400).send('No report ID provided'); + } + const { status: newStatus } = req.body; + + if (!newStatus) { + return res.status(400).send('No new status provided'); + } + const updatedReport = await Report.findByIdAndUpdate(id, { status: newStatus }, { new: true }); return res.status(200).send(updatedReport); case 'DELETE': - const { id: reportId } = req.query; + const { id: reportId } = req.body; + + if (!reportId) { + return res.status(400).send('No report ID provided'); + } + const deletedReport = await Report.findByIdAndDelete(reportId); + + // Use the GitHub API to delete an issue + const octokit2 = new Octokit({ + auth: process.env.GITHUB_TOKEN, + }); + return res.status(200).send(deletedReport); default: diff --git a/pages/report.tsx b/pages/report.tsx index 0afaf331..33218246 100644 --- a/pages/report.tsx +++ b/pages/report.tsx @@ -2,20 +2,36 @@ import { Button, Form, Layout } from 'antd'; import styles from '../styles/Report.module.css'; import Head from 'next/head'; import { useSession } from 'next-auth/react'; -import { BugOutlined, SendOutlined } from '@ant-design/icons'; -import React from 'react'; +import { + ArrowLeftOutlined, + BackwardFilled, + BugOutlined, + CheckCircleFilled, + CloseCircleFilled, + SendOutlined, + UndoOutlined, +} from '@ant-design/icons'; +import React, { useState } from 'react'; import TextArea from 'antd/lib/input/TextArea'; +import Link from 'next/link'; const ReportBug = () => { const { data: session, status } = useSession(); + const [success, setSuccess] = useState(null); + const [loading, setLoading] = useState(false); + const handleSubmitBug = async (input: any) => { + setLoading(true); const newReport = { email: session?.user?.email, name: session?.user?.name, role: session?.userType || 'HACKER', date: new Date().toISOString(), description: input.description, + status: 'OPEN', + // will replace with on-call dev + ghAssignee: 'jacoblurie29', }; const res = await fetch('/api/report', { method: 'POST', @@ -24,6 +40,13 @@ const ReportBug = () => { }, body: JSON.stringify(newReport), }); + + if (res.status === 200) { + setSuccess(true); + } else { + setSuccess(false); + } + setLoading(false); }; return ( @@ -55,40 +78,73 @@ const ReportBug = () => { alignItems: 'center', }}>
-

- Report a bug! -

-
-

Oh no! You've found a bug.

-

 Help us squash it!

-
+ {success == null ? ( + <> +

+ Report a bug! +

+
+

Oh no! You've found a bug.

+

 Help us squash it!

+
-
- -