feat: add a control for querying store in dogfooding app (#142)

This commit is contained in:
Arseniy Klempner 2025-10-27 15:10:23 -07:00 committed by GitHub
parent 07c9a6a1eb
commit 90c7927283
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 248 additions and 1 deletions

View File

@ -51,6 +51,17 @@
</div>
</section>
<section class="store-query">
<h2>Store Protocol Query</h2>
<div class="store-controls">
<label for="storeMessageCount">Number of messages to fetch:</label>
<input type="number" id="storeMessageCount" value="5" min="1" max="100" />
<button id="queryStoreButton" class="btn btn-primary">Query Store</button>
</div>
<div id="storeQueryStatus" class="store-status"></div>
<div id="storeQueryResults" class="store-results"></div>
</section>
<section class="charts">
<h2>Discovery Peers Over Time</h2>
<canvas id="discoveryChart"></canvas>

View File

@ -92,6 +92,24 @@ h2 {
background-color: #2980b9;
}
.btn-success {
background-color: #27ae60;
color: white;
}
.btn-success:hover {
background-color: #229954;
}
.btn-danger {
background-color: #e74c3c;
color: white;
}
.btn-danger:hover {
background-color: #c0392b;
}
.search-container {
margin-top: 15px;
display: flex;
@ -106,6 +124,40 @@ h2 {
font-size: 0.9em;
}
/* Store Query Section */
.store-controls {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.store-controls label {
font-weight: 500;
color: #555;
}
.store-controls input[type="number"] {
width: 80px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 0.9em;
}
.store-status {
margin-top: 15px;
padding: 10px;
border-radius: 5px;
min-height: 20px;
}
.store-results {
margin-top: 15px;
max-height: 500px;
overflow-y: auto;
}
/* Message Display */
.message-list {
max-height: 400px;
@ -214,7 +266,7 @@ footer {
grid-template-columns: repeat(2, 1fr);
}
.message-stats, .message-controls {
.message-stats, .message-controls, .store-query, .charts {
grid-column: span 1;
}

View File

@ -27,6 +27,9 @@ import {
trackMessageSent,
trackMessageReceived,
recordLatency,
updateStoreQueryStatus,
displayStoreQueryResults,
clearStoreQueryResults,
} from "./ui-manager";
const NUM_MESSAGES_PER_BATCH = 5;
@ -206,6 +209,100 @@ async function initializeApp() {
console.log("Subscription active.");
};
const queryStoreMessages = async () => {
const storeMessageCountInput = document.getElementById("storeMessageCount") as HTMLInputElement;
const messageLimit = storeMessageCountInput ? parseInt(storeMessageCountInput.value, 10) : 5;
if (isNaN(messageLimit) || messageLimit < 1) {
updateStoreQueryStatus("Please enter a valid number of messages (minimum 1)", true);
return;
}
clearStoreQueryResults();
updateStoreQueryStatus("Querying store...", false);
console.log(`Querying store for up to ${messageLimit} messages...`);
try {
const decoder = createWakuDecoder();
const allMessages: ChatMessage[] = [];
console.log("Decoder content topic:", decoder.contentTopic);
console.log("Decoder pubsub topic:", decoder.pubsubTopic);
// Query for messages from the last hour, using paginationLimit to control result size
const timeEnd = new Date();
const timeStart = new Date(Date.now() - 1000 * 60 * 60);
const queryOptions = {
timeStart,
timeEnd,
paginationForward: false, // Start from newest
paginationLimit: messageLimit, // Limit the number of messages returned
};
console.log("Store query options:", queryOptions);
console.log("Time range:", timeStart.toISOString(), "to", timeEnd.toISOString());
// Collect messages - stop once we have enough
await node.store.queryWithOrderedCallback(
[decoder],
async (wakuMessage) => {
// Check if we already have enough messages before processing more
if (allMessages.length >= messageLimit) {
console.log(`Already collected ${messageLimit} messages, stopping`);
return true; // Stop processing
}
const chatMessage = decodeMessage(wakuMessage.payload);
if (chatMessage) {
allMessages.push(chatMessage);
console.log(`Store found message ${allMessages.length}/${messageLimit}:`, {
id: chatMessage.id,
content: chatMessage.content.substring(0, 50),
timestamp: new Date(chatMessage.timestamp).toISOString(),
sender: chatMessage.senderPeerId.substring(0, 12)
});
// Stop if we've reached the limit
if (allMessages.length >= messageLimit) {
console.log(`Reached limit of ${messageLimit} messages, stopping`);
return true; // Stop processing
}
} else {
console.warn("Failed to decode message from store");
}
return false; // Continue to next message
},
queryOptions
);
console.log(`Store query completed. Collected ${allMessages.length} messages.`);
if (allMessages.length > 0) {
// Sort by timestamp descending (newest first)
// Since we're querying with paginationForward: false, we're getting recent messages,
// but they may not be in perfect order, so we sort them
allMessages.sort((a, b) => b.timestamp - a.timestamp);
console.log(`Returning ${allMessages.length} message(s)`);
console.log("Newest message timestamp:", new Date(allMessages[0].timestamp).toISOString());
if (allMessages.length > 1) {
console.log("Oldest returned message timestamp:", new Date(allMessages[allMessages.length - 1].timestamp).toISOString());
}
updateStoreQueryStatus(`✓ Successfully retrieved ${allMessages.length} message${allMessages.length !== 1 ? 's' : ''} from store`, false);
displayStoreQueryResults(allMessages);
} else {
updateStoreQueryStatus("✓ Query completed successfully, but no messages found in store", false);
displayStoreQueryResults([]);
}
} catch (error) {
console.error("Error querying store:", error);
updateStoreQueryStatus(`✗ Error querying store: ${error instanceof Error ? error.message : String(error)}`, true);
}
};
const sendMessageButton = document.getElementById("sendMessageButton");
if (sendMessageButton) {
sendMessageButton.addEventListener("click", () => {
@ -241,6 +338,14 @@ async function initializeApp() {
});
}
const queryStoreButton = document.getElementById("queryStoreButton");
if (queryStoreButton) {
queryStoreButton.addEventListener("click", () => {
console.log("Query Store button clicked");
queryStoreMessages();
});
}
initCharts();
(window as any).onDiscoveryUpdate = onDiscoveryUpdate;
(window as any).onConnectionsUpdate = onConnectionsUpdate;

View File

@ -322,3 +322,82 @@ export function recordLatency(id: string, sent: number, received?: number) {
export function wireUiToggles() {
setupCollapsibles();
}
// Store Query UI
const storeQueryStatusEl = document.getElementById("storeQueryStatus") as HTMLDivElement | null;
const storeQueryResultsEl = document.getElementById("storeQueryResults") as HTMLDivElement | null;
export function updateStoreQueryStatus(message: string, isError: boolean = false) {
if (!storeQueryStatusEl) return;
storeQueryStatusEl.textContent = message;
storeQueryStatusEl.style.color = isError ? '#d32f2f' : '#2e7d32';
storeQueryStatusEl.style.fontWeight = 'bold';
storeQueryStatusEl.style.marginTop = '10px';
}
export function displayStoreQueryResults(messages: ChatMessage[]) {
if (!storeQueryResultsEl) return;
storeQueryResultsEl.innerHTML = "";
if (messages.length === 0) {
storeQueryResultsEl.innerHTML = "<p style='color: #666; font-style: italic;'>No messages found.</p>";
return;
}
const title = document.createElement("h3");
title.textContent = `Found ${messages.length} message${messages.length !== 1 ? 's' : ''}:`;
title.style.marginTop = "15px";
storeQueryResultsEl.appendChild(title);
messages.forEach((message, index) => {
const item = document.createElement("div");
item.classList.add("message-item");
item.style.marginBottom = "10px";
item.style.padding = "10px";
item.style.border = "1px solid #ddd";
item.style.borderRadius = "4px";
item.style.backgroundColor = "#f9f9f9";
const indexLabel = document.createElement("p");
indexLabel.style.fontWeight = "bold";
indexLabel.style.marginBottom = "5px";
indexLabel.textContent = `Message ${index + 1}`;
const idText = document.createElement("p");
idText.style.fontSize = "0.9em";
idText.style.color = "#666";
idText.textContent = `ID: ${message.id}`;
const contentP = document.createElement("p");
contentP.style.margin = "8px 0";
contentP.textContent = message.content;
const senderInfoP = document.createElement("p");
senderInfoP.style.fontSize = "0.9em";
senderInfoP.style.color = "#666";
senderInfoP.textContent = `From: ${message.senderPeerId.substring(0, 12)}...`;
const timestampP = document.createElement("p");
timestampP.style.fontSize = "0.9em";
timestampP.style.color = "#666";
timestampP.textContent = `Time: ${new Date(message.timestamp).toLocaleString()}`;
item.appendChild(indexLabel);
item.appendChild(idText);
item.appendChild(contentP);
item.appendChild(senderInfoP);
item.appendChild(timestampP);
storeQueryResultsEl.appendChild(item);
});
}
export function clearStoreQueryResults() {
if (storeQueryResultsEl) {
storeQueryResultsEl.innerHTML = "";
}
if (storeQueryStatusEl) {
storeQueryStatusEl.textContent = "";
}
}