Skip to content

Commit

Permalink
feat: Adds Analytics Code to URLs (#87)
Browse files Browse the repository at this point in the history
# Description

Appends the analytics codes to the URL for internal analytics

Currently this doesn't support SDK Semver or Tech Version due to being
unable to get version imports working without having to manually
configure it each time. will open a separate issue to address this

## Issue Ticket Number

<!-- Specifiy which issue this fixes by referencing the issue number
(`#11`) or issue URL. -->
<!-- Example: Fixes
#1 -->

Fixes #70 

## Type of change

<!-- Please select all options that are applicable. -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] This change requires a documentation update


# Checklist

<!-- These must all be followed and checked. -->

- [ ] I have followed the contributing guidelines of this project as
mentioned in [CONTRIBUTING.md](/CONTRIBUTING.md)
- [ ] I have created an
[issue](https://github.com/colbyfayock/netlify-plugin-cloudinary/issues)
ticket for this PR
- [ ] I have checked to ensure there aren't other open [Pull
Requests](https://github.com/colbyfayock/netlify-plugin-cloudinary/pulls)
for the same update/change?
- [ ] I have performed a self-review of my own code
- [ ] I have run tests locally to ensure they all pass
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes needed to the documentation
  • Loading branch information
colbyfayock authored Feb 13, 2024
1 parent c2c4b44 commit 4940fb0
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 59 deletions.
2 changes: 1 addition & 1 deletion netlify-plugin-cloudinary/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"license": "MIT",
"dependencies": {
"cloudinary": "^1.40.0",
"cloudinary": "^2.0.1",
"glob": "^10.3.3",
"jsdom": "21",
"node-fetch": "2",
Expand Down
3 changes: 3 additions & 0 deletions netlify-plugin-cloudinary/src/data/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ANALYTICS_SDK_CODE = 'F';
export const ANALYTICS_SDK_SEMVER = '0.0.0';
export const ANALYTICS_PRODUCT = 'B';
26 changes: 20 additions & 6 deletions netlify-plugin-cloudinary/src/lib/cloudinary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fetch from 'node-fetch'
import { JSDOM } from 'jsdom'
import { v2 as cloudinary, ConfigOptions, TransformationOptions } from 'cloudinary'

import { ANALYTICS_SDK_CODE, ANALYTICS_SDK_SEMVER, ANALYTICS_PRODUCT } from '../data/analytics';
import { isRemoteUrl, determineRemoteUrl } from './util'
import { ERROR_API_CREDENTIALS_REQUIRED, ERROR_ASSET_UPLOAD, ERROR_CLOUD_NAME_REQUIRED, ERROR_UPLOAD_PRESET } from '../data/errors'

Expand Down Expand Up @@ -63,6 +64,13 @@ export type CloudinaryOptions = {
transformations?: Array<TransformationOptions>;
} & (FetchDelivery | OtherDelivery)

export type CloudinaryAnalytics = {
sdkCode?: string;
sdkSemver?: string;
techVersion?: string;
product?: string;
}

export type Assets = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
images: Array<any>
Expand Down Expand Up @@ -136,7 +144,7 @@ export async function createPublicId({ path: filePath }: { path: string }) {
* getCloudinaryUrl
*/

export async function getCloudinaryUrl(options: CloudinaryOptions) {
export async function getCloudinaryUrl(options: CloudinaryOptions, analytics?: CloudinaryAnalytics) {
const {
deliveryType,
folder,
Expand Down Expand Up @@ -255,8 +263,14 @@ export async function getCloudinaryUrl(options: CloudinaryOptions) {
},
...transformations
],
})

urlAnalytics: true,
sdkCode: ANALYTICS_SDK_CODE,
sdkSemver: ANALYTICS_SDK_SEMVER,
techVersion: '0.0.0',
product: ANALYTICS_PRODUCT,
...analytics
});

return {
sourceUrl: fileLocation,
cloudinaryUrl,
Expand All @@ -280,7 +294,7 @@ function getAsset(imgUrl: string, assets: Assets) {
return cloudinaryAsset
}

export async function updateHtmlImagesToCloudinary(html: string, options: UpdateCloudinaryOptions) {
export async function updateHtmlImagesToCloudinary(html: string, options: UpdateCloudinaryOptions, analytics?: CloudinaryAnalytics) {
const {
assets,
deliveryType,
Expand Down Expand Up @@ -325,7 +339,7 @@ export async function updateHtmlImagesToCloudinary(html: string, options: Update
uploadPreset,
remoteHost,
transformations
})
}, analytics)
cloudinaryUrl = url
} catch (e) {
if (e instanceof Error) {
Expand Down Expand Up @@ -365,7 +379,7 @@ export async function updateHtmlImagesToCloudinary(html: string, options: Update
localDir,
uploadPreset,
remoteHost,
})
}, analytics)
} catch (e) {
if (e instanceof Error) {
errors.push({
Expand Down
16 changes: 13 additions & 3 deletions netlify-plugin-cloudinary/tests/lib/cloudinary-cname.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { vi, expect, describe, test, beforeEach, afterAll, it } from 'vitest';

import { configureCloudinary, getCloudinaryUrl, updateHtmlImagesToCloudinary } from '../../src/lib/cloudinary';
import { ANALYTICS_SDK_CODE, ANALYTICS_PRODUCT } from '../../src/data/analytics';

const TEST_ANALYTICS_CONFIG = {
sdkCode: ANALYTICS_SDK_CODE,
sdkSemver: '1.1.1',
techVersion: '1.1.1',
product: ANALYTICS_PRODUCT,
}

const TEST_ANALYTICS_STRING = 'BBFCd1Bl0';

describe('lib/util', () => {
const ENV_ORIGINAL = process.env;
Expand Down Expand Up @@ -36,7 +46,7 @@ describe('lib/util', () => {
remoteHost: 'https://cloudinary.netlify.app'
});

expect(cloudinaryUrl).toEqual(`https://${cname}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
expect(cloudinaryUrl).toMatch(`https://${cname}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
});

});
Expand All @@ -51,9 +61,9 @@ describe('lib/util', () => {
localDir: 'tests',
remoteHost: 'https://cloudinary.netlify.app',
loadingStrategy: 'lazy'
});
}, TEST_ANALYTICS_CONFIG);

expect(html).toEqual(`<html><head></head><body><p><img src=\"https://${cname}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg\" loading=\"lazy"\></p></body></html>`);
expect(html).toEqual(`<html><head></head><body><p><img src=\"https://${cname}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg?_a=${TEST_ANALYTICS_STRING}\" loading=\"lazy"\></p></body></html>`);
});

});
Expand Down
16 changes: 13 additions & 3 deletions netlify-plugin-cloudinary/tests/lib/cloudinary-privatecdn.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { vi, expect, describe, test, beforeEach, afterAll, it } from 'vitest';

import { configureCloudinary, getCloudinaryUrl, updateHtmlImagesToCloudinary } from '../../src/lib/cloudinary';
import { ANALYTICS_SDK_CODE, ANALYTICS_PRODUCT } from '../../src/data/analytics';

const TEST_ANALYTICS_CONFIG = {
sdkCode: ANALYTICS_SDK_CODE,
sdkSemver: '1.1.1',
techVersion: '1.1.1',
product: ANALYTICS_PRODUCT,
}

const TEST_ANALYTICS_STRING = 'BBFCd1Bl0';

describe('lib/util', () => {
const ENV_ORIGINAL = process.env;
Expand Down Expand Up @@ -35,7 +45,7 @@ describe('lib/util', () => {
remoteHost: 'https://cloudinary.netlify.app'
});

expect(cloudinaryUrl).toEqual(`https://${process.env.CLOUDINARY_CLOUD_NAME}-res.cloudinary.com/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
expect(cloudinaryUrl).toMatch(`https://${process.env.CLOUDINARY_CLOUD_NAME}-res.cloudinary.com/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
});

});
Expand All @@ -50,9 +60,9 @@ describe('lib/util', () => {
localDir: 'tests',
remoteHost: 'https://cloudinary.netlify.app',
loadingStrategy: 'lazy'
});
}, TEST_ANALYTICS_CONFIG);

expect(html).toEqual(`<html><head></head><body><p><img src=\"https://${process.env.CLOUDINARY_CLOUD_NAME}-res.cloudinary.com/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg\" loading=\"lazy"\></p></body></html>`);
expect(html).toEqual(`<html><head></head><body><p><img src=\"https://${process.env.CLOUDINARY_CLOUD_NAME}-res.cloudinary.com/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg?_a=${TEST_ANALYTICS_STRING}\" loading=\"lazy"\></p></body></html>`);
});

});
Expand Down
38 changes: 24 additions & 14 deletions netlify-plugin-cloudinary/tests/lib/cloudinary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@ import { vi, expect, describe, test, beforeEach, afterAll, it } from 'vitest';

import { ERROR_ASSET_UPLOAD } from '../../src/data/errors';
import { getCloudinary, createPublicId, configureCloudinary, getCloudinaryUrl, updateHtmlImagesToCloudinary } from '../../src/lib/cloudinary';
import { ANALYTICS_SDK_CODE, ANALYTICS_PRODUCT } from '../../src/data/analytics';

const mockDemo = require('../mocks/demo.json');

const cloudinary = getCloudinary();

const TEST_ANALYTICS_CONFIG = {
sdkCode: ANALYTICS_SDK_CODE,
sdkSemver: '1.1.1',
techVersion: '1.1.1',
product: ANALYTICS_PRODUCT,
}

const TEST_ANALYTICS_STRING = 'BBFCd1Bl0';

describe('lib/util', () => {
const ENV_ORIGINAL = process.env;

Expand Down Expand Up @@ -59,7 +69,7 @@ describe('lib/util', () => {
remoteHost: 'https://cloudinary.netlify.app'
});

expect(cloudinaryUrl).toEqual(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
expect(cloudinaryUrl).toMatch(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
});

test('should create a Cloudinary URL with delivery type of fetch from a remote image', async () => {
Expand All @@ -69,7 +79,7 @@ describe('lib/util', () => {
path: 'https://i.imgur.com/vtYmp1x.png'
});

expect(cloudinaryUrl).toEqual(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://i.imgur.com/vtYmp1x.png`);
expect(cloudinaryUrl).toMatch(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://i.imgur.com/vtYmp1x.png`);
});


Expand All @@ -94,7 +104,7 @@ describe('lib/util', () => {
remoteHost: 'https://cloudinary.netlify.app'
});

expect(cloudinaryUrl).toEqual(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/f_auto,q_auto/stranger-things-dustin-fc571e771d5ca7d9223a7eebfd2c505d`);
expect(cloudinaryUrl).toMatch(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/f_auto,q_auto/stranger-things-dustin-fc571e771d5ca7d9223a7eebfd2c505d`);
});

test('should fail to create a Cloudinary URL with delivery type of upload', async () => {
Expand All @@ -121,7 +131,7 @@ describe('lib/util', () => {
// path: 'https://i.imgur.com/vtYmp1x.png'
// });

// expect(cloudinaryUrl).toEqual(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/f_auto,q_auto/vtYmp1x-ae71a79c9c36b8d5dba872c3b274a444`);
// expect(cloudinaryUrl).toMatch(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/f_auto,q_auto/vtYmp1x-ae71a79c9c36b8d5dba872c3b274a444`);
// });

test('should apply transformations', async () => {
Expand All @@ -139,7 +149,7 @@ describe('lib/util', () => {
transformations: [maxSize]
});

expect(cloudinaryUrl).toEqual(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/c_${maxSize.crop},dpr_${maxSize.dpr},h_${maxSize.height},w_${maxSize.width}/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
expect(cloudinaryUrl).toMatch(`https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/c_${maxSize.crop},dpr_${maxSize.dpr},h_${maxSize.height},w_${maxSize.width}/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg`);
});

});
Expand All @@ -154,9 +164,9 @@ describe('lib/util', () => {
localDir: 'tests',
remoteHost: 'https://cloudinary.netlify.app',
loadingStrategy: 'lazy'
});
}, TEST_ANALYTICS_CONFIG);

expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg\" loading=\"lazy"\></p></body></html>`);
expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg?_a=${TEST_ANALYTICS_STRING}\" loading=\"lazy"\></p></body></html>`);
});

it('should replace a remote image with a Cloudinary URL', async () => {
Expand All @@ -165,9 +175,9 @@ describe('lib/util', () => {
const { html } = await updateHtmlImagesToCloudinary(sourceHtml, {
deliveryType: 'fetch',
loadingStrategy: 'lazy'
});
}, TEST_ANALYTICS_CONFIG);

expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://i.imgur.com/vtYmp1x.png\" loading=\"lazy"\></p></body></html>`);
expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://i.imgur.com/vtYmp1x.png?_a=${TEST_ANALYTICS_STRING}\" loading=\"lazy"\></p></body></html>`);
});


Expand All @@ -179,9 +189,9 @@ describe('lib/util', () => {
localDir: 'tests',
remoteHost: 'https://cloudinary.netlify.app',
loadingStrategy: 'lazy'
});
}, TEST_ANALYTICS_CONFIG);

expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg\" srcset=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg 1x, https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg 2x\" loading=\"lazy"\></p></body></html>`);
expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg?_a=${TEST_ANALYTICS_STRING}\" srcset=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg?_a=${TEST_ANALYTICS_STRING} 1x, https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://cloudinary.netlify.app/images/stranger-things-dustin.jpeg?_a=${TEST_ANALYTICS_STRING} 2x\" loading=\"lazy"\></p></body></html>`);
});

it('should add eager loading to image when eager option is provided for loadingStrategy', async () => {
Expand All @@ -192,9 +202,9 @@ describe('lib/util', () => {
localDir: 'tests',
remoteHost: 'https://cloudinary.netlify.app',
loadingStrategy: 'eager'
});
}, TEST_ANALYTICS_CONFIG);

expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://i.imgur.com/vtYmp1x.png\" loading=\"eager"\></p></body></html>`);
expect(html).toEqual(`<html><head></head><body><p><img src=\"https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/fetch/f_auto,q_auto/https://i.imgur.com/vtYmp1x.png?_a=${TEST_ANALYTICS_STRING}\" loading=\"eager"\></p></body></html>`);
});

it('should test uploading multiple assets', async () => {
Expand All @@ -207,7 +217,7 @@ describe('lib/util', () => {
loadingStrategy: 'lazy',
folder: 'netlify-plugin-cloudinary',
assets: mockDemo.assets
});
}, TEST_ANALYTICS_CONFIG);

expect(html).toEqual(mockDemo.htmlAfter);
});
Expand Down
13 changes: 12 additions & 1 deletion netlify-plugin-cloudinary/tests/on-build.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('onBuild', () => {
const imagesFunctionName = 'cld_images';

fs.readdir.mockResolvedValue([imagesFunctionName]);
})
});

function validate(imagesPath, redirects, url) {
if (typeof (imagesPath) === 'string') {
Expand All @@ -112,6 +112,17 @@ describe('onBuild', () => {
imagesPath?.reverse().forEach(element => {
let i = element.split(path.win32.sep).join(path.posix.sep).replace('/', '');

// The analytics string that's added to the URLs is dynamic based on package version.
// The resulting value is also not static, so we can't simply add it to the end of the
// URL, so strip the analytics from the URLs as it's not important for this particular
// test, being covered elsewhere.

redirects.forEach(redirect => {
if ( redirect.to.includes('https://res.cloudinary.com') ) {
redirect.to = redirect.to.split('?')[0];
}
})

expect(redirects[count]).toEqual({
from: `/${i}/*`,
to: `https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/${deliveryType}/f_auto,q_auto/${process.env.URL}/cld-assets/${i}/:splat`,
Expand Down
Loading

0 comments on commit 4940fb0

Please sign in to comment.