Skip to content
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

Add visitor support #1564

Merged
merged 7 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ function initAnswers() {
apiKey: '<API_KEY_HERE>',
// Required, the key used for your Answers experience
experienceKey: '<EXPERIENCE_KEY_HERE>',
// Optional, visitor interacting with the experience, see Visitor Configuration below for details
visitor: {
// Required, see below
id: '<ID_HERE>',
// Optional, see below
idMethod: '<ID_METHOD_HERE>',
},
// 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() {},
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion THIRD-PARTY-NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
}
}
}
15 changes: 14 additions & 1 deletion src/answers-search-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {};

Expand Down Expand Up @@ -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.`);
}
}
}

/**
Expand Down
15 changes: 14 additions & 1 deletion src/answers-umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,11 @@ class Answers {

this._setDefaultInitialSearch(parsedConfig.search);

this.core.init();
if (parsedConfig.visitor) {
this.setVisitor(parsedConfig.visitor);
nmanu1 marked this conversation as resolved.
Show resolved Hide resolved
} else {
this.core.init();
}

this._onReady = parsedConfig.onReady || function () {};

Expand Down Expand Up @@ -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.`);
}
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/core/analytics/analyticsreporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/core/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -136,7 +136,8 @@ export default class Core {
endpoints: this._getServiceUrls(),
additionalQueryParams: {
jsLibVersion: LIB_VERSION
}
},
...config
};

this._coreLibrary = provideCore(params);
Expand Down
39 changes: 39 additions & 0 deletions tests/answers-umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' } });
});
});
11 changes: 11 additions & 0 deletions tests/core/analytics/analyticsreporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' } }) }));
});
});