Skip to content

Commit

Permalink
fix, test: [M3-8830] - Use unit tested function for Pendo url transfo…
Browse files Browse the repository at this point in the history
…rmation (#11211)

* Use transform function and unit test it

* Use singular function name

* Added changeset: Use unit tested function for Pendo url transformation

* Address feedback @coliu-akamai: add missing test case

* Address feedback @hkhalil-akamai: remove unnecessary conditionals
  • Loading branch information
mjac0bs authored Nov 5, 2024
1 parent 7adb6cf commit 4ec07b0
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

Use unit tested function for Pendo url transformation ([#11211](https://github.com/linode/manager/pull/11211))
102 changes: 102 additions & 0 deletions packages/manager/src/hooks/usePendo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { transformUrl } from './usePendo';

const ID_URLS = [
{
expectedTransform: 'https://cloud.linode.com/nodebalancers/XXXX',
position: 'end',
url: 'https://cloud.linode.com/nodebalancers/123',
},
{
expectedTransform:
'https://cloud.linode.com/nodebalancers/XXXX/configurations',
position: 'middle',
url: 'https://cloud.linode.com/nodebalancers/123/configurations',
},
{
expectedTransform:
'https://cloud.linode.com/nodebalancers/XXXX/configurations/XXXX',
position: 'multiple',
url: 'https://cloud.linode.com/nodebalancers/123/configurations/456',
},
];

const USERNAME_URLS = [
{
path: 'my-username',
},
{
path: 'my-username/profile',
},
{
path: 'my-username/permissions',
},
{
path: '123-my-username/profile',
},
];

const OBJ_URLS = [
{
expectedTransform:
'http://cloud.linode.com/object-storage/buckets/XXXX/XXXX',
path: 'us-west/abc123',
},
{
expectedTransform:
'http://cloud.linode.com/object-storage/buckets/XXXX/XXXX/ssl',
path: 'us-west/abc123/ssl',
},
{
expectedTransform:
'http://cloud.linode.com/object-storage/buckets/XXXX/XXXX',
path: 'us-west/123abc',
},
{
expectedTransform:
'http://cloud.linode.com/object-storage/buckets/XXXX/XXXX/access',
path: 'us-west/123abc/access',
},
];

describe('transformUrl', () => {
it.each(ID_URLS)(
'replaces id(s) in $position positions in the url path',
({ expectedTransform, url }) => {
const actualTransform = transformUrl(url);
expect(actualTransform).toEqual(expectedTransform);
}
);

it.each(USERNAME_URLS)(
'truncates $path from the /users url path',
({ path }) => {
const baseUrl = 'https://cloud.linode.com/account/users/';
const actualTransform = transformUrl(`${baseUrl}${path}`);
expect(actualTransform).toEqual(baseUrl);
}
);

it.each(OBJ_URLS)(
'replaces the OBJ region and bucket name in the url path ($path)',
({ expectedTransform, path }) => {
const baseUrl = 'http://cloud.linode.com/object-storage/buckets/';
const actualTransform = transformUrl(`${baseUrl}${path}`);
expect(actualTransform).toEqual(expectedTransform);
}
);

it('truncates the url after "access_token" in the url path', () => {
const url =
'https://cloud.linode.com/oauth/callback#access_token=12345&token_type=bearer&expires_in=5678';
const actualTransform = transformUrl(url);
const expectedTransform =
'https://cloud.linode.com/oauth/callback#access_token';
expect(actualTransform).toEqual(expectedTransform);
});

it('returns the original url if no transformation is needed', () => {
const url = 'https://cloud.linode.com/linodes/create';
const actualTransform = transformUrl(url);
expect(actualTransform).toEqual(url);
});
});
50 changes: 31 additions & 19 deletions packages/manager/src/hooks/usePendo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@ const hashUniquePendoId = (id: string | undefined) => {
return sha256(id + pendoEnv);
};

/**
* This function uses string matching and replacement to transform the page url into a sanitized url without unwanted data.
* @param url The url of the page.
* @returns A clean, transformed url of the page.
*/
export const transformUrl = (url: string) => {
const idMatchingRegex = /(\/\d+)/g;
const bucketPathMatchingRegex = /(buckets\/[^\/]+\/[^\/]+)/;
const userPathMatchingRegex = /(users\/).*/;
const oauthPathMatchingRegex = /(#access_token).*/;
let transformedUrl = url;

// Replace any ids with XXXX and keep the rest of the URL intact
transformedUrl = url.replace(idMatchingRegex, '/XXXX');

// Replace the region and bucket names with XXXX and keep the rest of the URL intact.
// Object storage file navigation is truncated via the 'clear search' transform.
transformedUrl = transformedUrl.replace(
bucketPathMatchingRegex,
'buckets/XXXX/XXXX'
);

// Remove everything after access_token
transformedUrl = transformedUrl.replace(oauthPathMatchingRegex, '$1');

// Remove everything after /users
transformedUrl = transformedUrl.replace(userPathMatchingRegex, '$1');
return transformedUrl;
};

/**
* Initializes our Pendo analytics script on mount if a valid `PENDO_API_KEY` exists.
*/
Expand Down Expand Up @@ -105,25 +135,7 @@ export const usePendo = () => {
action: 'Replace',
attr: 'pathname',
data(url: string) {
const idMatchingRegex = /(\/\d+)/;
const bucketPathMatchingRegex = /(buckets\/[^\/]+\/[^\/]+)/;
const userPathMatchingRegex = /(users\/).*/;
const oauthPathMatchingRegex = /(#access_token).*/;

if (idMatchingRegex.test(url)) {
// Replace any ids with XXXX and keep the rest of the URL intact
return url.replace(idMatchingRegex, '/XXXX');
} else if (bucketPathMatchingRegex.test(url)) {
// Replace the region and bucket names with XXXX and keep the rest of the URL intact
return url.replace(bucketPathMatchingRegex, 'XXXX/XXXX');
} else if (oauthPathMatchingRegex.test(url)) {
// Remove everything after access_token/
url.replace(oauthPathMatchingRegex, '$1');
} else if (userPathMatchingRegex.test(url)) {
// Remove everything after /users
return url.replace(userPathMatchingRegex, '$1');
}
return url;
return transformUrl(url);
},
},
],
Expand Down

0 comments on commit 4ec07b0

Please sign in to comment.