Skip to content

Commit

Permalink
Create a Library for Content Analytics and track pageview (#30758)
Browse files Browse the repository at this point in the history
### Proposed Changes
- Create a new library (First step) for Content Analytics
- Track pageview (`PAGE_REQUEST `)
- Gather information from the browser and send it to Content Analytics.

This pull request introduces a new analytics library for DotCMS,
including configuration files, core library implementation, and
documentation. The most important changes include adding ESLint
configuration, setting up SWC compilation, creating the main analytics
library, and providing detailed documentation.

Configuration and Setup:

*
[`core-web/libs/sdk/analytics/.eslintrc.json`](diffhunk://#diff-d0201a0661aff8947ea8a7ac6b7d6927a0ad048f8efd8fb8170d7b633042d686R1-R25):
Added ESLint configuration for the analytics library.
*
[`core-web/libs/sdk/analytics/.swcrc`](diffhunk://#diff-56054e79593c4390d295b0d7665afc753fd6cb7741a2481f08d936b89d48c6ddR1-R29):
Added SWC configuration for TypeScript compilation.
*
[`core-web/libs/sdk/analytics/jest.config.ts`](diffhunk://#diff-16e075e62c0541db94df4d7ec7c316a2e172719755708f83e8f18c4e1f1bc83aR1-R28):
Configured Jest to use SWC for compiling test files.

Library Implementation:

*
[`core-web/libs/sdk/analytics/src/lib/analytics.ts`](diffhunk://#diff-e9c6efb834ad15a51ff282f7a03636c996d1d576bfa62da009ab4cfbdfb8de9eR1-R51):
Implemented the `DotAnalytics` class for tracking events and collecting
browser metadata.
*
[`core-web/libs/sdk/analytics/src/lib/plugin/dot-analytics.plugin.ts`](diffhunk://#diff-d7df5e441bb8e01e05714068f836f14f5c55a053fc2ca3645e3175d652fd6acbR1-R43):
Created a plugin for sending analytics events to the server.
*
[`core-web/libs/sdk/analytics/src/lib/shared/analytics.http.ts`](diffhunk://#diff-ea929c6b59ebaf5550c622a87d50b3f3986899e5b1c64579b39a3958a27de4e7R1-R33):
Added a utility function to send events to the server.

Documentation:

*
[`core-web/libs/sdk/analytics/README.md`](diffhunk://#diff-678db695238cf1fa2a53b558165d16dcab62eb0f68f98d3c76acfe5f9500425eR1-R90):
Added comprehensive documentation for the analytics library, including
installation, configuration, usage, and contribution guidelines.

Additional Changes:

*
[`core-web/libs/sdk/analytics/package.json`](diffhunk://#diff-93c259abff97ad5871d7eec76693b940dbc165ce49f5e38998f0d6b35c80c0bcR1-R28):
Added package metadata and dependencies for the analytics library.
*
[`core-web/libs/sdk/analytics/project.json`](diffhunk://#diff-df52caa5b0f2f8d41b786ae93254df474b91c1bf3f7183d3f9b9a6504a900000R1-R42):
Configured build and test targets for the analytics library using Nx.

### Checklist
- [x] Tests
- [ ] Translations
- [ ] Security Implications Contemplated (add notes if applicable)

### Event sent

```
{
   "type":"track",
   "key":"js.cluster1.customer1.5kwawrgnttvwtgw1yd",
   "event_type":"PAGE_REQUEST",
   "utc_time":"2024-11-25T19:02:32.621Z",
   "local_tz_offset":360,
   "referer":"",
   "url":"http://localhost:8080/test",
   "page_title":"",
   "doc_path":"/test",
   "doc_host":"localhost",
   "doc_protocol":"http:",
   "doc_hash":"",
   "doc_search":"",
   "screen_resolution":"1680x1050",
   "vp_size":"1680x424",
   "user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
   "user_language":"es",
   "doc_encoding":"UTF-8",
   "utm":{
      
   },
   "src":"dotAnalytics",
   "timestamp":"2024-11-25T19:02:32.621Z"
}
```
  • Loading branch information
oidacra authored Nov 26, 2024
1 parent e9c8559 commit 7e89a7e
Show file tree
Hide file tree
Showing 24 changed files with 1,634 additions and 51 deletions.
25 changes: 25 additions & 0 deletions core-web/libs/sdk/analytics/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": ["../../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}
29 changes: 29 additions & 0 deletions core-web/libs/sdk/analytics/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "es6"
},
"sourceMaps": true,
"exclude": [
"jest.config.ts",
".*\\.spec.tsx?$",
".*\\.test.tsx?$",
"./src/jest-setup.ts$",
"./**/jest-setup.ts$",
".*.js$"
]
}
91 changes: 91 additions & 0 deletions core-web/libs/sdk/analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# @dotcms/analytics

`@dotcms/analytics` is the official dotCMS JavaScript library for Content Analytics that helps track events and analytics in your webapps. Currently available as an IIFE (Immediately Invoked Function Expression) module for direct browser usage.

## Features

- **Simple Browser Integration**: Easy to implement via script tags using IIFE implementation
- **Event Tracking**: Simple API to track custom events with additional properties
- **Automatic PageView**: Option to automatically track page views
- **Debug Mode**: Optional debug logging for development

## Installation

Include the script in your HTML page:

```html
<script src="analytics.iife.js"></script>
```

## Configuration

The script can be configured using data attributes:

- **data-analytics-server**: URL of the server where events will be sent. If not provided, it defaults to the current location (window.location.href).
- **data-analytics-debug**: Presence of this attribute enables debug logging (no value needed)
- **data-analytics-auto-page-view**: Presence of this attribute enables automatic page view tracking (no value needed)
- **data-analytics-key**: Required. API key for authentication with the analytics server. This key is provided by the DotCMS Analytics app.

## Usage

### Automatic PageView Tracking

When `data-analytics-auto-page-view` is enabled, the library will automatically send a page view event to dotCMS when the page loads. If this attribute is not present, you'll need to manually track page views and other events using the tracking API.

```html
<!-- Automatic page view tracking enabled & debug logging enabled -->
<script
src="ca.min.js"
data-analytics-server="http://localhost:8080"
data-analytics-key="dev-key-123"
data-analytics-auto-page-view
data-analytics-debug></script>

<!-- Without automatic tracking - events must be sent manually -->
<script
src="ca.min.js"
data-analytics-server="http://localhost:8080"
data-analytics-debug
data-analytics-key="dev-key-123"></script>
```

## Roadmap

The following features are planned for future releases:

1. **Manual Event Tracking**

- Manual track events support for IIFE implementation

2. **Headless Support**

- React integration for event tracking
- Next.js integration for event tracking
- Angular integration for event tracking

## Contributing

GitHub pull requests are the preferred method to contribute code to dotCMS. Before any pull requests can be accepted, an automated tool will ask you to agree to the [dotCMS Contributor's Agreement](https://gist.github.com/wezell/85ef45298c48494b90d92755b583acb3).

## Licensing

dotCMS comes in multiple editions and as such is dual licensed. The dotCMS Community Edition is licensed under the GPL 3.0 and is freely available for download, customization and deployment for use within organizations of all stripes. dotCMS Enterprise Editions (EE) adds a number of enterprise features and is available via a supported, indemnified commercial license from dotCMS. For the differences between the editions, see [the feature page](http://dotcms.com/cms-platform/features).

## Support

If you need help or have any questions, please [open an issue](https://github.com/dotCMS/core/issues/new/choose) in the GitHub repository.

## Documentation

Always refer to the official [DotCMS documentation](https://www.dotcms.com/docs/latest/) for comprehensive guides and API references.

## Getting Help

| Source | Location |
| --------------- | ------------------------------------------------------------------- |
| Installation | [Installation](https://dotcms.com/docs/latest/installation) |
| Documentation | [Documentation](https://dotcms.com/docs/latest/table-of-contents) |
| Videos | [Helpful Videos](http://dotcms.com/videos/) |
| Forums/Listserv | [via Google Groups](https://groups.google.com/forum/#!forum/dotCMS) |
| Twitter | @dotCMS |
| Main Site | [dotCMS.com](https://dotcms.com/) |
28 changes: 28 additions & 0 deletions core-web/libs/sdk/analytics/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable */
import { readFileSync } from 'fs';

// Reading the SWC compilation config and remove the "exclude"
// for the test files to be compiled by SWC
const { exclude: _, ...swcJestConfig } = JSON.parse(readFileSync(`${__dirname}/.swcrc`, 'utf-8'));

// disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves.
// If we do not disable this, SWC Core will read .swcrc and won't transform our test files due to "exclude"
if (swcJestConfig.swcrc === undefined) {
swcJestConfig.swcrc = false;
}

// Uncomment if using global setup/teardown files being transformed via swc
// https://nx.dev/nx-api/jest/documents/overview#global-setupteardown-with-nx-libraries
// jest needs EsModule Interop to find the default exported setup/teardown functions
// swcJestConfig.module.noInterop = false;

export default {
displayName: 'analytics',
preset: '../../../jest.preset.js',
transform: {
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig]
},
moduleFileExtensions: ['ts', 'js', 'html'],
testEnvironment: 'jsdom',
coverageDirectory: '../../../coverage/libs/sdk/analytics'
};
28 changes: 28 additions & 0 deletions core-web/libs/sdk/analytics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@dotcms/analytics",
"version": "0.0.1-alpha.38",
"description": "Official JavaScript library for Content Analytics with DotCMS.",
"repository": {
"type": "git",
"url": "git+https://github.com/dotCMS/core.git#main"
},
"keywords": [
"dotCMS",
"CMS",
"Content Management",
"Analytics",
"Tracking"
],
"author": "dotcms <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/dotCMS/core/issues"
},
"homepage": "https://github.com/dotCMS/core/tree/main/core-web/libs/sdk/analytics/README.md",
"peerDependencies": {
"analytics": "^0.8.14",
"vite": "^5.0.0"
},
"main": "./index.cjs.js",
"module": "./index.esm.js"
}
43 changes: 43 additions & 0 deletions core-web/libs/sdk/analytics/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "analytics",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/sdk/analytics/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/sdk/analytics",
"main": "libs/sdk/analytics/src/index.ts",
"tsConfig": "libs/sdk/analytics/tsconfig.lib.json",
"project": "libs/sdk/analytics/package.json",
"compiler": "swc",
"format": ["esm", "cjs"]
}
},
"build:standalone": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "../../core/dotCMS/src/main/resources/ca/html",
"main": "libs/sdk/analytics/src/lib/standalone.ts",
"tsConfig": "libs/sdk/analytics/tsconfig.lib.json",
"project": "libs/sdk/analytics/package.json"
}
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/sdk/analytics/jest.config.ts"
}
}
},
"tags": ["type:lib", "scope:sdk", "feature:analytics"]
}
1 change: 1 addition & 0 deletions core-web/libs/sdk/analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/analytics';
92 changes: 92 additions & 0 deletions core-web/libs/sdk/analytics/src/lib/analytics.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import Analytics from 'analytics';

import { DotAnalytics } from './analytics';
import { dotAnalyticsPlugin } from './plugin/dot-analytics.plugin';

// Mock the analytics library
jest.mock('analytics');
jest.mock('./plugin/dot-analytics.plugin');

describe('DotAnalytics', () => {
const mockConfig = {
debug: false,
server: 'http://test.com',
key: 'test-key',
autoPageView: false
};

beforeEach(() => {
jest.clearAllMocks();
// Reset singleton instance between tests
(DotAnalytics as any).instance = null;
});

describe('getInstance', () => {
it('should create single instance', () => {
const instance1 = DotAnalytics.getInstance(mockConfig);
const instance2 = DotAnalytics.getInstance(mockConfig);

expect(instance1).toBe(instance2);
});

it('should maintain same instance even with different config', () => {
const instance1 = DotAnalytics.getInstance(mockConfig);
const instance2 = DotAnalytics.getInstance({ ...mockConfig, debug: true });

expect(instance1).toBe(instance2);
});
});

describe('ready', () => {
it('should initialize analytics with correct config', async () => {
const instance = DotAnalytics.getInstance(mockConfig);
const mockAnalytics = {};
(Analytics as jest.Mock).mockReturnValue(mockAnalytics);
(dotAnalyticsPlugin as jest.Mock).mockReturnValue({ name: 'mock-plugin' });

await instance.ready();

expect(Analytics).toHaveBeenCalledWith({
app: 'dotAnalytics',
debug: false,
plugins: [{ name: 'mock-plugin' }]
});
expect(dotAnalyticsPlugin).toHaveBeenCalledWith(mockConfig);
});

it('should only initialize once', async () => {
const instance = DotAnalytics.getInstance(mockConfig);

await instance.ready();
await instance.ready();

expect(Analytics).toHaveBeenCalledTimes(1);
});

it('should throw error if initialization fails', async () => {
const instance = DotAnalytics.getInstance(mockConfig);
const error = new Error('Init failed');
(Analytics as jest.Mock).mockImplementation(() => {
throw error;
});

// eslint-disable-next-line @typescript-eslint/no-empty-function
const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {});

try {
await instance.ready();
} catch (e) {
expect(e).toEqual(error);
expect(console.error).toHaveBeenCalledWith(
'Failed to initialize DotAnalytics:',
error
);
}

// Restore console.error
consoleErrorMock.mockRestore();
});
});
});
51 changes: 51 additions & 0 deletions core-web/libs/sdk/analytics/src/lib/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Analytics, { AnalyticsInstance } from 'analytics';

import { dotAnalyticsPlugin } from './plugin/dot-analytics.plugin';
import { DotAnalyticsConfig } from './shared/analytics.model';

/**
* DotAnalytics class for sending events to Content Analytics.
* This class handles tracking events and automatically collects browser information
* like user agent, viewport size, and other relevant browser metadata to provide
* better analytics insights.
*
* The class follows a singleton pattern to ensure only one analytics instance
* is running at a time.
*/
export class DotAnalytics {
private static instance: DotAnalytics | null = null;
#initialized = false;
#analytics: AnalyticsInstance | null = null;
#config: DotAnalyticsConfig;

private constructor(config: DotAnalyticsConfig) {
this.#config = config;
}

static getInstance(config: DotAnalyticsConfig): DotAnalytics {
if (!DotAnalytics.instance) {
DotAnalytics.instance = new DotAnalytics(config);
}

return DotAnalytics.instance;
}

async ready(): Promise<void> {
if (this.#initialized) {
return Promise.resolve();
}

try {
this.#analytics = Analytics({
app: 'dotAnalytics',
debug: this.#config.debug,
plugins: [dotAnalyticsPlugin(this.#config)]
});

this.#initialized = true;
} catch (error) {
console.error('Failed to initialize DotAnalytics:', error);
throw error;
}
}
}
Loading

0 comments on commit 7e89a7e

Please sign in to comment.