Skip to content

Commit

Permalink
Merge pull request #19 from GiuseppeArbore/OQ-21-Notification-UI
Browse files Browse the repository at this point in the history
OQ-21-Notification-UI
  • Loading branch information
golnazsaraji authored Oct 16, 2024
2 parents 66eb642 + 866484d commit 377d5d8
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 46 deletions.
Binary file added client/public/loading.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import GetCounter from './Components/Counter/Counter';
import OfficeDesk from './Components/Counter/OfficeDesk';
import React from 'react';
import GetTicket from './Components/Client/Ticket';
import SeeCounter from './Components/Customer/ToGo/seeCounter';
import HomePage from './Components/Homepage/HomePage';


Expand All @@ -22,6 +23,7 @@ function App(): JSX.Element {
<Route path="/" element={ <HomePage /> }/>
<Route path="/services" element={ <GetServices /> }/>
<Route path="/counter" element={ <GetCounter total_counter={9}/> }/>
<Route path="/customer/togo" element={ <SeeCounter/> }/>
<Route path="/counter/:number/:ticket_number?" element={ <OfficeDesk /> }/>
<Route path='/ticket/:service_type/:ticket_number' element={ <GetTicket /> } />
</Routes>
Expand Down
73 changes: 73 additions & 0 deletions client/src/Components/Customer/ToGo/seeCounter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react'
import { useEffect, useState } from "react"
import { Button, Card } from 'react-bootstrap';

function SeeCounter() {

const [counter, setCounter] = useState<string>(""); // this value will be updated
const [error, setError] = useState<string>("");
const [ticketId, setTicketId] = useState<number>(1);


useEffect(() => {

const server_url='ws://localhost:3001/tickets/notification/';
const ws = new WebSocket(server_url + ticketId);

ws.onmessage = (event) => {
const counter_ = event.data;
setCounter(counter_);
console.log("Message received:", counter_);
};

ws.onerror = (err) => {
console.log("WebSocket error:", err);
setError("Failed to connect to the server.");
};

// Clean up WebSocket connection when the component is unmounted
return () => {
ws.close();
};
}, []);
return (
<>

<div>
{
counter == "" &&
<>
<h3>Please wait to be called by a counter... </h3>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '3rem', justifyContent: 'space-around' }} >

<Card key={counter} >
<Card.Body>
<img src="../../../../public/loading.gif"/>
</Card.Body>
</Card>
</div>
</>
}
{
counter != "" &&
<>
<h1>Please go to the counter: </h1>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '3rem', justifyContent: 'space-around' }} >

<Card key={counter} >
<Card.Body>
<Button value={counter} variant="primary">
{counter}
</Button>
</Card.Body>
</Card>
</div>
</>
}
</div>

</>
)
}

export default SeeCounter
34 changes: 17 additions & 17 deletions server/src/controllers/notificationController.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { WebSocket } from 'ws';

class NotificationController {
private active: Map<number, WebSocket | null>;
private active: Map<string, WebSocket | null>;

constructor() {
this.active = new Map();
}

hasTicket(ticketNumber: number): boolean {
return this.active.has(ticketNumber);
hasTicket(serviceType: number, ticketNumber: number): boolean {
return this.active.has(`${serviceType}-${ticketNumber}`);
}

/**
* To be called when delivering the ticket number to the customer.
* @returns `false` if the ticket already exists, `true` otherwise
*/
addTicket(ticketNumber: number): boolean {
if (this.hasTicket(ticketNumber))
addTicket(serviceType: number, ticketNumber: number): boolean {
if (this.hasTicket(serviceType, ticketNumber))
return false;
this.active.set(ticketNumber, null);
this.active.set(`${serviceType}-${ticketNumber}`, null);
return true;
}

Expand All @@ -27,48 +27,48 @@ class NotificationController {
* If a connection already exists, it is overridden.
* @returns `false` if the ticket does not exist, `true` otherwise
*/
addConnection(ticketNumber: number, ws: WebSocket): boolean {
if (!this.removeConnection(ticketNumber))
addConnection(serviceType: number, ticketNumber: number, ws: WebSocket): boolean {
if (!this.removeConnection(serviceType, ticketNumber))
return false;

this.active.set(ticketNumber, ws);
this.active.set(`${serviceType}-${ticketNumber}`, ws);
return true;
}

/**
* Removes a ticket from the active tickets. To be called when an user has been served.
* @returns `false` if the ticket does not exist, `true` otherwise
*/
removeTicket(ticketNumber: number): boolean {
if (!this.removeConnection(ticketNumber))
removeTicket(serviceType: number, ticketNumber: number): boolean {
if (!this.removeConnection(serviceType, ticketNumber))
return false;

return this.active.delete(ticketNumber);
return this.active.delete(`${serviceType}-${ticketNumber}`);
}

/**
* Closes an existing connection for a specific ticket.
* To be called in case the connection is interrupted.
* @returns `false` if the ticket does not exist, `true` otherwise
*/
removeConnection(ticketNumber: number): boolean {
const curr = this.active.get(ticketNumber);
removeConnection(serviceType: number, ticketNumber: number): boolean {
const curr = this.active.get(`${serviceType}-${ticketNumber}`);
if (curr === undefined)
return false;
if (curr && curr.readyState !== curr.CLOSED && curr.readyState !== curr.CLOSING) {
curr.close();
}

this.active.set(ticketNumber, null);
this.active.set(`${serviceType}-${ticketNumber}`, null);
return true;
}

/**
* Notifies the user corresponding to the `ticketNumber`
* @returns `false` if the ticket does not exist, `true` otherwise
*/
notify(ticketNumber: number, counter: number): boolean {
const ws = this.active.get(ticketNumber);
notify(serviceType: number, ticketNumber: number, counter: number): boolean {
const ws = this.active.get(`${serviceType}-${ticketNumber}`);
if(!ws)
return false;

Expand Down
2 changes: 1 addition & 1 deletion server/src/controllers/ticketController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TicketController {

try {
const ticket = await this.dao.getTicket(Number(service_id));
notificationController.addTicket(ticket)
notificationController.addTicket(Number(service_id), ticket)
res.status(200).json("S"+ service_id + "-" + ticket);
} catch (error) {
const status = (error as any).status || 500;
Expand Down
12 changes: 7 additions & 5 deletions server/src/routers/notificationRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import { validateWsRequest } from "../errorHandlers";

const router = new Router()

router.ws("/tickets/notification/:ticketId", [
param("ticketId").exists().isInt({min: 0})
router.ws("/tickets/notification/:serviceType/:ticketId", [
param("ticketId").exists().isInt({min: 0}),
param("serviceType").exists().isInt({min: 0})
],
validateWsRequest,
async (req: any, res: WSResponse) => {
const ticketId = Number(req.params.ticketId);
if (!notificationController.hasTicket(ticketId)) {
const serviceType = Number(req.params.serviceType);
if (!notificationController.hasTicket(serviceType, ticketId)) {
return res.status(422);
}

const ws = await res.accept();
notificationController.addConnection(ticketId, ws)
notificationController.addConnection(serviceType, ticketId, ws)
ws.on('close', () => {
notificationController.removeConnection(ticketId);
notificationController.removeConnection(serviceType, ticketId);
});
}
);
Expand Down
10 changes: 6 additions & 4 deletions server/test_integration/notificationIntegration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,34 @@ describe('Notification controller API tests', () => {

const response = await request(server).get(`/api/tickets/${serviceId}`);
const ticketNumber = Number((response.body as string).split('-')[1]);
const ws = await request(server).ws(`/api/tickets/notification/${ticketNumber}`);
const ws = await request(server).ws(`/api/tickets/notification/${serviceId}/${ticketNumber}`);

ws.on('message', (data: WebSocket.RawData, isBinary: boolean) => {
expect(isBinary).toBe(false);
expect(data).toStrictEqual(Buffer.from(`${counter}\n`))
});

const ret = notificationController.notify(ticketNumber, counter);
const ret = notificationController.notify(serviceId, ticketNumber, counter);
expect(ret).toBe(true);
});

it('2 - negative ticket number', async () => {
const serviceId = 3;
const ticketNumber = -3;

try {
await request(server).ws(`/api/tickets/notification/${ticketNumber}`);
await request(server).ws(`/api/tickets/notification/${serviceId}/${ticketNumber}`);
} catch (error) {
expect((error as Error).message).toContain('422');
}
});

it('3 - non numeric ticket number', async () => {
const serviceId = 3;
const ticketNumber = "hello";

try {
await request(server).ws(`/api/tickets/notification/${ticketNumber}`);
await request(server).ws(`/api/tickets/notification/${serviceId}/${ticketNumber}`);
} catch (error) {
expect((error as Error).message).toContain('422');
}
Expand Down
39 changes: 20 additions & 19 deletions server/test_unit/notificationController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { WebSocket } from "ws";

jest.mock("ws");
const ws = new WebSocket("");
const serviceType = 3;
const ticketNumber = 4;

jest.mock("")
Expand All @@ -16,67 +17,67 @@ describe("NotificationController unit tests", () => {
})

test("1 - normal usage", () => {
const res = notificationController.addTicket(ticketNumber);
const res = notificationController.addTicket(serviceType, ticketNumber);

expect(res).toStrictEqual(true);
})
test("2 - add already existing ticket", () => {
notificationController.addTicket(ticketNumber);
const res = notificationController.addTicket(ticketNumber);
notificationController.addTicket(serviceType, ticketNumber);
const res = notificationController.addTicket(serviceType, ticketNumber);

expect(res).toStrictEqual(false);
})
test("3 - add connection", () => {
notificationController.addTicket(ticketNumber);
const res = notificationController.addConnection(ticketNumber, ws);
notificationController.addTicket(serviceType, ticketNumber);
const res = notificationController.addConnection(serviceType, ticketNumber, ws);

expect(res).toStrictEqual(true);
})
test("4 - add connection for non-existing ticket", () => {
const res = notificationController.addConnection(ticketNumber, ws);
const res = notificationController.addConnection(serviceType, ticketNumber, ws);

expect(res).toStrictEqual(false);
})
test("5 - remove connection", () => {
notificationController.addTicket(ticketNumber);
notificationController.addConnection(ticketNumber, ws);
const res = notificationController.removeConnection(ticketNumber);
notificationController.addTicket(serviceType, ticketNumber);
notificationController.addConnection(serviceType, ticketNumber, ws);
const res = notificationController.removeConnection(serviceType, ticketNumber);

expect(res).toStrictEqual(true);
expect(ws.close).toHaveBeenCalled();
})
test("6 - remove non-existing connection", () => {
const res = notificationController.removeConnection(ticketNumber);
const res = notificationController.removeConnection(serviceType, ticketNumber);

expect(res).toStrictEqual(false);
expect(ws.close).toHaveBeenCalled();
})
test("7 - remove ticket", () => {
notificationController.addTicket(ticketNumber);
const res = notificationController.removeTicket(ticketNumber);
notificationController.addTicket(serviceType, ticketNumber);
const res = notificationController.removeTicket(serviceType, ticketNumber);

expect(res).toStrictEqual(true);
})
test("8 - remove non-existing ticket", () => {
const res = notificationController.removeTicket(ticketNumber);
const res = notificationController.removeTicket(serviceType, ticketNumber);

expect(res).toStrictEqual(false);
})
test("9 - notify", () => {
notificationController.addTicket(ticketNumber);
notificationController.addConnection(ticketNumber, ws);
const res = notificationController.notify(ticketNumber, 1);
notificationController.addTicket(serviceType, ticketNumber);
notificationController.addConnection(serviceType, ticketNumber, ws);
const res = notificationController.notify(serviceType, ticketNumber, 1);

expect(res).toStrictEqual(true);
})
test("10 - notify non-existing connection", () => {
notificationController.addTicket(ticketNumber);
const res = notificationController.notify(ticketNumber, 1);
notificationController.addTicket(serviceType, ticketNumber);
const res = notificationController.notify(serviceType, ticketNumber, 1);

expect(res).toStrictEqual(false);
})
test("10 - notify non-existing ticket", () => {
const res = notificationController.notify(ticketNumber, 1);
const res = notificationController.notify(serviceType, ticketNumber, 1);

expect(res).toStrictEqual(false);
})
Expand Down

0 comments on commit 377d5d8

Please sign in to comment.