diff --git a/package.json b/package.json index 6c978f333e..aebb61de16 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@xmldom/xmldom": "0.8.13", "argparse/underscore": "1.13.8", "east/underscore": "1.13.8", + "referer-parser/js-yaml": "^4.1.0", "sandboxed-module": "patch:sandboxed-module@npm%3A2.0.4#~/.yarn/patches/sandboxed-module-npm-2.0.4-f8b45aacc9.patch", "request/tough-cookie": "5.1.2", "request/form-data": "2.5.5", diff --git a/services/web/test/unit/src/Analytics/RequestHelper.test.mjs b/services/web/test/unit/src/Analytics/RequestHelper.test.mjs new file mode 100644 index 0000000000..023e5c0fbf --- /dev/null +++ b/services/web/test/unit/src/Analytics/RequestHelper.test.mjs @@ -0,0 +1,88 @@ +import { assert } from 'vitest' +import RequestHelper from '../../../../app/src/Features/Analytics/RequestHelper.mjs' + +describe('RequestHelper', function () { + describe('parseUtm', function () { + it('returns null when query has no UTM keys', function () { + assert.isNull(RequestHelper.parseUtm({ foo: 'bar' })) + }) + + it('returns null for empty query', function () { + assert.isNull(RequestHelper.parseUtm({})) + }) + + it('extracts a single UTM key', function () { + assert.deepEqual( + RequestHelper.parseUtm({ utm_source: 'google', other: 'ignored' }), + { utm_source: 'google' } + ) + }) + + it('extracts all UTM keys', function () { + const query = { + utm_campaign: 'camp', + utm_source: 'src', + utm_term: 'term', + utm_content: 'cont', + utm_medium: 'med', + utm_count: '1', + utm_id: 'id1', + } + assert.deepEqual(RequestHelper.parseUtm(query), query) + }) + + it('ignores falsy UTM values', function () { + assert.isNull(RequestHelper.parseUtm({ utm_source: '' })) + }) + }) + + describe('parseReferrer', function () { + const pageUrl = 'https://www.overleaf.com/project' + + describe('with no referrer', function () { + it('returns direct medium', function () { + assert.deepEqual(RequestHelper.parseReferrer(null, pageUrl), { + medium: 'direct', + }) + }) + + it('returns direct medium for undefined', function () { + assert.deepEqual(RequestHelper.parseReferrer(undefined, pageUrl), { + medium: 'direct', + }) + }) + }) + + describe('with a search engine referrer', function () { + it('returns search medium and source', function () { + const result = RequestHelper.parseReferrer( + 'https://www.google.com/search?q=overleaf', + pageUrl + ) + assert.equal(result.medium, 'search') + assert.equal(result.source, 'Google') + }) + }) + + describe('with an internal referrer (same site)', function () { + it('returns internal medium', function () { + const result = RequestHelper.parseReferrer( + 'https://www.overleaf.com/login', + pageUrl + ) + assert.equal(result.medium, 'internal') + }) + }) + + describe('with an unknown external referrer', function () { + it('returns link medium with hostname as source', function () { + const result = RequestHelper.parseReferrer( + 'https://some.example.com/article', + pageUrl + ) + assert.equal(result.medium, 'link') + assert.equal(result.source, 'some.example.com') + }) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 6967a09966..6fb3ffbc55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12980,16 +12980,6 @@ __metadata: languageName: node linkType: hard -"argparse@npm:~ 0.1.11": - version: 0.1.16 - resolution: "argparse@npm:0.1.16" - dependencies: - underscore: "npm:~1.7.0" - underscore.string: "npm:~2.4.0" - checksum: 10c0/fbd081b942546b481cfec863778baa09af2224eaea6b2af086fd1e39b9ca3159585ee50bf066c6feff17fe991baea16f45054d226430ec83f5d5aad6d8f2d781 - languageName: node - linkType: hard - "args-js@npm:0.10.12": version: 0.10.12 resolution: "args-js@npm:0.10.12" @@ -18077,16 +18067,6 @@ __metadata: languageName: node linkType: hard -"esprima@npm:~ 1.0.2": - version: 1.0.4 - resolution: "esprima@npm:1.0.4" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10c0/c07e43a78f4711aee4c9514d2672b66cf22a35b93eb7844bc99e511b506cac44d753d75a319c88b556d331adacad9065fcb958dad02faefd27833a7718f75bcd - languageName: node - linkType: hard - "esquery@npm:^1.4.2, esquery@npm:^1.6.0": version: 1.7.0 resolution: "esquery@npm:1.7.0" @@ -22065,18 +22045,6 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:~2.1.0": - version: 2.1.3 - resolution: "js-yaml@npm:2.1.3" - dependencies: - argparse: "npm:~ 0.1.11" - esprima: "npm:~ 1.0.2" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/178709184752f47f703de3bc3ca779c6b4443f73acf1c5e5a7cfb37f04f24f7c1bd9b520cb30081cd2d73f054061a765c7bd35a4ce3718ae01608a7333dae036 - languageName: node - linkType: hard - "jsbn@npm:~0.1.0": version: 0.1.1 resolution: "jsbn@npm:0.1.1" @@ -33233,13 +33201,6 @@ __metadata: languageName: node linkType: hard -"underscore.string@npm:~2.4.0": - version: 2.4.0 - resolution: "underscore.string@npm:2.4.0" - checksum: 10c0/30a9d096eee6680c27c3bdcc5a81af8f1c8357b5bfdc1dc45e719e842b0db322c2705c61ebf5166d2b41f2cfcc6dbf44e026651d8042bab638a99de15c8ec078 - languageName: node - linkType: hard - "underscore@npm:1.13.8, underscore@npm:>=1.8.3": version: 1.13.8 resolution: "underscore@npm:1.13.8"