Skip to content

Commit

Permalink
chore: add new utm attributes to all mongodb links VSCODE-356 (#526)
Browse files Browse the repository at this point in the history
* chore: add new utm attributes to all mongodb links

* chore: add ajs_aid back

* chore: move more links to utils/links

* chore: add tests

* chore: add documentation for anonymous id param
  • Loading branch information
gribnoysup authored Jun 2, 2023
1 parent 2974843 commit 1fa3a31
Show file tree
Hide file tree
Showing 17 changed files with 183 additions and 42 deletions.
22 changes: 12 additions & 10 deletions src/explorer/helpTree.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as vscode from 'vscode';
import path from 'path';

import { getImagesPath } from '../extensionConstants';
import { TelemetryService } from '../telemetry';
import { openLink } from '../utils/linkHelper';
import LINKS from '../utils/links';

const HELP_LINK_CONTEXT_VALUE = 'HELP_LINK';

Expand Down Expand Up @@ -76,47 +76,49 @@ export default class HelpTree
if (!element) {
const whatsNew = new HelpLinkTreeItem(
"What's New",
'https://github.com/mongodb-js/vscode/blob/main/CHANGELOG.md',
LINKS.changelog,
'whatsNew',
'megaphone'
);

const extensionDocs = new HelpLinkTreeItem(
'Extension Documentation',
'https://docs.mongodb.com/mongodb-vscode/',
LINKS.extensionDocs(),
'extensionDocumentation',
'book'
);

const mdbDocs = new HelpLinkTreeItem(
'MongoDB Documentation',
'https://docs.mongodb.com/manual/',
LINKS.mongodbDocs,
'mongoDBDocumentation',
'leaf'
);

const feedback = new HelpLinkTreeItem(
'Suggest a Feature',
'https://feedback.mongodb.com/forums/929236-mongodb-for-vs-code/',
LINKS.feedback,
'feedback',
'lightbulb'
);

const reportBug = new HelpLinkTreeItem(
'Report a Bug',
'https://github.com/mongodb-js/vscode/issues',
LINKS.reportBug,
'reportABug',
'report'
);

const telemetryUserIdentity =
this._telemetryService?.getTelemetryUserIdentity();
const ajsAid = telemetryUserIdentity
? `&ajs_aid=${telemetryUserIdentity[0]}`
: '';

const atlas = new HelpLinkTreeItem(
'Create Free Atlas Cluster',
`https://mongodb.com/products/vs-code/vs-code-atlas-signup?utm_campaign=vs-code-extension&utm_source=visual-studio&utm_medium=product${ajsAid}`,
LINKS.createAtlasCluster(
telemetryUserIdentity?.userId ??
telemetryUserIdentity?.anonymousId ??
''
),
'freeClusterCTA',
'atlas',
true
Expand Down
7 changes: 4 additions & 3 deletions src/language/mongoDBService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import type {
import type { ClearCompletionsCache } from '../types/completionsCache';
import { Visitor } from './visitor';
import type { CompletionState } from './visitor';
import LINKS from '../utils/links';

import DIAGNOSTIC_CODES from './diagnosticCodes';

Expand Down Expand Up @@ -445,7 +446,7 @@ export default class MongoDBService {
description?: string;
}) {
const title = operator.replace(/[$]/g, '');
const link = `https://www.mongodb.com/docs/manual/reference/operator/aggregation/${title}/`;
const link = LINKS.aggregationDocs(title);
return {
kind: MarkupKind.Markdown,
value: description
Expand All @@ -461,7 +462,7 @@ export default class MongoDBService {
bsonType: string;
description?: string;
}) {
const link = `https://www.mongodb.com/docs/mongodb-shell/reference/data-types/#${bsonType}`;
const link = LINKS.bsonDocs(bsonType);
return {
kind: MarkupKind.Markdown,
value: description
Expand All @@ -478,7 +479,7 @@ export default class MongoDBService {
description?: string;
}) {
const title = variable.replace(/[$]/g, '');
const link = `https://www.mongodb.com/docs/manual/reference/aggregation-variables/#mongodb-variable-variable.${title}`;
const link = LINKS.systemVariableDocs(title);
return {
kind: MarkupKind.Markdown,
value: description
Expand Down
7 changes: 4 additions & 3 deletions src/test/suite/explorer/helpExplorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ suite('Help Explorer Test Suite', function () {
const atlasHelpItem = helpTreeItems[5];

assert.strictEqual(atlasHelpItem.label, 'Create Free Atlas Cluster');
const telemetryUserIdentity =
assert.strictEqual(atlasHelpItem.url.includes('mongodb.com'), true);
const { userId, anonymousId } =
mdbTestExtension.testExtensionController._telemetryService.getTelemetryUserIdentity();
assert.strictEqual(
atlasHelpItem.url,
`https://mongodb.com/products/vs-code/vs-code-atlas-signup?utm_campaign=vs-code-extension&utm_source=visual-studio&utm_medium=product&ajs_aid=${telemetryUserIdentity[0]}`
new URL(atlasHelpItem.url).searchParams.get('ajs_aid'),
userId ?? anonymousId
);
assert.strictEqual(atlasHelpItem.iconName, 'atlas');
assert.strictEqual(atlasHelpItem.linkId, 'freeClusterCTA');
Expand Down
53 changes: 53 additions & 0 deletions src/test/suite/utils/links.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect } from 'chai';
import LINKS from '../../../utils/links';

const expectedLinks = {
changelog: 'https://github.com/mongodb-js/vscode/blob/main/CHANGELOG.md',
feedback:
'https://feedback.mongodb.com/forums/929236-mongodb-for-vs-code/?utm_source=vscode&utm_medium=product',
github: 'https://github.com/mongodb-js/vscode',
reportBug: 'https://github.com/mongodb-js/vscode/issues',
atlas:
'https://www.mongodb.com/cloud/atlas?utm_source=vscode&utm_medium=product',
createAtlasCluster:
'https://mongodb.com/products/vs-code/vs-code-atlas-signup?ajs_aid=hi&utm_source=vscode&utm_medium=product',
docs: 'https://docs.mongodb.com/?utm_source=vscode&utm_medium=product',
mongodbDocs:
'https://docs.mongodb.com/manual/?utm_source=vscode&utm_medium=product',
extensionDocs:
'https://docs.mongodb.com/mongodb-vscode/hi?utm_source=vscode&utm_medium=product',
aggregationDocs:
'https://www.mongodb.com/docs/manual/reference/operator/aggregation/hi/?utm_source=vscode&utm_medium=product',
bsonDocs:
'https://www.mongodb.com/docs/mongodb-shell/reference/data-types/?utm_source=vscode&utm_medium=product#hi',
systemVariableDocs:
'https://www.mongodb.com/docs/manual/reference/aggregation-variables/?utm_source=vscode&utm_medium=product#mongodb-variable-variable.hi',
kerberosPrincipalDocs:
'https://docs.mongodb.com/manual/core/kerberos/?utm_source=vscode&utm_medium=product#principals',
ldapDocs:
'https://docs.mongodb.com/manual/core/security-ldap/?utm_source=vscode&utm_medium=product',
authDatabaseDocs:
'https://docs.mongodb.com/manual/core/security-users/?utm_source=vscode&utm_medium=product#user-authentication-database',
sshConnectionDocs:
'https://docs.mongodb.com/compass/current/connect/advanced-connection-options/ssh-connection/?utm_source=vscode&utm_medium=product#ssh-connection',
configureSSLDocs:
'https://docs.mongodb.com/manual/tutorial/configure-ssl/hi?utm_source=vscode&utm_medium=product',
pemKeysDocs:
'https://docs.mongodb.com/manual/reference/configuration-options/?utm_source=vscode&utm_medium=product#net.ssl.PEMKeyPassword',
};

suite('LINKS', () => {
test('should have all links', () => {
expect(Object.keys(expectedLinks)).to.deep.eq(Object.keys(LINKS));
});

Object.entries(expectedLinks).forEach(([name, expected]) => {
test(`${name} link should return ${expected}`, () => {
if (typeof LINKS[name] === 'function') {
expect(expected).to.eq(LINKS[name]('hi'));
} else {
expect(expected).to.eq(LINKS[name]);
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,16 @@ describe('Resources Panel Component Test Suite', () => {
'OPEN_TRUSTED_LINK'
);
assert.strictEqual(
fakeVscodeWindowPostMessage.firstCall.args[0].linkTo,
'https://mongodb.com/products/vs-code/vs-code-atlas-signup?utm_campaign=vs-code-extension&utm_source=visual-studio&utm_medium=product&ajs_aid=mockAnonymousID'
fakeVscodeWindowPostMessage.firstCall.args[0].linkTo.includes(
'mongodb.com'
),
true
);
assert.strictEqual(
new URL(
fakeVscodeWindowPostMessage.firstCall.args[0].linkTo
).searchParams.get('ajs_aid'),
'mockAnonymousID'
);
// The assert below is a bit redundant but will prevent us from redirecting to a non-https URL by mistake
assert(
Expand Down
65 changes: 65 additions & 0 deletions src/utils/links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const addUTMAttrs = (url: string) => {
const parsed = new URL(url);
if (!parsed.host.includes('mongodb')) {
return url;
}
parsed.searchParams.set('utm_source', 'vscode');
parsed.searchParams.set('utm_medium', 'product');
return parsed.toString();
};

const LINKS = {
changelog: 'https://github.com/mongodb-js/vscode/blob/main/CHANGELOG.md',
feedback: 'https://feedback.mongodb.com/forums/929236-mongodb-for-vs-code/',
github: 'https://github.com/mongodb-js/vscode',
reportBug: 'https://github.com/mongodb-js/vscode/issues',
atlas: 'https://www.mongodb.com/cloud/atlas',
/**
* @param anonymousId Segment analytics `anonymousId` (not `userId`) {@link https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/querystring/}
*/
createAtlasCluster: (anonymousId: string) => {
const ajsAid = anonymousId
? `?ajs_aid=${encodeURIComponent(anonymousId)}`
: '';
return `https://mongodb.com/products/vs-code/vs-code-atlas-signup${ajsAid}`;
},
docs: 'https://docs.mongodb.com/',
mongodbDocs: 'https://docs.mongodb.com/manual/',
extensionDocs(subcategory = '') {
return `https://docs.mongodb.com/mongodb-vscode/${subcategory}`;
},
aggregationDocs: (title: string) => {
return `https://www.mongodb.com/docs/manual/reference/operator/aggregation/${title}/`;
},
bsonDocs: (type: string) => {
return `https://www.mongodb.com/docs/mongodb-shell/reference/data-types/#${type}`;
},
systemVariableDocs: (name: string) => {
return `https://www.mongodb.com/docs/manual/reference/aggregation-variables/#mongodb-variable-variable.${name}`;
},
kerberosPrincipalDocs:
'https://docs.mongodb.com/manual/core/kerberos/#principals',
ldapDocs: 'https://docs.mongodb.com/manual/core/security-ldap/',
authDatabaseDocs:
'https://docs.mongodb.com/manual/core/security-users/#user-authentication-database',
sshConnectionDocs:
'https://docs.mongodb.com/compass/current/connect/advanced-connection-options/ssh-connection/#ssh-connection',
configureSSLDocs(subsection = '') {
return `https://docs.mongodb.com/manual/tutorial/configure-ssl/${subsection}`;
},
pemKeysDocs:
'https://docs.mongodb.com/manual/reference/configuration-options/#net.ssl.PEMKeyPassword',
};

export default Object.fromEntries(
Object.entries(LINKS).map(([k, v]) => {
return [
k,
typeof v === 'string'
? addUTMAttrs(v)
: (name: string) => {
return addUTMAttrs(v(name));
},
];
})
) as typeof LINKS;
5 changes: 3 additions & 2 deletions src/views/webview-app/components/atlas-cta/atlas-cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { VSCODE_EXTENSION_SEGMENT_ANONYMOUS_ID } from '../../extension-app-messa
import AtlasLogo from './atlas-logo';

import styles from './atlas-cta.less';
import LINKS from '../../../../utils/links';

type DispatchProps = {
onLinkClicked: (screen: string, linkId: string) => void;
Expand All @@ -19,7 +20,7 @@ type DispatchProps = {
class AtlasCTA extends React.Component<DispatchProps> {
onAtlasCtaClicked = (): void => {
const telemetryUserId = window[VSCODE_EXTENSION_SEGMENT_ANONYMOUS_ID];
const atlasLink = `https://mongodb.com/products/vs-code/vs-code-atlas-signup?utm_campaign=vs-code-extension&utm_source=visual-studio&utm_medium=product&ajs_aid=${telemetryUserId}`;
const atlasLink = LINKS.createAtlasCluster(telemetryUserId);
this.props.openTrustedLink(atlasLink);

this.onLinkClicked('overviewPage', 'freeClusterCTA');
Expand All @@ -43,7 +44,7 @@ class AtlasCTA extends React.Component<DispatchProps> {
className={styles['atlas-cta-text-link']}
target="_blank"
rel="noopener"
href="https://www.mongodb.com/cloud/atlas"
href={LINKS.atlas}
onClick={this.onLinkClicked.bind(
this,
'overviewPage',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import FormInput from '../../../form/form-input';

import styles from '../../../../connect.module.less';
import LINKS from '../../../../../../utils/links';

type DispatchProps = {
kerberosParametersChanged: (newParams: KerberosParameters) => void;
Expand Down Expand Up @@ -126,7 +127,7 @@ class Kerberos extends React.Component<props> {
changeHandler={this.onPrincipalChanged}
value={kerberosPrincipal || ''}
// Open the help page for the principal.
linkTo="https://docs.mongodb.com/manual/core/kerberos/#principals"
linkTo={LINKS.kerberosPrincipalDocs}
/>
<FormInput
label="Password"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
LDAPUsernameChangedAction,
} from '../../../../store/actions';
import FormInput from '../../../form/form-input';
import LINKS from '../../../../../../utils/links';

type DispatchProps = {
onLDAPPasswordChanged: (newPassword: string) => void;
Expand Down Expand Up @@ -58,7 +59,7 @@ class LDAP extends React.Component<props> {
changeHandler={this.onUsernameChanged}
value={ldapUsername || ''}
// Open the help page for LDAP.
linkTo="https://docs.mongodb.com/manual/core/security-ldap/"
linkTo={LINKS.ldapDocs}
/>
<FormInput
label="Password"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
UsernameChangedAction,
} from '../../../../store/actions';
import FormInput from '../../../form/form-input';
import LINKS from '../../../../../../utils/links';

type DispatchProps = {
onAuthSourceChanged: (newAuthSource: string) => void;
Expand Down Expand Up @@ -78,7 +79,7 @@ class MongoDBAuthentication extends React.Component<props> {
changeHandler={this.onAuthSourceChanged}
value={mongodbDatabaseName || ''}
// Opens "Authentication Database" documentation.
linkTo="https://docs.mongodb.com/manual/core/security-users/#user-authentication-database"
linkTo={LINKS.authDatabaseDocs}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
UsernameChangedAction,
} from '../../../../store/actions';
import FormInput from '../../../form/form-input';
import LINKS from '../../../../../../utils/links';

type DispatchProps = {
onAuthSourceChanged: (newAuthSource: string) => void;
Expand Down Expand Up @@ -78,7 +79,7 @@ class ScramSha256 extends React.Component<props> {
changeHandler={this.onAuthSourceChanged}
value={mongodbDatabaseName || ''}
// Opens "Authentication Database" documentation.
linkTo="https://docs.mongodb.com/manual/core/security-users/#user-authentication-database"
linkTo={LINKS.authDatabaseDocs}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AppState } from '../../../store/store';
import FormInput from '../../form/form-input';
import FileInputButton from '../../form/file-input-button';
import FormGroup from '../../form/form-group';
import LINKS from '../../../../../utils/links';

type DispatchProps = {
onChangeSSHTunnelIdentityFile: () => void;
Expand Down Expand Up @@ -97,7 +98,7 @@ class SSHTunnelIdentityFileValidation extends React.Component<props> {
error={!isValid && sshTunnelHostname === undefined}
changeHandler={this.onSSHTunnelHostnameChanged}
value={sshTunnelHostname || ''}
linkTo="https://docs.mongodb.com/compass/current/connect"
linkTo={LINKS.sshConnectionDocs}
/>
<FormInput
label="SSH Tunnel Port"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { AppState } from '../../../store/store';
import FormInput from '../../form/form-input';
import FormGroup from '../../form/form-group';
import LINKS from '../../../../../utils/links';

type DispatchProps = {
onSSHTunnelHostnameChanged: (sshTunnelHostname: string) => void;
Expand Down Expand Up @@ -83,7 +84,7 @@ class SSHTunnelPasswordValidation extends React.Component<props> {
error={!isValid && sshTunnelHostname === undefined}
changeHandler={this.onSSHTunnelHostnameChanged}
value={sshTunnelHostname || ''}
linkTo="https://docs.mongodb.com/compass/current/connect"
linkTo={LINKS.sshConnectionDocs}
/>
<FormInput
label="SSH Tunnel Port"
Expand Down
Loading

0 comments on commit 1fa3a31

Please sign in to comment.