From 877605e15bf966ed8e0e102a0ddff6d4a0417554 Mon Sep 17 00:00:00 2001 From: Cyrus Mobini <68962752+cyrus2281@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:12:16 -0400 Subject: [PATCH] Added the feature of searching and filtering messages + the option to open messages as saved files. (#19) * Added message filtering and highlighting logic feature. closes #18 * Added the feature to save payloads to file * 0.0.7 * Fix error when opening file in new tab --- CHANGELOG.md | 3 + package-lock.json | 5 +- package.json | 2 +- src/extension.ts | 48 +++++++-- webview/src/ConfigView/SettingsView.tsx | 71 +++++++++++-- webview/src/Shared/constants.ts | 5 + webview/src/Shared/interfaces.ts | 2 + webview/src/Shared/utils.ts | 19 +++- webview/src/SubscribeView/MessagesView.tsx | 109 ++++++++++++++++++++ webview/src/SubscribeView/SolaceMessage.tsx | 62 +++++++++-- webview/src/SubscribeView/SubscribeView.tsx | 24 ++--- 11 files changed, 302 insertions(+), 48 deletions(-) create mode 100644 webview/src/SubscribeView/MessagesView.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index c8fb035..a046dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to the "solace-try-me-vsc-extension" extension will be documented in this file. +## [0.0.7] - 2024-10-20 +- Added the feature of searching and filtering messages + the option to open messages as saved files. + ## [0.0.6] - 2024-10-17 - Added Settings Dialog with configurable extension settings, fixed topic issue in subscribe view disconnection. diff --git a/package-lock.json b/package-lock.json index 5d748d7..7d720ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { "name": "solace-try-me-vsc-extension", - "version": "0.0.6", + "version": "0.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "solace-try-me-vsc-extension", - "version": "0.0.6", + "version": "0.0.7", + "license": "Apache-2.0 license", "devDependencies": { "@types/mocha": "^10.0.7", "@types/node": "20.x", diff --git a/package.json b/package.json index d7bc0e4..b22ee7a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "solace-try-me-vsc-extension", "displayName": "Solace Try Me VSC Extension", "description": "VSC built-in tool to visualize and observe events flowing through Solace PubSub+ Broker.", - "version": "0.0.6", + "version": "0.0.7", "publisher": "solace-tools", "author": { "name": "Cyrus Mobini" diff --git a/src/extension.ts b/src/extension.ts index ae4bbcd..c4118a3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,3 +1,4 @@ +import path from "path"; import * as vscode from "vscode"; const openTabs = new Set(); @@ -46,16 +47,51 @@ export function activate(context: vscode.ExtensionContext) { class SolaceTryMeViewProvider implements vscode.WebviewViewProvider { constructor(private readonly context: vscode.ExtensionContext) {} - resolveWebviewView(webviewView: vscode.WebviewView| vscode.WebviewPanel) { + resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel) { const { webview } = webviewView; webview.onDidReceiveMessage(async (message) => { if (message.command === "openInNewTab") { - const document = await vscode.workspace.openTextDocument({ - content: message.content, - language: message.language ?? "plaintext", - }); - vscode.window.showTextDocument(document, { preview: true }); + try { + if (message.filePath && message.fileName) { + // Write content to a file and open it in a new tab + let uri = vscode.Uri.joinPath( + vscode.Uri.file(message.filePath), + message.fileName + ); + const isAbsolutePath = path.isAbsolute(message.filePath); + const workspaceFolders = vscode.workspace.workspaceFolders; + + if (!isAbsolutePath && workspaceFolders) { + const workspacePath = workspaceFolders[0].uri.fsPath; + uri = vscode.Uri.joinPath( + vscode.Uri.file(workspacePath), + message.filePath, + message.fileName + ); + } + + await vscode.workspace.fs.writeFile( + uri, + Buffer.from(message.content, "utf8") + ); + const document = await vscode.workspace.openTextDocument(uri); + vscode.window.showTextDocument(document, { preview: true }); + } else { + const document = await vscode.workspace.openTextDocument({ + content: message.content, + language: message.language ?? "plaintext", + }); + vscode.window.showTextDocument(document, { preview: true }); + } + } catch (error: unknown) { + console.error("Error opening file in new tab: ", error); + // Show VSC error message + vscode.window.showErrorMessage( + "Error opening file in new tab: " + + ((error as Error)?.message ?? error) + ); + } } else if (message.command === "savePreferences") { this.context.globalState.update("preferences", message.preferences); } else if (message.command === "getPreferences") { diff --git a/webview/src/ConfigView/SettingsView.tsx b/webview/src/ConfigView/SettingsView.tsx index 23c5e14..78da131 100644 --- a/webview/src/ConfigView/SettingsView.tsx +++ b/webview/src/ConfigView/SettingsView.tsx @@ -1,4 +1,5 @@ -import { Button, Input } from "@nextui-org/react"; +import { Button, Input, Switch, Tooltip } from "@nextui-org/react"; +import { Info } from "lucide-react"; import { ModalBody, @@ -10,7 +11,7 @@ import { import { useSettings } from "../Shared/components/SettingsContext"; import { DEFAULT_SETTINGS } from "../Shared/constants"; -const getInteger = (value: string, min=0) => { +const getInteger = (value: string, min = 0) => { return Math.max(Math.abs(parseInt(value) || 0), min); }; @@ -27,6 +28,8 @@ const SettingsView = ({ maxPayloadLength, maxPropertyLength, brokerDisconnectTimeout, + savePayloads, + payloadBasePath, }, setSettings, } = useSettings(); @@ -48,13 +51,16 @@ const SettingsView = ({ {(onModalClose) => ( <> - - Solace Try Me Extension Settings - + Solace Try Me Extension Settings + + + } value={maxDisplayMessages.toString()} onChange={(e) => setSettings((prev) => ({ @@ -62,13 +68,18 @@ const SettingsView = ({ maxDisplayMessages: getInteger(e.target.value, 1), })) } - max={1000} + max={100} min={1} step={1} /> + + + } value={maxPayloadLength.toString()} onChange={(e) => setSettings((prev) => ({ @@ -82,6 +93,11 @@ const SettingsView = ({ + + + } value={maxPropertyLength.toString()} onChange={(e) => setSettings((prev) => ({ @@ -95,17 +111,56 @@ const SettingsView = ({ + + + } value={(brokerDisconnectTimeout / 1000 / 60).toString()} onChange={(e) => setSettings((prev) => ({ ...prev, - brokerDisconnectTimeout: getInteger(e.target.value, 1) * 1000 * 60, + brokerDisconnectTimeout: + getInteger(e.target.value, 1) * 1000 * 60, })) } min={1} step={1} /> + { + setSettings((prev) => ({ + ...prev, + savePayloads, + })); + }} + > +
+ Save payloads on open + + + +
+
+ + + + } + isDisabled={!savePayloads} + value={payloadBasePath || ""} + onChange={(e) => + setSettings((prev) => ({ + ...prev, + payloadBasePath: e.target.value, + })) + } + />
+ + + {filteredMessages.length > 0 && + {filteredMessages.map((message) => ( + + ))} + } + {filteredMessages.length === 0 && ( +

No messages found.

+ )} + + ); +}; + +export default MessagesView; diff --git a/webview/src/SubscribeView/SolaceMessage.tsx b/webview/src/SubscribeView/SolaceMessage.tsx index 8775d4b..b7d1d9c 100644 --- a/webview/src/SubscribeView/SolaceMessage.tsx +++ b/webview/src/SubscribeView/SolaceMessage.tsx @@ -20,6 +20,8 @@ interface SolaceMessageProps { message: Message; maxPayloadLength: number; maxPropertyLength: number; + baseFilePath?: string; + highlight: string; } const keyNameMap: { [k: string]: string } = { @@ -57,10 +59,35 @@ const transformMetaItem = ([key, value]: [string, unknown]) => { return [newKey, newValue]; }; -const getContent = (content: string, maxLength: number) => { +const getHighlightedContent = ( + content: string, + highlight: string | null +): JSX.Element | string => { + if (!highlight) return content; + // Check if content has highlight (case insensitive) + // split and add tag if so + const parts = content.split(new RegExp(highlight, "i")); + if (parts.length === 1) return content; + return parts.reduce((acc, part, index) => { + if (index === 0) return part; + return ( + <> + {acc} + {highlight} + {part} + + ); + }, ""); +}; + +const getContent = ( + content: string, + maxLength: number, + highlight: string | null +) => { return content.length > maxLength ? ( <> - {content.slice(0, maxLength)} + {getHighlightedContent(content.slice(0, maxLength), highlight)} @@ -68,11 +95,17 @@ const getContent = (content: string, maxLength: number) => { ) : ( - content + getHighlightedContent(content, highlight) ); }; -const SolaceMessage = ({ message, maxPayloadLength, maxPropertyLength }: SolaceMessageProps) => { +const SolaceMessage = ({ + message, + maxPayloadLength, + maxPropertyLength, + baseFilePath, + highlight, +}: SolaceMessageProps) => { const dataStr = formatDate( message.metadata.senderTimestamp ?? message.metadata.receiverTimestamp ); @@ -83,13 +116,14 @@ const SolaceMessage = ({ message, maxPayloadLength, maxPropertyLength }: SolaceM const userProperties = Object.entries(message.userProperties); - const payload = getContent(message.payload, maxPayloadLength); + const payload = getContent(message.payload, maxPayloadLength, highlight); return ( - - {messages.length !== 0 && ( - - {messages.map((message) => ( - - ))} - - )} - {messages.length === 0 && ( -

No messages received yet.

- )} + );