diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf6de48e1..e903c43847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Fixed + +- Improved performance for `useActivityWithRenderer`, in PR [#5172](https://github.com/microsoft/BotFramework-WebChat/pull/5172), by [@OEvgeny](https://github.com/OEvgeny) +- Fixes [#5162](https://github.com/microsoft/BotFramework-WebChat/issues/5162). Improved performance for `useActivityTreeWithRenderer`, in PR [#5163](https://github.com/microsoft/BotFramework-WebChat/pull/5163), by [@compulim](https://github.com/compulim) + ### Changed - Bumped all dependencies to the latest versions, by [@compulim](https://github.com/compulim) in PR [#5174](https://github.com/microsoft/BotFramework-WebChat/pull/5174) @@ -128,7 +133,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixes missing exports of `useNotifications`, in PR [#5148](https://github.com/microsoft/BotFramework-WebChat/pull/5148), by [@compulim](https://github.com/compulim) - Fixes suggested actions keyboard navigation skips actions after suggested actions got updated, in PR [#5150](https://github.com/microsoft/BotFramework-WebChat/pull/5150), by [@OEvgeny](https://github.com/OEvgeny) - Fixes [#5155](https://github.com/microsoft/BotFramework-WebChat/issues/5155). Fixed "Super constructor null of anonymous class is not a constructor" error in CDN bundle by bumping to [`webpack@5.91.0`](https://www.npmjs.com/package/webpack/v/5.91.0), in PR [#5156](https://github.com/microsoft/BotFramework-WebChat/pull/5156), by [@compulim](https://github.com/compulim) -- Improved performance for `useActivityWithRenderer`, in PR [#5172](https://github.com/microsoft/BotFramework-WebChat/pull/5172), by [@OEvgeny](https://github.com/OEvgeny) ### Changed diff --git a/__tests__/html/performance/README.md b/__tests__/html/performance/README.md new file mode 100644 index 0000000000..d01855da90 --- /dev/null +++ b/__tests__/html/performance/README.md @@ -0,0 +1 @@ +Tests in this folder may not have its corresponding `.js` file as we are still exploring how to write tests for tracking performance issues. diff --git a/__tests__/html/performance/manyMessages.batched.html b/__tests__/html/performance/manyMessages.batched.html new file mode 100644 index 0000000000..0c60fc9602 --- /dev/null +++ b/__tests__/html/performance/manyMessages.batched.html @@ -0,0 +1,88 @@ + + + + + + + + + +
+ + + diff --git a/__tests__/html/performance/manyMessages.oneByOne.html b/__tests__/html/performance/manyMessages.oneByOne.html new file mode 100644 index 0000000000..707a7f458f --- /dev/null +++ b/__tests__/html/performance/manyMessages.oneByOne.html @@ -0,0 +1,46 @@ + + + + + + + + + +
+ + + diff --git a/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts b/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts index e2fc09d5ac..db0f8c2a02 100644 --- a/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts +++ b/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts @@ -1,6 +1,7 @@ import { hooks } from 'botframework-webchat-api'; import { useMemo } from 'react'; +import type { WebChatActivity } from 'botframework-webchat-core'; import intersectionOf from '../../../Utils/intersectionOf'; import removeInline from '../../../Utils/removeInline'; import type { ActivityWithRenderer, ReadonlyActivityTree } from './types'; @@ -35,6 +36,10 @@ function validateAllEntriesTagged(entries: readonly T[], bins: readonly (read function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]): ReadonlyActivityTree { const groupActivities = useGroupActivities(); + const entryMap: Map = useMemo( + () => new Map(entries.map(entry => [entry.activity, entry])), + [entries] + ); // We bin activities in 2 different ways: // - `activitiesBySender` is a 2D array containing activities with same sender @@ -45,7 +50,7 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]): entriesBySender: readonly (readonly ActivityWithRenderer[])[]; entriesByStatus: readonly (readonly ActivityWithRenderer[])[]; }>(() => { - const visibleActivities = entries.map(({ activity }) => activity); + const visibleActivities = [...entryMap.keys()]; const groupActivitiesResult = groupActivities({ activities: visibleActivities }); @@ -53,7 +58,7 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]): const activitiesByStatus = groupActivitiesResult?.status || []; const [entriesBySender, entriesByStatus] = [activitiesBySender, activitiesByStatus].map(bins => - bins.map(bin => bin.map(activity => entries.find(entry => entry.activity === activity))) + bins.map(bin => bin.map(activity => entryMap.get(activity))) ); if (!validateAllEntriesTagged(visibleActivities, activitiesBySender)) { @@ -72,7 +77,7 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]): entriesBySender, entriesByStatus }; - }, [entries, groupActivities]); + }, [entryMap, groupActivities]); // Create a tree of activities with 2 dimensions: sender, followed by status. diff --git a/packages/test/harness/src/host/dev/hostOverrides/done.js b/packages/test/harness/src/host/dev/hostOverrides/done.js index eb9aa4646b..7751cf3384 100644 --- a/packages/test/harness/src/host/dev/hostOverrides/done.js +++ b/packages/test/harness/src/host/dev/hostOverrides/done.js @@ -1,14 +1,11 @@ // In dev mode, draw a green tick when test succeeded. -const checkAccessibility = require('../../common/host/checkAccessibility'); const dumpLogs = require('../../common/dumpLogs'); const override = require('../utils/override'); // Send the completion back to the browser console. module.exports = (webDriver, done) => override(done, undefined, async () => { - await checkAccessibility(webDriver)(); - /* istanbul ignore next */ await webDriver.executeScript(() => { console.log( diff --git a/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js b/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js index cbbcf098d6..6e00de3e51 100644 --- a/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js +++ b/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js @@ -1,11 +1,11 @@ -import createDeferred from 'p-defer'; import Observable from 'core-js/features/observable'; import random from 'math-random'; +import createDeferred from 'p-defer'; import updateIn from 'simple-update-in'; -import { createStoreWithOptions } from './createStore'; -import became from '../pageConditions/became'; import createDeferredObservable from '../../utils/createDeferredObservable'; +import became from '../pageConditions/became'; +import { createStoreWithOptions } from './createStore'; import shareObservable from './shareObservable'; function isNativeClock() { @@ -119,7 +119,7 @@ export default function createDirectLineEmulator({ autoConnect = true, ponyfill }; }, emulateConnected: connectedDeferred.resolve, - emulateIncomingActivity: async activity => { + emulateIncomingActivity: async (activity, { skipWait } = {}) => { if (typeof activity === 'string') { activity = { from: { id: 'bot', role: 'bot' }, @@ -145,11 +145,12 @@ export default function createDirectLineEmulator({ autoConnect = true, ponyfill activityDeferredObservable.next(activity); - await became( - 'incoming activity appears in the store', - () => store.getState().activities.find(activity => activity.id === id), - 1000 - ); + skipWait || + (await became( + 'incoming activity appears in the store', + () => store.getState().activities.find(activity => activity.id === id), + 1000 + )); }, emulateOutgoingActivity: (activity, options) => { if (typeof activity === 'string') {