diff --git a/README.md b/README.md index 05be90255..19718a3f9 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,13 @@ function initAnswers() { apiKey: '', // Required, the key used for your Answers experience experienceKey: '', + // Optional, visitor interacting with the experience, see Visitor Configuration below for details + visitor: { + // Required, see below + id: '', + // Optional, see below + idMethod: '', + }, // Optional, initialize components here, invoked when the Answers component library is loaded/ready. // If components are not added here, they can also be added when the init promise resolves onReady: function() {}, @@ -323,6 +330,21 @@ function (searchParams) => { }), ``` +## Visitor Configuration + +Below is a list of configuration attributes related to a visitor, used in the [base configuration](#answersinit-configuration-options) above. + +The visitor object ties a user's identity to their searches and actions. The visitor can also be set or changed using the `ANSWERS.setVisitor` function. + +```js + visitor: { + // Required, the ID associated with the user. This will be the yextUserId if Yext Auth is used. + id: '123919', + // Optional, the method used to generate the visitor ID. + idMethod: 'YEXT_USER', + }, +``` + # Component Usage The Answers Component Library exposes an easy to use interface for adding and customizing various types of UI components on your page. diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES index f8b2774c8..c16a2d7a0 100644 --- a/THIRD-PARTY-NOTICES +++ b/THIRD-PARTY-NOTICES @@ -304,7 +304,7 @@ MIT License The following NPM package may be included in this product: - - @yext/answers-core@1.3.0 + - @yext/answers-core@1.3.3-beta.0 This package contains the following license and notice below: diff --git a/package-lock.json b/package-lock.json index 2aba765ce..6085925a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5803,9 +5803,9 @@ } }, "@yext/answers-core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@yext/answers-core/-/answers-core-1.3.0.tgz", - "integrity": "sha512-/OBnC04w4Z6PL9pv0qwkmZLqgrBDm7Wca5/aKe7ksN7EbItSaXN1FF1M1Q69sp8Db/3rNHcKwKg9x8dT8dfh2g==", + "version": "1.3.3-beta.0", + "resolved": "https://registry.npmjs.org/@yext/answers-core/-/answers-core-1.3.3-beta.0.tgz", + "integrity": "sha512-BNVKmraa0xTR0B5d3LzBKO48wnYxao7QwI3Pn9XB7ZaaSyMpjmlr0UxPqIpRJr8fsQ7Sx8gLPXzEodog9C6elg==", "requires": { "@babel/runtime-corejs3": "^7.12.5", "cross-fetch": "^3.1.4" diff --git a/package.json b/package.json index cb952d55d..39aa44715 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ ], "dependencies": { "@mapbox/mapbox-gl-language": "^0.10.1", - "@yext/answers-core": "^1.3.0", + "@yext/answers-core": "^1.3.3-beta.0", "@yext/answers-storage": "^1.1.0", "@yext/rtf-converter": "^1.5.0", "cross-fetch": "^3.1.4", @@ -239,4 +239,4 @@ "./zh-Hant/modern.min": "./dist/zh-Hant-answers-modern.min.js", "./zh-Hant/template": "./dist/zh-Hant-answerstemplates.compiled.min.js" } -} +} \ No newline at end of file diff --git a/src/answers-search-bar.js b/src/answers-search-bar.js index 9a19878b1..ab11b56d9 100644 --- a/src/answers-search-bar.js +++ b/src/answers-search-bar.js @@ -207,7 +207,11 @@ class AnswersSearchBar { this._setDefaultInitialSearch(parsedConfig.search); - this.core.init(); + if (parsedConfig.visitor) { + this.setVisitor(parsedConfig.visitor); + } else { + this.core.init(); + } this._onReady = parsedConfig.onReady || function () {}; @@ -450,6 +454,15 @@ class AnswersSearchBar { return value; } } + + setVisitor (visitor) { + if (visitor.id) { + this._analyticsReporterService?.setVisitor(visitor); + this.core.init({ visitor: visitor }); + } else { + console.error(`Invalid visitor. Visitor was not set because "${JSON.stringify(visitor)}" does not have an id.`); + } + } } /** diff --git a/src/answers-umd.js b/src/answers-umd.js index 7c144d578..5d468fdcf 100644 --- a/src/answers-umd.js +++ b/src/answers-umd.js @@ -297,7 +297,11 @@ class Answers { this._setDefaultInitialSearch(parsedConfig.search); - this.core.init(); + if (parsedConfig.visitor) { + this.setVisitor(parsedConfig.visitor); + } else { + this.core.init(); + } this._onReady = parsedConfig.onReady || function () {}; @@ -693,6 +697,15 @@ class Answers { return value; } } + + setVisitor (visitor) { + if (visitor.id) { + this._analyticsReporterService?.setVisitor(visitor); + this.core.init({ visitor: visitor }); + } else { + console.error(`Invalid visitor. Visitor was not set because "${JSON.stringify(visitor)}" does not have an id.`); + } + } } /** diff --git a/src/core/analytics/analyticsreporter.js b/src/core/analytics/analyticsreporter.js index bd38cbcd4..1c6dde854 100644 --- a/src/core/analytics/analyticsreporter.js +++ b/src/core/analytics/analyticsreporter.js @@ -74,6 +74,10 @@ export default class AnalyticsReporter { this._globalOptions.queryId = queryId; } + setVisitor (visitor) { + this._globalOptions.visitor = visitor; + } + /** * Opt in or out of analytics click events * @param {boolean} analyticsEventsEnabled diff --git a/src/core/core.js b/src/core/core.js index 679928cda..252f63258 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -127,7 +127,7 @@ export default class Core { /** * Initializes the {@link Core} by providing it with an instance of the Core library. */ - init () { + init (config) { const params = { apiKey: this._apiKey, experienceKey: this._experienceKey, @@ -136,7 +136,8 @@ export default class Core { endpoints: this._getServiceUrls(), additionalQueryParams: { jsLibVersion: LIB_VERSION - } + }, + ...config }; this._coreLibrary = provideCore(params); diff --git a/tests/answers-umd.js b/tests/answers-umd.js index 57fe2f67a..74bfa0c35 100644 --- a/tests/answers-umd.js +++ b/tests/answers-umd.js @@ -46,4 +46,43 @@ describe('ANSWERS instance integration testing', () => { }; expect(ANSWERS._analyticsReporterService.report).toHaveBeenCalledWith(expectedEvent, expect.anything()); }); + + it('Visitor is set if passed to ANSWERS.init', async () => { + mockWindow(windowSpy, { + location: { + search: '?query=test' + } + }); + await initAnswers(ANSWERS, { + visitor: { id: '123' } + }); + expect(ANSWERS._analyticsReporterService.setVisitor).toHaveBeenCalledWith({ id: '123' }); + }); + + it('Visitor is not set if missing id', async () => { + const consoleSpy = jest.spyOn(console, 'error'); + mockWindow(windowSpy, { + location: { + search: '?query=test' + } + }); + await initAnswers(ANSWERS, { + visitor: { idMethod: 'test' } + }); + expect(ANSWERS._analyticsReporterService.setVisitor).toHaveBeenCalledTimes(0); + expect(consoleSpy).toHaveBeenCalled(); + }); + + it('Visitor can be changed', async () => { + mockWindow(windowSpy, { + location: { + search: '?query=test' + } + }); + await initAnswers(ANSWERS, { visitor: { id: '123', idMethod: 'test' } }); + const initSpy = jest.spyOn(ANSWERS.core, 'init'); + ANSWERS.setVisitor({ id: '456' }); + expect(ANSWERS._analyticsReporterService.setVisitor).toHaveBeenLastCalledWith({ id: '456' }); + expect(initSpy).toHaveBeenCalledWith({ visitor: { id: '456' } }); + }); }); diff --git a/tests/core/analytics/analyticsreporter.js b/tests/core/analytics/analyticsreporter.js index 2bbc8bb64..38dce2ed1 100644 --- a/tests/core/analytics/analyticsreporter.js +++ b/tests/core/analytics/analyticsreporter.js @@ -121,4 +121,15 @@ describe('reporting events', () => { expect.anything(), expect.objectContaining({ data: expect.not.objectContaining({ queryId: '123' }) })); }); + + it('Includes visitor when set', () => { + analyticsReporter.setVisitor({ id: '123' }); + const expectedEvent = new AnalyticsEvent('thumbs_up'); + analyticsReporter.report(expectedEvent); + + expect(mockedBeacon).toBeCalledTimes(1); + expect(mockedBeacon).toBeCalledWith( + expect.anything(), + expect.objectContaining({ data: expect.objectContaining({ visitor: { id: '123' } }) })); + }); });