@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 CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@webex/cc-ui-logging",
3
- "version": "1.28.0-ccwidgets.121",
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.121"
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
@@ -1,6 +0,0 @@
1
- const jestConfig = require('../../../jest.config.js');
2
-
3
- jestConfig.rootDir = '../../../';
4
- jestConfig.testMatch = ['**/ui-logging/tests/**/*.ts', '**/ui-logging/tests/**/*.tsx'];
5
-
6
- module.exports = jestConfig;
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- import withMetrics from './withMetrics';
2
- import {WidgetMetrics} from './metricsLogger';
3
-
4
- export {withMetrics};
5
- export type {WidgetMetrics};
@@ -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
- }
@@ -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
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "include": [
4
- "./src"
5
- ],
6
- "compilerOptions": {
7
- "outDir": "./dist",
8
- "declaration": true,
9
- "declarationDir": "./dist/types"
10
- },
11
- }
@@ -1,9 +0,0 @@
1
- // This config is to do type checking in our files while running tests.
2
- {
3
- "extends": "./tsconfig.json",
4
- "include": ["./tests"],
5
- "exclude": ["node_modules"],
6
- "compilerOptions": {
7
- "noEmit": true // Don't output any files
8
- }
9
- }
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
- });