-
Notifications
You must be signed in to change notification settings - Fork 0
[DX-931] Fix missing message fields for channel subscribe/history #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3f54bc1
abc6747
c034670
7c78e27
a08e9f2
35f63f8
094cee2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,18 @@ | ||
| import { Args, Flags } from "@oclif/core"; | ||
| import * as Ably from "ably"; | ||
| import chalk from "chalk"; | ||
|
|
||
| import { AblyBaseCommand } from "../../base-command.js"; | ||
| import { productApiFlags, timeRangeFlags } from "../../flags.js"; | ||
| import { formatMessageData } from "../../utils/json-formatter.js"; | ||
| import { buildHistoryParams } from "../../utils/history.js"; | ||
| import { errorMessage } from "../../utils/errors.js"; | ||
| import { | ||
| countLabel, | ||
| formatMessagesOutput, | ||
| formatTimestamp, | ||
| formatMessageTimestamp, | ||
| limitWarning, | ||
| resource, | ||
| toMessageJson, | ||
| } from "../../utils/output.js"; | ||
| import type { MessageDisplayFields } from "../../utils/output.js"; | ||
|
|
||
| export default class ChannelsHistory extends AblyBaseCommand { | ||
| static override args = { | ||
|
|
@@ -85,41 +84,32 @@ export default class ChannelsHistory extends AblyBaseCommand { | |
| const history = await channel.history(historyParams); | ||
| const messages = history.items; | ||
|
|
||
| // Build display fields from history results | ||
| const displayMessages: MessageDisplayFields[] = messages.map( | ||
| (message, index) => ({ | ||
| channel: channelName, | ||
| clientId: message.clientId, | ||
| data: message.data, | ||
| event: message.name || "(none)", | ||
| id: message.id, | ||
| indexPrefix: `[${index + 1}] ${formatTimestamp(formatMessageTimestamp(message.timestamp))}`, | ||
| serial: message.serial, | ||
| timestamp: message.timestamp ?? Date.now(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. History shows redundant timestamp in two different formatsLow Severity History messages display the timestamp twice in different formats. The Additional Locations (1) |
||
| version: message.version, | ||
| annotations: message.annotations, | ||
| }), | ||
| ); | ||
|
|
||
| // Display results based on format | ||
| if (this.shouldOutputJson(flags)) { | ||
| this.log(this.formatJsonOutput({ messages }, flags)); | ||
| } else { | ||
| if (messages.length === 0) { | ||
| this.log("No messages found in the channel history."); | ||
| return; | ||
| } | ||
|
|
||
| this.log( | ||
| `Found ${countLabel(messages.length, "message")} in the history of channel: ${resource(channelName)}`, | ||
| this.formatJsonOutput( | ||
| displayMessages.map((msg) => toMessageJson(msg)), | ||
| flags, | ||
| ), | ||
| ); | ||
| this.log(""); | ||
|
|
||
| for (const [index, message] of messages.entries()) { | ||
| const timestampDisplay = message.timestamp | ||
| ? formatTimestamp(formatMessageTimestamp(message.timestamp)) | ||
| : chalk.dim("[Unknown timestamp]"); | ||
|
|
||
| this.log(`${chalk.dim(`[${index + 1}]`)} ${timestampDisplay}`); | ||
| this.log( | ||
| `${chalk.dim("Event:")} ${chalk.yellow(message.name || "(none)")}`, | ||
| ); | ||
|
|
||
| if (message.clientId) { | ||
| this.log( | ||
| `${chalk.dim("Client ID:")} ${chalk.blue(message.clientId)}`, | ||
| ); | ||
| } | ||
|
|
||
| this.log(chalk.dim("Data:")); | ||
| this.log(formatMessageData(message.data)); | ||
|
|
||
| this.log(""); | ||
| } | ||
| } else { | ||
| this.log(formatMessagesOutput(displayMessages)); | ||
|
|
||
| const warning = limitWarning(messages.length, flags.limit, "messages"); | ||
| if (warning) this.log(warning); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,15 +9,15 @@ import { | |
| productApiFlags, | ||
| rewindFlag, | ||
| } from "../../flags.js"; | ||
| import { formatMessageData } from "../../utils/json-formatter.js"; | ||
| import { | ||
| formatMessagesOutput, | ||
| listening, | ||
| progress, | ||
| resource, | ||
| success, | ||
| formatTimestamp, | ||
| formatMessageTimestamp, | ||
| toMessageJson, | ||
| } from "../../utils/output.js"; | ||
| import type { MessageDisplayFields } from "../../utils/output.js"; | ||
|
|
||
| export default class ChannelsSubscribe extends AblyBaseCommand { | ||
| static override args = { | ||
|
|
@@ -195,46 +195,32 @@ export default class ChannelsSubscribe extends AblyBaseCommand { | |
|
|
||
| channel.subscribe((message: Ably.Message) => { | ||
| this.sequenceCounter++; | ||
| const timestamp = formatMessageTimestamp(message.timestamp); | ||
| const messageEvent = { | ||
|
|
||
| const msgFields: MessageDisplayFields = { | ||
| channel: channel.name, | ||
| clientId: message.clientId, | ||
| connectionId: message.connectionId, | ||
| data: message.data, | ||
| encoding: message.encoding, | ||
| event: message.name || "(none)", | ||
| id: message.id, | ||
| timestamp, | ||
| serial: message.serial, | ||
| timestamp: message.timestamp ?? Date.now(), | ||
| version: message.version, | ||
| annotations: message.annotations, | ||
| ...(flags["sequence-numbers"] | ||
| ? { sequence: this.sequenceCounter } | ||
| ? { sequencePrefix: `${chalk.dim(`[${this.sequenceCounter}]`)} ` } | ||
| : {}), | ||
| }; | ||
| this.logCliEvent( | ||
| flags, | ||
| "subscribe", | ||
| "messageReceived", | ||
| `Received message on channel ${channel.name}`, | ||
| messageEvent, | ||
| ); | ||
|
|
||
| if (this.shouldOutputJson(flags)) { | ||
| this.log(this.formatJsonOutput(messageEvent, flags)); | ||
| } else { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verbose logging removed for received messages in subscribeLow Severity The |
||
| const name = message.name || "(none)"; | ||
| const sequencePrefix = flags["sequence-numbers"] | ||
| ? `${chalk.dim(`[${this.sequenceCounter}]`)}` | ||
| : ""; | ||
|
|
||
| // Message header with timestamp and channel info | ||
| this.log( | ||
| `${formatTimestamp(timestamp)}${sequencePrefix} ${chalk.cyan(`Channel: ${channel.name}`)} | ${chalk.yellow(`Event: ${name}`)}`, | ||
| ); | ||
|
|
||
| // Message data with consistent formatting | ||
| this.log(chalk.dim("Data:")); | ||
| this.log(formatMessageData(message.data)); | ||
| const jsonMsg = toMessageJson(msgFields); | ||
| if (flags["sequence-numbers"]) { | ||
| jsonMsg.sequence = this.sequenceCounter; | ||
| } | ||
|
|
||
| this.log(""); // Empty line for better readability | ||
| this.log(this.formatJsonOutput(jsonMsg, flags)); | ||
| } else { | ||
| this.log(formatMessagesOutput([msgFields])); | ||
| this.log(""); // Empty line for better readability between messages | ||
| } | ||
| }); | ||
| } | ||
|
|
@@ -258,6 +244,7 @@ export default class ChannelsSubscribe extends AblyBaseCommand { | |
| } | ||
|
|
||
| this.log(listening("Listening for messages.")); | ||
| this.log(""); | ||
| } | ||
|
|
||
| this.logCliEvent( | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,4 +1,6 @@ | ||||||
| import chalk, { type ChalkInstance } from "chalk"; | ||||||
| import type * as Ably from "ably"; | ||||||
| import { formatMessageData, isJsonData } from "./json-formatter.js"; | ||||||
|
|
||||||
| export function progress(message: string): string { | ||||||
| return `${message}...`; | ||||||
|
|
@@ -63,6 +65,129 @@ export function limitWarning( | |||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Fields for consistent message display across subscribe and history commands. | ||||||
| * All fields use the same names and format for both human-readable and JSON output. | ||||||
| * Timestamp is raw milliseconds (Unix epoch) — not converted to ISO string. | ||||||
| */ | ||||||
| export interface MessageDisplayFields { | ||||||
| channel: string; | ||||||
| clientId?: string; | ||||||
| data: unknown; | ||||||
| event: string; | ||||||
| id?: string; | ||||||
| indexPrefix?: string; | ||||||
| sequencePrefix?: string; | ||||||
| serial?: string; | ||||||
| timestamp: number; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normalize timestamps in the shared message shape. Line 81 stores Also applies to: 101-105, 146-146 🤖 Prompt for AI Agents |
||||||
| version?: Ably.MessageVersion; | ||||||
| annotations?: Ably.MessageAnnotations; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Format an array of messages for human-readable console output. | ||||||
| * Each message shows all fields on separate lines, messages separated by blank lines. | ||||||
| * Returns "No messages found." for empty arrays. | ||||||
| */ | ||||||
| export function formatMessagesOutput(messages: MessageDisplayFields[]): string { | ||||||
| if (messages.length === 0) { | ||||||
| return "No messages found."; | ||||||
| } | ||||||
|
|
||||||
| const formatted = messages.map((msg) => { | ||||||
| const lines: string[] = []; | ||||||
|
|
||||||
| if (msg.indexPrefix) { | ||||||
| lines.push(chalk.dim(msg.indexPrefix)); | ||||||
| } | ||||||
|
|
||||||
| const timestampLine = `${chalk.dim("Timestamp:")} ${msg.timestamp}`; | ||||||
| lines.push( | ||||||
| msg.sequencePrefix | ||||||
| ? `${msg.sequencePrefix}${timestampLine}` | ||||||
| : timestampLine, | ||||||
| `${chalk.dim("Channel:")} ${resource(msg.channel)}`, | ||||||
| `${chalk.dim("Event:")} ${chalk.yellow(msg.event)}`, | ||||||
| ); | ||||||
|
|
||||||
| if (msg.id) { | ||||||
| lines.push(`${chalk.dim("ID:")} ${msg.id}`); | ||||||
| } | ||||||
|
|
||||||
| if (msg.clientId) { | ||||||
| lines.push(`${chalk.dim("Client ID:")} ${chalk.blue(msg.clientId)}`); | ||||||
| } | ||||||
|
|
||||||
| if (msg.serial) { | ||||||
| lines.push(`${chalk.dim("Serial:")} ${msg.serial}`); | ||||||
| } | ||||||
|
|
||||||
| if ( | ||||||
| msg.version && | ||||||
| Object.keys(msg.version).length > 0 && | ||||||
| msg.version.serial && | ||||||
| msg.version.serial !== msg.serial | ||||||
| ) { | ||||||
| lines.push(`${chalk.dim("Version:")}`); | ||||||
| if (msg.version.serial) { | ||||||
| lines.push(` ${chalk.dim("Serial:")} ${msg.version.serial}`); | ||||||
| } | ||||||
| if (msg.version.timestamp !== undefined) { | ||||||
| lines.push(` ${chalk.dim("Timestamp:")} ${msg.version.timestamp}`); | ||||||
| } | ||||||
| if (msg.version.clientId) { | ||||||
| lines.push( | ||||||
| ` ${chalk.dim("Client ID:")} ${chalk.blue(msg.version.clientId)}`, | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (msg.annotations && Object.keys(msg.annotations.summary).length > 0) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accessing
|
||||||
| lines.push(`${chalk.dim("Annotations:")}`); | ||||||
| for (const [type, entry] of Object.entries(msg.annotations.summary)) { | ||||||
| lines.push( | ||||||
| ` ${chalk.dim(`${type}:`)}`, | ||||||
| ` ${formatMessageData(entry)}`, | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (isJsonData(msg.data)) { | ||||||
| lines.push(`${chalk.dim("Data:")}\n${formatMessageData(msg.data)}`); | ||||||
| } else { | ||||||
| lines.push(`${chalk.dim("Data:")} ${String(msg.data)}`); | ||||||
| } | ||||||
|
|
||||||
| return lines.join("\n"); | ||||||
| }); | ||||||
|
|
||||||
| return formatted.join("\n\n"); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Convert a single MessageDisplayFields to a plain object for JSON output. | ||||||
| * Includes all required fields, omits undefined optional fields. | ||||||
| * | ||||||
| * Usage: | ||||||
| * Single message (subscribe): toMessageJson(msg) | ||||||
| * Array of messages (history): messages.map(toMessageJson) | ||||||
| */ | ||||||
| export function toMessageJson( | ||||||
| msg: MessageDisplayFields, | ||||||
| ): Record<string, unknown> { | ||||||
| return { | ||||||
| timestamp: msg.timestamp, | ||||||
| channel: msg.channel, | ||||||
| event: msg.event, | ||||||
| ...(msg.id ? { id: msg.id } : {}), | ||||||
| ...(msg.clientId ? { clientId: msg.clientId } : {}), | ||||||
| ...(msg.serial ? { serial: msg.serial } : {}), | ||||||
| ...(msg.version ? { version: msg.version } : {}), | ||||||
| ...(msg.annotations ? { annotations: msg.annotations } : {}), | ||||||
| data: msg.data, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep If 💡 Proposed fix- data: msg.data,
+ data: msg.data ?? null,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| export function formatPresenceAction(action: string): { | ||||||
| symbol: string; | ||||||
| color: ChalkInstance; | ||||||
|
|
||||||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dim the history index marker to match the standard format.
Line 95 leaves
[${index + 1}]in normal intensity, so the prefix no longer matches the repo’s required[index] timestampstyling. Usechalk.dim()on the index segment beforeformatTimestamp(...).As per coding guidelines, "Use
[index] timestampordering format:${chalk.dim([${index + 1}])} ${formatTimestamp(timestamp)}for history output".🤖 Prompt for AI Agents