Skip to content

Commit

Permalink
Add defensive code against bad configuration in standalone recommenda…
Browse files Browse the repository at this point in the history
  • Loading branch information
olamothe committed Mar 30, 2017
1 parent d9f64b5 commit 7413b20
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 24 deletions.
2 changes: 2 additions & 0 deletions lib/coveoanalytics/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ declare namespace CoveoAnalytics {

setHistory(history: HistoryElement[]): void;
clear(): void;

getMostRecentElement(): HistoryElement;
}

interface History {
Expand Down
7 changes: 6 additions & 1 deletion src/ui/Base/Initialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,13 @@ export class Initialization {

if (searchInterface.options.autoTriggerQuery) {
Initialization.logFirstQueryCause(searchInterface);
let shouldLogInActionHistory = true;
// We should not log an action history if the interface is a standalone recommendation component.
if (Coveo['Recommendation']) {
shouldLogInActionHistory = !(searchInterface instanceof Coveo['Recommendation']);
}
(<QueryController>Component.get(element, QueryController)).executeQuery({
logInActionsHistory: Coveo['Recommendation'] && searchInterface instanceof Coveo['Recommendation'],
logInActionsHistory: shouldLogInActionHistory,
isFirstQuery: true
});
}
Expand Down
22 changes: 21 additions & 1 deletion src/ui/Recommendation/Recommendation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { get } from '../Base/RegisteredNamedMethods';
import { InitializationEvents } from '../../events/InitializationEvents';
import { ComponentOptionsModel } from '../../models/ComponentOptionsModel';
import _ = require('underscore');
import HistoryQueryElement = CoveoAnalytics.HistoryQueryElement;

export interface IRecommendationOptions extends ISearchInterfaceOptions {
mainSearchInterface?: HTMLElement;
Expand Down Expand Up @@ -209,6 +210,10 @@ export class Recommendation extends SearchInterface implements IComponentBinding


this.historyStore = new history.HistoryStore();
if (!this.options.mainSearchInterface) {
// When the recommendation component is "standalone", we add additional safeguard against bad config.
this.ensureCurrentPageViewExistsInStore();
}
ResponsiveRecommendation.init(this.root, this, options);
}

Expand Down Expand Up @@ -240,6 +245,21 @@ export class Recommendation extends SearchInterface implements IComponentBinding
this.element.style.display = this.displayStyle;
}

private ensureCurrentPageViewExistsInStore() {
// It's not 100% sure that the element will actually be added in the store.
// It's possible that an external script configured by the end user to log the page view already did that.
// So we *could* end up with duplicate values in the store :
// We rely on the fact that the coveo.analytics lib has defensive code against consecutive page view that are identical.
// This is mainly done if the recommendation component is being initialized before the page view could be logged correctly by the coveo.analytics externa lib.
const historyElement = {
name: 'PageView',
value: document.location.toString(),
time: JSON.stringify(new Date()),
title: document.title
};
this.historyStore.addElement(historyElement);
}

private bindToMainSearchInterface() {
this.bindComponentOptionsModelToMainSearchInterface();
this.bindQueryEventsToMainSearchInterface();
Expand Down Expand Up @@ -380,6 +400,6 @@ export class Recommendation extends SearchInterface implements IComponentBinding
Recommendation.NEXT_ID++;
this.options.id = id;
}

}

// We do not register the Recommendation component since it is done with .coveo('initRecommendation')
17 changes: 17 additions & 0 deletions test/Simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,23 @@ export class Simulate {
return modalBox;
}

static analyticsStoreModule(actionsHistory = []) {
return {
addElement: (query: IQuery) => {
},
getHistory: () => {
return actionsHistory;
},
setHistory: (history: any[]) => {
},
clear: () => {
},
getMostRecentElement: () => {
return null;
}
};
}

static omnibox(env: IMockEnvironment, options?): IOmniboxData {
let expression = {
word: 'foo',
Expand Down
15 changes: 2 additions & 13 deletions test/controllers/QueryControllerTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { QueryController } from '../../src/controllers/QueryController';
import { $$ } from '../../src/utils/Dom';
import { FakeResults } from '../Fake';
import { QueryBuilder } from '../../src/ui/Base/QueryBuilder';
import { IQuery } from '../../src/rest/Query';
import { QueryEvents, IBuildingQueryEventArgs } from '../../src/events/QueryEvents';
import { Simulate } from '../Simulate';

export function QueryControllerTest() {
describe('QueryController', function () {
Expand Down Expand Up @@ -217,18 +217,7 @@ export function QueryControllerTest() {
let store: CoveoAnalytics.HistoryStore;

beforeEach(function () {
store = {
addElement: (query: IQuery) => {
},
getHistory: () => {
return [];
},
setHistory: (history: any[]) => {
},
clear: () => {
}
};

store = Simulate.analyticsStoreModule();
test.cmp.historyStore = store;
spyOn(store, 'addElement');
});
Expand Down
36 changes: 34 additions & 2 deletions test/ui/InitializationTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Facet } from '../../src/ui/Facet/Facet';
import { Pager } from '../../src/ui/Pager/Pager';
import { ResultList } from '../../src/ui/ResultList/ResultList';
import { Simulate } from '../Simulate';
declare let coveoanalytics;
declare let $;

export function InitializationTest() {
Expand Down Expand Up @@ -192,6 +191,32 @@ export function InitializationTest() {
Simulate.removeJQuery();
});

it('will trigger a query automatically by default', () => {
Initialization.initializeFramework(root, searchInterfaceOptions, () => {
Initialization.initSearchInterface(root, searchInterfaceOptions);
});
expect(endpoint.search).toHaveBeenCalled();
});

it('will not trigger a query automatically if specified', () => {
searchInterfaceOptions['SearchInterface'].autoTriggerQuery = false;
Initialization.initializeFramework(root, searchInterfaceOptions, () => {
Initialization.initSearchInterface(root, searchInterfaceOptions);
});
expect(endpoint.search).not.toHaveBeenCalled();
});

it('will send action history on automatic query', () => {
localStorage.removeItem('__coveo.analytics.history');
expect(localStorage.getItem('__coveo.analytics.history')).toBeNull();
Initialization.initializeFramework(root, searchInterfaceOptions, () => {
Initialization.initSearchInterface(root, searchInterfaceOptions);
});
const actionHistory = localStorage.getItem('__coveo.analytics.history');
expect(actionHistory).not.toBeNull();
expect(JSON.parse(actionHistory)[0].name).toEqual('Query');
});

describe('when initializing recommendation interface', function () {
let options;
beforeEach(function () {
Expand All @@ -207,14 +232,21 @@ export function InitializationTest() {

afterEach(function () {
options = null;
coveoanalytics = undefined;
});

it('should be able to generate to components', function () {
expect(Component.get(queryBox) instanceof Querybox).toBe(false);
Initialization.initRecommendationInterface(root, options);
expect(Component.get(queryBox) instanceof Querybox).toBe(true);
});

it('should not send action history on automatic query', () => {
localStorage.removeItem('__coveo.analytics.history');
expect(localStorage.getItem('__coveo.analytics.history')).toBeNull();
Initialization.initRecommendationInterface(root, options);
const actionHistory = localStorage.getItem('__coveo.analytics.history');
expect(actionHistory).toBeNull();
});
});
});
}
8 changes: 1 addition & 7 deletions test/ui/RecommendationTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as Mock from '../MockEnvironment';
import { SearchInterface } from '../../src/ui/SearchInterface/SearchInterface';
import { Recommendation } from '../../src/ui/Recommendation/Recommendation';
import { IRecommendationOptions } from '../../src/ui/Recommendation/Recommendation';
import { IQuery } from '../../src/rest/Query';
import { Simulate } from '../Simulate';
import { QueryBuilder } from '../../src/ui/Base/QueryBuilder';
import { FakeResults } from '../Fake';
Expand All @@ -25,12 +24,7 @@ export function RecommendationTest() {
user_id: userId
})
};
store = {
addElement: (query: IQuery) => { },
getHistory: () => { return actionsHistory; },
setHistory: (history: any[]) => { },
clear: () => { }
};
store = Simulate.analyticsStoreModule(actionsHistory);
test = Mock.optionsSearchInterfaceSetup<Recommendation, IRecommendationOptions>(Recommendation, options);
test.cmp.historyStore = store;
});
Expand Down

0 comments on commit 7413b20

Please sign in to comment.