Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
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
3 changes: 2 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"proxy": "http://localhost:9324"
}
7 changes: 6 additions & 1 deletion ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import React from "react";
import Main from "./Main/Main";
import { SnackbarProvider } from "./context/SnackbarContext";

function App() {
return <Main/>
return (
<SnackbarProvider>
<Main />
</SnackbarProvider>
);
}

export default App;
14 changes: 7 additions & 7 deletions ui/src/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import NavBar from "../NavBar/NavBar";
import QueuesTable from "../Queues/QueuesTable";

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

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

import { sendMessage } from "../services/QueueService";
import getErrorMessage from "../utils/getErrorMessage";

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 { showSnackbar } = useSnackbar();

const handleSendMessage = async () => {
if (!messageBody.trim()) {
showSnackbar("Message body cannot be empty");
return;
}

setLoading(true);
try {
await sendMessage(queueName, messageBody);
showSnackbar("Message sent successfully!");
setMessageBody("");
onClose();
} catch (error) {
const errorMessage = getErrorMessage(error);
showSnackbar(`Failed to send message: ${errorMessage}`);
} 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?: Record<string, string>;
messageAttributes?: Record<string, unknown>;
isExpanded?: boolean;
}

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

const mockUpdateMessageExpandedState = jest.fn();
const mockOnRefreshMessages = jest.fn();

const renderWithSnackbarProvider = (component: React.ReactElement) => {
return render(<SnackbarProvider>{component}</SnackbarProvider>);
};

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",
},
];

renderWithSnackbarProvider(
<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",
},
];

renderWithSnackbarProvider(
<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",
},
];

renderWithSnackbarProvider(
<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", () => {
renderWithSnackbarProvider(
<QueueMessagesList
queueName="test-queue"
messages={[]}
loading={true}
error={null}
updateMessageExpandedState={mockUpdateMessageExpandedState}
onRefreshMessages={mockOnRefreshMessages}
/>
);

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

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

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