From 3dca08021292b11c672bcece0728151bd8ca70ec Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 7 Jan 2025 09:06:28 +0100 Subject: [PATCH 1/5] fix: smart update debounce --- packages/slack/src/app.ts | 8 ++------ packages/slack/src/utils.ts | 39 +++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/slack/src/app.ts b/packages/slack/src/app.ts index eabe40e..f8c5dca 100644 --- a/packages/slack/src/app.ts +++ b/packages/slack/src/app.ts @@ -3,7 +3,6 @@ import { ContextBlock, RichTextBlock, WebClient } from '@slack/web-api'; import { Application } from '@callstack/byorg-core'; import { logger } from '@callstack/byorg-utils'; import { parseMarkdownToRichTextBlock } from '@callstack/slack-rich-text'; -import pDebounce from 'p-debounce'; import { SlackMessage, SlackApplicationConfig as SlackApplicationConfig, @@ -11,8 +10,8 @@ import { } from './types.js'; import { fetchFileInfo, getThread } from './slack-api.js'; import { formatGroupMessage, toCoreMessage } from './messages.js'; -import { wait } from './utils.js'; import { getBotId } from './bot-id.js'; +import { debounceUpdateResponse } from './utils.js'; type MessageBlock = RichTextBlock | ContextBlock; @@ -91,8 +90,6 @@ function configureSlackApp(app: Application, slack: Slack.App): void { blocks, }); responseMessageTs = responseMessage.ts as string; - - await wait(UPDATE_THROTTLE_TIME); return; } @@ -104,12 +101,11 @@ function configureSlackApp(app: Application, slack: Slack.App): void { parse: 'none', }); - await wait(UPDATE_THROTTLE_TIME); return; }; let updatePartialResponsePromise: Promise = Promise.resolve(); - const updateResponseMessageWithThrottle = pDebounce.promise(updateResponseMessage); + const updateResponseMessageWithThrottle = debounceUpdateResponse(updateResponseMessage); const handlePartialResponse = (response: string): void => { updatePartialResponsePromise = updateResponseMessageWithThrottle(response); diff --git a/packages/slack/src/utils.ts b/packages/slack/src/utils.ts index 7b18fbe..9410450 100644 --- a/packages/slack/src/utils.ts +++ b/packages/slack/src/utils.ts @@ -1,3 +1,38 @@ -export function wait(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); +const MIN_UPDATE_TIME = 2000; +const MIN_RESPONSE_LENGTH = 200; + +export type UpdateResponseFn = (text: string, isFinalResponse?: boolean) => Promise; + +export function debounceUpdateResponse(updateResponse: UpdateResponseFn) { + let pendingPromise: Promise | undefined; + let lastUpdate: number = performance.now(); + + return async function (text: string, isFinalResponse?: boolean) { + // Wait for the last update to finish + if (pendingPromise) { + return pendingPromise; + } + + // Final message should be always sent + if (!isFinalResponse) { + // Debouncing + if (performance.now() - lastUpdate < MIN_UPDATE_TIME) { + return Promise.resolve(); + } + + // Drop too short initial messages + if (text.length < MIN_RESPONSE_LENGTH) { + return Promise.resolve(); + } + } + + try { + lastUpdate = performance.now(); + pendingPromise = updateResponse(text, isFinalResponse); + return await pendingPromise; + } finally { + pendingPromise = undefined; + } + } } +z \ No newline at end of file From 96f7c186ba9d3570de6f9100ec8bfaf800984738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Tue, 7 Jan 2025 10:15:47 +0100 Subject: [PATCH 2/5] move debounce to slack utils --- packages/slack/src/app.ts | 13 +++++++----- packages/slack/src/utils.ts | 38 ---------------------------------- packages/utils/src/debounce.ts | 38 ++++++++++++++++++++++++++++++++++ packages/utils/src/index.ts | 3 +++ 4 files changed, 49 insertions(+), 43 deletions(-) delete mode 100644 packages/slack/src/utils.ts create mode 100644 packages/utils/src/debounce.ts diff --git a/packages/slack/src/app.ts b/packages/slack/src/app.ts index f8c5dca..f5711b8 100644 --- a/packages/slack/src/app.ts +++ b/packages/slack/src/app.ts @@ -1,7 +1,7 @@ import Slack, { LogLevel, SayFn } from '@slack/bolt'; import { ContextBlock, RichTextBlock, WebClient } from '@slack/web-api'; import { Application } from '@callstack/byorg-core'; -import { logger } from '@callstack/byorg-utils'; +import { debouncePartialUpdate, logger } from '@callstack/byorg-utils'; import { parseMarkdownToRichTextBlock } from '@callstack/slack-rich-text'; import { SlackMessage, @@ -11,11 +11,11 @@ import { import { fetchFileInfo, getThread } from './slack-api.js'; import { formatGroupMessage, toCoreMessage } from './messages.js'; import { getBotId } from './bot-id.js'; -import { debounceUpdateResponse } from './utils.js'; type MessageBlock = RichTextBlock | ContextBlock; -const UPDATE_THROTTLE_TIME = 1000; +const MIN_UPDATE_TIME = 3000; +const MIN_RESPONSE_LENGTH = 250; const NOTIFICATION_TEXT_LIMIT = 200; const RESPONDING_BLOCK: MessageBlock = { @@ -105,10 +105,13 @@ function configureSlackApp(app: Application, slack: Slack.App): void { }; let updatePartialResponsePromise: Promise = Promise.resolve(); - const updateResponseMessageWithThrottle = debounceUpdateResponse(updateResponseMessage); + const updateResponseMessageWithDebounce = debouncePartialUpdate(updateResponseMessage, { + minUpdateTime: MIN_UPDATE_TIME, + minResponseLength: MIN_RESPONSE_LENGTH, + }); const handlePartialResponse = (response: string): void => { - updatePartialResponsePromise = updateResponseMessageWithThrottle(response); + updatePartialResponsePromise = updateResponseMessageWithDebounce(response); }; const startTime = performance.now(); diff --git a/packages/slack/src/utils.ts b/packages/slack/src/utils.ts deleted file mode 100644 index 9410450..0000000 --- a/packages/slack/src/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -const MIN_UPDATE_TIME = 2000; -const MIN_RESPONSE_LENGTH = 200; - -export type UpdateResponseFn = (text: string, isFinalResponse?: boolean) => Promise; - -export function debounceUpdateResponse(updateResponse: UpdateResponseFn) { - let pendingPromise: Promise | undefined; - let lastUpdate: number = performance.now(); - - return async function (text: string, isFinalResponse?: boolean) { - // Wait for the last update to finish - if (pendingPromise) { - return pendingPromise; - } - - // Final message should be always sent - if (!isFinalResponse) { - // Debouncing - if (performance.now() - lastUpdate < MIN_UPDATE_TIME) { - return Promise.resolve(); - } - - // Drop too short initial messages - if (text.length < MIN_RESPONSE_LENGTH) { - return Promise.resolve(); - } - } - - try { - lastUpdate = performance.now(); - pendingPromise = updateResponse(text, isFinalResponse); - return await pendingPromise; - } finally { - pendingPromise = undefined; - } - } -} -z \ No newline at end of file diff --git a/packages/utils/src/debounce.ts b/packages/utils/src/debounce.ts new file mode 100644 index 0000000..b2f42dc --- /dev/null +++ b/packages/utils/src/debounce.ts @@ -0,0 +1,38 @@ +export type PartialUpdateFn = (text: string) => Promise; +export type DebouncePartialUpdateOptions = { + minUpdateTime: number; + minResponseLength: number; +}; + +export function debouncePartialUpdate( + partialUpdate: PartialUpdateFn, + { minResponseLength, minUpdateTime }: DebouncePartialUpdateOptions, +) { + let pendingPromise: Promise | undefined; + let lastUpdate: number | undefined; + + return async function (text: string) { + // Wait for the last update to finish + if (pendingPromise) { + return pendingPromise; + } + + // Time-based debounce + if (lastUpdate !== undefined && performance.now() - lastUpdate < minUpdateTime) { + return Promise.resolve(); + } + + // Drop too short initial messages + if (text.length < minResponseLength) { + return Promise.resolve(); + } + + try { + lastUpdate = performance.now(); + pendingPromise = partialUpdate(text); + return await pendingPromise; + } finally { + pendingPromise = undefined; + } + }; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 65898b9..267c125 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -5,3 +5,6 @@ export type { Logger, LogLevel } from './logger/types.js'; export { requireEnv } from './env.js'; export { withCache } from './with-cache.js'; + +export { debouncePartialUpdate } from './debounce.js'; +export type { PartialUpdateFn, DebouncePartialUpdateOptions } from './debounce.js'; From 91e4b342e673a3ab2f3aaf0390daa59c269238ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Tue, 7 Jan 2025 10:16:16 +0100 Subject: [PATCH 3/5] drop p-debounce dep --- packages/slack/package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/slack/package.json b/packages/slack/package.json index c3cc40c..56cc096 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -34,7 +34,6 @@ "@callstack/slack-rich-text": "workspace:*", "@slack/bolt": "^4.1.1", "@slack/web-api": "^7.8.0", - "p-debounce": "^4.0.0", "ts-regex-builder": "^1.8.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68353d2..13bb9ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -294,9 +294,6 @@ importers: '@slack/web-api': specifier: ^7.8.0 version: 7.8.0 - p-debounce: - specifier: ^4.0.0 - version: 4.0.0 ts-regex-builder: specifier: ^1.8.2 version: 1.8.2 From b53b97a169c446d3993f630b5ecdf9b4d5aa593e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Tue, 7 Jan 2025 10:23:36 +0100 Subject: [PATCH 4/5] changeset --- .changeset/wet-hotels-end.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/wet-hotels-end.md diff --git a/.changeset/wet-hotels-end.md b/.changeset/wet-hotels-end.md new file mode 100644 index 0000000..52d5a05 --- /dev/null +++ b/.changeset/wet-hotels-end.md @@ -0,0 +1,6 @@ +--- +'@callstack/byorg-utils': minor +'@callstack/byorg-slack': patch +--- + +utils: debouncePartialUpdate function for smart debounce handling From 6bb0f9574326d35ba4e010f6b3d47fee8d495146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Tue, 7 Jan 2025 11:48:18 +0100 Subject: [PATCH 5/5] . --- packages/utils/src/debounce.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/debounce.ts b/packages/utils/src/debounce.ts index b2f42dc..6ed615d 100644 --- a/packages/utils/src/debounce.ts +++ b/packages/utils/src/debounce.ts @@ -19,12 +19,12 @@ export function debouncePartialUpdate( // Time-based debounce if (lastUpdate !== undefined && performance.now() - lastUpdate < minUpdateTime) { - return Promise.resolve(); + return; } - // Drop too short initial messages + // Skip too short initial messages if (text.length < minResponseLength) { - return Promise.resolve(); + return; } try {