feat(kafka): add message display ordering
This commit is contained in:
1 parent
42c0dc7f89
commit
2fe26052a7
12 files changed
+171
-26
No files matched your search
@@ -7,6 +7,7 @@ import { mqPeekMessages } from "@/lib/backend/api";
|
||||
import { formatError } from "@/lib/backend/errorUtils";
|
||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||
import { buildKafkaMessageSearchText, kafkaMessageSearchTextMatches, normalizeKafkaMessageSearchQuery } from "@/lib/mq/kafkaMessageSearch";
|
||||
import { sortKafkaMessagesByPublishTime, type KafkaMessageDisplayOrder } from "@/lib/mq/kafkaMessageSort";
|
||||
import { parseNonNegativeSafeInteger } from "@/lib/mq/mqPeekFilters";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -40,6 +41,7 @@ const advancedExpanded = ref(false);
|
||||
const messageSearchQuery = ref("");
|
||||
type KafkaPeekStartPosition = NonNullable<PeekMessagesOptions["startPosition"]>;
|
||||
const kafkaStartPosition = ref<KafkaPeekStartPosition>("latest");
|
||||
const kafkaMessageDisplayOrder = ref<KafkaMessageDisplayOrder>("newest");
|
||||
let messageRequestVersion = 0;
|
||||
|
||||
const isKafka = computed(() => props.mqSystemKind === "kafka");
|
||||
@@ -57,6 +59,7 @@ const filteredMessages = computed(() => {
|
||||
if (!query) return messages.value;
|
||||
return searchableMessages.value.filter(({ searchText }) => kafkaMessageSearchTextMatches(searchText, query)).map(({ message }) => message);
|
||||
});
|
||||
const displayedMessages = computed(() => (isKafka.value ? sortKafkaMessagesByPublishTime(filteredMessages.value, kafkaMessageDisplayOrder.value) : filteredMessages.value));
|
||||
|
||||
function peekGroupName(): string {
|
||||
if (props.mqSystemKind === "rocketmq") return "__dbx_rocketmq_viewer__";
|
||||
@@ -223,6 +226,18 @@ watch(kafkaStartPosition, () => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</label>
|
||||
<label v-if="isKafka">
|
||||
<span>{{ t("mqMessages.timeOrder") }}</span>
|
||||
<Select v-model="kafkaMessageDisplayOrder">
|
||||
<SelectTrigger data-testid="kafka-message-display-order" class="message-browser-start-position">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper" class="message-browser-start-position-content">
|
||||
<SelectItem value="newest">{{ t("mqMessages.newestFirst") }}</SelectItem>
|
||||
<SelectItem value="oldest">{{ t("mqMessages.oldestFirst") }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</label>
|
||||
<label v-if="isKafka">
|
||||
<span>{{ t("mqMessages.partition") }}</span>
|
||||
<input v-model="partition" data-testid="kafka-peek-partition" type="number" min="0" :placeholder="t('mqMessages.partitionPlaceholderAll')" :disabled="loading" />
|
||||
@@ -263,7 +278,7 @@ watch(kafkaStartPosition, () => {
|
||||
<div v-else-if="!messages.length" class="message-empty">{{ t("mqMessages.noMessages") }}</div>
|
||||
<div v-else-if="!filteredMessages.length" class="message-empty" data-testid="kafka-message-filter-empty">{{ t("mqMessages.noMatchingMessages") }}</div>
|
||||
<div v-else class="message-list">
|
||||
<article v-for="message in filteredMessages" :key="`${message.properties?.partition ?? 'p'}-${message.messageId || message.position}`" class="message-row">
|
||||
<article v-for="message in displayedMessages" :key="`${message.properties?.partition ?? 'p'}-${message.messageId || message.position}`" class="message-row">
|
||||
<div class="message-meta">
|
||||
<span>#{{ message.position }}</span>
|
||||
<span v-if="message.properties?.partition != null">{{ t("mqMessages.metaPartition", { partition: message.properties.partition }) }}</span>
|
||||
|
||||
@@ -74,13 +74,14 @@ async function setSelectValue(trigger: HTMLElement, value: string) {
|
||||
await flushUi();
|
||||
}
|
||||
|
||||
async function mountBrowser(mqSystemKind: "kafka" | "rabbitmq" = "kafka") {
|
||||
async function mountBrowser(mqSystemKind: "kafka" | "rabbitmq" = "kafka", appearance: "form" | "monitoring" = "form") {
|
||||
root = document.createElement("div");
|
||||
document.body.appendChild(root);
|
||||
app = createApp(MessageBrowser, {
|
||||
connectionId: "mq-1",
|
||||
topic: { ...TOPIC, tenant: mqSystemKind === "rabbitmq" ? "_rabbitmq" : "_kafka" },
|
||||
mqSystemKind,
|
||||
appearance,
|
||||
});
|
||||
app.mount(root);
|
||||
await flushUi();
|
||||
@@ -121,6 +122,10 @@ async function loadMessages(container: ParentNode) {
|
||||
await flushUi();
|
||||
}
|
||||
|
||||
function displayedPayloads(container: ParentNode): string[] {
|
||||
return [...container.querySelectorAll<HTMLElement>(".message-payload")].map((element) => element.textContent || "");
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
backend.mqPeekMessages.mockReset();
|
||||
clipboard.copyToClipboard.mockReset();
|
||||
@@ -229,6 +234,51 @@ describe("MessageBrowser", () => {
|
||||
expect(backend.mqPeekMessages).toHaveBeenCalledWith("mq-1", expect.objectContaining({ topic: "events" }), "__dbx_kafka_viewer__", 20, { startPosition: "latest" });
|
||||
});
|
||||
|
||||
it("orders loaded Kafka messages newest first by default and changes presentation without reloading", async () => {
|
||||
backend.mqPeekMessages.mockResolvedValueOnce([
|
||||
{ position: 1, messageId: "1", publishTime: "1000", payloadBase64: "", payloadText: "oldest", properties: {}, headers: {} },
|
||||
{ position: 2, messageId: "2", publishTime: "3000", payloadBase64: "", payloadText: "newest", properties: {}, headers: {} },
|
||||
{ position: 3, messageId: "3", publishTime: "2000", payloadBase64: "", payloadText: "middle", properties: {}, headers: {} },
|
||||
]);
|
||||
const browser = await mountBrowser();
|
||||
|
||||
await loadMessages(browser);
|
||||
expect(displayedPayloads(browser)).toEqual(["newest", "middle", "oldest"]);
|
||||
|
||||
const displayOrder = browser.querySelector<HTMLElement>('[data-testid="kafka-message-display-order"]');
|
||||
if (!displayOrder) throw new Error("Kafka message display order select not found");
|
||||
await setSelectValue(displayOrder, "oldest");
|
||||
|
||||
expect(displayedPayloads(browser)).toEqual(["oldest", "middle", "newest"]);
|
||||
expect(backend.mqPeekMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps the selected Kafka display order when filtering loaded messages", async () => {
|
||||
backend.mqPeekMessages.mockResolvedValueOnce([
|
||||
{ position: 1, messageId: "1", publishTime: "1000", payloadBase64: "", payloadText: "matching oldest", properties: {}, headers: {} },
|
||||
{ position: 2, messageId: "2", publishTime: "3000", payloadBase64: "", payloadText: "matching newest", properties: {}, headers: {} },
|
||||
{ position: 3, messageId: "3", publishTime: "2000", payloadBase64: "", payloadText: "excluded", properties: {}, headers: {} },
|
||||
]);
|
||||
const browser = await mountBrowser();
|
||||
|
||||
await loadMessages(browser);
|
||||
const displayOrder = browser.querySelector<HTMLElement>('[data-testid="kafka-message-display-order"]');
|
||||
const filter = browser.querySelector<HTMLInputElement>('[data-testid="kafka-message-filter-input"]');
|
||||
if (!displayOrder || !filter) throw new Error("Kafka message presentation controls not found");
|
||||
await setSelectValue(displayOrder, "oldest");
|
||||
await setInputValue(filter, "matching");
|
||||
|
||||
expect(displayedPayloads(browser)).toEqual(["matching oldest", "matching newest"]);
|
||||
expect(backend.mqPeekMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("shows Kafka display ordering in the monitoring appearance", async () => {
|
||||
const browser = await mountBrowser("kafka", "monitoring");
|
||||
|
||||
expect(browser.querySelector('[data-testid="kafka-message-display-order"]')).not.toBeNull();
|
||||
expect(browser.querySelector('[data-testid="kafka-peek-start-position"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("normalizes a decimal count before sending the request", async () => {
|
||||
const browser = await mountBrowser();
|
||||
const countInput = browser.querySelector<HTMLInputElement>('[data-testid="peek-count"]');
|
||||
@@ -383,6 +433,7 @@ describe("MessageBrowser", () => {
|
||||
await loadMessages(browser);
|
||||
|
||||
expect(browser.querySelector('[data-testid="kafka-message-filter"]')).toBeNull();
|
||||
expect(browser.querySelector('[data-testid="kafka-message-display-order"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("sends explicit earliest and offset read positions", async () => {
|
||||
|
||||
Reference in new issue
Block a user