@webex/cc-ui-logging 1.28.0-ccwidgets.121 → 1.28.0-ccwidgets.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -2
- package/jest.config.js +0 -6
- package/src/index.ts +0 -5
- package/src/metricsLogger.ts +0 -102
- package/src/withMetrics.tsx +0 -29
- package/tests/metricsLogger.test.ts +0 -86
- package/tests/withMetrics.test.tsx +0 -105
- package/tsconfig.json +0 -11
- package/tsconfig.test.json +0 -9
- package/webpack.config.js +0 -17
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/cc-ui-logging",
|
|
3
|
-
"version": "1.28.0-ccwidgets.
|
|
3
|
+
"version": "1.28.0-ccwidgets.122",
|
|
4
4
|
"description": "UI metrics tracking for Webex widgets",
|
|
5
|
+
"license": "Cisco's General Terms (https://www.cisco.com/site/us/en/about/legal/contract-experience/index.html)",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
8
|
"publishConfig": {
|
|
8
9
|
"access": "public"
|
|
9
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"package.json"
|
|
14
|
+
],
|
|
10
15
|
"scripts": {
|
|
11
16
|
"build:src": "webpack --mode=development",
|
|
12
17
|
"clean": "rm -rf dist",
|
|
@@ -15,7 +20,7 @@
|
|
|
15
20
|
"test:styles": "echo 'No styles to test'"
|
|
16
21
|
},
|
|
17
22
|
"dependencies": {
|
|
18
|
-
"@webex/cc-store": "1.28.0-ccwidgets.
|
|
23
|
+
"@webex/cc-store": "1.28.0-ccwidgets.122"
|
|
19
24
|
},
|
|
20
25
|
"devDependencies": {
|
|
21
26
|
"@testing-library/dom": "10.4.0",
|
package/jest.config.js
DELETED
package/src/index.ts
DELETED
package/src/metricsLogger.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import store from '@webex/cc-store';
|
|
2
|
-
|
|
3
|
-
export type WidgetMetrics = {
|
|
4
|
-
widgetName: string;
|
|
5
|
-
event: 'WIDGET_MOUNTED' | 'ERROR' | 'WIDGET_UNMOUNTED' | 'PROPS_UPDATED';
|
|
6
|
-
props?: Record<string, any>;
|
|
7
|
-
timestamp: number;
|
|
8
|
-
additionalContext?: Record<string, any>;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Logs UI metrics for contact center widgets.
|
|
13
|
-
*
|
|
14
|
-
* This function logs widget lifecycle events and errors to help monitor
|
|
15
|
-
* widget performance and user interactions. If no logger is available,
|
|
16
|
-
* it will emit a warning and skip logging.
|
|
17
|
-
*
|
|
18
|
-
* @param metric - The metrics data to be logged
|
|
19
|
-
* @param metric.widgetName - Name of the widget generating the metric
|
|
20
|
-
* @param metric.event - Type of event being logged
|
|
21
|
-
* @param metric.props - Optional properties associated with the widget
|
|
22
|
-
* @param metric.timestamp - Unix timestamp when the event occurred
|
|
23
|
-
* @param metric.additionalContext - Optional additional context data
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
|
-
* logMetrics({
|
|
28
|
-
* widgetName: 'CallControl',
|
|
29
|
-
* event: 'WIDGET_MOUNTED',
|
|
30
|
-
* props: { callId: '123' },
|
|
31
|
-
* timestamp: Date.now(),
|
|
32
|
-
* additionalContext: { userId: 'user123' }
|
|
33
|
-
* });
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export const logMetrics = (metric: WidgetMetrics) => {
|
|
37
|
-
if (!store.logger) {
|
|
38
|
-
console.warn('CC-Widgets: UI Metrics: No logger found');
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
store.logger.log(`CC-Widgets: UI Metrics: ${JSON.stringify(metric, null, 2)}`, {
|
|
42
|
-
module: 'metricsLogger.tsx',
|
|
43
|
-
method: 'logMetrics',
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Determines if props have changed between two objects using shallow comparison.
|
|
49
|
-
*
|
|
50
|
-
* This function performs a shallow comparison between two objects to detect changes.
|
|
51
|
-
* It compares object keys and primitive values, but does not recursively compare
|
|
52
|
-
* nested objects. This is useful for determining when to log metrics based on prop changes.
|
|
53
|
-
*
|
|
54
|
-
* @param prev - The previous props object
|
|
55
|
-
* @param next - The next props object to compare against
|
|
56
|
-
* @returns `true` if the props have changed, `false` otherwise
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```typescript
|
|
60
|
-
* const oldProps = { name: 'John', age: 30 };
|
|
61
|
-
* const newProps = { name: 'John', age: 31 };
|
|
62
|
-
*
|
|
63
|
-
* if (havePropsChanged(oldProps, newProps)) {
|
|
64
|
-
* // Props have changed, log metrics
|
|
65
|
-
* logMetrics({
|
|
66
|
-
* widgetName: 'UserProfile',
|
|
67
|
-
* event: 'WIDGET_MOUNTED',
|
|
68
|
-
* props: newProps,
|
|
69
|
-
* timestamp: Date.now()
|
|
70
|
-
* });
|
|
71
|
-
* } * ```
|
|
72
|
-
*
|
|
73
|
-
* @remarks
|
|
74
|
-
* The function is important as we dont sanitize our props right now.
|
|
75
|
-
* Once we start sanitizing we can do a deep comparison. This is used to only re-render
|
|
76
|
-
* the HOC if the props have changed.
|
|
77
|
-
*/
|
|
78
|
-
export function havePropsChanged(prev: any, next: any): boolean {
|
|
79
|
-
if (prev === next) return false;
|
|
80
|
-
|
|
81
|
-
// Do shallow comparison
|
|
82
|
-
if (typeof prev !== typeof next) return true;
|
|
83
|
-
if (!prev || !next) return prev !== next;
|
|
84
|
-
|
|
85
|
-
const prevKeys = Object.keys(prev);
|
|
86
|
-
const nextKeys = Object.keys(next);
|
|
87
|
-
|
|
88
|
-
if (prevKeys.length !== nextKeys.length) return true;
|
|
89
|
-
|
|
90
|
-
// Check if any primitive values changed
|
|
91
|
-
for (const key of prevKeys) {
|
|
92
|
-
const prevVal = prev[key];
|
|
93
|
-
const nextVal = next[key];
|
|
94
|
-
|
|
95
|
-
if (prevVal === nextVal) continue;
|
|
96
|
-
if (typeof prevVal !== 'object' || prevVal === null) return true;
|
|
97
|
-
if (typeof nextVal !== 'object' || nextVal === null) return true;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// All shallow comparisons passed, consider props unchanged
|
|
101
|
-
return false;
|
|
102
|
-
}
|
package/src/withMetrics.tsx
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import React, {useEffect, useRef} from 'react';
|
|
2
|
-
import {havePropsChanged, logMetrics} from './metricsLogger';
|
|
3
|
-
|
|
4
|
-
export default function withMetrics<P extends object>(Component: any, widgetName: string) {
|
|
5
|
-
return React.memo(
|
|
6
|
-
(props: P) => {
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
logMetrics({
|
|
9
|
-
widgetName,
|
|
10
|
-
event: 'WIDGET_MOUNTED',
|
|
11
|
-
timestamp: Date.now(),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
return () => {
|
|
15
|
-
logMetrics({
|
|
16
|
-
widgetName,
|
|
17
|
-
event: 'WIDGET_UNMOUNTED',
|
|
18
|
-
timestamp: Date.now(),
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
// TODO: https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6890 PROPS_UPDATED event
|
|
24
|
-
|
|
25
|
-
return <Component {...props} />;
|
|
26
|
-
},
|
|
27
|
-
(prevProps, nextProps) => !havePropsChanged(prevProps, nextProps)
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import store from '@webex/cc-store';
|
|
2
|
-
import {logMetrics, havePropsChanged, WidgetMetrics} from '../src/metricsLogger';
|
|
3
|
-
|
|
4
|
-
describe('metricsLogger', () => {
|
|
5
|
-
store.store.logger = {
|
|
6
|
-
log: jest.fn(),
|
|
7
|
-
info: jest.fn(),
|
|
8
|
-
warn: jest.fn(),
|
|
9
|
-
error: jest.fn(),
|
|
10
|
-
trace: jest.fn(),
|
|
11
|
-
};
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
jest.clearAllMocks();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('logMetrics', () => {
|
|
17
|
-
it('should log metrics when logger is available', () => {
|
|
18
|
-
const metric: WidgetMetrics = {
|
|
19
|
-
widgetName: 'TestWidget',
|
|
20
|
-
event: 'WIDGET_MOUNTED',
|
|
21
|
-
timestamp: 1234567890,
|
|
22
|
-
props: {test: 'prop'},
|
|
23
|
-
additionalContext: {context: 'test'},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
logMetrics(metric);
|
|
27
|
-
|
|
28
|
-
expect(store.logger.log).toHaveBeenCalledWith(`CC-Widgets: UI Metrics: ${JSON.stringify(metric, null, 2)}`, {
|
|
29
|
-
module: 'metricsLogger.tsx',
|
|
30
|
-
method: 'logMetrics',
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should handle case when logger is not available', () => {
|
|
35
|
-
const consoleSpy = jest.spyOn(console, 'warn');
|
|
36
|
-
store.store.logger = undefined;
|
|
37
|
-
|
|
38
|
-
const metric: WidgetMetrics = {
|
|
39
|
-
widgetName: 'TestWidget',
|
|
40
|
-
event: 'WIDGET_MOUNTED',
|
|
41
|
-
timestamp: 1234567890,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
logMetrics(metric);
|
|
45
|
-
|
|
46
|
-
expect(consoleSpy).toHaveBeenCalledWith('CC-Widgets: UI Metrics: No logger found');
|
|
47
|
-
consoleSpy.mockRestore();
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('havePropsChanged', () => {
|
|
52
|
-
it('should return false for identical primitives', () => {
|
|
53
|
-
expect(havePropsChanged(1, 1)).toBe(false);
|
|
54
|
-
expect(havePropsChanged('test', 'test')).toBe(false);
|
|
55
|
-
expect(havePropsChanged(true, true)).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should return true for different primitives', () => {
|
|
59
|
-
expect(havePropsChanged('test', 'test2')).toBe(true);
|
|
60
|
-
expect(havePropsChanged(true, false)).toBe(true);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should return true for different types', () => {
|
|
64
|
-
expect(havePropsChanged(1, '1')).toBe(true);
|
|
65
|
-
expect(havePropsChanged(null, undefined)).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should return true when object keys differ', () => {
|
|
69
|
-
const obj1 = {a: 1, b: 2};
|
|
70
|
-
const obj2 = {a: 1};
|
|
71
|
-
expect(havePropsChanged(obj1, obj2)).toBe(true);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should return false when nested values differ', () => {
|
|
75
|
-
const obj1 = {a: {b: 1}};
|
|
76
|
-
const obj2 = {a: {b: 2}};
|
|
77
|
-
expect(havePropsChanged(obj1, obj2)).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should handle null and undefined', () => {
|
|
81
|
-
expect(havePropsChanged(null, null)).toBe(false);
|
|
82
|
-
expect(havePropsChanged(undefined, undefined)).toBe(false);
|
|
83
|
-
expect(havePropsChanged(null, undefined)).toBe(true);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {render} from '@testing-library/react';
|
|
3
|
-
import '@testing-library/jest-dom';
|
|
4
|
-
import withMetrics from '../src/withMetrics';
|
|
5
|
-
import store from '@webex/cc-store';
|
|
6
|
-
import * as metricsLogger from '../src/metricsLogger';
|
|
7
|
-
|
|
8
|
-
interface TestComponentProps {
|
|
9
|
-
name?: string;
|
|
10
|
-
[key: string]: any;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe('withMetrics HOC', () => {
|
|
14
|
-
store.store.logger = {
|
|
15
|
-
log: jest.fn(),
|
|
16
|
-
info: jest.fn(),
|
|
17
|
-
warn: jest.fn(),
|
|
18
|
-
error: jest.fn(),
|
|
19
|
-
trace: jest.fn(),
|
|
20
|
-
};
|
|
21
|
-
const logMetricsSpy = jest.spyOn(metricsLogger, 'logMetrics');
|
|
22
|
-
|
|
23
|
-
const TestComponent: React.FC<TestComponentProps> = (props) => <div>Test Component {props.name}</div>;
|
|
24
|
-
const WrappedComponent = withMetrics<TestComponentProps>(TestComponent, 'TestWidget');
|
|
25
|
-
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
jest.clearAllMocks();
|
|
28
|
-
jest.useFakeTimers();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
jest.useRealTimers();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should log metrics on mount', () => {
|
|
36
|
-
const mockTime = 1234567890;
|
|
37
|
-
jest.setSystemTime(mockTime);
|
|
38
|
-
|
|
39
|
-
render(<WrappedComponent name="test" />);
|
|
40
|
-
|
|
41
|
-
expect(logMetricsSpy).toHaveBeenCalledWith({
|
|
42
|
-
widgetName: 'TestWidget',
|
|
43
|
-
event: 'WIDGET_MOUNTED',
|
|
44
|
-
timestamp: mockTime,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should log metrics on unmount', () => {
|
|
49
|
-
const mockTime = 1234567890;
|
|
50
|
-
jest.setSystemTime(mockTime);
|
|
51
|
-
|
|
52
|
-
const {unmount} = render(<WrappedComponent name="test" />);
|
|
53
|
-
|
|
54
|
-
// Clear the mount log
|
|
55
|
-
logMetricsSpy.mockClear();
|
|
56
|
-
|
|
57
|
-
// Unmount the component
|
|
58
|
-
unmount();
|
|
59
|
-
|
|
60
|
-
expect(logMetricsSpy).toHaveBeenCalledWith({
|
|
61
|
-
widgetName: 'TestWidget',
|
|
62
|
-
event: 'WIDGET_UNMOUNTED',
|
|
63
|
-
timestamp: mockTime,
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should pass through props to wrapped component', () => {
|
|
68
|
-
const {getByText} = render(<WrappedComponent name="test-name" />);
|
|
69
|
-
expect(getByText('Test Component test-name')).toBeInTheDocument();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should not re-render when props have not changed', () => {
|
|
73
|
-
const renderSpy = jest.fn();
|
|
74
|
-
const SpyComponent: React.FC<TestComponentProps> = (props) => {
|
|
75
|
-
renderSpy();
|
|
76
|
-
return <div>Test Component {props.name}</div>;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const WrappedSpy = withMetrics<TestComponentProps>(SpyComponent, 'TestWidget');
|
|
80
|
-
|
|
81
|
-
const {rerender} = render(<WrappedSpy name="test" />);
|
|
82
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
83
|
-
|
|
84
|
-
// Re-render with same props
|
|
85
|
-
rerender(<WrappedSpy name="test" />);
|
|
86
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should re-render when props have changed', () => {
|
|
90
|
-
const renderSpy = jest.fn();
|
|
91
|
-
const SpyComponent: React.FC<TestComponentProps> = (props) => {
|
|
92
|
-
renderSpy();
|
|
93
|
-
return <div>Test Component {props.name}</div>;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const WrappedSpy = withMetrics<TestComponentProps>(SpyComponent, 'TestWidget');
|
|
97
|
-
|
|
98
|
-
const {rerender} = render(<WrappedSpy name="test" />);
|
|
99
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
100
|
-
|
|
101
|
-
// Re-render with different props
|
|
102
|
-
rerender(<WrappedSpy name="different" />);
|
|
103
|
-
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
104
|
-
});
|
|
105
|
-
});
|
package/tsconfig.json
DELETED
package/tsconfig.test.json
DELETED
package/webpack.config.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const {merge} = require('webpack-merge');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const baseConfig = require('../../../webpack.config');
|
|
5
|
-
|
|
6
|
-
module.exports = merge(baseConfig, {
|
|
7
|
-
output: {
|
|
8
|
-
path: path.resolve(__dirname, 'dist'),
|
|
9
|
-
filename: 'index.js', // Set the output filename to index.js
|
|
10
|
-
libraryTarget: 'commonjs2',
|
|
11
|
-
},
|
|
12
|
-
externals: {
|
|
13
|
-
react: 'react',
|
|
14
|
-
'react-dom': 'react-dom',
|
|
15
|
-
'@webex/cc-store': '@webex/cc-store',
|
|
16
|
-
},
|
|
17
|
-
});
|