From b2e6b02b2e0526ddbad55894c7d6f4ada05c08ec Mon Sep 17 00:00:00 2001 From: Pijus Kamandulis Date: Sat, 19 Oct 2024 17:07:17 +0300 Subject: [PATCH] Allow user to import tickets from JIRA via CSV upload --- bun.lockb | Bin 201931 -> 204460 bytes package.json | 4 + src/components/Button.tsx | 2 + src/components/Card.tsx | 13 ++- src/components/Drawer.tsx | 2 +- src/components/HtmlEmbed.tsx | 2 +- src/index.css | 12 +++ src/lib/context/estimation.tsx | 21 +++-- src/lib/parsers/ticketUpload.ts | 66 +++++++++++++ src/main.tsx | 2 +- src/pages/Estimation/Estimation.tsx | 2 +- .../Estimation/components/PlayerList.tsx | 4 +- .../Estimation/components/TaskSidebar.tsx | 21 ++++- .../components/TicketImportForm.tsx | 87 ++++++++++++++++++ 14 files changed, 220 insertions(+), 18 deletions(-) create mode 100644 src/lib/parsers/ticketUpload.ts create mode 100644 src/pages/Estimation/components/TicketImportForm.tsx diff --git a/bun.lockb b/bun.lockb index e26707900577dd7ec71f0b07aabf7c8b4b624ce7..8cd3e053f507123f08f6f270ad3926cb98ffe663 100755 GIT binary patch delta 41250 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 delta 39661 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 diff --git a/package.json b/package.json index 61b48a7..7f190ec 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,18 @@ "@tanstack/yup-form-adapter": "^0.33.0", "appwrite": "^16.0.2", "classnames": "^2.5.1", + "papaparse": "^5.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "showdown": "^2.1.0", "yup": "^1.4.0" }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/papaparse": "^5.3.15", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", + "@types/showdown": "^2.0.6", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.20", "eslint": "^9.11.1", diff --git a/src/components/Button.tsx b/src/components/Button.tsx index f89c1c6..f392699 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -20,6 +20,7 @@ const Button: React.FC = ({ fullWidth = false, disabled = false, isLoading = false, + className, ...props }) => { disabled = disabled || isLoading; @@ -39,6 +40,7 @@ const Button: React.FC = ({ 'text-white': !disabled, 'w-full': fullWidth, }, + className, ); return ( diff --git a/src/components/Card.tsx b/src/components/Card.tsx index f862969..6f0ad86 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,14 +1,20 @@ import classNames from 'classnames'; import { PencilIcon } from './icons'; -interface CardProps { +interface CardProps extends React.PropsWithChildren { title: string; description?: string; onClick?: () => void; onEdit?: () => void; } -const Card: React.FC = ({ title, description, onClick, onEdit }) => { +const Card: React.FC = ({ + title, + description, + onClick, + onEdit, + children, +}) => { const className = classNames( 'p-4 border rounded-lg shadow-sm transition', { @@ -20,7 +26,7 @@ const Card: React.FC = ({ title, description, onClick, onEdit }) => { ); return ( -
+

{title}

{onEdit && ( @@ -36,6 +42,7 @@ const Card: React.FC = ({ title, description, onClick, onEdit }) => { )}
+ {children} {description &&

{description}

}
); diff --git a/src/components/Drawer.tsx b/src/components/Drawer.tsx index 3a93a3d..5d5b227 100644 --- a/src/components/Drawer.tsx +++ b/src/components/Drawer.tsx @@ -32,7 +32,7 @@ const Drawer: React.FC = ({ isOpen, onClose, children }) => {
= ({ body, className }) => { diff --git a/src/index.css b/src/index.css index 6e569a5..61625a2 100644 --- a/src/index.css +++ b/src/index.css @@ -62,3 +62,15 @@ button:focus-visible { background-color: #f9f9f9; } } + +@layer utilities { + /* Hide scrollbar for Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} diff --git a/src/lib/context/estimation.tsx b/src/lib/context/estimation.tsx index 4985b80..cad23b3 100644 --- a/src/lib/context/estimation.tsx +++ b/src/lib/context/estimation.tsx @@ -23,6 +23,7 @@ interface EstimationContextType { setRevealed: (revealed: boolean) => Promise; setVote: (estimate: string) => Promise; createTicket: (ticket: CreateTicketRequest) => Promise; + createTickets: (tickets: CreateTicketRequest[]) => Promise; updateTicket: (ticket: EditTicketRequest) => Promise; currentSessionData?: EntityModels.EstimationSession; } @@ -120,14 +121,19 @@ export const EstimationContextProvider = (props: PropsWithChildren) => { }); }; - const createTicket = async ({ name, content }: CreateTicketRequest) => { - const newTicket: EstimationSessionTicket = { - id: crypto.randomUUID(), - name, - content, - }; + const createTicket = (ticket: CreateTicketRequest) => createTickets([ticket]); - const newTicketsValue = [newTicket] + const createTickets = async (tickets: CreateTicketRequest[]) => { + const newTickets = tickets.map( + ({ content, name, estimate }) => ({ + id: crypto.randomUUID(), + name, + content, + estimate, + }), + ); + + const newTicketsValue = newTickets .concat(currentSessionData?.tickets ?? []) .map((x) => JSON.stringify(x)); @@ -186,6 +192,7 @@ export const EstimationContextProvider = (props: PropsWithChildren) => { setRevealed, setVote, createTicket, + createTickets, updateTicket, currentSessionData, }} diff --git a/src/lib/parsers/ticketUpload.ts b/src/lib/parsers/ticketUpload.ts new file mode 100644 index 0000000..118a2a3 --- /dev/null +++ b/src/lib/parsers/ticketUpload.ts @@ -0,0 +1,66 @@ +import { EntityModels } from '../types'; +import Papa from 'papaparse'; +import Showdown from 'showdown'; + +export interface TicketFileUploadResponse { + tickets: EntityModels.EstimationSessionTicket[]; + error?: string; +} + +export enum FileSchema { + Jira, +} + +export const handleTicketFileUpload = ( + event: React.ChangeEvent, + callback: (response: TicketFileUploadResponse) => void, + fileSchema: FileSchema = FileSchema.Jira, +) => { + const file = event.target.files?.[0]; + if (!file) return; + + // Parse the CSV file using PapaParse + Papa.parse(file, { + header: true, + skipEmptyLines: true, + complete: (results) => { + let parsedTickets: EntityModels.EstimationSessionTicket[] | null = null; + switch (fileSchema) { + // TODO: Add more file schemas + case FileSchema.Jira: + parsedTickets = parseJiraCSV(results.data); + break; + default: + } + + callback({ + tickets: parsedTickets ?? [], + error: parsedTickets ? undefined : 'Failed to parse JIRA CSV file.', + }); + }, + error: (err) => { + callback({ + tickets: [], + error: `Error parsing CSV file: ${err.message}`, + }); + }, + }); +}; + +const parseJiraCSV = ( + data: any[], +): EntityModels.EstimationSessionTicket[] | null => { + const converter = new Showdown.Converter(); + + try { + return data.map((row) => ({ + id: crypto.randomUUID(), + name: row['Summary'], + estimate: row['Story point estimate'] || '', + content: converter.makeHtml(row['Description'] || ''), + })); + } catch (error) { + console.error('Error parsing CSV:', error); + return null; + } +}; diff --git a/src/main.tsx b/src/main.tsx index 19be276..f961e9c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -46,7 +46,7 @@ const authenticatedRoute = createRoute({ return (
-
+
diff --git a/src/pages/Estimation/Estimation.tsx b/src/pages/Estimation/Estimation.tsx index 76bea8f..f387925 100644 --- a/src/pages/Estimation/Estimation.tsx +++ b/src/pages/Estimation/Estimation.tsx @@ -47,7 +47,7 @@ const Estimation: React.FC = () => { return (
setActiveTicket(ticket.id)} onAddTicket={() => setIsDrawerOpen(true)} diff --git a/src/pages/Estimation/components/PlayerList.tsx b/src/pages/Estimation/components/PlayerList.tsx index a95698d..c803ade 100644 --- a/src/pages/Estimation/components/PlayerList.tsx +++ b/src/pages/Estimation/components/PlayerList.tsx @@ -14,12 +14,12 @@ const PlayerList: React.FC = ({ title = 'Players', }) => { return ( -
+

{title}

-
    +
      {players.length > 0 ? ( players.map((player) => (
    • = ({ onAddTicket, onEditTicket, }) => { + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const containerClassName = classNames( + className, + 'flex flex-col justify-between', + ); + return ( -
      +
      ( @@ -30,8 +40,15 @@ const TaskSidebar: React.FC = ({ onEdit={() => onEditTicket(item.id)} /> )} + addItemLabel="+ Add Ticket" onAddItem={onAddTicket} /> + + setIsDrawerOpen(false)}> + setIsDrawerOpen(false)} /> +
      ); }; diff --git a/src/pages/Estimation/components/TicketImportForm.tsx b/src/pages/Estimation/components/TicketImportForm.tsx new file mode 100644 index 0000000..c50f7e6 --- /dev/null +++ b/src/pages/Estimation/components/TicketImportForm.tsx @@ -0,0 +1,87 @@ +import { useState } from 'react'; +import { + handleTicketFileUpload, + TicketFileUploadResponse, +} from '../../../lib/parsers/ticketUpload'; +import { EstimationSessionTicket } from '../../../lib/types/entityModels'; +import { Button, Card, GridList, Loader } from '../../../components'; +import HtmlEmbed from '../../../components/HtmlEmbed'; +import { useEstimationContext } from '../../../lib/context/estimation'; + +interface TicketImportFormProps { + onTicketsImported: () => void; +} + +const TicketImportForm: React.FC = ({ + onTicketsImported, +}) => { + const [error, setError] = useState(''); + const [tickets, setTickets] = useState([]); + const estimationContext = useEstimationContext(); + + if (!estimationContext) { + return ; + } + + const { createTickets } = estimationContext; + + const onParsedTickets = ({ tickets, error }: TicketFileUploadResponse) => { + if (error) { + setError(error); + } + setTickets(tickets); + }; + + const onCreateTickets = async () => { + await createTickets(tickets); + onTicketsImported(); + }; + + return ( +
      +

      + Upload Ticket List CSV +

      + + { + setTickets([]); + setError(''); + handleTicketFileUpload(e, onParsedTickets); + }} + className="mb-4 block w-full text-sm text-gray-900 dark:text-gray-100" + /> + + {error &&

      {error}

      } + + {tickets.length > 0 && ( +
      +

      + Tickets to be imported +

      + ( + + + + )} + /> + +
      + )} +
      + ); +}; + +export default TicketImportForm;