Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@types/yup": "^0.29.8",
"axios": "^0.21.2",
"jest-environment-jsdom-sixteen": "^1.0.3",
"notistack": "^3.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.0",
Expand Down Expand Up @@ -44,5 +45,6 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"proxy": "http://localhost:9324"
}
15 changes: 8 additions & 7 deletions ui/src/Main/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from "react";
import NavBar from "../NavBar/NavBar";
import QueuesTable from "../Queues/QueuesTable";
import { SnackbarProvider } from "notistack";

const Main: React.FC = () => {
return (
<>
<NavBar/>
<QueuesTable/>
</>
);
return (
<SnackbarProvider maxSnack={3}>
<NavBar />
<QueuesTable />
</SnackbarProvider>
);
};

export default Main;
export default Main;
97 changes: 97 additions & 0 deletions ui/src/Queues/NewMessageModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useState } from "react";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Button,
} from "@material-ui/core";
import { useSnackbar } from "notistack";

import QueueService from "../services/QueueService";

interface NewMessageModalProps {
open: boolean;
onClose: () => void;
queueName: string;
}

const NewMessageModal: React.FC<NewMessageModalProps> = ({
open,
onClose,
queueName,
}) => {
const [messageBody, setMessageBody] = useState("");
const [loading, setLoading] = useState(false);
const { enqueueSnackbar } = useSnackbar();

const handleSendMessage = async () => {
if (!messageBody.trim()) {
enqueueSnackbar("Message body cannot be empty", {
variant: "error",
});
return;
}

setLoading(true);
try {
await QueueService.sendMessage(queueName, messageBody);
enqueueSnackbar("Message sent successfully!", {
variant: "success",
});
setMessageBody("");
onClose();
} catch (error) {
enqueueSnackbar("Failed to send message", {
variant: "error",
});
console.error("Error sending message:", error);
} finally {
setLoading(false);
}
};

const handleClose = () => {
setMessageBody("");
onClose();
};

return (
<>
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
<DialogTitle>New Message - {queueName}</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Message Body"
type="text"
fullWidth
multiline
rows={6}
variant="outlined"
value={messageBody}
onChange={(e) => setMessageBody(e.target.value)}
placeholder="Enter your message body here..."
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="secondary">
Cancel
</Button>
<Button
onClick={handleSendMessage}
color="primary"
variant="contained"
disabled={loading}
>
{loading ? "Sending..." : "Send"}
</Button>
</DialogActions>
</Dialog>
</>
);
};

export default NewMessageModal;
44 changes: 31 additions & 13 deletions ui/src/Queues/QueueMessageData.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
interface QueueMessagesData {
queueName: string;
currentMessagesNumber: number;
delayedMessagesNumber: number;
notVisibleMessagesNumber: number;
isOpened: boolean
queueName: string;
currentMessagesNumber: number;
delayedMessagesNumber: number;
notVisibleMessagesNumber: number;
isOpened: boolean;
messages?: QueueMessage[];
messagesLoading?: boolean;
messagesError?: string | null;
}

interface QueueStatistic {
name: string;
statistics: Statistics;
name: string;
statistics: Statistics;
}

interface Statistics {
approximateNumberOfVisibleMessages: number;
approximateNumberOfMessagesDelayed: number;
approximateNumberOfInvisibleMessages: number;
approximateNumberOfVisibleMessages: number;
approximateNumberOfMessagesDelayed: number;
approximateNumberOfInvisibleMessages: number;
}

interface QueueRedrivePolicyAttribute {
deadLetterTargetArn: string,
maxReceiveCount: number
deadLetterTargetArn: string;
maxReceiveCount: number;
}

export type {QueueMessagesData, QueueStatistic, QueueRedrivePolicyAttribute}
interface QueueMessage {
messageId: string;
body: string;
sentTimestamp: string;
receiptHandle?: string;
attributes?: { [key: string]: string };
messageAttributes?: { [key: string]: any };
isExpanded?: boolean;
}

export type {
QueueMessagesData,
QueueStatistic,
QueueRedrivePolicyAttribute,
QueueMessage,
};
111 changes: 111 additions & 0 deletions ui/src/Queues/QueueMessagesList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import QueueMessagesList from "./QueueMessagesList";
import { QueueMessage } from "./QueueMessageData";

const mockUpdateMessageExpandedState = jest.fn();

describe("<QueueMessagesList /> - New Features", () => {
describe("HTML Entity Decoding (New Feature)", () => {
test("decodes HTML entities in message body preview", () => {
const messageWithEntities: QueueMessage[] = [
{
messageId: "msg-html-entities",
body: "&quot;organizationId&quot;:&quot;test&quot;",
sentTimestamp: "1609459200000",
},
];

render(
<QueueMessagesList
queueName="test-queue"
messages={messageWithEntities}
loading={false}
error={null}
updateMessageExpandedState={mockUpdateMessageExpandedState}
/>
);

// Should display decoded version in preview
expect(screen.getByText('"organizationId":"test"')).toBeInTheDocument();
});

test("decodes HTML entities in full message body when expanded", () => {
const messageWithEntities: QueueMessage[] = [
{
messageId: "msg-html-entities",
body: "&quot;data&quot;:&quot;value&quot;&amp;&lt;test&gt;",
sentTimestamp: "1609459200000",
},
];

render(
<QueueMessagesList
queueName="test-queue"
messages={messageWithEntities}
loading={false}
error={null}
updateMessageExpandedState={mockUpdateMessageExpandedState}
/>
);

// Should display decoded version in preview (truncated)
expect(screen.getByText('"data":"value"&<test>')).toBeInTheDocument();
});
});

describe("Props-based State Management (New Feature)", () => {
test("uses messages from props when provided", () => {
const propsMessages: QueueMessage[] = [
{
messageId: "props-msg-1",
body: "Message from props",
sentTimestamp: "1609459200000",
},
];

render(
<QueueMessagesList
queueName="test-queue"
messages={propsMessages}
loading={false}
error={null}
updateMessageExpandedState={mockUpdateMessageExpandedState}
/>
);

expect(screen.getByText("Messages (1)")).toBeInTheDocument();
expect(screen.getByText("Message from props")).toBeInTheDocument();
});

test("shows loading state from props", () => {
render(
<QueueMessagesList
queueName="test-queue"
messages={[]}
loading={true}
error={null}
updateMessageExpandedState={mockUpdateMessageExpandedState}
/>
);

expect(screen.getByText("Loading...")).toBeInTheDocument();
expect(screen.getByRole("progressbar")).toBeInTheDocument();
});

test("shows error state from props", () => {
const errorMessage = "Failed to fetch messages from parent";
render(
<QueueMessagesList
queueName="test-queue"
messages={[]}
loading={false}
error={errorMessage}
updateMessageExpandedState={mockUpdateMessageExpandedState}
/>
);

expect(screen.getByText(errorMessage)).toBeInTheDocument();
});
});
});
Loading